Files
DashBoard/frontend/src/mid-section-defect/components/DetailTable.vue

140 lines
4.4 KiB
Vue

<script setup>
import { computed, ref } from 'vue';
import Pagination from '../../shared-ui/components/PaginationControl.vue';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
pagination: {
type: Object,
default: () => ({ page: 1, page_size: 200, total_count: 0, total_pages: 1 }),
},
});
const emit = defineEmits(['export-csv', 'prev-page', 'next-page']);
const sortField = ref('DEFECT_RATE');
const sortAsc = ref(false);
const COLUMNS = [
{ key: 'CONTAINERNAME', label: 'LOT ID', width: '140px' },
{ key: 'PJ_TYPE', label: 'TYPE', width: '80px' },
{ key: 'PRODUCTLINENAME', label: 'PACKAGE', width: '90px' },
{ key: 'WORKFLOW', label: 'WORKFLOW', width: '100px' },
{ key: 'TMTT_EQUIPMENTNAME', label: 'TMTT 設備', width: '110px' },
{ key: 'INPUT_QTY', label: '投入數', width: '70px', numeric: true },
{ key: 'LOSS_REASON', label: '不良原因', width: '130px' },
{ key: 'DEFECT_QTY', label: '不良數', width: '70px', numeric: true },
{ key: 'DEFECT_RATE', label: '不良率(%)', width: '90px', numeric: true },
{ key: 'ANCESTOR_COUNT', label: '上游LOT數', width: '80px', numeric: true },
{ key: 'UPSTREAM_MACHINES', label: '上游機台', width: '200px' },
];
const sortedData = computed(() => {
if (!props.data || !props.data.length) return [];
const field = sortField.value;
const asc = sortAsc.value;
return [...props.data].sort((a, b) => {
const va = a[field] ?? '';
const vb = b[field] ?? '';
if (typeof va === 'number' && typeof vb === 'number') {
return asc ? va - vb : vb - va;
}
const sa = String(va);
const sb = String(vb);
return asc ? sa.localeCompare(sb) : sb.localeCompare(sa);
});
});
const tableInfo = computed(() => {
const p = props.pagination;
const total = Number(p.total_count || 0);
if (total <= 0) return '暫無資料';
const page = Number(p.page || 1);
const pageSize = Number(p.page_size || 200);
const start = (page - 1) * pageSize + 1;
const end = Math.min(page * pageSize, total);
return `顯示 ${start} - ${end} 筆,共 ${total.toLocaleString()}`;
});
function toggleSort(field) {
if (sortField.value === field) {
sortAsc.value = !sortAsc.value;
} else {
sortField.value = field;
sortAsc.value = false;
}
}
function sortIcon(field) {
if (sortField.value !== field) return '';
return sortAsc.value ? ' ▲' : ' ▼';
}
function formatCell(value, col) {
if (value == null || value === '') return '-';
if (col.key === 'DEFECT_RATE') return Number(value).toFixed(2);
if (col.numeric) return Number(value).toLocaleString();
return value;
}
</script>
<template>
<section class="section-card">
<div class="section-inner">
<div class="detail-header">
<h3 class="section-title">LOT 明細</h3>
<div class="detail-actions">
<span class="detail-count">{{ tableInfo }}</span>
<button type="button" class="btn-sm" :disabled="loading" @click="$emit('export-csv')">
匯出 CSV
</button>
</div>
</div>
<div class="table-wrapper">
<table class="detail-table">
<thead>
<tr>
<th
v-for="col in COLUMNS"
:key="col.key"
:style="{ width: col.width }"
class="sortable"
@click="toggleSort(col.key)"
>
{{ col.label }}{{ sortIcon(col.key) }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, idx) in sortedData" :key="idx">
<td v-for="col in COLUMNS" :key="col.key" :class="{ numeric: col.numeric }">
{{ formatCell(row[col.key], col) }}
</td>
</tr>
<tr v-if="!sortedData.length">
<td :colspan="COLUMNS.length" class="empty-row">暫無資料</td>
</tr>
</tbody>
</table>
</div>
<Pagination
:visible="Number(pagination.total_pages || 1) > 1"
:page="Number(pagination.page || 1)"
:total-pages="Number(pagination.total_pages || 1)"
@prev="emit('prev-page')"
@next="emit('next-page')"
/>
</div>
</section>
</template>