<template>
|
<view class="main-production-plan">
|
<!-- 使用通用页面头部组件 -->
|
<PageHeader title="主生产计划"
|
@back="goBack">
|
<!-- <template #right>
|
<view class="header-right">
|
<u-button v-if="!selectMode"
|
size="mini"
|
@click="enterSelectMode">合并下发</u-button>
|
<u-button v-else
|
size="mini"
|
@click="exitSelectMode">取消</u-button>
|
</view>
|
</template> -->
|
</PageHeader>
|
<!-- 搜索区域 -->
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input class="search-text"
|
placeholder="请输入计划号或产品名称"
|
v-model="searchForm.keyword"
|
@change="handleQuery"
|
clearable />
|
</view>
|
<view class="filter-button"
|
@click="handleQuery">
|
<up-icon name="search"
|
size="24"
|
color="#999"></up-icon>
|
</view>
|
</view>
|
</view>
|
<!-- 列表区域 -->
|
<scroll-view scroll-y
|
class="list-container"
|
v-if="tableData.length > 0">
|
<up-checkbox-group v-if="selectMode"
|
v-model="selectedIds"
|
placement="column">
|
<view v-for="(item, index) in tableData"
|
:key="item.id || index"
|
@click="toggleSelect(item)">
|
<view class="ledger-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="file-text"
|
size="16"
|
color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.mpsNo }}</text>
|
</view>
|
<view class="item-right">
|
<up-checkbox :name="String(item.id)"
|
:label="''"
|
@click.stop></up-checkbox>
|
</view>
|
</view>
|
<up-divider></up-divider>
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">产品名称</text>
|
<text class="detail-value">{{ item.productName || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">规格型号</text>
|
<text class="detail-value">{{ item.model || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">可下发数量</text>
|
<text class="detail-value highlight">{{ getRemainingQty(item) }} {{ item.unit || '方' }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</up-checkbox-group>
|
<view v-else>
|
<view v-for="(item, index) in tableData"
|
:key="item.id || index"
|
@click="goDetail(item)">
|
<view class="ledger-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="file-text"
|
size="16"
|
color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.mpsNo }}</text>
|
</view>
|
<view class="item-right">
|
<up-tag :text="getStatusText(item.status)"
|
:type="getStatusType(item.status)"
|
size="mini" />
|
</view>
|
</view>
|
<up-divider></up-divider>
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">产品名称</text>
|
<text class="detail-value">{{ item.productName || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">规格型号</text>
|
<text class="detail-value">{{ item.model || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">所需数量</text>
|
<text class="detail-value highlight">{{ item.qtyRequired || 0 }} {{ item.unit || '方' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">已下发数量</text>
|
<text class="detail-value">{{ item.quantityIssued || 0 }} {{ item.unit || '方' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">需求日期</text>
|
<text class="detail-value">{{ formatDate(item.requiredDate) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">来源</text>
|
<text class="detail-value">{{ item.source === '销售' ? '销售' : '内部' }}</text>
|
</view>
|
</view>
|
<view class="action-buttons">
|
<u-button v-if="canEdit(item)"
|
size="small"
|
class="action-btn"
|
@click.stop="goEdit(item)">编辑</u-button>
|
<u-button v-if="canIssue(item)"
|
size="small"
|
class="action-btn"
|
type="primary"
|
@click.stop="goIssue([item])">下发</u-button>
|
<u-button v-if="canDelete(item)"
|
size="small"
|
class="action-btn"
|
type="error"
|
@click.stop="handleDelete(item)">删除</u-button>
|
</view>
|
</view>
|
</view>
|
</view>
|
</scroll-view>
|
<view v-else
|
class="no-data">
|
<up-empty mode="data"
|
text="暂无主生产计划数据"></up-empty>
|
</view>
|
<FooterButtons v-if="selectMode"
|
cancelText="取消"
|
confirmText="下发"
|
:confirmDisabled="selectedIds.length === 0"
|
@cancel="exitSelectMode"
|
@confirm="goIssueSelected" />
|
<view class="fab-button"
|
@click="goAdd">
|
<up-icon name="plus"
|
size="24"
|
color="#ffffff"></up-icon>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, toRefs } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import dayjs from "dayjs";
|
import {
|
productionPlanDelete,
|
productionPlanListPage,
|
} from "@/api/productionManagement/productionPlan.js";
|
import PageHeader from "@/components/PageHeader.vue";
|
import FooterButtons from "@/components/FooterButtons.vue";
|
|
// 加载状态
|
const loading = ref(false);
|
// 列表数据
|
const tableData = ref([]);
|
|
const page = reactive({
|
current: -1,
|
size: -1,
|
});
|
|
// 搜索表单数据
|
const data = reactive({
|
searchForm: {
|
keyword: "",
|
mpsNo: "",
|
productName: "",
|
},
|
});
|
const { searchForm } = toRefs(data);
|
|
const selectMode = ref(false);
|
const selectedIds = ref([]);
|
|
// 返回上一页
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
// 格式化日期
|
const formatDate = date => {
|
return date ? dayjs(date).format("YYYY-MM-DD") : "-";
|
};
|
|
// 获取状态文本
|
const getStatusText = status => {
|
const statusMap = {
|
0: "待下发",
|
1: "部分下发",
|
2: "已下发",
|
};
|
return statusMap[status] || "未知";
|
};
|
|
// 获取状态类型 (uView tag type)
|
const getStatusType = status => {
|
const typeMap = {
|
0: "warning",
|
1: "primary",
|
2: "info",
|
};
|
return typeMap[status] || "info";
|
};
|
|
const getRemainingQty = row => {
|
const required = Number(row?.qtyRequired || 0);
|
const issued = Number(row?.quantityIssued || 0);
|
const remaining = required - issued;
|
return Number((remaining > 0 ? remaining : 0).toFixed(4));
|
};
|
|
const canEdit = row => {
|
return String(row?.status) === "0" && row?.source !== "销售";
|
};
|
|
const canDelete = row => {
|
return String(row?.status) === "0";
|
};
|
|
const canIssue = row => {
|
return String(row?.status) !== "2" && getRemainingQty(row) > 0;
|
};
|
|
const enterSelectMode = () => {
|
selectMode.value = true;
|
selectedIds.value = [];
|
};
|
|
const exitSelectMode = () => {
|
selectMode.value = false;
|
selectedIds.value = [];
|
};
|
|
const toggleSelect = item => {
|
if (!item?.id) return;
|
const id = String(item.id);
|
const idx = selectedIds.value.indexOf(id);
|
if (idx >= 0) selectedIds.value.splice(idx, 1);
|
else selectedIds.value.push(id);
|
};
|
|
// 查询列表
|
const handleQuery = () => {
|
getList();
|
};
|
|
// 获取列表数据
|
const getList = () => {
|
loading.value = true;
|
exitSelectMode();
|
|
const params = {
|
current: page.current,
|
size: page.size,
|
mpsNo: searchForm.value.keyword, // 简单处理:搜索号
|
productName: searchForm.value.keyword, // 简单处理:搜索名称
|
};
|
|
productionPlanListPage(params)
|
.then(res => {
|
loading.value = false;
|
const payload = res?.data;
|
if (Array.isArray(payload)) {
|
tableData.value = payload;
|
} else if (payload && typeof payload === "object") {
|
tableData.value = payload.records || payload.rows || payload.data || [];
|
} else {
|
tableData.value = [];
|
}
|
})
|
.catch(() => {
|
loading.value = false;
|
uni.showToast({
|
title: "加载失败",
|
icon: "error",
|
});
|
});
|
};
|
|
// 跳转详情
|
const goDetail = item => {
|
if (selectMode.value) return;
|
uni.navigateTo({
|
url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(
|
JSON.stringify(item)
|
)}`,
|
});
|
};
|
|
const goAdd = () => {
|
uni.navigateTo({
|
url: "/pages/productionManagement/mainProductionPlan/edit",
|
});
|
};
|
|
const goEdit = item => {
|
uni.navigateTo({
|
url: `/pages/productionManagement/mainProductionPlan/edit?data=${encodeURIComponent(
|
JSON.stringify(item)
|
)}`,
|
});
|
};
|
|
const goIssue = rows => {
|
const list = Array.isArray(rows) ? rows : [];
|
if (list.length === 0) return;
|
const first = list[0];
|
const sumRemaining = Number(
|
list.reduce((sum, r) => sum + getRemainingQty(r), 0).toFixed(4)
|
);
|
const payload = {
|
productName: first.productName || "",
|
model: first.model || "",
|
productId: first.productId ?? undefined,
|
ids: list.map(r => r.id),
|
planCompleteTime:
|
formatDate(first.requiredDate) === "-"
|
? ""
|
: formatDate(first.requiredDate),
|
totalAssignedQuantity: sumRemaining,
|
maxQuantity: sumRemaining,
|
};
|
uni.navigateTo({
|
url: `/pages/productionManagement/mainProductionPlan/issue?data=${encodeURIComponent(
|
JSON.stringify(payload)
|
)}`,
|
});
|
};
|
|
const goIssueSelected = () => {
|
const rows = tableData.value.filter(r =>
|
selectedIds.value.includes(String(r.id))
|
);
|
if (rows.length === 0) {
|
uni.showToast({ title: "请选择要下发的计划", icon: "none" });
|
return;
|
}
|
const first = rows[0];
|
const modelId = first.productModelId;
|
const invalid = rows.some(r => r.productModelId !== modelId || !canIssue(r));
|
if (invalid) {
|
uni.showToast({
|
title: "只能合并下发相同规格且可下发的计划",
|
icon: "none",
|
});
|
return;
|
}
|
goIssue(rows);
|
};
|
|
const handleDelete = item => {
|
if (!item?.id) return;
|
uni.showModal({
|
title: "删除提示",
|
content: "确认删除该生产计划?删除后无法恢复",
|
success: res => {
|
if (!res.confirm) return;
|
uni.showLoading({ title: "删除中...", mask: true });
|
productionPlanDelete([item.id])
|
.then(() => {
|
uni.showToast({ title: "删除成功", icon: "success" });
|
getList();
|
})
|
.catch(() => {
|
uni.showToast({ title: "删除失败", icon: "error" });
|
})
|
.finally(() => {
|
uni.hideLoading();
|
});
|
},
|
});
|
};
|
|
// 页面显示时加载数据
|
onShow(() => {
|
handleQuery();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/sales-common.scss";
|
|
.main-production-plan {
|
min-height: 100vh;
|
background: #f8f9fa;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.list-container {
|
flex: 1;
|
height: 0;
|
padding-bottom: 140rpx;
|
}
|
|
.header-right {
|
display: flex;
|
align-items: center;
|
padding-right: 12rpx;
|
}
|
|
.ledger-item {
|
background: #fff;
|
margin: 20rpx;
|
padding: 20rpx;
|
border-radius: 12rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
.item-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-bottom: 10rpx;
|
|
.item-left {
|
display: flex;
|
align-items: center;
|
|
.document-icon {
|
width: 40rpx;
|
height: 40rpx;
|
background: #3c9cff;
|
border-radius: 8rpx;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
margin-right: 16rpx;
|
}
|
|
.item-id {
|
font-size: 28rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
}
|
|
.action-buttons {
|
display: flex;
|
justify-content: flex-end;
|
align-items: center;
|
gap: 16rpx;
|
padding-top: 16rpx;
|
border-top: 1rpx solid #f0f0f0;
|
}
|
|
.item-details {
|
padding: 10rpx 0;
|
|
.detail-row {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 12rpx;
|
|
.detail-label {
|
font-size: 26rpx;
|
color: #999;
|
}
|
|
.detail-value {
|
font-size: 26rpx;
|
color: #333;
|
|
&.highlight {
|
color: #f56c6c;
|
font-weight: bold;
|
}
|
}
|
}
|
}
|
|
.item-right :deep(.up-checkbox__label) {
|
display: none;
|
}
|
}
|
|
.fab-button {
|
position: fixed;
|
right: 30rpx;
|
bottom: 140rpx;
|
width: 100rpx;
|
height: 100rpx;
|
background: #3c9cff;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.3);
|
z-index: 1001;
|
}
|
|
.no-data {
|
padding-top: 200rpx;
|
}
|
</style>
|