迁移BOM物料清单功能
This commit is contained in:
20
docs/功能测试-BOM物料清单.md
Normal file
20
docs/功能测试-BOM物料清单.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 功能测试任务列表 - BOM物料清单
|
||||
|
||||
> 路由:`/production_configuration/matetial_model/bom`
|
||||
|
||||
| 序号 | 测试项 | 操作步骤 | 预期结果 | 结果 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 页面入口 | 从菜单进入“BOM物料清单”,或直接访问路由 | 页面正常打开,表格展示 BOM 编码、名称、产品型号、状态、创建人、创建时间和备注 | ☐ |
|
||||
| 2 | 条件查询 | 输入 BOM 编码、名称或选择产品型号后点击“查询” | 列表按条件刷新,分页回到第一页 | ☐ |
|
||||
| 3 | 重置查询 | 点击“重置” | 查询条件清空,列表恢复默认数据 | ☐ |
|
||||
| 4 | 新增校验 | 点击“新增”,不填写编码、名称或产品型号直接确认 | 表单提示必填校验,不提交请求 | ☐ |
|
||||
| 5 | 新增 BOM | 填写 BOM 编码、名称、产品型号、状态和备注后确认 | 弹窗关闭,提示操作成功,列表出现新增 BOM | ☐ |
|
||||
| 6 | 编辑 BOM | 点击“编辑”,修改名称、状态或备注后确认 | 表单回显旧值,保存后列表展示新值 | ☐ |
|
||||
| 7 | 删除 BOM | 点击“删除”并确认 | 提示操作成功,记录被删除,分页刷新正确 | ☐ |
|
||||
| 8 | 打开 BOM 关系 | 点击“设置BOM” | 全屏关系弹窗打开,左侧显示工序单元,右侧显示当前工序 IN/OUT 关系 | ☐ |
|
||||
| 9 | 切换工序 | 在左侧选择不同工序单元 | IN/OUT 列表按工序刷新 | ☐ |
|
||||
| 10 | 添加 IN 物料 | 在 IN 区域点击“新增”,选择一个或多个物料后确认 | 物料加入 IN 列表,重复物料提示不可重复选择 | ☐ |
|
||||
| 11 | 编辑 IN 投入数量 | 修改 IN 列表的投入数量并移出输入框 | 数量保存成功,刷新后仍保持新值 | ☐ |
|
||||
| 12 | 添加 OUT 物料限制 | 在 OUT 区域尝试选择多个物料或已有 OUT 后继续新增 | 系统提示 OUT 结构只允许 1 个半成品 | ☐ |
|
||||
| 13 | 删除 BOM 关系 | 勾选 IN/OUT 关系后点击删除,或点击行内删除 | 关系删除成功,列表刷新 | ☐ |
|
||||
| 14 | 权限按钮 | 使用缺少新增/编辑/删除/设置BOM权限的账号进入页面 | 对应按钮不显示或不可操作 | ☐ |
|
||||
@@ -3,8 +3,8 @@
|
||||
> 根据 `后台Webman界面截图对照表.md` 生成。状态以当前 V2 项目中已落地的页面目录为准。
|
||||
|
||||
- 总功能数:79
|
||||
- 已迁移:74
|
||||
- 未迁移:5
|
||||
- 已迁移:75
|
||||
- 未迁移:4
|
||||
|
||||
| 状态 | 一级模块 | 二级模块 | 三级模块 | 功能说明 | V2 目标路径 |
|
||||
|:---:|---|---|---|---|---|
|
||||
@@ -23,7 +23,7 @@
|
||||
| ✅ | 生产配置 (Production Master Data) | 产品管理 (Product Management) | 不良管理 (Defect Management) | 不良代码及描述管理,支持批量导入 | `src/views/production-master-data/product-model/product-ng-info/` |
|
||||
| ✅ | 生产配置 (Production Master Data) | 物料模型 (Material Model) | 物料类别列表 (Material Category) | 区分原材料和半成品 | `src/views/production-master-data/material-model/material-category/` |
|
||||
| ✅ | 生产配置 (Production Master Data) | 物料模型 (Material Model) | 物料信息管理 (Material Master) | 维护物料编码、名称、规格等属性 | `src/views/production-master-data/material-model/material-master/` |
|
||||
| ⬜ | 生产配置 (Production Master Data) | 物料模型 (Material Model) | BOM物料清单 (Bill of Materials) | 产品BOM管理 | 待确认 |
|
||||
| ✅ | 生产配置 (Production Master Data) | 物料模型 (Material Model) | BOM物料清单 (Bill of Materials) | 产品BOM管理 | `src/views/production-master-data/material-model/bill-of-materials/` |
|
||||
| ✅ | 生产配置 (Production Master Data) | 物料模型 (Material Model) | 计量单位 (Unit of Measure) | 计量单位配置与管理 | `src/views/production-master-data/material-model/material-unit/` |
|
||||
| ✅ | 生产配置 (Production Master Data) | SPC采集模型 (SPC Configuration) | SPC采集配置 (Data Collection Configuration) | 配置SPC采集参数 | `src/views/production-master-data/spc-configuration/data-collection-configuration/` |
|
||||
| ⬜ | 生产配置 (Production Master Data) | 班组模型 (Team Model) | 班组管理 (Team Management) | 管理生产班组 | 待确认 |
|
||||
|
||||
56
src/api/production-master-data/bill-of-materials.js
Normal file
56
src/api/production-master-data/bill-of-materials.js
Normal 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) })
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "重置",
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user