|
@@ -73,6 +73,29 @@
|
|
|
<el-table-column prop="executionTime" label="执行时间" width="180" />
|
|
<el-table-column prop="executionTime" label="执行时间" width="180" />
|
|
|
<el-table-column prop="finishTime" label="完成时间" width="180" />
|
|
<el-table-column prop="finishTime" label="完成时间" width="180" />
|
|
|
<el-table-column prop="note" label="备注" />
|
|
<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>
|
|
</el-table>
|
|
|
|
|
|
|
|
<div class="mt-4">
|
|
<div class="mt-4">
|
|
@@ -90,14 +113,91 @@
|
|
|
</el-result>
|
|
</el-result>
|
|
|
</div>
|
|
</div>
|
|
|
</el-card>
|
|
</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>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup name="TaskDetail" lang="ts">
|
|
<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 { 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 {
|
|
interface TaskDetailItem {
|
|
@@ -105,6 +205,8 @@ interface TaskDetailItem {
|
|
|
document: number;
|
|
document: number;
|
|
|
documentName: string;
|
|
documentName: string;
|
|
|
ossId: number;
|
|
ossId: number;
|
|
|
|
|
+ actualDocument: string;
|
|
|
|
|
+ actualDocumentName: string;
|
|
|
executor: number;
|
|
executor: number;
|
|
|
executorName: string;
|
|
executorName: string;
|
|
|
status: number;
|
|
status: number;
|
|
@@ -129,6 +231,8 @@ const taskItemsQuery = ref({
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
const route = useRoute();
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
|
|
+const userStore = useUserStore();
|
|
|
|
|
+const { qc_question_type } = toRefs<any>(proxy?.useDict('qc_question_type'));
|
|
|
|
|
|
|
|
// 从路由参数获取任务ID
|
|
// 从路由参数获取任务ID
|
|
|
const taskId = computed(() => {
|
|
const taskId = computed(() => {
|
|
@@ -142,6 +246,41 @@ const taskId = computed(() => {
|
|
|
const loading = ref(true);
|
|
const loading = ref(true);
|
|
|
const taskDetail = ref<TaskVO | null>(null);
|
|
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 () => {
|
|
const fetchTaskDetail = async () => {
|
|
|
if (!taskId.value) return;
|
|
if (!taskId.value) return;
|
|
|
|
|
|
|
@@ -191,8 +330,105 @@ const handleTaskItemsPagination = (pageNum: number, pageSize: number) => {
|
|
|
fetchTaskItems();
|
|
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 = () => {
|
|
const handleBack = () => {
|
|
|
- router.push('/qc/task');
|
|
|
|
|
|
|
+ router.push('/task');
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
watch(
|
|
watch(
|