| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- <template>
- <div class="page-container">
- <div class="page-header">
- <h1>{{ taskConfig.title }}</h1>
- <div class="header-actions">
- <el-button plain @click="handleShowGuide"><Icon icon="ep:question-filled" class="mr-5px" />使用指引</el-button>
- </div>
- </div>
- <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="task-tabs">
- <el-tab-pane v-for="tab in taskConfig.tabs" :key="tab.key" :label="tab.label" :name="tab.key">
- <el-table v-loading="loading" :data="tableData" stripe @selection-change="handleSelectionChange">
- <el-table-column v-for="column in taskColumns" :key="column.key || column.type" v-bind="getColumnProps(column)">
- <template v-if="column.type !== 'selection'" #default="{ row }">
- <template v-if="column.type === 'status'"><IqcStatusBadge :status="row.status" /></template>
- <template v-else-if="column.type === 'taskLink'">
- <el-link type="primary" @click="handleView(row)">{{ row[column.key] || '-' }}</el-link>
- </template>
- <template v-else-if="column.type === 'datetime'">{{ formatDateTime(row[column.key]) }}</template>
- <template v-else-if="column.type === 'priority'">
- <el-tag :color="getPriorityColor(row.priority)" size="small">{{ getPriorityName(row.priority) }}</el-tag>
- </template>
- <template v-else-if="column.type === 'batch'">
- <div class="batch-info">{{ row.batch || '-' }}<span v-if="row.batchCount > 1" class="batch-count">批次×{{ row.batchCount }}</span></div>
- </template>
- <template v-else-if="column.type === 'textLines'">
- <div class="cell-multi-line"><div v-for="line in column.lines" :key="line" class="cell-line">{{ row[line] || '-' }}</div></div>
- </template>
- <template v-else-if="column.type === 'formatter'">{{ column.formatter ? column.formatter(row) : (row[column.key] || '-') }}</template>
- <template v-else>{{ row[column.key] ?? '-' }}</template>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="240" fixed="right">
- <template #default="{ row }">
- <div class="action-buttons">
- <IqcPermissionButton v-if="row.status === 'pending'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleStartInspection(row)">开始检验</IqcPermissionButton>
- <IqcPermissionButton v-if="row.status === 'processing'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleContinueInspection(row)">继续检验</IqcPermissionButton>
- <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
- </div>
- </template>
- </el-table-column>
- </el-table>
- <div class="pagination-container">
- <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" />
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- </template>
- <script setup lang="ts">
- defineOptions({ name: 'IqcTaskList' })
- import { computed, ref, watch, onMounted } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import IqcStatusBadge from '@/components/Qms/StatusBadge.vue'
- import IqcPermissionButton from '@/components/Qms/PermissionButton.vue'
- import { useQmsTaskStore } from '@/store/modules/qms/task'
- import { getQmsModuleConfig, DEFAULT_QMS_MODULE } from '@/config/qmsModules'
- const route = useRoute()
- const router = useRouter()
- const taskStore = useQmsTaskStore()
- const loading = ref(false)
- const selectedRows = ref<any[]>([])
- const moduleCode = computed(() => (route.meta.module as string) || DEFAULT_QMS_MODULE)
- const taskConfig = computed(() => getQmsModuleConfig(moduleCode.value).taskList)
- const useRemoteData = computed(() => taskStore.isRemoteModule(moduleCode.value))
- const activeTab = computed({
- get: () => taskStore.activeTab(moduleCode.value),
- set: (value) => taskStore.setActiveTab(moduleCode.value, value)
- })
- const pagination = computed(() => taskStore.pagination[moduleCode.value])
- const taskColumns = computed(() => taskConfig.value.columns || [])
- const filteredList = computed(() => taskStore.tasksByStatus(moduleCode.value, activeTab.value))
- const tableData = computed(() => {
- const list = filteredList.value || []
- if (useRemoteData.value) return list
- const { currentPage, pageSize } = pagination.value
- const start = (currentPage - 1) * pageSize
- return list.slice(start, start + pageSize)
- })
- const totalCount = computed(() => (useRemoteData.value ? pagination.value.total : (filteredList.value || []).length))
- const PRIORITY_MAP: Record<string, string> = { high: '高', medium: '中', low: '低' }
- const PRIORITY_COLOR: Record<string, string> = { high: '#F56C6C', medium: '#E6A23C', low: '#67C23A' }
- const getPriorityName = (priority: string) => PRIORITY_MAP[priority] || '未知'
- const getPriorityColor = (priority: string) => PRIORITY_COLOR[priority] || '#909399'
- const formatDateTime = (value: any) => {
- if (!value) return '-'
- const date = typeof value === 'number' || /^\d+$/.test(String(value))
- ? new Date(Number(value))
- : new Date(String(value))
- if (Number.isNaN(date.getTime())) return String(value)
- const yyyy = String(date.getFullYear())
- const MM = String(date.getMonth() + 1).padStart(2, '0')
- const dd = String(date.getDate()).padStart(2, '0')
- const HH = String(date.getHours()).padStart(2, '0')
- const mm = String(date.getMinutes()).padStart(2, '0')
- const ss = String(date.getSeconds()).padStart(2, '0')
- return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
- }
- const getColumnProps = (column: any) => {
- if (column.type === 'selection') return { type: 'selection', width: column.width || 55, fixed: column.fixed || false }
- return { prop: column.key, label: column.label, width: column.width, minWidth: column.minWidth, fixed: column.fixed, showOverflowTooltip: column.type === 'tooltip' }
- }
- const handleSelectionChange = (selection: any[]) => { selectedRows.value = selection }
- const handleSizeChange = (size: number) => { taskStore.updatePagination(moduleCode.value, { pageSize: size, currentPage: 1 }); fetchData() }
- const handleCurrentChange = (page: number) => { taskStore.updatePagination(moduleCode.value, { currentPage: page }); fetchData() }
- const handleTabChange = () => { taskStore.updatePagination(moduleCode.value, { currentPage: 1 }); fetchData() }
- const goToProcessDetail = (row: any, processInstanceId?: string) => {
- const targetId = processInstanceId || row?.processInstanceId
- if (targetId) {
- router.push({ name: 'BpmProcessInstanceDetail', query: { id: targetId } })
- return
- }
- ElMessage.warning('流程未发起,请先点击开始检验')
- }
- const handleStartInspection = async (row: any) => {
- try {
- const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
- ElMessage.success('开始检验成功')
- goToProcessDetail(row, processInstanceId)
- } catch {
- ElMessage.error('开始检验失败')
- }
- }
- const handleContinueInspection = async (row: any) => {
- if (row?.processInstanceId) {
- goToProcessDetail(row)
- return
- }
- try {
- const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
- goToProcessDetail(row, processInstanceId)
- } catch {
- ElMessage.error('继续检验失败')
- }
- }
- const handleView = (row: any) => { goToProcessDetail(row) }
- const handleShowGuide = () => { ElMessage.info('使用指引功能开发中...') }
- const fetchData = async () => { loading.value = true; try { await taskStore.fetchTasks(moduleCode.value) } catch { ElMessage.error('获取任务列表失败') } finally { loading.value = false } }
- watch(moduleCode, () => { fetchData() })
- onMounted(() => { fetchData() })
- </script>
- <style lang="scss" scoped>
- .page-container { padding: 20px; min-height: calc(100vh - 72px); }
- .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; } }
- .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; } }
- .pagination-container { padding: 16px; display: flex; justify-content: flex-end; }
- .cell-multi-line { display: flex; flex-direction: column; gap: 2px; .cell-line { color: #606266; font-size: 12px; line-height: 1.4; } }
- .batch-info { display: flex; align-items: center; gap: 8px; .batch-count { color: #67C23A; font-size: 12px; } }
- .action-buttons { display: flex; gap: 8px; }
- @media (max-width: 768px) { .page-container { padding: 12px; } .page-header { flex-direction: column; align-items: flex-start; gap: 12px; } }
- </style>
|