Pārlūkot izejas kodu

fix: 文件的权限控制

tanzx 2 dienas atpakaļ
vecāks
revīzija
72262018e0

+ 35 - 14
src/views/doc/components/file-table-list.vue

@@ -50,7 +50,7 @@
             >
           </el-dropdown-menu>
         </el-dropdown>
-        <el-dropdown trigger="click" v-if="lcyStatus == 1">
+        <el-dropdown trigger="click" v-if="lcyStatus == 1 && canEditSelection">
           <el-button type="primary">
             编辑<i class="el-icon-arrow-down el-icon--right"></i>
           </el-button>
@@ -103,7 +103,7 @@
           :disabled="
             !!(selection.filter((item) => item.checkOutStatus != 1).length)
           "
-          v-if="lcyStatus == 1"
+          v-if="lcyStatus == 1 && canEditSelection"
         >
           检入
         </el-button>
@@ -119,14 +119,14 @@
           :disabled="
             !!(selection.filter((item) => item.checkOutStatus == 1).length)
           "
-          v-if="lcyStatus == 1"
+          v-if="lcyStatus == 1 && canEditSelection"
         >
           检出
         </el-button>
 
         <el-popconfirm
           class="ele-action"
-          v-if="lcyStatus == 1 && fileType === 0"
+          v-if="lcyStatus == 1 && fileType === 0 && canEditSelection"
           title="归档后文档无法修改,是否确认归档"
           @confirm="
             updateLcyStatus({
@@ -142,7 +142,7 @@
         </el-popconfirm>
         <el-button
           type="primary"
-          v-if="lcyStatus == 2"
+          v-if="lcyStatus == 2 && canEditSelection"
           @click="
             noUpdateLcyStatus({
               power: 'cancelArchive',
@@ -163,7 +163,8 @@
           type="primary"
           v-if="
             (lcyStatus == 1 || lcyStatus == 2) &&
-            (fileType == 0 || fileType == 1)
+            (fileType == 0 || fileType == 1) &&
+            canEditSelection
           "
           @click="
             openSendEdit({
@@ -176,7 +177,7 @@
           发布
         </el-button>
         <el-button
-          v-if="fileType == 2"
+          v-if="fileType == 2 && canEditSelection"
           type="primary"
           @click="processSubmit_template"
         >
@@ -184,7 +185,7 @@
         </el-button>
 
         <el-button
-          v-if="fileType === 0 && lcyStatus != 4"
+          v-if="fileType === 0 && lcyStatus != 4 && canEditSelection"
           type="primary"
           :disabled="selection.length > 1"
           @click="
@@ -199,6 +200,7 @@
         </el-button>
         <el-popconfirm
           class="ele-action"
+          v-if="canEditSelection"
           title="确定要删除选中的文档吗?"
           @confirm="
             removeAll({
@@ -213,7 +215,7 @@
           </template>
         </el-popconfirm>
         <el-button
-          v-if="fileType === 0 && lcyStatus != 4"
+          v-if="fileType === 0 && lcyStatus != 4 && canEditSelection"
           type="primary"
           @click="powerOpen"
         >
@@ -234,14 +236,14 @@
         <el-button
           :disabled="selection.length > 1"
           type="primary"
-          v-if="fileType == 1"
+          v-if="fileType == 1 && canEditSelection"
           @click.native="move"
         >
           移动
         </el-button>
         <el-button
           type="primary"
-          v-if="lcyStatus == 1 && fileType === 0"
+          v-if="lcyStatus == 1 && fileType === 0 && canEditSelection"
           @click.native="
             moveAll({
               power: 'edit',
@@ -279,7 +281,8 @@
           v-if="
             lcyStatus == 1 &&
             (row.reviewStatus == 0 || row.reviewStatus == 3) &&
-            row.status == 0
+            row.status == 0 &&
+            canEdit(row)
           "
           type="primary"
           :underline="false"
@@ -298,7 +301,8 @@
           v-if="
             lcyStatus == 1 &&
             (row.reviewStatus == 0 || row.reviewStatus == 3) &&
-            row.status == 0
+            row.status == 0 &&
+            canEdit(row)
           "
           class="ele-action"
           title="确定要删除此文档吗?"
@@ -434,7 +438,13 @@
   import browse from './browse.vue';
   import sendEdit from '@/views/doc/issue_doc/components/edit.vue';
 
-  import { isPower, isCheckOut, fileStatus, isCreateUserId } from '../util.js';
+  import {
+    isPower,
+    canEdit,
+    isCheckOut,
+    fileStatus,
+    isCreateUserId
+  } from '../util.js';
   import { getFile } from '@/api/system/file/index.js';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import {
@@ -497,6 +507,16 @@
     },
     //add:新建 fileEdit:修改  filePigeonhole:归档 fileUnPigeonhole:取消归档  fileIssue:发布 fileChange:变更 fileDel:删除
 
+    computed: {
+      // 批量写按钮只在所有选中文档都由当前用户可写时展示
+      canEditSelection() {
+        return (
+          this.selection.length > 0 &&
+          this.selection.every((row) => canEdit(row))
+        );
+      }
+    },
+
     data() {
       return {
         // 关闭 mixin 默认的 getTabColumns(与 ele-pro-table 工具栏共享同一 localStorage key,
@@ -504,6 +524,7 @@
         // 仅在 cacheKeyUrl 非空时才会真正发起同步(见 loadColumnsFromServer / onColumnsChange 的守卫)
         tabMixinsInit: false,
         isPower,
+        canEdit,
         isCheckOut,
         isCreateUserId,
         powerOpenShow: false,

+ 8 - 2
src/views/doc/components/main.vue

@@ -73,7 +73,7 @@
                 >
                   <div>
                     <el-link
-                      v-if="lcyStatus == 1 && isPower(current)"
+                      v-if="lcyStatus == 1 && canEdit(current)"
                       :underline="false"
                       @click.native="setPower(data)"
                       >更改权限
@@ -163,7 +163,7 @@
   } from '@/api/doc-manage';
   import { mapGetters } from 'vuex';
   import Power from './power/index.vue';
-  import { isPower } from '../util.js';
+  import { isPower, canEdit } from '../util.js';
 
   import ElementTreeLine from 'element-tree-line';
   // css
@@ -190,6 +190,7 @@
     data() {
       return {
         isPower,
+        canEdit,
         powerArr: [
           { name: 'add', label: '新建' },
           { name: 'revise', label: '修改' },
@@ -417,6 +418,11 @@
           .catch(() => {});
       },
       nodeDrop(to, form) {
+        if (!canEdit(to.data) || !canEdit(form.data)) {
+          this.$message.warning('抱歉,您没有操作权限!');
+          this.query();
+          return;
+        }
         moveDirectory({
           targetId: form.data.id,
           replaceId: to.data.id

+ 5 - 1
src/views/doc/recycleBin/index.vue

@@ -16,6 +16,7 @@
         <!-- 操作列 -->
         <template v-slot:action="{ row }">
           <el-popconfirm
+            v-if="!row.delUserId || isCurrentUser(row.delUserId)"
             class="ele-action"
             title="确定要还原吗?"
             @confirm="revoke(row)"
@@ -28,6 +29,7 @@
           </el-popconfirm>
 
           <el-popconfirm
+            v-if="!row.delUserId || isCurrentUser(row.delUserId)"
             class="ele-action"
             title="确定要删除吗?"
             @confirm="remove(row)"
@@ -61,6 +63,7 @@
 <script>
   import search from './components/search.vue';
   import { mapGetters } from 'vuex';
+  import { isCurrentUser } from '../util.js';
   import dictMixins from '@/mixins/dictMixins';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import {
@@ -76,6 +79,7 @@
     },
     data() {
       return {
+        isCurrentUser,
         // 关闭 mixin 默认的 getTabColumns(与 ele-pro-table 工具栏共享同一 localStorage key,
         // 混用会破坏缓存结构,由本组件自行接管"远端拉取 + 应用"流程)。
         // 仅在 cacheKeyUrl 非空时才会真正发起同步(见 loadColumnsFromServer / onColumnsChange 的守卫)
@@ -306,4 +310,4 @@
   :deep(.el-input__suffix) {
     display: none;
   }
-</style>
+</style>

+ 77 - 55
src/views/doc/util.js

@@ -1,73 +1,95 @@
 import store from '@/store';
 import vue from 'vue';
-const userId = store.state.user.info.userId;
-const userName = store.state.user.info.name;
+const userId = store.state.user?.info?.userId;
 
+/** 避免 undefined == undefined 被误判为同一用户。 */
+function sameUser(a, b) {
+  return a != null && b != null && String(a) === String(b);
+}
+
+/** 当前用户是否为该行(文档/文件夹)的创建人。 */
 export function isCreateUserId(row) {
-  if (vue.prototype.$hasPermission('fm:doc:viewAll')) {
-    return true;
-  }
-  return userId == row?.createUserId;
+  return sameUser(row?.createUserId, userId);
+}
+
+/** 当前用户是否为指定 ID,用于回收站按删除人判断。 */
+export function isCurrentUser(id) {
+  return sameUser(id, userId);
+}
+
+/**
+ * 与后端 FmAuthorityHelper 完全同口径的可写判定:
+ * 创建人 + userAuthority 名单内 visible!='0' 的用户可写;
+ * 未设置权限(空数组)时其他人只读。
+ */
+export function canEdit(row) {
+  if (!row) return false;
+  if (isCreateUserId(row)) return true;
+  const list = row.userAuthority;
+  if (!Array.isArray(list) || list.length === 0) return false;
+  const item = list.find((x) => sameUser(x?.id, userId));
+  return !!item && item?.visible != '0';
+}
+
+/** 与后端 isVisible 同口径:空权限全员可见,设置权限后收敛到名单。 */
+export function canRead(row) {
+  if (!row) return false;
+  if (isCreateUserId(row)) return true;
+  const list = row.userAuthority;
+  if (!Array.isArray(list) || list.length === 0) return true;
+  return canEdit(row);
+}
+
+/** 「全部查看浏览权限」只用于查全部与读操作,不再放行任何写操作。 */
+export function canViewAll() {
+  return vue.prototype.$hasPermission('fm:doc:viewAll');
 }
+// 写操作权限位:统一按「创建人 + 已设置权限的用户」判定,与后端文档级鉴权一致。
+// 前端权限面板里细分的 revise/del/checkOut 等勾选位不再决定能否写,避免前端放行、后端拒绝。
+const WRITE_POWERS = [
+  'revise',
+  'del',
+  'checkEnter',
+  'checkOut',
+  'archive',
+  'cancelArchive',
+  'release',
+  'abolish',
+  'edit'
+];
+
 export function isPower(data = {}, power, selection = []) {
-  if (!data.id && selection.length == 0) {
+  const list = selection || [];
+  if (!data?.id && list.length == 0) {
     return false;
   }
-  if (
-    //文件查看浏览权限
-    vue.prototype.$hasPermission('fm:doc:viewAll')
-  ) {
-    return true;
-  }
 
-  if (power == 'add' && data.userAuthority.length == 0) {
-    return true;
-  }
-  if (power == 'checkEnter' && data?.checkOutUserId != userId) {
-    //检入特殊处理
-    return false;
+  // 在目录下新建子项:沿用产品既有逻辑,未设置权限的目录开放新增;
+  // 显式设置过权限的目录仅创建人 + 名单内用户可新增。
+  if (power == 'add') {
+    if (isCreateUserId(data)) return true;
+    const auth = data?.userAuthority;
+    if (!Array.isArray(auth) || auth.length === 0) return true;
+    return canEdit(data);
   }
-  if (selection.length > 0) {
-    //多选情况处理
-    let _isPower = true;
-    let userAuthorityS = [];
-    let createUserIdS = selection
-      .map((item) => item.createUserId)
-      .filter((createUserId) => createUserId == userId);
-    if (createUserIdS.length == selection.length || userName == 'admin') {
-      //创建人/admin所有权限
-      return true;
-    }
 
-    selection.forEach((val) => {
-      const powerObj = val.userAuthority.find((item) => item.id == userId);
-      if (powerObj) {
-        userAuthorityS.push(powerObj);
-      }
-    });
+  // 检入额外要求当前用户就是检出人
+  const checkEnterOk = (row) =>
+    power != 'checkEnter' || sameUser(row?.checkOutUserId, userId);
 
-    if (userAuthorityS.length == 0) {
-      _isPower = false;
-    }
-    userAuthorityS.forEach((item) => {
-      if (item[power] != '1') {
-        _isPower = false;
-      }
-    });
-    return _isPower;
+  if (list.length > 0) {
+    return list.every((row) =>
+      WRITE_POWERS.includes(power)
+        ? checkEnterOk(row) && canEdit(row)
+        : canViewAll() || canRead(row)
+    );
   }
 
-  if (data.createUserId == userId || userName == 'admin') {
-    //创建人/admin所有权限
-    return true;
-  }
-  if (!power) {
-    return false;
-  }
-  const powerObj = data.userAuthority?.find((item) => item.id == userId);
-  if (powerObj) {
-    return powerObj[power] == '1';
+  if (WRITE_POWERS.includes(power)) {
+    return checkEnterOk(data) && canEdit(data);
   }
+  // 查看 / 浏览 / 下载等读操作:全员可见的文档放行,受限文档按名单
+  return canViewAll() || canRead(data);
 }
 export function isCheckOut(data = {}) {
   //检出判断