<script lang="ts" setup>
|
import {ref, computed, watch, onMounted} from 'vue'
|
import {useI18n} from 'vue-i18n'
|
import {ElMessage, ElTransfer, ElTabs, ElTabPane, ElButton} from 'element-plus'
|
import request from '@/utils/request'
|
|
const {t} = useI18n()
|
|
const activeTab = ref('cutting1')
|
|
// Transfer组件所需的数据格式
|
interface TransferDataItem {
|
key: string
|
label: string
|
projectNo: string
|
projectName: string
|
}
|
|
// Transfer组件所需的数据格式 - 完善字段定义
|
interface TransferDataItem {
|
key: string
|
projectNo: string
|
projectName: string
|
state?: number
|
type?: number
|
glassThickness?: string // 玻璃厚度
|
glassType?: string // 玻璃类型
|
glassTotal?: number // 玻璃总数
|
glassTotalArea?: number // 玻璃总面积
|
}
|
|
// 所有可选数据(左侧表格)
|
const dataSource = ref<TransferDataItem[]>([])
|
// 已选数据(右侧表格)
|
const rightDataSource = ref<TransferDataItem[]>([])
|
// 左侧表格选中项
|
const selectedLeft = ref<TransferDataItem[]>([])
|
// 右侧表格选中项
|
const selectedRight = ref<TransferDataItem[]>([])
|
// 详情数据状态(改为数组类型,适应列表返回)
|
const detailData = ref<any[]>([]);
|
const showDetail = ref(false);
|
// 获取项目详情接口(更新为实际接口)
|
const fetchProjectDetail = async (projectNo: string) => {
|
try {
|
const response = await request.post('/loadGlass/optimizeProject/selectProgress', {
|
projectNo: projectNo // 传入工程ID(projectNo)
|
});
|
if (response.code === 200) {
|
detailData.value = response.data; // 假设返回数组
|
showDetail.value = true;
|
} else {
|
ElMessage.error(`获取详情失败: ${response.message || '未知错误'}`);
|
}
|
} catch (error) {
|
console.error('获取项目详情失败:', error);
|
ElMessage.error('获取详情失败,请稍后重试');
|
}
|
};
|
|
// 获取左侧数据源 - 更新映射逻辑(包含所有字段)
|
const fetchDataSource = async () => {
|
try {
|
const apiUrl = activeTab.value === 'tempered'
|
? '/cacheVerticalGlass/bigStorageCageDetails/queryEngineer'
|
: '/loadGlass/optimizeProject/queryEngineer';
|
|
const response = await request.post(apiUrl)
|
|
if (response.code === 200) {
|
dataSource.value = response.data.map((item: any) => ({
|
key: activeTab.value === 'tempered' ? item.engineerId : item.projectNo,
|
projectNo: activeTab.value === 'tempered' ? item.engineerId : item.projectNo,
|
projectName: item.projectName || '',
|
state: item.state,
|
type: item.type,
|
glassThickness: item.glassThickness || '',
|
glassType: item.glassType || '',
|
glassTotal: item.glassTotal || 0,
|
glassTotalArea: item.glassTotalArea || 0
|
}))
|
} else {
|
ElMessage.error(`获取数据失败: ${response.message || '未知错误'}`)
|
}
|
} catch (error) {
|
console.error('获取左侧数据失败:', error)
|
ElMessage.error('请稍后重试')
|
}
|
}
|
|
// 获取右侧已选数据 - 直接填充右侧表格
|
const fetchTargetKeys = async () => {
|
try {
|
let type = 1;
|
let response;
|
if (activeTab.value === 'cutting2') {
|
type = 2;
|
response = await request.post('/loadGlass/optimizeProject/engineerScheduling', {type})
|
} else if (activeTab.value === 'tempered') {
|
type = 3;
|
response = await request.post('/cacheVerticalGlass/bigStorageCageDetails/queryTemperingOrder')
|
} else {
|
response = await request.post('/loadGlass/optimizeProject/engineerScheduling', {type})
|
}
|
if (response.code === 200) {
|
// 右侧表格数据
|
rightDataSource.value = response.data.map((item: any) => ({
|
key: item.projectNo,
|
projectNo: item.projectNo,
|
projectName: item.projectName || '',
|
state: item.state,
|
type: item.type,
|
glassThickness: item.glassThickness || '',
|
glassType: item.glassType || '',
|
glassTotal: item.glassTotal || 0,
|
glassTotalArea: item.glassTotalArea || 0
|
}))
|
|
// 从左侧数据源移除右侧已选数据
|
dataSource.value = dataSource.value.filter(
|
item => !rightDataSource.value.some(rightItem => rightItem.key === item.key)
|
)
|
} else {
|
ElMessage.error(`获取数据失败: ${response.message || '未知错误'}`)
|
}
|
} catch (error) {
|
console.error('获取右侧数据失败:', error)
|
ElMessage.error('请稍后重试')
|
}
|
}
|
|
// 修改:支持单行数据移动到右侧表格末尾
|
const moveToRight = (row: TransferDataItem) => {
|
if (!row) return;
|
// 从左侧表格移除当前行
|
dataSource.value = dataSource.value.filter(item => item.key !== row.key);
|
// 添加到右侧表格末尾
|
rightDataSource.value.push(row);
|
ElMessage.success(`已添加项目 ${row.projectNo}`);
|
}
|
|
|
// 修改:支持单行数据从右侧表格移回左侧表格
|
const moveToLeft = (row: TransferDataItem) => {
|
if (!row) return;
|
|
// 从右侧表格移除当前行
|
rightDataSource.value = rightDataSource.value.filter(item => item.key !== row.key);
|
|
// 添加到左侧表格
|
dataSource.value.push(row);
|
|
ElMessage.success(`已移除项目 ${row.projectNo}`);
|
}
|
|
// 更新保存逻辑 - 使用右侧表格数据
|
const saveScheduling = async () => {
|
try {
|
let type = 1;
|
if (activeTab.value === 'cutting2') type = 2;
|
else if (activeTab.value === 'tempered') type = 3;
|
|
// 右侧表格数据即为需要保存的排产数据
|
const engineerList = rightDataSource.value.map(item => ({
|
projectNo: item.projectNo,
|
projectName: item.projectName
|
}))
|
|
const response = await request.post(
|
`/loadGlass/optimizeProject/updateEngineerScheduling?type=${type}`,
|
engineerList
|
)
|
|
if (response.code === 200) {
|
ElMessage.success('保存成功')
|
await fetchDataSource()
|
await fetchTargetKeys()
|
} else {
|
ElMessage.error(`保存失败: ${response.message || '未知错误'}`)
|
}
|
} catch (error) {
|
console.error('保存排产信息失败:', error)
|
ElMessage.error('请稍后重试')
|
}
|
}
|
|
// 重置排产信息
|
const resetScheduling = async () => {
|
await fetchDataSource()
|
await fetchTargetKeys()
|
ElMessage.info(t('scheduling.cancelled'))
|
}
|
|
// 监听标签页切换,根据不同标签页加载对应的数据
|
watch(activeTab, async (newTab) => {
|
// 重新获取数据
|
await fetchDataSource()
|
await fetchTargetKeys()
|
})
|
|
|
// 移除formatFunc,使用默认配置,Element Plus Transfer组件默认就会显示数量统计
|
// 如果需要自定义标题显示,可以通过titles属性处理
|
|
// 组件挂载时获取数据
|
onMounted(async () => {
|
await fetchDataSource()
|
await fetchTargetKeys()
|
})
|
|
// 判断是否为第一行
|
const isFirstRow = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
return index === 0;
|
};
|
|
// 判断是否为最后一行
|
const isLastRow = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
return index === rightDataSource.value.length - 1;
|
};
|
|
// 判断上方是否有进行中的行(修复:使用正确的状态值1)
|
const hasInProgressAbove = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
// 查找当前行上方是否有进行中的任务
|
return rightDataSource.value.some((item, i) => i < index && item.state === 1);
|
};
|
|
// 向上移动一行
|
const moveUp = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
if (index > 0) {
|
// 检查是否会超过进行中的任务
|
const prevItem = rightDataSource.value[index - 1];
|
if (prevItem.state === 1) {
|
// 直接上方是进行中任务,提示用户不可超过
|
ElMessage.warning('操作不可超过进行中的任务');
|
return;
|
}
|
// 交换位置
|
[rightDataSource.value[index], rightDataSource.value[index - 1]] =
|
[rightDataSource.value[index - 1], rightDataSource.value[index]];
|
// 触发响应式更新
|
rightDataSource.value = [...rightDataSource.value];
|
}
|
};
|
|
// 向下移动一行
|
const moveDown = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
if (index < rightDataSource.value.length - 1) {
|
// 交换位置
|
[rightDataSource.value[index], rightDataSource.value[index + 1]] =
|
[rightDataSource.value[index + 1], rightDataSource.value[index]];
|
// 触发响应式更新
|
rightDataSource.value = [...rightDataSource.value];
|
}
|
};
|
|
// 判断是否可以置顶(即是否有可置顶的位置)
|
const canMoveToTop = (row: TransferDataItem) => {
|
// 进行中任务不能置顶
|
if (row.state === 1) return false;
|
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
// 已经是第一行的不能置顶
|
if (index === 0) return false;
|
|
// 查找第一个非进行中任务的位置
|
const firstNonProgressIndex = rightDataSource.value.findIndex(item => item.state !== 1);
|
|
// 如果当前行已经在第一个非进行中任务的位置或之前,则不能置顶
|
return index > firstNonProgressIndex;
|
};
|
|
// ... existing code (moveUp和moveDown函数保持不变)
|
|
// 置顶(移动到所有非进行中任务的最前面)
|
const moveToTop = (row: TransferDataItem) => {
|
const index = rightDataSource.value.findIndex(item => item.key === row.key);
|
if (index > 0) {
|
// 移除当前行
|
const newList = rightDataSource.value.filter(item => item.key !== row.key);
|
|
// 查找所有非进行中任务的位置
|
const nonProgressIndices = newList
|
.map((item, i) => ({ item, index: i }))
|
.filter(item => item.item.state !== 1)
|
.map(item => item.index);
|
|
if (nonProgressIndices.length > 0) {
|
// 有非进行中任务,添加到第一个非进行中任务的位置
|
newList.splice(nonProgressIndices[0], 0, row);
|
} else {
|
// 全部都是进行中任务,添加到开头
|
newList.unshift(row);
|
}
|
|
// 更新数据源
|
rightDataSource.value = newList;
|
}
|
};
|
// 创建计算属性,根据标签页返回不同的表头配置
|
const tableHeaders = computed(() => {
|
const baseHeaders = {
|
serial: t('scheduling.serial'),
|
projectNo: t('scheduling.projectNo'),
|
thickness: t('scheduling.thickness'),
|
glassType: t('scheduling.glassType'),
|
totalCount: t('scheduling.totalCount'),
|
totalArea: t('scheduling.totalArea'),
|
operate: t('scheduling.operate')
|
};
|
// 根据不同标签页返回不同的表头配置
|
if (activeTab.value === 'tempered') {
|
return {
|
...baseHeaders,
|
// 更改钢化标签页的特定表头
|
totalCount: t('scheduling.totalFireCount'),
|
totalArea: t('scheduling.fullFireCount')
|
};
|
}
|
|
return baseHeaders;
|
});
|
// 状态格式化函数
|
const stateFormatter = (row: any) => {
|
return row.state === 1 ? '进行中' : '未开始';
|
};
|
|
</script>
|
|
<template>
|
<div class="engineer-scheduling-container">
|
<el-tabs v-model="activeTab" class="custom-tabs">
|
<el-tab-pane :label="t('large.countOutOne')" name="cutting1"/>
|
<el-tab-pane :label="t('large.countOutTwo')" name="cutting2"/>
|
<el-tab-pane :label="t('large.temp')" name="tempered"/>
|
</el-tabs>
|
|
<!-- 表格布局容器 -->
|
<div class="table-container">
|
<!-- 左侧表格:待排产 -->
|
<div class="table-wrapper" v-if="activeTab !== 'tempered'">
|
<h3 class="table-title">{{ t('scheduling.unfinished') }} ({{ dataSource.length }})</h3>
|
<el-table
|
:data="dataSource"
|
border
|
class="custom-table"
|
height="400"
|
row-key="key"
|
>
|
<el-table-column type="index" :label="$t('scheduling.serial')" width="55"/>
|
<el-table-column prop="projectNo" :label="$t('scheduling.projectNo')" width="150"/>
|
<el-table-column prop="glassThickness" :label="$t('scheduling.thickness')" width="120"/>
|
<el-table-column prop="glassType" :label="$t('scheduling.glassType')" width="120"/>
|
<el-table-column prop="glassTotal" :label="$t('scheduling.totalCount')" width="90"/>
|
<el-table-column prop="glassTotalArea" :label="$t('scheduling.totalArea')" width="100"/>
|
<el-table-column :label="$t('scheduling.operate')" width="90" align="center">
|
<template #default="{ row }">
|
<el-button type="primary" @click="moveToRight(row)">
|
{{ t('scheduling.add') }}
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
|
<!-- 右侧表格:已排产 -->
|
<div class="table-wrapper">
|
<h3 class="table-title">{{ t('scheduling.completed') }} ({{ rightDataSource.length }})</h3>
|
<el-table
|
:data="rightDataSource"
|
border
|
class="custom-table"
|
height="400"
|
row-key="key"
|
@row-click="(row) => fetchProjectDetail(row.key)"
|
highlight-current-row
|
>
|
<el-table-column type="index" :label="tableHeaders.serial" width="55"/>
|
<el-table-column prop="projectNo" :label="tableHeaders.projectNo" width="120"/>
|
<el-table-column prop="glassThickness" :label="tableHeaders.thickness" width="120"/>
|
<el-table-column prop="glassType" :label="tableHeaders.glassType" width="120"/>
|
<el-table-column prop="glassTotal" :label="tableHeaders.totalCount" width="90"/>
|
<el-table-column prop="glassTotalArea" :label="tableHeaders.totalArea" width="120"/>
|
<el-table-column
|
prop="state"
|
:label="t('scheduling.state')"
|
width="90"
|
:formatter="stateFormatter"
|
/>
|
<el-table-column :label="t('scheduling.operate')" :width="activeTab === 'tempered' ? 200 : 300" :align="center">
|
<template #default="{ row }">
|
<div style="display: flex; gap: 5px; align-items: center;">
|
<el-button
|
v-if="activeTab !== 'tempered'"
|
type="default"
|
size="small"
|
@click="moveToLeft(row)"
|
:disabled="row.state === 1"
|
style="background: #ff4d4f; color: white; border-radius: 8px; min-width: 60px; display: inline-flex; justify-content: center; align-items: center;"
|
>
|
{{ t('scheduling.remove') }}
|
</el-button>
|
<!-- 向上按钮 -->
|
<el-button
|
type="default"
|
size="small"
|
@click="moveUp(row)"
|
:disabled="row.state === 1 || isFirstRow(row)"
|
style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 40px; display: inline-flex; justify-content: center; align-items: center;"
|
>
|
↑
|
</el-button>
|
<!-- 向下按钮 -->
|
<el-button
|
type="default"
|
size="small"
|
@click="moveDown(row)"
|
:disabled="row.state === 1 || isLastRow(row)"
|
style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 40px; display: inline-flex; justify-content: center; align-items: center;"
|
>
|
↓
|
</el-button>
|
|
<!-- 置顶按钮 -->
|
<el-button
|
size="small"
|
@click="moveToTop(row)"
|
:disabled="row.state === 1 || isFirstRow(row) || !canMoveToTop(row)"
|
style="background: #E6F4FF; color: #1890FF; border-radius: 8px; min-width: 60px; display: inline-flex; justify-content: center; align-items: center;"
|
>
|
{{ t('scheduling.top') }}
|
</el-button>
|
</div>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</div>
|
|
<!-- 保存和重置按钮 -->
|
<div class="transfer-save">
|
<el-button type="primary" @click="saveScheduling">
|
{{ t('searchOrder.makesure') }}
|
</el-button>
|
<el-button type="primary" @click="resetScheduling">
|
{{ t('delivery.cancel') }}
|
</el-button>
|
</div>
|
</div>
|
<!-- 详情展示区域(改为表格展示列表数据) -->
|
<div v-if="showDetail" class="project-detail">
|
<!-- <h3 class="detail-title">工程详情列表 ({{ detailData.length }})</h3>-->
|
<el-table
|
:data="detailData"
|
border
|
class="detail-table"
|
height="230"
|
>
|
<el-table-column prop="engineerId" :label="$t('scheduling.projectNo')" width="120"/>
|
<el-table-column prop="flowCardId" :label="$t('scheduling.flowCardId')" width="150"/>
|
<el-table-column prop="layer" :label="$t('scheduling.layerCount')" width="80"/>
|
<el-table-column prop="glassType" :label="$t('scheduling.serial')" width="80"/>
|
<el-table-column prop="thickness" :label="$t('scheduling.thickness')" width="100"/>
|
<el-table-column prop="filmsid" :label="$t('scheduling.coatingType')" width="120"/>
|
<el-table-column prop="width" :label="$t('scheduling.width')" width="90"/>
|
<el-table-column prop="height" :label="$t('scheduling.height')" width="90"/>
|
<el-table-column prop="glassIdCount" :label="$t('scheduling.glassCount')" width="80"/>
|
<el-table-column prop="cuttingCount" :label="$t('scheduling.cuttingCount')" width="80"/>
|
<el-table-column prop="edgingCount" :label="$t('scheduling.edgingCount')" width="80"/>
|
<el-table-column prop="temperingCount" :label="$t('scheduling.temperingCount')" width="80"/>
|
<el-table-column prop="insulatingCount" :label="$t('scheduling.insulatingCount')" width="80"/>
|
</el-table>
|
</div>
|
</template>
|
|
|
<style scoped>
|
/* 表格布局容器 */
|
.table-container {
|
display: flex;
|
align-items: center;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
/* 详情表格样式 */
|
.project-detail {
|
margin-top: 0px;
|
padding: 15px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
width: 100%;
|
box-sizing: border-box;
|
height: auto; /* 确保容器高度自适应 */
|
}
|
|
.detail-title {
|
margin-bottom: 15px;
|
font-size: 16px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.detail-table {
|
width: 100%;
|
font-size: 14px;
|
}
|
|
/* 强制表格内容区域滚动 */
|
::v-deep(.detail-table .el-table__body-wrapper) {
|
overflow-y: auto !important; /* 优先确保垂直滚动 */
|
max-height: calc(230px - 50px); /* 适配表格高度(需与el-table height匹配) */
|
}
|
|
.table-wrapper {
|
flex: 1;
|
}
|
|
.table-title {
|
margin-bottom: 10px;
|
font-size: 18px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.custom-table {
|
width: 100%;
|
font-size: 14px;
|
}
|
|
/* 转移按钮样式 */
|
.transfer-buttons {
|
display: flex;
|
flex-direction: column;
|
}
|
|
/* 调整按钮区域位置 */
|
.transfer-save {
|
display: flex;
|
justify-content: center;
|
gap: 10px;
|
margin-top: 20px;
|
}
|
|
.engineer-scheduling-container {
|
padding: 20px;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
background-color: #fff;
|
}
|
|
.custom-tabs {
|
margin-bottom: 20px;
|
}
|
|
::v-deep(.custom-tabs .el-tabs__item) {
|
font-size: 20px;
|
}
|
|
.transfer-wrapper {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 20px;
|
}
|
|
.transfer-save {
|
display: flex;
|
gap: 10px;
|
}
|
|
/* 自定义Transfer组件的样式 */
|
::v-deep(.el-transfer-panel) {
|
font-size: 16px;
|
width: 350px;
|
height: 440px;
|
}
|
|
.custom-transfer {
|
--el-transfer-panel-body-height: 400px;
|
}
|
|
::v-deep(.el-transfer-panel__header) {
|
font-weight: bold;
|
font-size: 18px;
|
}
|
|
::v-deep(.el-checkbox__label) {
|
font-size: 16px;
|
}
|
</style>
|