Просмотр исходного кода

质控创建包括审核已完成,差质控递交

Huanyi 2 месяцев назад
Родитель
Сommit
176156c088

+ 13 - 1
src/api/Qc/task/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { TaskVO, TaskForm, TaskQuery } from '@/api/qc/task/types';
+import { TaskVO, TaskForm, TaskQuery, TaskDetailAuditForm } from '@/api/qc/task/types';
 
 /**
  * 查询文档质控任务列表
@@ -96,3 +96,15 @@ export const listTaskDetail = (query?: any): AxiosPromise<any> => {
     params: query
   });
 };
+
+/**
+ * 审核质控任务详情
+ * @param data
+ */
+export const auditTaskDetail = (data: TaskDetailAuditForm): AxiosPromise<any> => {
+  return request({
+    url: '/qcTask/detail/audit',
+    method: 'put',
+    data: data
+  });
+};

+ 41 - 0
src/api/Qc/task/types.ts

@@ -338,3 +338,44 @@ export interface TaskDetailForm {
    */
   params?: any;
 }
+
+
+/**
+ * 质控任务详情审核表单
+ */
+export interface TaskDetailAuditForm {
+  /**
+   * 任务ID
+   */
+  taskId: number;
+
+  /**
+   * 详情ID
+   */
+  detailId: number;
+
+  /**
+   * 审核结果:1-通过,2-驳回
+   */
+  result: number;
+
+  /**
+   * 驳回类型(字典:qc_question_type)
+   */
+  rejectionType?: string;
+
+  /**
+   * 意见
+   */
+  opinion?: string;
+
+  /**
+   * 指定处理人ID
+   */
+  designatedDealer?: number;
+
+  /**
+   * 截止日期
+   */
+  deadline?: string;
+}

+ 15 - 0
src/api/home/taskCenter/qc/index.ts

@@ -0,0 +1,15 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { QcTaskQuery, QcTaskVO } from './types';
+
+/**
+ * 查询质控任务列表
+ * @param query
+ */
+export const listQcTasks = (query: QcTaskQuery): AxiosPromise<QcTaskVO[]> => {
+    return request({
+        url: '/home/taskCenter/qc/list',
+        method: 'get',
+        params: query
+    });
+};

+ 37 - 0
src/api/home/taskCenter/qc/types.ts

@@ -0,0 +1,37 @@
+/**
+ * 质控任务查询参数
+ */
+export interface QcTaskQuery extends PageQuery {
+    /** 任务名称 */
+    taskName?: string;
+    /** 项目名称 */
+    projectName?: string;
+    /** 文档名称 */
+    documentName?: string;
+    /** 状态 */
+    status?: number;
+}
+
+/**
+ * 质控任务VO
+ */
+export interface QcTaskVO {
+    /** 序号 */
+    id: number;
+    /** 任务名称 */
+    name: string;
+    /** 项目名称 */
+    projectName: string;
+    /** 状态 */
+    status: number;
+    /** 发起人 */
+    initiator: number;
+    /** 执行人 */
+    executor: number;
+    /** 备注 */
+    note: string;
+    /** 执行时间 */
+    executeTime: string;
+    /** 创建时间 */
+    createTime: string;
+}

+ 13 - 2
src/components/DocumentStatusTag/index.vue

@@ -1,7 +1,7 @@
 <template>
-  <el-tag v-if="config" :type="config.type" :color="config.color" size="small">
+  <span v-if="config" class="status-tag" :style="{ backgroundColor: config.color, color: config.textColor }">
     {{ t(config.label) }}
-  </el-tag>
+  </span>
   <span v-else>-</span>
 </template>
 
@@ -19,3 +19,14 @@ const { t } = useI18n();
 
 const config = computed(() => getDocumentStatusConfig(props.status));
 </script>
+
+<style scoped>
+.status-tag {
+  display: inline-block;
+  padding: 2px 8px;
+  font-size: 12px;
+  line-height: 18px;
+  border-radius: 4px;
+  white-space: nowrap;
+}
+</style>

+ 17 - 8
src/enums/documentStatus.ts

@@ -27,6 +27,7 @@ export interface DocumentStatusConfig {
   label: string;
   type?: 'success' | 'warning' | 'danger' | 'info' | '';
   color?: string;
+  textColor?: string;
 }
 
 /**
@@ -35,35 +36,43 @@ export interface DocumentStatusConfig {
 export const DOCUMENT_STATUS_CONFIG: Record<DocumentStatus, DocumentStatusConfig> = {
   [DocumentStatus.UN_UPLOAD]: {
     label: 'document.document.documentList.statusOptions.unUpload',
-    type: 'info'
+    color: 'rgba(140, 140, 140, 0.15)',
+    textColor: '#8C8C8C'
   },
   [DocumentStatus.UN_AUDIT]: {
     label: 'document.document.documentList.statusOptions.unAudit',
-    type: 'warning'
+    color: 'rgba(250, 173, 20, 0.15)',
+    textColor: '#D48806'
   },
   [DocumentStatus.AUDIT_REJECT]: {
     label: 'document.document.documentList.statusOptions.auditReject',
-    type: 'danger'
+    color: 'rgba(245, 34, 45, 0.15)',
+    textColor: '#CF1322'
   },
   [DocumentStatus.UN_FILING]: {
     label: 'document.document.documentList.statusOptions.unFiling',
-    type: ''
+    color: 'rgba(24, 144, 255, 0.15)',
+    textColor: '#096DD9'
   },
   [DocumentStatus.FILING]: {
     label: 'document.document.documentList.statusOptions.filing',
-    type: 'success'
+    color: 'rgba(82, 196, 26, 0.15)',
+    textColor: '#389E0D'
   },
   [DocumentStatus.UN_QUALITY_CONTROL]: {
     label: 'document.document.documentList.statusOptions.unQualityControl',
-    color: '#E6A23C'
+    color: 'rgba(250, 84, 28, 0.15)',
+    textColor: '#D4380D'
   },
   [DocumentStatus.QUALITY_CONTROL_PASS]: {
     label: 'document.document.documentList.statusOptions.qualityControlPass',
-    color: '#67C23A'
+    color: 'rgba(19, 194, 194, 0.15)',
+    textColor: '#08979C'
   },
   [DocumentStatus.QUALITY_CONTROL_REJECT]: {
     label: 'document.document.documentList.statusOptions.qualityControlReject',
-    color: '#F56C6C'
+    color: 'rgba(235, 47, 150, 0.15)',
+    textColor: '#C41D7F'
   }
 };
 

+ 1 - 1
src/router/index.ts

@@ -158,7 +158,7 @@ export const constantRoutes: RouteRecordRaw[] = [
   },
   // QC任务详情页路由
   {
-    path: '/qc/task/detail',
+    path: '/task/detail',
     component: Layout,
     hidden: true,
     children: [

+ 240 - 4
src/views/Qc/task/detail.vue

@@ -73,6 +73,29 @@
             <el-table-column prop="executionTime" label="执行时间" width="180" />
             <el-table-column prop="finishTime" label="完成时间" width="180" />
             <el-table-column prop="note" label="备注" />
+            <el-table-column label="操作" width="180" align="center" fixed="right">
+              <template #default="scope">
+                <el-button
+                  v-if="taskDetail?.status === 1 && (scope.row.status === 0 || scope.row.status === 3) && scope.row.executor === userStore.userId"
+                  v-hasPermi="['qc:task:audit']"
+                  type="primary"
+                  icon="Check"
+                  style="padding: 0 5px; font-size: 10px; height: 24px"
+                  @click="handleAudit(scope.row)"
+                >
+                  审核
+                </el-button>
+                <el-button
+                  v-if="scope.row.actualDocument"
+                  type="success"
+                  icon="Download"
+                  style="padding: 0 5px; font-size: 10px; height: 24px"
+                  @click="handleDownload(scope.row)"
+                >
+                  下载
+                </el-button>
+              </template>
+            </el-table-column>
           </el-table>
 
           <div class="mt-4">
@@ -90,14 +113,91 @@
         </el-result>
       </div>
     </el-card>
+
+    <!-- 审核弹窗 -->
+    <el-dialog v-model="auditDialog.visible" title="审核" width="500px" append-to-body>
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
+        <el-form-item label="结果" prop="result">
+          <el-radio-group v-model="auditForm.result">
+            <el-radio :label="1">通过</el-radio>
+            <el-radio :label="2">驳回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <template v-if="auditForm.result === 2">
+          <el-form-item label="问题类型" prop="rejectionType">
+            <el-select v-model="auditForm.rejectionType" placeholder="请选择问题类型" style="width: 100%">
+              <el-option
+                v-for="dict in qc_question_type"
+                :key="dict.value"
+                :label="parseI18nName(dict.label)"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="意见" prop="opinion">
+            <el-input
+              v-model="auditForm.opinion"
+              type="textarea"
+              :rows="3"
+              placeholder="请输入意见"
+            />
+          </el-form-item>
+
+          <el-form-item label="指定处理人" prop="designatedDealer">
+            <el-select
+              v-model="auditForm.designatedDealer"
+              filterable
+              remote
+              reserve-keyword
+              placeholder="请输入成员昵称搜索"
+              :remote-method="searchDealers"
+              :loading="dealerSearchLoading"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="dealer in dealerOptions"
+                :key="dealer.id"
+                :label="`${dealer.name} / ${dealer.dept} --- ${dealer.phoneNumber}`"
+                :value="dealer.id"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="截止日期" prop="deadline">
+            <el-date-picker
+              v-model="auditForm.deadline"
+              type="date"
+              value-format="YYYY-MM-DD"
+              placeholder="请选择截止日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </template>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancelAudit">取消</el-button>
+          <el-button type="primary" :loading="auditLoading" @click="submitAudit">确认</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="TaskDetail" lang="ts">
-import { ref, onMounted, watch, computed } from 'vue';
+import { ref, reactive, onMounted, watch, computed, toRefs } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-import { getTask, listTaskDetail } from '@/api/qc/task';
-import { TaskVO } from '@/api/qc/task/types';
+import { getTask, listTaskDetail, auditTaskDetail } from '@/api/qc/task';
+import { TaskVO, TaskDetailAuditForm } from '@/api/qc/task/types';
+import { downloadDocumentFile } from '@/api/document/document';
+import { queryMemberNotInCenter } from '@/api/project/management';
+import { MemberNotInCenterVO, MemberNotInCenterQuery } from '@/api/project/management/types';
+import { ElMessage } from 'element-plus';
+import type { FormInstance } from 'element-plus';
+import { useUserStore } from '@/store/modules/user';
+import { parseI18nName } from '@/utils/i18n';
 
 // 定义任务详情项类型
 interface TaskDetailItem {
@@ -105,6 +205,8 @@ interface TaskDetailItem {
   document: number;
   documentName: string;
   ossId: number;
+  actualDocument: string;
+  actualDocumentName: string;
   executor: number;
   executorName: string;
   status: number;
@@ -129,6 +231,8 @@ const taskItemsQuery = ref({
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const route = useRoute();
 const router = useRouter();
+const userStore = useUserStore();
+const { qc_question_type } = toRefs<any>(proxy?.useDict('qc_question_type'));
 
 // 从路由参数获取任务ID
 const taskId = computed(() => {
@@ -142,6 +246,41 @@ const taskId = computed(() => {
 const loading = ref(true);
 const taskDetail = ref<TaskVO | null>(null);
 
+// 审核相关
+const auditFormRef = ref<FormInstance>();
+const auditDialog = reactive({
+  visible: false
+});
+const auditLoading = ref(false);
+const currentAuditRow = ref<TaskDetailItem | null>(null);
+
+// 审核表单初始数据
+const initAuditForm: TaskDetailAuditForm = {
+  taskId: 0,
+  detailId: 0,
+  result: 1,
+  rejectionType: undefined,
+  opinion: undefined,
+  designatedDealer: undefined,
+  deadline: undefined
+};
+
+const auditForm = ref<TaskDetailAuditForm>({ ...initAuditForm });
+
+// 审核表单验证规则
+const auditRules = computed(() => ({
+  result: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
+  rejectionType: auditForm.value.result === 2 ? [{ required: true, message: '请选择问题类型', trigger: 'change' }] : [],
+  opinion: auditForm.value.result === 2 ? [{ required: true, message: '请输入意见', trigger: 'blur' }] : [],
+  designatedDealer: auditForm.value.result === 2 ? [{ required: true, message: '请选择指定处理人', trigger: 'change' }] : [],
+  deadline: auditForm.value.result === 2 ? [{ required: true, message: '请选择截止日期', trigger: 'change' }] : []
+}));
+
+// 处理人搜索相关
+const dealerSearchLoading = ref(false);
+const dealerOptions = ref<MemberNotInCenterVO[]>([]);
+let dealerSearchTimer: NodeJS.Timeout | null = null;
+
 const fetchTaskDetail = async () => {
   if (!taskId.value) return;
 
@@ -191,8 +330,105 @@ const handleTaskItemsPagination = (pageNum: number, pageSize: number) => {
   fetchTaskItems();
 };
 
+/** 审核任务 */
+const handleAudit = (row: TaskDetailItem) => {
+  currentAuditRow.value = row;
+  auditForm.value = {
+    ...initAuditForm,
+    taskId: taskDetail.value?.id as number,
+    detailId: row.id
+  };
+  dealerOptions.value = [];
+  auditDialog.visible = true;
+};
+
+/** 搜索处理人 */
+const searchDealers = async (query: string) => {
+  if (!query || query.trim() === '') {
+    dealerOptions.value = [];
+    return;
+  }
+
+  if (dealerSearchTimer) {
+    clearTimeout(dealerSearchTimer);
+  }
+
+  dealerSearchTimer = setTimeout(async () => {
+    dealerSearchLoading.value = true;
+    try {
+      const queryParams: MemberNotInCenterQuery = {
+        pageNum: 1,
+        pageSize: 10,
+        projectId: taskDetail.value?.projectId || 0,
+        folderId: 0,
+        name: query
+      };
+      const res = await queryMemberNotInCenter(queryParams);
+      dealerOptions.value = res.rows || [];
+    } catch (error) {
+      console.error('搜索处理人失败:', error);
+      ElMessage.error('搜索处理人失败');
+    } finally {
+      dealerSearchLoading.value = false;
+    }
+  }, 300);
+};
+
+/** 取消审核 */
+const cancelAudit = () => {
+  auditDialog.visible = false;
+  auditFormRef.value?.resetFields();
+  currentAuditRow.value = null;
+};
+
+/** 提交审核 */
+const submitAudit = () => {
+  auditFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      auditLoading.value = true;
+      try {
+        const submitData: TaskDetailAuditForm = {
+          taskId: auditForm.value.taskId,
+          detailId: auditForm.value.detailId,
+          result: auditForm.value.result
+        };
+
+        // 驳回时添加额外字段
+        if (auditForm.value.result === 2) {
+          submitData.rejectionType = auditForm.value.rejectionType;
+          submitData.opinion = auditForm.value.opinion;
+          submitData.designatedDealer = auditForm.value.designatedDealer;
+          submitData.deadline = auditForm.value.deadline;
+        }
+
+        await auditTaskDetail(submitData);
+        proxy?.$modal.msgSuccess('审核成功');
+        auditDialog.visible = false;
+        // 刷新任务列表
+        await fetchTaskItems();
+        // 刷新任务详情
+        await fetchTaskDetail();
+      } catch (error) {
+        console.error('审核失败:', error);
+      } finally {
+        auditLoading.value = false;
+      }
+    }
+  });
+};
+
+/** 下载文件 */
+const handleDownload = async (row: TaskDetailItem) => {
+  if (!row.actualDocument) {
+    ElMessage.warning('暂无文件可下载');
+    return;
+  }
+
+  await downloadDocumentFile(row.actualDocument, row.actualDocumentName || row.documentName);
+};
+
 const handleBack = () => {
-  router.push('/qc/task');
+  router.push('/task');
 };
 
 watch(

+ 2 - 2
src/views/Qc/task/list.vue

@@ -108,7 +108,7 @@
               @click="handleView(scope.row.id)">
               查看
             </el-button>
-            <el-button v-if="scope.row.status === 0" v-hasPermi="['qc:task:start']" type="primary" icon="VideoPlay"
+            <el-button v-if="scope.row.status === 0 && scope.row.initiator === userStore.userId" v-hasPermi="['qc:task:start']" type="primary" icon="VideoPlay"
               style="padding: 0 5px; font-size: 10px; height: 24px; margin-right: 5px" @click="handleStart(scope.row)">
               开始
             </el-button>
@@ -354,7 +354,7 @@ const submitForm = async () => {
 /** 查看任务详情 */
 const handleView = (taskId: string | number) => {
   proxy?.$router.push({
-    path: '/qc/task/detail',
+    path: '/task/detail',
     query: { id: taskId }
   });
 };

+ 155 - 0
src/views/home/taskCenter/qc/index.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="qc-task-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="text-lg font-bold">文件质控任务</span>
+        </div>
+      </template>
+
+      <!-- 搜索栏 -->
+      <el-form :model="queryParams" :inline="true" class="search-form">
+        <el-form-item label="任务名称">
+          <el-input v-model="queryParams.taskName" placeholder="请输入任务名称" clearable style="width: 200px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="项目名称">
+          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 200px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="文档名称">
+          <el-input v-model="queryParams.documentName" placeholder="请输入文档名称" clearable style="width: 200px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 120px">
+            <el-option label="待审核" :value="0" />
+            <el-option label="审核通过" :value="1" />
+            <el-option label="审核拒绝" :value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 任务列表 -->
+      <el-table v-loading="loading" :data="taskList" border style="margin-top: 10px">
+        <el-table-column prop="id" label="序号" width="80" align="center" />
+        <el-table-column prop="name" label="任务名称" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="projectName" label="项目名称" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="initiator" label="发起人" width="120" align="center" />
+        <el-table-column prop="executor" label="执行人" width="120" align="center" />
+        <el-table-column prop="status" label="状态" width="120" align="center">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0 || scope.row.status === 3" type="info">待审核</el-tag>
+            <el-tag v-else-if="scope.row.status === 1" type="success">审核通过</el-tag>
+            <el-tag v-else-if="scope.row.status === 2" type="danger">审核拒绝</el-tag>
+            <el-tag v-else type="warning">未知</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="note" label="备注" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="executeTime" label="执行时间" width="180" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.executeTime">{{ scope.row.executeTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="180" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.createTime">{{ scope.row.createTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getTaskList" />
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import { listQcTasks } from '@/api/home/taskCenter/qc';
+import { QcTaskVO, QcTaskQuery } from '@/api/home/taskCenter/qc/types';
+
+const loading = ref(false);
+
+// 查询参数
+const queryParams = reactive<QcTaskQuery>({
+  taskName: '',
+  projectName: '',
+  documentName: '',
+  status: undefined,
+  pageNum: 1,
+  pageSize: 10
+});
+
+// 任务列表数据
+const taskList = ref<QcTaskVO[]>([]);
+const total = ref(0);
+
+/**
+ * 获取任务列表
+ */
+const getTaskList = async () => {
+  try {
+    loading.value = true;
+    const res = await listQcTasks(queryParams);
+    if (res.code === 200) {
+      taskList.value = res.rows || [];
+      total.value = res.total || 0;
+    } else {
+      ElMessage.error(res.msg || '获取任务列表失败');
+    }
+  } catch (error) {
+    console.error('获取任务列表失败:', error);
+    ElMessage.error('获取任务列表失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+/**
+ * 处理搜索
+ */
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getTaskList();
+};
+
+/**
+ * 重置搜索
+ */
+const resetQuery = () => {
+  queryParams.taskName = '';
+  queryParams.projectName = '';
+  queryParams.documentName = '';
+  queryParams.status = undefined;
+  handleQuery();
+};
+
+onMounted(() => {
+  getTaskList();
+});
+</script>
+
+<style scoped lang="scss">
+.qc-task-container {
+  padding: 20px;
+
+  .search-form {
+    margin-bottom: 15px;
+  }
+
+  :deep(.el-table) {
+    .cell {
+      white-space: nowrap;
+    }
+  }
+}
+</style>

+ 0 - 7
src/views/home/taskCenter/qualityControl/index.vue

@@ -1,7 +0,0 @@
-<script setup lang="ts"></script>
-
-<template>
-  <div>文件质控任务</div>
-</template>
-
-<style scoped lang="scss"></style>