TaskList.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <template>
  2. <div class="page-container">
  3. <div class="page-header">
  4. <h1>{{ taskConfig.title }}</h1>
  5. <div class="header-actions">
  6. <el-button plain @click="handleShowGuide"><Icon icon="ep:question-filled" class="mr-5px" />使用指引</el-button>
  7. </div>
  8. </div>
  9. <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="task-tabs">
  10. <el-tab-pane v-for="tab in taskConfig.tabs" :key="tab.key" :label="tab.label" :name="tab.key">
  11. <el-table v-loading="loading" :data="tableData" stripe @selection-change="handleSelectionChange">
  12. <el-table-column v-for="column in taskColumns" :key="column.key || column.type" v-bind="getColumnProps(column)">
  13. <template v-if="column.type !== 'selection'" #default="{ row }">
  14. <template v-if="column.type === 'status'"><IqcStatusBadge :status="row.status" /></template>
  15. <template v-else-if="column.type === 'taskLink'">
  16. <el-link type="primary" @click="handleView(row)">{{ row[column.key] || '-' }}</el-link>
  17. </template>
  18. <template v-else-if="column.type === 'datetime'">{{ formatDateTime(row[column.key]) }}</template>
  19. <template v-else-if="column.type === 'priority'">
  20. <el-tag :color="getPriorityColor(row.priority)" size="small">{{ getPriorityName(row.priority) }}</el-tag>
  21. </template>
  22. <template v-else-if="column.type === 'batch'">
  23. <div class="batch-info">{{ row.batch || '-' }}<span v-if="row.batchCount > 1" class="batch-count">批次×{{ row.batchCount }}</span></div>
  24. </template>
  25. <template v-else-if="column.type === 'textLines'">
  26. <div class="cell-multi-line"><div v-for="line in column.lines" :key="line" class="cell-line">{{ row[line] || '-' }}</div></div>
  27. </template>
  28. <template v-else-if="column.type === 'formatter'">{{ column.formatter ? column.formatter(row) : (row[column.key] || '-') }}</template>
  29. <template v-else>{{ row[column.key] ?? '-' }}</template>
  30. </template>
  31. </el-table-column>
  32. <el-table-column label="操作" width="240" fixed="right">
  33. <template #default="{ row }">
  34. <div class="action-buttons">
  35. <IqcPermissionButton v-if="row.status === 'pending'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleStartInspection(row)">开始检验</IqcPermissionButton>
  36. <IqcPermissionButton v-if="row.status === 'processing'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleContinueInspection(row)">继续检验</IqcPermissionButton>
  37. <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
  38. </div>
  39. </template>
  40. </el-table-column>
  41. </el-table>
  42. <div class="pagination-container">
  43. <el-pagination :current-page="pagination.currentPage" :page-size="pagination.pageSize" :total="totalCount" :page-sizes="[20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  44. </div>
  45. </el-tab-pane>
  46. </el-tabs>
  47. </div>
  48. </template>
  49. <script setup lang="ts">
  50. defineOptions({ name: 'IqcTaskList' })
  51. import { computed, ref, watch, onMounted } from 'vue'
  52. import { useRoute, useRouter } from 'vue-router'
  53. import IqcStatusBadge from '@/components/Qms/StatusBadge.vue'
  54. import IqcPermissionButton from '@/components/Qms/PermissionButton.vue'
  55. import { useQmsTaskStore } from '@/store/modules/qms/task'
  56. import { getQmsModuleConfig, DEFAULT_QMS_MODULE } from '@/config/qmsModules'
  57. const route = useRoute()
  58. const router = useRouter()
  59. const taskStore = useQmsTaskStore()
  60. const loading = ref(false)
  61. const selectedRows = ref<any[]>([])
  62. const moduleCode = computed(() => (route.meta.module as string) || DEFAULT_QMS_MODULE)
  63. const taskConfig = computed(() => getQmsModuleConfig(moduleCode.value).taskList)
  64. const useRemoteData = computed(() => taskStore.isRemoteModule(moduleCode.value))
  65. const activeTab = computed({
  66. get: () => taskStore.activeTab(moduleCode.value),
  67. set: (value) => taskStore.setActiveTab(moduleCode.value, value)
  68. })
  69. const pagination = computed(() => taskStore.pagination[moduleCode.value])
  70. const taskColumns = computed(() => taskConfig.value.columns || [])
  71. const filteredList = computed(() => taskStore.tasksByStatus(moduleCode.value, activeTab.value))
  72. const tableData = computed(() => {
  73. const list = filteredList.value || []
  74. if (useRemoteData.value) return list
  75. const { currentPage, pageSize } = pagination.value
  76. const start = (currentPage - 1) * pageSize
  77. return list.slice(start, start + pageSize)
  78. })
  79. const totalCount = computed(() => (useRemoteData.value ? pagination.value.total : (filteredList.value || []).length))
  80. const PRIORITY_MAP: Record<string, string> = { high: '高', medium: '中', low: '低' }
  81. const PRIORITY_COLOR: Record<string, string> = { high: '#F56C6C', medium: '#E6A23C', low: '#67C23A' }
  82. const getPriorityName = (priority: string) => PRIORITY_MAP[priority] || '未知'
  83. const getPriorityColor = (priority: string) => PRIORITY_COLOR[priority] || '#909399'
  84. const formatDateTime = (value: any) => {
  85. if (!value) return '-'
  86. const date = typeof value === 'number' || /^\d+$/.test(String(value))
  87. ? new Date(Number(value))
  88. : new Date(String(value))
  89. if (Number.isNaN(date.getTime())) return String(value)
  90. const yyyy = String(date.getFullYear())
  91. const MM = String(date.getMonth() + 1).padStart(2, '0')
  92. const dd = String(date.getDate()).padStart(2, '0')
  93. const HH = String(date.getHours()).padStart(2, '0')
  94. const mm = String(date.getMinutes()).padStart(2, '0')
  95. const ss = String(date.getSeconds()).padStart(2, '0')
  96. return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
  97. }
  98. const getColumnProps = (column: any) => {
  99. if (column.type === 'selection') return { type: 'selection', width: column.width || 55, fixed: column.fixed || false }
  100. return { prop: column.key, label: column.label, width: column.width, minWidth: column.minWidth, fixed: column.fixed, showOverflowTooltip: column.type === 'tooltip' }
  101. }
  102. const handleSelectionChange = (selection: any[]) => { selectedRows.value = selection }
  103. const handleSizeChange = (size: number) => { taskStore.updatePagination(moduleCode.value, { pageSize: size, currentPage: 1 }); fetchData() }
  104. const handleCurrentChange = (page: number) => { taskStore.updatePagination(moduleCode.value, { currentPage: page }); fetchData() }
  105. const handleTabChange = () => { taskStore.updatePagination(moduleCode.value, { currentPage: 1 }); fetchData() }
  106. const goToProcessDetail = (row: any, processInstanceId?: string) => {
  107. const targetId = processInstanceId || row?.processInstanceId
  108. if (targetId) {
  109. router.push({ name: 'BpmProcessInstanceDetail', query: { id: targetId } })
  110. return
  111. }
  112. ElMessage.warning('流程未发起,请先点击开始检验')
  113. }
  114. const handleStartInspection = async (row: any) => {
  115. try {
  116. const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
  117. ElMessage.success('开始检验成功')
  118. goToProcessDetail(row, processInstanceId)
  119. } catch {
  120. ElMessage.error('开始检验失败')
  121. }
  122. }
  123. const handleContinueInspection = async (row: any) => {
  124. if (row?.processInstanceId) {
  125. goToProcessDetail(row)
  126. return
  127. }
  128. try {
  129. const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
  130. goToProcessDetail(row, processInstanceId)
  131. } catch {
  132. ElMessage.error('继续检验失败')
  133. }
  134. }
  135. const handleView = (row: any) => { goToProcessDetail(row) }
  136. const handleShowGuide = () => { ElMessage.info('使用指引功能开发中...') }
  137. const fetchData = async () => { loading.value = true; try { await taskStore.fetchTasks(moduleCode.value) } catch { ElMessage.error('获取任务列表失败') } finally { loading.value = false } }
  138. watch(moduleCode, () => { fetchData() })
  139. onMounted(() => { fetchData() })
  140. </script>
  141. <style lang="scss" scoped>
  142. .page-container { padding: 20px; min-height: calc(100vh - 72px); }
  143. .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; h1 { font-size: 20px; font-weight: 600; color: #303133; margin: 0; } .header-actions { display: flex; gap: 12px; } }
  144. .task-tabs { background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); :deep(.el-tabs__header) { margin: 0; padding: 0 20px; background: #fafafa; border-bottom: 1px solid #ebeef5; } :deep(.el-tabs__content) { padding: 0; } }
  145. .pagination-container { padding: 16px; display: flex; justify-content: flex-end; }
  146. .cell-multi-line { display: flex; flex-direction: column; gap: 2px; .cell-line { color: #606266; font-size: 12px; line-height: 1.4; } }
  147. .batch-info { display: flex; align-items: center; gap: 8px; .batch-count { color: #67C23A; font-size: 12px; } }
  148. .action-buttons { display: flex; gap: 8px; }
  149. @media (max-width: 768px) { .page-container { padding: 12px; } .page-header { flex-direction: column; align-items: flex-start; gap: 12px; } }
  150. </style>