同一个大屏页面(两个版本,些微不同)需要根据不同的租户,上一级领导看本级及其下面所有的单位全部内容。下一级租户只能限定看各自单位的内容。

代码逻辑:

1. 身份判断:谁是“领导”,谁是“下一级租户”?

这是所有权限控制的起点。代码通过 computed 属性 isCenterTernat 来完成这一核心判断。

const centerTenatId = ""; // 定义了“上级领导”的租户ID

const isCenterTernat = computed(() => {
  // 从全局用户信息中,判断当前登录用户的租户ID是否是中心ID
  return userStore.info != null && userStore.info.tenant_id == centerTenatId;
})
  • 作用:isCenterTernat 成为了一个全局的“开关”或“身份标识”。后续所有的UI显示和逻辑判断,都基于这个 true/false 值。
  • 优势:将身份逻辑集中管理,清晰明了。如果“领导”的ID需要变更,只需修改 centerTenatId 这一处即可。

2. UI动态展示:给不同的人看不同的界面

有了身份标识,代码就能根据它来决定显示哪些操作控件,实现了界面的“千人千面”。

<!-- 上级领导看到的UI -->
<el-tree-select
  v-if="isCenterTernat"  <!-- 核心:只有领导才显示 -->
  v-model="selectedBoard"
  ...
/>

<!-- 下级租户看到的UI -->
<el-cascader 
  v-if="!isCenterTernat && selectedBoard !== '0' && isShow"  <!-- 核心:只有非领导才显示 -->
  ...
/>
  • 上级领导 (isCenterTernattrue):

    • 看到一个树形选择器 (el-tree-select),可以选择“上一级看板”或切换到任意一个“各单位看板”。
    • 这给了他查看全局数据的最高权限。
  • 下级租户 (isCenterTernatfalse):

    • 树形选择器被隐藏,他们无法切换到别的单位。
    • 他们看到的是一个级联选择器 (el-cascader),只能在自己单位内部选择部门,进行更细粒度的筛选。
    • 这限制了他们只能查看自己单位范围内的数据。

3. 数据隔离与请求:确保拿到的数据是正确的

这是最关键的一步,确保界面和后端数据保持一致。代码通过一个核心状态 selectedBoard 来实现。

A. 状态的初始化

在组件挂载前,根据用户身份,自动设置一个默认的数据筛选范围。

1 onBeforeMount(() => {
2   if(userStore.info){
3     if (userStore.info.tenant_id == centerTenatId){ // 如果是领导
4       selectedBoard.value = "0"; // 默认看“上一级看板”(看全部)
5     } else { // 如果是下级租户
6       selectedBoard.value = userStore.info.tenant_id; // 默认只能看自己的
7     }
8   }
9 })

B. 状态的联动与数据刷新

selectedBoard 变量就像一个“总阀门”,它的变化会触发所有相关数据的重新获取。

watch(
  () => selectedBoard.value, // 监听“总阀门”的变化
  async (newVal) => {
    // 阀门一变,立刻重置并刷新所有依赖它的数据
    selectCorp.value = "";      
    await initSelectCorp();     // 1. 重新加载法人单位列表
    await initSelectOrgId();     // 2. 重新加载部门列表
    await initProjectIds(...);  // 3. 重新加载项目ID列表
  }
)

C. 将状态作为参数传递给后端

所有的数据获取函数,都会将 selectedBoard.value 作为 tenantId 参数发送给后端。

 1 // 以 initSelectOrgId 为例
 2 const initSelectOrgId = async () => {
 3   const req = {
 4     tenantId: selectedBoard.value, // 核心:把当前选中的租户ID传给后端
 5     corpId: selectCorp.value || "",
 6     type: createIncomeValue.value === "1" ? 1 : 0,
 7   };
 8   const resp = await getQuerySelectProduce(req);
 9   // ...
10 };
  • 工作流程:
    1. 领导在 el-tree-select 中选择了“单位A”,selectedBoard 变为“单位A的ID”。
    2. watch 触发,调用 initSelectOrgId
    3. 请求发往后端,参数是 { tenantId: "单位A的ID", ... }
    4. 后端API接收到 tenantId,查询数据库,只返回属于“单位A”的部门列表。
    5. 前端拿到数据,渲染界面。整个过程实现了数据层面的严格隔离。

 

总结与借鉴 

    1. 权限判断集中化:通过 isCenterTernat 这个 computed 属性,将复杂的权限逻辑封装成一个简单的布尔值,供全局使用。
    2. 状态驱动一切:selectedBoard 是整个页面的“单一数据源”和“总开关”。UI展示、数据请求、组件传参都围绕它展开,逻辑非常清晰,易于维护。
    3. 前后端协作默契:前端负责根据用户操作构建好带有 tenantId 的请求,后端负责根据 tenantId 进行数据过滤。职责分明,共同完成了权限控制。
    4. 组件化思维:父组件(总指挥)管理所有状态和逻辑,子组件 (LeftCenterRight) 只负责接收 props 并渲染,实现了高内聚、低耦合。

后端核心代码

 1 // 1.获取当前登录用户的租户id
 2     user := ginx.GetUser(c)
 3     // 2.比对是否为上一级租户
 4     // 2.1 如何为上一级租户 则不做处理
 5     // 2.2 如果不为上一级心租户 则只能查询自己的 需要比对租户id,一样放行 不一样报错返回
 6     if user.TenantID != constants.CenterTenantId {
 7         //不是中心租户
 8         if user.TenantID != params.TenantID {
 9             // 查询的不是自己的数据 无权限
10             ginx.ResError(c, errors.ErrNoPerm)
11             return
12         }
13     }

步骤1:身份识别与参数解析

ginx.ParseJSON(c, &params) // 1. 解析请求体,获取用户“想看什么”
user := ginx.GetUser(c)      // 2. 从认证信息中,获取用户“是谁”

步骤2:权限校验(核心关卡)

 函数 建立了一个分层的权限模型:

这个逻辑可以翻译成以下决策树:

  1. 你是“中心领导”(user.TenantID == constants.CenterTenantId)吗?

    • 是 -> 直接通过。你有查看所有数据的特权,params.TenantID 是什么无所谓。
    • 否 -> 进入下一步检查。
  2. (普通用户)你请求的数据是你自己的吗?(user.TenantID == params.TenantID

    • 是 -> 通过。你可以查看自己的数据。
    • 否 -> 拒绝!你试图访问别人的数据,立即返回 ErrNoPerm 权限错误。

步骤3:执行业务与返回结果

  • 执行业务逻辑:权限验证通过后,才调用真正的业务逻辑层 ToubiaoBll 去数据库查询数据。
  • 返回结果:将查询到的结果返回给前端。

 

代码亮点与最佳实践

    1. 分层架构清晰:

      • API层(GetToubiaoCountInfo)只负责HTTP协议处理和权限校验。
      • BLL层(ToubiaoBll)只负责核心业务逻辑。
      • 职责分离,代码易于维护和测试。
    2. 防御性编程:

      • 权限前置:在任何业务逻辑执行之前就完成权限校验,避免了无效的数据库查询和资源浪费。
      • 不信任前端:后端是权限的最后一道防线,它永远只相信服务器端的认证信息(user.TenantID),这是安全的基石。
    3. 权限逻辑的集中管理:

      • constants.CenterTenantId 将“超级租户”这个概念硬编码为一个常量。这样,当需要更换“中心”的标识时,只需修改一处代码即可。
    4. 明确且专业的错误处理:

      • 对于权限问题,返回专门的 ErrNoPerm 错误。这让前端调用者可以非常清晰地区分是“请求参数错了”还是“权限不够”,便于进行不同的错误处理和用户提示。