Kaynağa Gözat

🎨 按要求限制跨级审核,显示状态

快乐的梦鱼 2 hafta önce
ebeveyn
işleme
1c98ed480c

+ 5 - 5
src/components/NavBar.vue

@@ -170,16 +170,16 @@ nav.main {
     align-items: center;
 
     img {
-      width: 45px;
-      height: 45px; 
+      width: 30px;
+      height: 30px; 
       margin-right: 10px;
+      margin-bottom: 6px;
     }
     > div {
       display: flex;
       flex-direction: column;
       justify-content: center;
       font-family: nzgrRuyinZouZhangKai;
-      margin-bottom: 6px;
 
       p {
         margin: 0;
@@ -292,8 +292,8 @@ nav.main {
   nav.main {
     .headerlogos {
       img {
-        width: 35px;
-        height: 35px; 
+        width: 25px;
+        height: 25px; 
         margin-right: 7px;
       }
       > div {

+ 11 - 10
src/pages/collect/assessment/argeement-sign-list.vue

@@ -20,6 +20,11 @@
     :load="(page: number, pageSize: number, _tag: number, searchText: string, drop: number[]) => loadAgreementSignAdminList(page, pageSize, searchText, drop)"
     :show-detail="handleShowDetail"
   >
+    <template #headLeft>
+      <div class="flex flex-row items-center gap-2">
+        <span>{{ progressHint }}</span>
+      </div>
+    </template>
     <template #itemRight="{ item }">
       <a-popover v-if="item.rejectType && item.rejectType > 0" title="退回原因" trigger="hover">
         <template #content>
@@ -31,9 +36,12 @@
       </a-popover>
       <span v-else class="mr-3 text-sm text-gray-600">{{ agreementProgressLabel(item.progress) }}</span>
       <a-button type="link" @click.stop="handleEdit(item)">编辑</a-button>
+      <div v-if="item.agreementId && canReviewButPrevNotCompleted(item.progress)" class="flex flex-row items-center mr-3 text-sm">
+        <ExclamationCircleOutlined /> 上一阶段未审核完成
+      </div>
       <a-button
         type="primary"
-        v-if="canReview(item)"
+        v-else-if="item.agreementId && canReview(item.progress)"
         @click.stop="handleReview(item)"
       >
         审核
@@ -51,6 +59,7 @@ import AssessmentContentApi, { type UserAgreementListRow } from '@/api/collect/A
 import { useMemorizeVar } from '@/composeables/useMemorizeVar';
 import { useAuthStore } from '@/stores/auth';
 import { GROUP_TO_REVIEW_PROGRESS } from './composeables/GroupData';
+import { useReview } from './composeables/Review';
 
 const router = useRouter();
 const authStore = useAuthStore();
@@ -76,15 +85,7 @@ const agreementProgressOptions: DropdownCommonItem[] = [
   { id: 5, name: '省文化和旅游厅审核完成' },
 ];
 
-const currentUserGroups = computed(() => authStore.userInfo?.adminGroup || []);
-
-function canReview(item: UserAgreementListRow) {
-  const currentUserGroup = currentUserGroups.value.find((group) => GROUP_TO_REVIEW_PROGRESS[group.id]);
-  const currentUserReviewProgress = GROUP_TO_REVIEW_PROGRESS[currentUserGroup?.id ?? 0] ?? 0;
-  return item.agreementId
-    && item.progress != null && item.progress >= 1 && item.progress < 5
-    && item.progress < currentUserReviewProgress;
-}
+const { canReview, canReviewButPrevNotCompleted, progressHint } = useReview();
 
 function agreementProgressLabel(progress: number | null | undefined) {
   if (progress === null || progress === undefined)

+ 7 - 33
src/pages/collect/assessment/argeement-sign-review.vue

@@ -103,14 +103,12 @@ import { useRoute, useRouter } from 'vue-router';
 import { message, Modal } from 'ant-design-vue';
 import { RequestApiError } from '@imengyu/imengyu-utils';
 import { ArrowLeftOutlined, UpOutlined, DownOutlined } from '@ant-design/icons-vue';
-import AssessmentContentApi, { AgreementDetail } from '@/api/collect/AssessmentContent';
-import AgreementFormDisplay from './components/AgreementFormDisplay.vue';
-import type { AgreementYmdParts } from './components/AgreementDateWriteBlock.vue';
 import { useSimpleDataLoader } from '@/composeables/useSimpleDataLoader';
-import { useAuthStore } from '@/stores/auth';
 import { useMemorizeVar } from '@/composeables/useMemorizeVar';
-import { GROUP_TO_REVIEW_PROGRESS } from './composeables/GroupData';
 import { isInMiniProgram } from '@/composeables/MiniProgramIng.ts';
+import AssessmentContentApi, { AgreementDetail } from '@/api/collect/AssessmentContent';
+import AgreementFormDisplay from './components/AgreementFormDisplay.vue';
+import { useReview } from './composeables/Review.ts';
 
 function formatErr(e: unknown): string {
   if (e instanceof RequestApiError)
@@ -122,12 +120,9 @@ function formatErr(e: unknown): string {
 
 const router = useRouter();
 const route = useRoute();
-const authStore = useAuthStore();
 
 const agreementFormRef = ref<InstanceType<typeof AgreementFormDisplay> | null>(null);
 
-const currentUserGroups = computed(() => authStore.userInfo?.adminGroup || []);
-
 const queryFormId = computed(() => Number(route.query.id) || 0);
 const queryUserId = computed(() => Number(route.query.userId) || 0);
 const queryProgress = computed(() => {
@@ -142,29 +137,8 @@ const submitLoading = ref(false);
 const rejectLoading = ref(false);
 const rejectReason = ref<string | null>(null);
 
-const reviewProgress = computed(() => {
-  const currentUserGroup = currentUserGroups.value.find((group) => GROUP_TO_REVIEW_PROGRESS[group.id]);
-  return GROUP_TO_REVIEW_PROGRESS[currentUserGroup?.id ?? 0] ?? 0;
-});
-
-const canSubmitReview = computed(() => reviewProgress.value >= 2 && reviewProgress.value <= 5);
-
-const reviewLevelLabel = computed(() => {
-  switch (reviewProgress.value) {
-    case 2: return '项目保护单位';
-    case 3: return '县(区)文旅部门';
-    case 4: return '设区市文旅部门、省非遗中心';
-    case 5: return '省文化和旅游厅';
-    default: return '';
-  }
-});
-
-const progressHint = computed(() => {
-  const roleText = canSubmitReview.value
-    ? `当前账号审核环节:${reviewLevelLabel.value}`
-    : `当前用户组 ${currentUserGroups.value.map((group) => group.name).join(',')} 无对应审核环节`;
-  return `${roleText}。`;
-});
+const currentProgress = computed(() => currentAgreement.value?.progress ?? 0);
+const { reviewProgressInfo, canSubmitReview, reviewLevelLabel, progressHint } = useReview(currentProgress);
 
 const loader = useSimpleDataLoader(async () => {
   const id = queryFormId.value;
@@ -205,7 +179,7 @@ async function submitReview() {
     submitLoading.value = true;
     await AssessmentContentApi.reviewAgreement({
       id: d.id,
-      progress: reviewProgress.value,
+      progress: reviewProgressInfo.value.target,
     });
     message.success('审核通过');
     router.back();
@@ -236,7 +210,7 @@ async function submitReject() {
     await AssessmentContentApi.reviewAgreement({
       id: d.id,
       progress: queryProgress.value,
-      rejectType: reviewProgress.value,
+      rejectType: reviewProgressInfo.value.rejectTarget,
       rejectReason: reason,
     });
     message.success('已回退');

+ 64 - 7
src/pages/collect/assessment/composeables/GroupData.ts

@@ -1,11 +1,68 @@
-/** 用户组 → 本环节提交后的 progress */
-export const GROUP_TO_REVIEW_PROGRESS: Record<number, number> = {
-  9: 2, // 项目保护单位
-  5: 3, // 县(区)文旅部门
-  10: 4, // 设区市文旅部门、省非遗中心
-  11: 5, // 省文化和旅游厅
-};
+/** 
+ * 用户组 → 本环节提交后的 progress 
+ * 
+ * 状态:
+  0=草稿,
+  1=已自评,
+  2=项目保护单位审核完成,
+  3=县(区)文旅部门审核完成,
+  4=设区市文旅部门、省非遗中心审核完成,
+  5=省文化和旅游厅审核完成
+
+ * 退回状态
+  0=无,
+  1=自评阶段退回,
+  2=项目保护单位退回,
+  3=县(区)文旅部门退回,
+  4=设区市文旅部门、省非遗中心退回,
+  5=省文化和旅游厅退回
+ * 
+*/
+export const GROUP_TO_REVIEW_PROGRESS = {
+  9: {
+    target: 2,
+    rejectTarget: 2,
+    minSource: 1
+  }, // 项目保护单位
+  5: {
+    target: 3,
+    rejectTarget: 3,
+    minSource: 2
+  }, // 县(区)文旅部门
+  10: {
+    target: 4, 
+    rejectTarget: 4,
+    minSource: 3,
+  },// 设区市文旅部门、省非遗中心
+  11: {
+    target: 5, 
+    rejectTarget: 5,
+    minSource: 4,
+  },// 省文化和旅游厅
+} as Record<number, { 
+  /**
+   * 当前级别审核完成后的 progress
+   */
+  target: number, 
+  /**
+   * 当前级别可审核状态
+   */
+  minSource: number,
+  /**
+   * 当前级别回退状态
+   */
+  rejectTarget: number,
+}>;
 
+export const SUBMITED_PROGRESS = 1;
+
+export const GROUP_LABELS: Record<number, string> = {
+  0: '无权限',
+  9: '项目保护单位',
+  5: '县(区)文旅部门',
+  10: '设区市文旅部门、省非遗中心',
+  11: '省文化和旅游厅',
+};
 export const PROGRESS_SUBMIT_LABELS: Record<number, string> = {
   2: '2 — 项目保护单位审核完成',
   3: '3 — 县(区)文旅部门审核完成',

+ 58 - 0
src/pages/collect/assessment/composeables/Review.ts

@@ -0,0 +1,58 @@
+import { computed, type ComputedRef } from "vue";
+import { GROUP_LABELS, GROUP_TO_REVIEW_PROGRESS, SUBMITED_PROGRESS } from "./GroupData";
+import { useAuthStore } from "@/stores/auth";
+
+export function useReview(sourceProgress?: ComputedRef<number>) {
+  
+  const authStore = useAuthStore();
+
+  const currentUserGroup = computed(() => 
+    (authStore.userInfo?.adminGroup || []).find((group) => GROUP_TO_REVIEW_PROGRESS[group.id])
+  );
+
+  const reviewProgressInfo = computed(() => {
+    return GROUP_TO_REVIEW_PROGRESS[currentUserGroup.value?.id ?? 0];
+  });
+
+  const canSubmitReview = computed(() => {
+    const info = reviewProgressInfo.value;
+    if (!info || !sourceProgress)
+      return false;
+    return sourceProgress.value >= info.minSource //上一阶段已完成
+      && sourceProgress.value < info.target;// 当前审核阶段未完成
+  });
+
+  const canReview = (sourceProgress: number) => {
+    const info = reviewProgressInfo.value;
+    if (!info)
+      return false;
+    return sourceProgress >= info.minSource //上一阶段已完成
+      && sourceProgress < info.target;// 当前审核阶段未完成
+  };
+
+  const canReviewButPrevNotCompleted = (sourceProgress: number) => {
+    const info = reviewProgressInfo.value;
+    if (!info)
+      return false;
+    return sourceProgress >= SUBMITED_PROGRESS  //已提交
+      && sourceProgress < info.minSource //上一阶段未完成
+  };
+
+  const reviewLevelLabel = computed(() => {
+    return GROUP_LABELS[currentUserGroup.value?.id ?? 0];
+  });
+
+  const progressHint = computed(() => {
+    const roleText = reviewLevelLabel.value ? `当前账号审核环节:${reviewLevelLabel.value}` : `当前用户组无权限审核`;
+    return `${roleText}。`;
+  });
+
+  return {
+    reviewProgressInfo,
+    canReview,
+    canReviewButPrevNotCompleted,
+    canSubmitReview,
+    reviewLevelLabel,
+    progressHint,
+  };
+}

+ 15 - 19
src/pages/collect/assessment/evaluation-form-list.vue

@@ -32,7 +32,10 @@
         :show-detail="handleReviewSelfAssessment"
       >
         <template #headLeft>
-          <a-button type="primary" @click="openDownloadModal">打包下载</a-button>
+          <div class="flex flex-row items-center gap-2">
+            <a-button type="primary" @click="openDownloadModal">打包下载</a-button>
+            <span>{{ progressHint }}</span>
+          </div>
         </template>
         <template #itemRight="{ item }">
           <a-popover v-if="item.rejectType && item.rejectType > 0" title="退回原因" trigger="hover">
@@ -50,9 +53,13 @@
             {{ selfAssessmentProgressLabel(item.progress) }}
           </span>
           <a-button type="link" @click.stop="router.push({ name: 'CollectEvaluationForm', query: { id: item.checkId ?? item.id, userId: item.userId } })">编辑</a-button>
+          
+          <div v-if="item.checkId && canReviewButPrevNotCompleted(item.progress)" class="flex flex-row items-center mr-3 text-sm">
+            <ExclamationCircleOutlined /> 上一阶段未审核完成
+          </div>
           <a-button
             type="primary"
-            v-if="canReview(item)"
+            v-else-if="item.checkId && canReview(item.progress)"
             @click.stop="handleReviewSelfAssessment(item)"
           >
             审核
@@ -112,15 +119,14 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { ref } from 'vue';
 import { useRouter } from 'vue-router';
+import { useMemorizeVar } from '@/composeables/useMemorizeVar';
+import { useReview } from './composeables/Review';
 import { message } from 'ant-design-vue';
-import { ExclamationCircleOutlined, InfoCircleOutlined } from '@ant-design/icons-vue';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
 import CommonListBlock, { type DropdownCommonItem } from '@/components/content/CommonListBlock.vue';
 import AssessmentContentApi, { SelfAssessmentDetail } from '@/api/collect/AssessmentContent';
-import { useMemorizeVar } from '@/composeables/useMemorizeVar';
-import { useAuthStore } from '@/stores/auth';
-import { GROUP_TO_REVIEW_PROGRESS } from './composeables/GroupData';
 
 const router = useRouter();
 
@@ -137,6 +143,8 @@ const { variable: lastSelfAssessmentLevel } = useMemorizeVar('adminSelfAssessmen
 const { variable: lastCheckLogStatus } = useMemorizeVar('adminCheckLogStatus', 0);
 const { variable: lastCheckLogReviewType } = useMemorizeVar('adminCheckLogReviewType', 0);
 
+const { canReview, canReviewButPrevNotCompleted, progressHint } = useReview();
+
 const selfAssessmentLevelOptions: DropdownCommonItem[] = [
   { id: 0, name: '全部等级' },
   { id: 23, name: '国家级' },
@@ -181,18 +189,6 @@ const downloadProgressOptions: DropdownCommonItem[] = [
   { id: 5, name: '省文化和旅游厅审核完成' },
 ];
 
-
-const authStore = useAuthStore();
-const currentUserGroups = computed(() => authStore.userInfo?.adminGroup || []);
-
-function canReview(item: SelfAssessmentDetail) { 
-  const currentUserGroup = currentUserGroups.value.find((group) => GROUP_TO_REVIEW_PROGRESS[group.id]);
-  const currentUserReviewProgress = GROUP_TO_REVIEW_PROGRESS[currentUserGroup?.id ?? 0] ?? 0;
-  return item.checkId 
-    && item.progress != null && item.progress >= 1 && item.progress < 5 //必须已经提交
-    && item.progress < currentUserReviewProgress; // 当前审核阶段未完成
-}
-
 function selfAssessmentProgressLabel(progress: number | null | undefined) {
   if (progress === null || progress === undefined)
     return '未填写';

+ 7 - 43
src/pages/collect/assessment/evaluation-form-review.vue

@@ -135,6 +135,7 @@ import { useAuthStore } from '@/stores/auth';
 import { useMemorizeVar } from '@/composeables/useMemorizeVar';
 import { GROUP_TO_REVIEW_PROGRESS, PROGRESS_SUBMIT_LABELS } from './composeables/GroupData.ts';
 import { isInMiniProgram } from '@/composeables/MiniProgramIng.ts';
+import { useReview } from './composeables/Review.ts';
 
 function formatErr(e: unknown): string {
   if (e instanceof RequestApiError)
@@ -170,8 +171,6 @@ const reviewOpinion = ref<number | null>(null);
 const reviewPoints = ref<number | null>(null);
 const rejectReason = ref<string | null>(null);
 
-
-
 const opinionSelectOptions = [
   { label: '优秀', value: 1 },
   { label: '合格', value: 2 },
@@ -180,42 +179,8 @@ const opinionSelectOptions = [
   { label: '取消资格', value: 5 },
 ];
 
-const reviewProgress = computed(
-  () => {
-    const currentUserGroup = currentUserGroups.value.find((group) => GROUP_TO_REVIEW_PROGRESS[group.id]);
-    return GROUP_TO_REVIEW_PROGRESS[currentUserGroup?.id ?? 0] ?? 0;
-  },
-);
-
-const canSubmitReview = computed(
-  () => reviewProgress.value >= 2 && reviewProgress.value <= 5,
-);
-
-const progressSubmitLabel = computed(
-  () => PROGRESS_SUBMIT_LABELS[reviewProgress.value] ?? '—',
-);
-
-const reviewLevelLabel = computed(() => {
-  switch (reviewProgress.value) {
-    case 2:
-      return '项目保护单位';
-    case 3:
-      return '县(区)文旅部门';
-    case 4:
-      return '设区市文旅部门、省非遗中心';
-    case 5:
-      return '省文化和旅游厅';
-    default:
-      return '';
-  }
-});
-
-const progressHint = computed(() => {
-  const roleText = canSubmitReview.value
-    ? `当前账号审核环节:${reviewLevelLabel.value}`
-    : `当前用户组 ${currentUserGroups.value.map((group) => group.name).join(',')} 无对应审核环节`;
-  return `${roleText}。`;
-});
+const currentProgress = computed(() => currentForm.value?.progress ?? 0);
+const { reviewProgressInfo, canSubmitReview, reviewLevelLabel, progressHint } = useReview(currentProgress);
 
 function levelTitleFromForm(f: SelfAssessmentDetail) {
   if (f.level === 23) return '国家级';
@@ -241,7 +206,7 @@ async function loadCheckItems(f: SelfAssessmentDetail) {
 }
 
 function applyPrefillFromDetail(f: SelfAssessmentDetail) {
-  const t = reviewProgress.value;
+  const t = reviewProgressInfo.value.target;
   if (!canSubmitReview.value)
     return;
   if (t === 2) {
@@ -274,7 +239,7 @@ const loader = useSimpleDataLoader(async () => {
   return detail;
 }, { immediate: false });
 
-watch(reviewProgress, () => {
+watch(reviewProgressInfo, () => {
   const f = currentForm.value;
   if (f)
     applyPrefillFromDetail(f as SelfAssessmentDetail);
@@ -306,13 +271,12 @@ async function submitReject() {
     message.warning('请输入回退原因');
     return;
   }
-  const t = reviewProgress.value;
   try {
     rejectLoading.value = true;
     await AssessmentContentApi.reviewSelfAssessment({
       id: f.id,
       progress: f.progress,
-      rejectType: t,
+      rejectType: reviewProgressInfo.value.rejectTarget,
       rejectReason: reason,
     });
     message.success('已回退');
@@ -338,7 +302,7 @@ async function submitReview() {
     message.warning('请选择审核意见');
     return;
   }
-  const t = reviewProgress.value;
+  const t = reviewProgressInfo.value.target;
   const base = { id: f.id, progress: t };
   const points = reviewPoints.value != null && reviewPoints.value >= 0 ? reviewPoints.value : undefined;
   const op = reviewOpinion.value;