<template>
|
<view class="bom-list">
|
<PageHeader title="BOM管理"
|
@back="goBack" />
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input class="search-text"
|
v-model="queryParams.productName"
|
placeholder="请输入产品名称"
|
clearable
|
@change="handleSearch" />
|
</view>
|
<view class="filter-button"
|
@click="handleSearch">
|
<up-icon name="search"
|
size="24"
|
color="#999999"></up-icon>
|
</view>
|
</view>
|
</view>
|
<view v-if="list.length > 0"
|
class="ledger-list">
|
<view v-for="item in list"
|
:key="item.id"
|
class="ledger-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="list-dot"
|
size="16"
|
color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.bomNo || "-" }}</text>
|
</view>
|
<up-tag :text="'V' + (item.version || '1.0')"
|
type="primary"
|
size="mini" />
|
</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.productModelName || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">备注</text>
|
<text class="detail-value">{{ item.remark || "-" }}</text>
|
</view>
|
</view>
|
<view class="action-buttons">
|
<up-button class="action-btn"
|
size="small"
|
type="primary"
|
@click="goStructure(item)">查看详情</up-button>
|
<up-button class="action-btn"
|
size="small"
|
type="warning"
|
@click="openEdit(item)">修改</up-button>
|
<up-button class="action-btn"
|
size="small"
|
type="error"
|
@click="handleDelete(item)">删除</up-button>
|
</view>
|
</view>
|
<up-loadmore :status="pageStatus" />
|
</view>
|
<view v-else
|
class="no-data">
|
<up-empty text="暂无BOM数据"
|
mode="list"></up-empty>
|
</view>
|
<view class="fab-button"
|
@click="openAdd">
|
<up-icon name="plus"
|
size="24"
|
color="#ffffff"></up-icon>
|
</view>
|
<up-popup :show="showFormPopup"
|
mode="bottom"
|
round
|
@close="closeFormPopup">
|
<view class="popup-container">
|
<view class="popup-header">
|
<text class="popup-cancel"
|
@click="closeFormPopup">取消</text>
|
<text class="popup-title">{{ formMode === 'add' ? '新增BOM' : '修改BOM' }}</text>
|
<text class="popup-confirm"
|
@click="submitForm">确定</text>
|
</view>
|
<view class="popup-body">
|
<up-form ref="bomFormRef"
|
:model="bomForm"
|
:rules="bomRules"
|
label-width="110">
|
<up-form-item label="产品"
|
prop="productModelId"
|
required>
|
<up-input v-model="bomForm.productName"
|
readonly
|
placeholder="点击选择产品"
|
@click="openProductPicker" />
|
<template #right>
|
<up-icon name="arrow-right"
|
@click="openProductPicker"></up-icon>
|
</template>
|
</up-form-item>
|
<up-form-item label="规格型号">
|
<up-input v-model="bomForm.productModelName"
|
readonly
|
placeholder="--" />
|
</up-form-item>
|
<up-form-item label="版本号"
|
prop="version"
|
required>
|
<up-input v-model="bomForm.version"
|
placeholder="请输入版本号"
|
clearable />
|
</up-form-item>
|
<up-form-item label="备注"
|
prop="remark">
|
<up-textarea v-model="bomForm.remark"
|
placeholder="请输入备注"
|
auto-height />
|
</up-form-item>
|
</up-form>
|
</view>
|
</view>
|
</up-popup>
|
<up-popup :show="showProductPicker"
|
mode="bottom"
|
round
|
@close="showProductPicker = false">
|
<view class="popup-container">
|
<view class="popup-header">
|
<text class="popup-cancel"
|
@click="showProductPicker = false">取消</text>
|
<text class="popup-title">选择产品</text>
|
<text class="popup-confirm"
|
@click="handleProductSearch">搜索</text>
|
</view>
|
<view class="popup-body">
|
<view class="picker-search">
|
<up-input v-model="productQuery.productName"
|
placeholder="产品名称"
|
clearable
|
@change="handleProductSearch" />
|
<up-input v-model="productQuery.model"
|
placeholder="规格型号"
|
clearable
|
@change="handleProductSearch" />
|
</view>
|
<scroll-view scroll-y
|
class="picker-list"
|
@scrolltolower="loadMoreProducts">
|
<view v-for="row in productList"
|
:key="row.id"
|
class="picker-item"
|
@click="selectProduct(row)">
|
<view class="picker-item__title">
|
<text>{{ row.productName || '-' }}</text>
|
</view>
|
<view class="picker-item__sub">
|
<text>{{ row.model || '-' }}</text>
|
<text class="picker-item__unit">{{ row.unit || '-' }}</text>
|
</view>
|
</view>
|
<up-loadmore :status="productPageStatus" />
|
</scroll-view>
|
</view>
|
</view>
|
</up-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { reactive, ref } from "vue";
|
import { onReachBottom, onShow } from "@dcloudio/uni-app";
|
import {
|
listPage,
|
add,
|
update,
|
batchDelete,
|
getProductList,
|
} from "@/api/productionManagement/bom";
|
|
const queryParams = reactive({
|
productName: "",
|
});
|
const list = ref([]);
|
const pageStatus = ref("loadmore");
|
|
const page = reactive({
|
current: 1,
|
size: 3,
|
total: 0,
|
});
|
const showFormPopup = ref(false);
|
const formMode = ref("add");
|
const bomFormRef = ref(null);
|
const bomForm = reactive({
|
id: undefined,
|
productName: "",
|
productModelName: "",
|
productModelId: "",
|
remark: "",
|
version: "",
|
});
|
const bomRules = {
|
productModelId: [{ required: true, message: "请选择产品", trigger: "blur" }],
|
version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
|
};
|
|
const showProductPicker = ref(false);
|
const productQuery = reactive({
|
productName: "",
|
model: "",
|
});
|
const productList = ref([]);
|
const productPage = reactive({
|
current: 1,
|
size: 20,
|
total: 0,
|
});
|
const productPageStatus = ref("loadmore");
|
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
const handleSearch = () => {
|
page.current = 1;
|
pageStatus.value = "loadmore";
|
list.value = [];
|
getList();
|
};
|
|
const getList = () => {
|
if (pageStatus.value === "loading" || pageStatus.value === "nomore") return;
|
|
pageStatus.value = "loading";
|
listPage({
|
current: page.current,
|
size: page.size,
|
productName: queryParams.productName,
|
})
|
.then(res => {
|
const records = res?.data?.records || res?.records || [];
|
const total = res?.data?.total || res?.total || 0;
|
|
if (page.current === 1) {
|
list.value = records;
|
} else {
|
list.value = [...list.value, ...records];
|
}
|
|
page.total = total;
|
if (list.value.length >= total) {
|
pageStatus.value = "nomore";
|
} else {
|
pageStatus.value = "loadmore";
|
page.current++;
|
}
|
})
|
.catch(() => {
|
uni.showToast({ title: "查询失败", icon: "error" });
|
pageStatus.value = "loadmore";
|
});
|
};
|
|
const goStructure = item => {
|
uni.navigateTo({
|
url: `/pages/productionDesign/bom/structure?id=${
|
item.id
|
}&bomNo=${encodeURIComponent(item.bomNo)}&productName=${encodeURIComponent(
|
item.productName || ""
|
)}&productModelName=${encodeURIComponent(
|
item.productModelName || ""
|
)}&remark=${encodeURIComponent(
|
item.remark || ""
|
)}&version=${encodeURIComponent(item.version || 1)}`,
|
});
|
};
|
|
const openAdd = () => {
|
formMode.value = "add";
|
Object.assign(bomForm, {
|
id: undefined,
|
productName: "",
|
productModelName: "",
|
productModelId: "",
|
remark: "",
|
version: "",
|
});
|
showFormPopup.value = true;
|
};
|
|
const openEdit = row => {
|
formMode.value = "edit";
|
Object.assign(bomForm, {
|
id: row.id,
|
productName: row.productName || "",
|
productModelName: row.productModelName || "",
|
productModelId: row.productModelId || "",
|
remark: row.remark || "",
|
version: row.version || "",
|
});
|
showFormPopup.value = true;
|
};
|
|
const closeFormPopup = () => {
|
showFormPopup.value = false;
|
};
|
|
const submitForm = () => {
|
if (!bomFormRef.value) return;
|
bomFormRef.value.validate(valid => {
|
if (!valid) return;
|
const payload = { ...bomForm };
|
const req = formMode.value === "add" ? add(payload) : update(payload);
|
req
|
.then(res => {
|
if (res && res.code !== undefined && res.code !== 200) {
|
uni.showToast({
|
title: res.msg || "提交失败",
|
icon: "none",
|
});
|
return;
|
}
|
uni.showToast({
|
title: "提交成功",
|
icon: "success",
|
});
|
closeFormPopup();
|
handleSearch();
|
})
|
.catch(() => {
|
uni.showToast({
|
title: "提交失败",
|
icon: "error",
|
});
|
});
|
});
|
};
|
|
const handleDelete = row => {
|
if (!row?.id) return;
|
uni.showModal({
|
title: "提示",
|
content: "确认删除该BOM?",
|
confirmText: "确认",
|
cancelText: "取消",
|
success: res => {
|
if (!res.confirm) return;
|
batchDelete([row.id])
|
.then(result => {
|
if (result && result.code !== undefined && result.code !== 200) {
|
uni.showToast({
|
title: result.msg || "删除失败",
|
icon: "none",
|
});
|
return;
|
}
|
uni.showToast({
|
title: "删除成功",
|
icon: "success",
|
});
|
handleSearch();
|
})
|
.catch(() => {
|
uni.showToast({
|
title: "删除失败",
|
icon: "error",
|
});
|
});
|
},
|
});
|
};
|
|
const openProductPicker = () => {
|
showProductPicker.value = true;
|
handleProductSearch();
|
};
|
|
const handleProductSearch = () => {
|
productPage.current = 1;
|
productPageStatus.value = "loadmore";
|
productList.value = [];
|
loadMoreProducts();
|
};
|
|
const loadMoreProducts = () => {
|
if (
|
productPageStatus.value === "loading" ||
|
productPageStatus.value === "nomore"
|
) {
|
return;
|
}
|
productPageStatus.value = "loading";
|
getProductList({
|
current: productPage.current,
|
size: productPage.size,
|
productName: productQuery.productName,
|
model: productQuery.model,
|
})
|
.then(res => {
|
const records = res?.data?.records || res?.records || res?.data || [];
|
const total = res?.data?.total || res?.total || 0;
|
const next = Array.isArray(records) ? records : [];
|
productList.value =
|
productPage.current === 1 ? next : [...productList.value, ...next];
|
productPage.total = Number(total || productList.value.length);
|
if (productList.value.length >= productPage.total) {
|
productPageStatus.value = "nomore";
|
} else {
|
productPageStatus.value = "loadmore";
|
productPage.current++;
|
}
|
})
|
.catch(() => {
|
productPageStatus.value = "loadmore";
|
});
|
};
|
|
const selectProduct = row => {
|
bomForm.productModelId = row.id;
|
bomForm.productName = row.productName || "";
|
bomForm.productModelName = row.model || "";
|
showProductPicker.value = false;
|
};
|
|
onReachBottom(() => {
|
getList();
|
});
|
|
onShow(() => {
|
handleSearch();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/procurement-common.scss";
|
|
.no-data {
|
padding-top: 100rpx;
|
text-align: center;
|
color: #999;
|
font-size: 28rpx;
|
}
|
|
.action-buttons {
|
display: flex;
|
justify-content: flex-end;
|
gap: 15rpx;
|
padding: 0 30rpx 30rpx;
|
flex-wrap: wrap;
|
}
|
|
.action-btn {
|
width: calc(50% - 15rpx);
|
margin: 0 !important;
|
margin-bottom: 15rpx !important;
|
}
|
|
.popup-container {
|
background: #fff;
|
border-radius: 20rpx 20rpx 0 0;
|
max-height: 80vh;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.popup-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 24rpx 28rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
}
|
|
.popup-title {
|
font-size: 30rpx;
|
font-weight: 600;
|
color: #333;
|
}
|
|
.popup-cancel {
|
font-size: 28rpx;
|
color: #666;
|
}
|
|
.popup-confirm {
|
font-size: 28rpx;
|
color: #006cfb;
|
font-weight: 600;
|
}
|
|
.popup-body {
|
padding: 20rpx 24rpx 30rpx;
|
overflow: hidden;
|
flex: 1;
|
}
|
|
.picker-search {
|
display: flex;
|
gap: 16rpx;
|
margin-bottom: 16rpx;
|
}
|
|
.picker-list {
|
height: 60vh;
|
}
|
|
.picker-item {
|
padding: 22rpx 0;
|
border-bottom: 1rpx solid #f5f5f5;
|
}
|
|
.picker-item__title {
|
font-size: 28rpx;
|
color: #333;
|
font-weight: 600;
|
}
|
|
.picker-item__sub {
|
margin-top: 6rpx;
|
font-size: 24rpx;
|
color: #666;
|
display: flex;
|
justify-content: space-between;
|
gap: 16rpx;
|
}
|
|
.picker-item__unit {
|
color: #999;
|
white-space: nowrap;
|
}
|
</style>
|