迁移BOM物料清单功能

This commit is contained in:
sheng
2026-06-22 23:10:00 +08:00
parent 964cf0b6ad
commit cf90981c5d
8 changed files with 690 additions and 3 deletions

View File

@@ -0,0 +1,56 @@
import { request } from '@/api/_service'
const BASE = 'production_configuration/matetial_model/bom/'
const RELATION_BASE = 'production_configuration/matetial_model/bom_relationship/'
function apiParams (method, data = {}) {
return {
method: `production_configuration_matetial_model_bom_${method}`,
platform: 'background',
...data
}
}
function relationApiParams (method, data = {}) {
return {
method: `production_configuration_matetial_model_bom_relationship_${method}`,
platform: 'background',
...data
}
}
export function getBomAll (data) {
return request({ url: BASE + 'all', method: 'get', params: apiParams('all', data) })
}
export function getBomList (data) {
return request({ url: BASE + 'list', method: 'get', params: apiParams('list', data) })
}
export function createBom (data) {
return request({ url: BASE + 'create', method: 'post', data: apiParams('create', data) })
}
export function editBom (data) {
return request({ url: BASE + 'edit', method: 'put', data: apiParams('edit', data) })
}
export function deleteBom (data) {
return request({ url: BASE + 'delete', method: 'delete', data: apiParams('delete', data) })
}
export function getBomRelationshipList (data) {
return request({ url: RELATION_BASE + 'list', method: 'get', params: relationApiParams('list', data) })
}
export function createBomRelationship (data) {
return request({ url: RELATION_BASE + 'create', method: 'post', data: relationApiParams('create', data) })
}
export function editBomRelationship (data) {
return request({ url: RELATION_BASE + 'edit', method: 'put', data: relationApiParams('edit', data) })
}
export function deleteBomRelationship (data) {
return request({ url: RELATION_BASE + 'delete', method: 'delete', data: relationApiParams('delete', data) })
}

View File

@@ -356,6 +356,55 @@
"please_enter": "Please enter {name}",
"help": "Material master data is used to maintain material code, name, specifications, etc."
},
"bill_of_materials": {
"query": "Search",
"reset": "Reset",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"set_bom": "Set BOM",
"operation": "Operation",
"confirm": "Confirm",
"cancel": "Cancel",
"return": "Back",
"prompt": "Notice",
"operation_success": "Operation successful",
"confirm_message": "Are you sure you want to perform this operation?",
"bom_version_code": "BOM Version Code",
"bom_version_name": "BOM Version Name",
"product_model_name": "Product Model",
"status": "Status",
"enable": "Enabled",
"disable": "Disabled",
"select_status": "Please select status",
"create_user": "Created By",
"create_time": "Created At",
"remark": "Remark",
"enter_remark": "Please enter remark",
"enter_bom_version_code": "Please enter BOM version code",
"enter_bom_version_name": "Please enter BOM version name",
"select_product_model_name": "Please select product model",
"enter_bom_code": "Please enter BOM code",
"enter_bom_name": "Please enter BOM name",
"select_product_model": "Please select product model",
"length_1_45": "Length must be 1-45 characters",
"add_bom_info": "Add BOM",
"edit_bom_info": "Edit BOM",
"bom_management": "BOM Management",
"material_category": "Material Category",
"material_code": "Material Code",
"material_name": "Material Name",
"input_quantity": "Input Quantity",
"enter_input_quantity": "Please enter input quantity",
"unit": "Unit",
"select_add_material": "Select Material",
"all": "All",
"selected": "Selected",
"search_by_code_or_name": "Search by code or name",
"duplicate_material_selected": "Selected materials cannot be selected again",
"out_only_one": "OUT structure allows only one semi-finished item",
"please_select_data": "Please select data first"
},
"material_unit": {
"search": "Search",
"reset": "Reset",

View File

@@ -356,6 +356,55 @@
"please_enter": "请输入{name}",
"help": "物料信息用于维护物料编码、名称、规格等属性"
},
"bill_of_materials": {
"query": "查询",
"reset": "重置",
"add": "新增",
"edit": "编辑",
"delete": "删除",
"set_bom": "设置BOM",
"operation": "操作",
"confirm": "确定",
"cancel": "取消",
"return": "返回",
"prompt": "提示",
"operation_success": "操作成功",
"confirm_message": "确定要执行该操作吗?",
"bom_version_code": "BOM版本编码",
"bom_version_name": "BOM版本名称",
"product_model_name": "型号名称",
"status": "状态",
"enable": "启用",
"disable": "禁用",
"select_status": "请选择状态",
"create_user": "创建人",
"create_time": "创建时间",
"remark": "备注",
"enter_remark": "请输入备注",
"enter_bom_version_code": "请输入BOM版本编码",
"enter_bom_version_name": "请输入BOM版本名称",
"select_product_model_name": "请选择型号名称",
"enter_bom_code": "请输入BOM编码",
"enter_bom_name": "请输入BOM名称",
"select_product_model": "请选择产品型号",
"length_1_45": "长度在 1 到 45 个字符",
"add_bom_info": "新增BOM信息",
"edit_bom_info": "编辑BOM信息",
"bom_management": "BOM管理",
"material_category": "物料类别",
"material_code": "物料编码",
"material_name": "物料名称",
"input_quantity": "投入数量",
"enter_input_quantity": "请输入投入数量",
"unit": "单位",
"select_add_material": "选择添加物料",
"all": "全部",
"selected": "已选",
"search_by_code_or_name": "按编码或名称搜索",
"duplicate_material_selected": "已选择物料请勿重复选择",
"out_only_one": "OUT结构只允许存在1个半成品请勿选择1个以上半成品",
"please_select_data": "请先选择数据"
},
"material_unit": {
"search": "查询",
"reset": "重置",

View File

@@ -68,6 +68,12 @@ export default {
meta: { ...meta, cache: true, title: '物料信息管理' },
component: _import('production-master-data/material-model/material-master')
},
{
path: 'matetial_model/bom',
name: `${pre}material_model-bill_of_materials`,
meta: { ...meta, cache: true, title: 'BOM物料清单' },
component: _import('production-master-data/material-model/bill-of-materials')
},
{
path: 'matetial_model/unit',
name: `${pre}material_model-material_unit`,

View File

@@ -0,0 +1,282 @@
<template>
<el-dialog :visible.sync="visibleProxy" append-to-body :close-on-click-modal="false" fullscreen :show-close="false" @open="bootstrap">
<template #title>
<div class="dialog-title">
<span>{{ relationshipTitle }}</span>
<el-button size="small" @click="handleClose">{{ $t(key('return')) }}</el-button>
</div>
</template>
<el-row class="relationship-layout">
<el-col :span="4" class="step-panel">
<el-menu :default-active="activeStep" @select="handleStepSelect">
<el-menu-item v-for="item in stepOptions" :key="item.code" :index="item.code">
<span slot="title">{{ item.name }}</span>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="20" class="relation-panel">
<el-card shadow="never" class="relation-card relation-card-in">
<div slot="header" class="card-header">
<span>IN</span>
<div>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected('in')">{{ $t(key('delete')) }}</el-button>
<el-button size="mini" type="success" icon="el-icon-plus" @click="openMaterialDialog('in')">{{ $t(key('add')) }}</el-button>
</div>
</div>
<el-table ref="inTable" :data="inData" border height="360" v-loading="loading" @selection-change="inSelection = $event">
<el-table-column type="selection" width="48" />
<el-table-column prop="bom_source_category_name" :label="$t(key('material_category'))" width="120" />
<el-table-column prop="bom_source_code" :label="$t(key('material_code'))" min-width="180" show-overflow-tooltip />
<el-table-column prop="bom_source_name" :label="$t(key('material_name'))" min-width="180" show-overflow-tooltip />
<el-table-column :label="$t(key('input_quantity'))" width="180">
<template slot-scope="{ row }">
<el-input v-model="row.input_quantity" size="mini" @blur="saveQuantity(row)" />
</template>
</el-table-column>
<el-table-column prop="unit_name" :label="$t(key('unit'))" width="100" />
<el-table-column :label="$t(key('operation'))" width="100">
<template slot-scope="{ row }">
<el-button type="text" size="mini" style="color:#F56C6C" @click="deleteRows([row])">{{ $t(key('delete')) }}</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-card shadow="never" class="relation-card">
<div slot="header" class="card-header">
<span>OUT</span>
<div>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected('out')">{{ $t(key('delete')) }}</el-button>
<el-button size="mini" type="success" icon="el-icon-plus" @click="openMaterialDialog('out')">{{ $t(key('add')) }}</el-button>
</div>
</div>
<el-table ref="outTable" :data="outData" border height="250" v-loading="loading" @selection-change="outSelection = $event">
<el-table-column type="selection" width="48" />
<el-table-column prop="bom_source_category_name" :label="$t(key('material_category'))" width="120" />
<el-table-column prop="bom_source_code" :label="$t(key('material_code'))" min-width="180" show-overflow-tooltip />
<el-table-column prop="bom_source_name" :label="$t(key('material_name'))" min-width="180" show-overflow-tooltip />
<el-table-column prop="unit_name" :label="$t(key('unit'))" width="140" />
<el-table-column :label="$t(key('operation'))" width="100">
<template slot-scope="{ row }">
<el-button type="text" size="mini" style="color:#F56C6C" @click="deleteRows([row])">{{ $t(key('delete')) }}</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-dialog :title="$t(key('select_add_material'))" :visible.sync="materialVisible" append-to-body width="820px" :close-on-click-modal="false" @open="loadMaterials(true)">
<el-form :inline="true" size="mini" @submit.native.prevent>
<el-form-item>
<el-radio-group v-model="materialOnlySelected" @change="loadMaterials(true)">
<el-radio-button :label="false">{{ $t(key('all')) }}</el-radio-button>
<el-radio-button :label="true">{{ $t(key('selected')) }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button-group>
<el-button @click="changeMaterialCategory('')">{{ $t(key('all')) }}</el-button>
<el-button v-for="item in materialCategories" :key="item.id" @click="changeMaterialCategory(item.id)">{{ item.name }}</el-button>
</el-button-group>
</el-form-item>
<el-form-item>
<el-input v-model.trim="materialSearch.all" :placeholder="$t(key('search_by_code_or_name'))" clearable style="width:220px" @keyup.enter.native="loadMaterials(true)">
<el-button slot="append" icon="el-icon-search" @click="loadMaterials(true)" />
</el-input>
</el-form-item>
</el-form>
<el-table ref="materialTable" :data="materialData" border height="380" row-key="id" @selection-change="materialSelection = $event">
<el-table-column type="selection" reserve-selection width="50" />
<el-table-column prop="bom_source_category_name" :label="$t(key('material_category'))" width="130" />
<el-table-column prop="code" :label="$t(key('material_code'))" min-width="150" show-overflow-tooltip />
<el-table-column prop="name" :label="$t(key('material_name'))" min-width="150" show-overflow-tooltip />
<el-table-column prop="remark" :label="$t(key('remark'))" show-overflow-tooltip />
</el-table>
<el-pagination class="material-pagination" :current-page="materialPagination.current" :page-size="materialPagination.size" :total="materialPagination.total" layout="total, prev, pager, next" @current-change="onMaterialPageChange" />
<span slot="footer">
<el-button size="small" @click="materialVisible = false">{{ $t(key('cancel')) }}</el-button>
<el-button size="small" type="primary" @click="confirmMaterialSelection">{{ $t(key('confirm')) }}</el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import { getWorkingsubclassAll } from '@/api/production-master-data/process-step'
import { getMaterialCategoryAll } from '@/api/production-master-data/material-category'
import { getMaterialMasterList } from '@/api/production-master-data/material-master'
import { getBomRelationshipList, createBomRelationship, editBomRelationship, deleteBomRelationship } from '@/api/production-master-data/bill-of-materials'
function readPageData (res) {
const data = res && res.data ? res.data : res
if (!data) return { list: [], total: 0 }
if (Array.isArray(data)) return { list: data, total: data.length }
return { list: data.data || data.list || [], total: Number(data.count || data.total || 0) }
}
export default {
name: 'BomRelationship',
mixins: [i18nMixin('page.production_master_data.material_model.bill_of_materials')],
props: {
visible: { type: Boolean, default: false },
bom: { type: Object, default: () => ({}) }
},
data () {
return {
loading: false,
stepOptions: [],
materialCategories: [],
activeStep: '',
inData: [],
outData: [],
inSelection: [],
outSelection: [],
materialVisible: false,
materialDirection: 'in',
materialCategoryId: '',
materialOnlySelected: false,
materialSearch: { all: '' },
materialData: [],
materialSelection: [],
materialPagination: { current: 1, size: 10, total: 0 }
}
},
computed: {
visibleProxy: {
get () { return this.visible },
set (val) { this.$emit('update:visible', val) }
},
relationshipTitle () {
return this.bom && this.bom.name ? `${this.bom.name}${this.$t(this.key('bom_management'))}` : this.$t(this.key('bom_management'))
},
currentCheckList () {
return this.materialDirection === 'in' ? this.inData : this.outData
}
},
methods: {
async bootstrap () {
this.inData = []
this.outData = []
this.stepOptions = []
const [steps, categories] = await Promise.all([getWorkingsubclassAll({}), getMaterialCategoryAll({})])
this.stepOptions = (steps && steps.data) || steps || []
this.materialCategories = (categories && categories.data) || categories || []
this.activeStep = this.stepOptions.length ? this.stepOptions[0].code : ''
if (this.activeStep) this.loadRelationship()
},
handleClose () {
this.visibleProxy = false
this.$emit('saved')
},
handleStepSelect (key) {
this.activeStep = key
this.loadRelationship()
},
async loadRelationship () {
if (!this.bom.id || !this.activeStep) return
this.loading = true
try {
const res = await getBomRelationshipList({ product_bom_id: this.bom.id, workingsubclass_in: this.activeStep, workingsubclass_out: this.activeStep })
const data = (res && res.data) || res || {}
this.inData = data.in || []
this.outData = data.out || []
} finally {
this.loading = false
}
},
openMaterialDialog (direction) {
this.materialDirection = direction
this.materialOnlySelected = false
this.materialCategoryId = ''
this.materialSearch = { all: '' }
this.materialPagination.current = 1
this.materialVisible = true
},
changeMaterialCategory (categoryId) {
this.materialCategoryId = categoryId
this.loadMaterials(true)
},
async loadMaterials (resetPage = false) {
if (resetPage) this.materialPagination.current = 1
this.$nextTick(() => this.$refs.materialTable && this.$refs.materialTable.clearSelection())
const idList = this.currentCheckList.map(item => item.bom_source_id).filter(Boolean)
if (this.materialOnlySelected && this.materialSelection.length === 0) {
this.materialData = []
this.materialPagination.total = 0
return
}
const selectedId = this.materialOnlySelected ? this.materialSelection.map(item => item.id) : undefined
const res = await getMaterialMasterList({
bom_source_category_id: this.materialCategoryId,
idList,
selected_id: selectedId,
all: this.materialSearch.all,
page_no: this.materialPagination.current,
page_size: this.materialPagination.size
})
const { list, total } = readPageData(res)
this.materialData = list
this.materialPagination.total = total
},
onMaterialPageChange (page) {
this.materialPagination.current = page
this.loadMaterials(false)
},
async confirmMaterialSelection () {
const relationData = this.materialSelection.map(item => ({ bom_source_id: item.id }))
if (this.materialDirection === 'in') {
const duplicates = relationData.filter(next => this.inData.some(old => old.bom_source_id === next.bom_source_id))
if (duplicates.length) {
this.$message.warning(this.$t(this.key('duplicate_material_selected')))
return
}
} else if (this.outData.length !== 0 || relationData.length > 1) {
this.$message.warning(this.$t(this.key('out_only_one')))
return
}
await createBomRelationship({
bom_id: this.bom.id,
type: this.materialDirection === 'in' ? 'in_workingsubclass' : 'out_workingsubclass',
workingsubclass: this.activeStep,
relation_data: relationData
})
this.materialVisible = false
this.$message.success(this.$t(this.key('operation_success')))
this.loadRelationship()
},
async saveQuantity (row) {
await editBomRelationship(row)
this.$message.success(this.$t(this.key('operation_success')))
this.loadRelationship()
},
deleteSelected (direction) {
const rows = direction === 'in' ? this.inSelection : this.outSelection
if (!rows.length) {
this.$message.warning(this.$t(this.key('please_select_data')))
return
}
this.deleteRows(rows)
},
async deleteRows (rows) {
await deleteBomRelationship({ id: rows.map(item => item.id) })
this.$message.success(this.$t(this.key('operation_success')))
this.loadRelationship()
}
}
}
</script>
<style scoped>
.dialog-title { display: flex; justify-content: space-between; align-items: center; padding: 0 20px; }
.relationship-layout { border-top: 1px solid #dcdfe6; }
.step-panel { height: calc(100vh - 100px); overflow: auto; border-right: 1px solid #ebeef5; }
.relation-panel { height: calc(100vh - 100px); overflow: auto; padding: 16px; }
.relation-card { margin-bottom: 12px; }
.relation-card-in { min-height: 430px; }
.card-header { display: flex; align-items: center; justify-content: space-between; }
.material-pagination { margin-top: 12px; text-align: right; }
/deep/ .el-dialog__body { padding: 0; }
</style>

View File

@@ -0,0 +1,225 @@
<template>
<d2-container>
<template #header>
<div class="search-bar">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('bom_version_code'))">
<el-input v-model.trim="search.code" :placeholder="$t(key('enter_bom_version_code'))" clearable style="width:220px" @keyup.enter.native="onSearch" />
</el-form-item>
<el-form-item :label="$t(key('bom_version_name'))">
<el-input v-model.trim="search.name" :placeholder="$t(key('enter_bom_version_name'))" clearable style="width:220px" @keyup.enter.native="onSearch" />
</el-form-item>
<el-form-item :label="$t(key('product_model_name'))">
<el-select v-model="search.product_model_id" :placeholder="$t(key('select_product_model_name'))" clearable filterable style="width:220px">
<el-option v-for="item in productOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">{{ $t(key('query')) }}</el-button>
<el-button icon="el-icon-refresh" @click="onReset">{{ $t(key('reset')) }}</el-button>
</el-form-item>
</el-form>
</div>
</template>
<page-table ref="pageTable" :columns="columns" :data="tableData" :loading="loading" :toolbar-buttons="toolbarButtons" :row-buttons="rowButtons" :pagination="pagination" auto-height @page-change="onPageChange">
<template #col-status="{ row }">
<span v-if="String(row.status) === '1'" class="status-on"><i class="el-icon-circle-check" /> {{ $t(key('enable')) }}</span>
<span v-else class="status-off"><i class="el-icon-circle-close" /> {{ $t(key('disable')) }}</span>
</template>
<template #col-remark="{ row }">
<el-popover v-if="row.remark && row.remark.length > 20" placement="top-start" width="300" trigger="hover" :content="row.remark">
<span slot="reference" style="cursor:pointer">{{ row.remark.substr(0, 20) }}...</span>
</el-popover>
<span v-else>{{ row.remark }}</span>
</template>
</page-table>
<page-dialog-form ref="dialogForm" :visible.sync="dialogVisible" :title="dialogTitle" width="38%" :form-cols="dialogFormCols" :form-data="formData" :rules="rules" label-width="120px" :submitting="submitting" :confirm-text="key('confirm')" :cancel-text="key('cancel')" @submit="onDialogSubmit" @close="onDialogClose" />
<bom-relationship :visible.sync="relationshipVisible" :bom="currentBom" @saved="fetchData" />
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
import { confirmMixin } from '@/composables/useConfirmHandle'
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
import BomRelationship from './components/BomRelationship'
import { getProductBatteryAll } from '@/api/production-master-data/product-management'
import { getBomList, createBom, editBom, deleteBom } from '@/api/production-master-data/bill-of-materials'
function readPageData (res) {
const data = res && res.data ? res.data : res
if (!data) return { list: [], total: 0 }
if (Array.isArray(data)) return { list: data, total: data.length }
return { list: data.data || data.list || [], total: Number(data.count || data.total || 0) }
}
export default {
name: 'production-master-data-bill-of-materials',
components: { PageTable, PageDialogForm, BomRelationship },
mixins: [i18nMixin('page.production_master_data.material_model.bill_of_materials'), confirmMixin],
data () {
return {
loading: false,
submitting: false,
tableData: [],
productOptions: [],
dialogVisible: false,
dialogTitle: '',
editId: '',
handleType: 'create',
relationshipVisible: false,
currentBom: {},
search: { code: '', name: '', product_model_id: '' },
pagination: { current: 1, size: 10, total: 0 },
formData: this.defaultFormData(),
rules: {
code: [
{ required: true, message: this.key('enter_bom_code'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('length_1_45'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_bom_name'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('length_1_45'), trigger: 'blur' }
],
product_model_id: [{ required: true, message: this.key('select_product_model'), trigger: 'change' }]
},
columns: [],
toolbarButtons: [],
rowButtons: [],
baseFormCols: [
[{ type: 'input', prop: 'code', label: this.key('bom_version_code'), placeholder: this.key('enter_bom_version_code'), clearable: true, style: { width: '90%' } }],
[{ type: 'input', prop: 'name', label: this.key('bom_version_name'), placeholder: this.key('enter_bom_version_name'), clearable: true, style: { width: '90%' } }],
[{ type: 'select', prop: 'product_model_id', label: this.key('product_model_name'), placeholder: this.key('select_product_model_name'), clearable: true, filterable: true, style: { width: '90%' }, options: [] }],
[{ type: 'select', prop: 'status', label: this.key('status'), placeholder: this.key('select_status'), clearable: true, style: { width: '90%' }, options: [{ label: this.$t(this.key('disable')), value: '0' }, { label: this.$t(this.key('enable')), value: '1' }] }],
[{ type: 'input', prop: 'remark', inputType: 'textarea', autosize: { minRows: 2, maxRows: 6 }, label: this.key('remark'), placeholder: this.key('enter_remark'), clearable: true, style: { width: '90%' } }]
]
}
},
computed: {
dialogFormCols () {
return this.baseFormCols.map(row => row.map(item => {
if (item.prop === 'product_model_id') return { ...item, options: this.productOptions }
return item
}))
}
},
created () {
this.loadProductOptions()
this.columns = useTableColumns([
{ prop: 'code', label: this.key('bom_version_code'), minWidth: 140 },
{ prop: 'name', label: this.key('bom_version_name'), minWidth: 160 },
{ prop: 'product_model_name', label: this.key('product_model_name'), minWidth: 160 },
{ prop: 'status', label: this.key('status'), slot: 'status', width: 110 },
{ prop: 'username', label: this.key('create_user'), minWidth: 100 },
{ prop: 'create_time', label: this.key('create_time'), minWidth: 160 },
{ prop: 'remark', label: this.key('remark'), slot: 'remark', minWidth: 160 },
{ prop: '_actions', label: this.key('operation'), width: 230, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [{ key: 'add', label: this.key('add'), icon: 'el-icon-plus', type: 'primary', auth: '/production_configuration/matetial_model/bom/create', onClick: this.openAdd }],
row: [
{ key: 'set_bom', label: this.key('set_bom'), icon: 'el-icon-setting', auth: '/production_configuration/matetial_model/bom/edit_bom_relationship', onClick: this.openRelationship },
{ key: 'edit', label: this.key('edit'), icon: 'el-icon-edit', auth: '/production_configuration/matetial_model/bom/edit', onClick: this.openEdit },
{ key: 'delete', label: this.key('delete'), icon: 'el-icon-delete', color: 'danger', auth: '/production_configuration/matetial_model/bom/delete', onClick: this.handleDelete }
]
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.fetchData()
},
methods: {
defaultFormData () {
return { code: '', name: '', product_model_id: '', status: '1', remark: '' }
},
async loadProductOptions () {
const res = await getProductBatteryAll({})
const list = (res && res.data) || res || []
this.productOptions = list.map(item => ({ label: item.name, value: item.product_model_id || item.id }))
},
async fetchData () {
this.loading = true
try {
const res = await getBomList({ ...this.search, page_no: this.pagination.current, page_size: this.pagination.size })
const { list, total } = readPageData(res)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = { code: '', name: '', product_model_id: '' }
this.pagination.current = 1
this.fetchData()
},
onPageChange (page) {
this.pagination.current = page.current
this.pagination.size = page.size
this.fetchData()
},
resetForm () {
this.formData = this.defaultFormData()
this.editId = ''
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_bom_info')
this.$nextTick(() => {
this.$refs.dialogForm && this.$refs.dialogForm.reset()
this.resetForm()
this.dialogVisible = true
})
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_bom_info')
this.editId = row.id
this.formData = { code: row.code, name: row.name, product_model_id: row.product_model_id, status: String(row.status), remark: row.remark || '' }
this.dialogVisible = true
},
openRelationship (row) {
this.currentBom = { id: row.id, name: row.name }
this.relationshipVisible = true
},
async onDialogSubmit () {
this.submitting = true
try {
if (this.handleType === 'create') await createBom(this.formData)
else await editBom({ ...this.formData, id: this.editId })
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
},
onDialogClose () {
this.resetForm()
},
async handleDelete (row) {
const cancelled = await this.$confirmAction({ message: this.key('confirm_message'), title: this.key('prompt'), confirmButtonText: this.key('confirm'), cancelButtonText: this.key('cancel') }, () => deleteBom({ id: [row.id] }))
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(this.pagination.current, Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1)
this.fetchData()
}
}
}
</script>
<style scoped>
.search-bar { padding: 10px 0; }
.status-on { color: #67C23A; }
.status-off { color: #909399; }
/deep/ .el-form-item--mini.el-form-item { margin-bottom: 4px; }
</style>