|
|
|
|
<template>
|
|
|
|
|
<basic-container>
|
|
|
|
|
<!-- <el-tabs v-model="activeName" @tab-click="handleClick">
|
|
|
|
|
<el-tab-pane label="部件-子件" name="1"></el-tab-pane>
|
|
|
|
|
<el-tab-pane label="部件-模具" name="2"></el-tab-pane>
|
|
|
|
|
<el-tab-pane label="模具-子件" name="3"></el-tab-pane>
|
|
|
|
|
</el-tabs> -->
|
|
|
|
|
<!-- <pieceChildQuery v-if="activeName=='1'"></pieceChildQuery>
|
|
|
|
|
<sinterMoldNew v-if="activeName=='2'"></sinterMoldNew>
|
|
|
|
|
<moldChild v-if="activeName=='3'"></moldChild> -->
|
|
|
|
|
<el-form :model="form" label-width="60" label-position="left">
|
|
|
|
|
<el-row :gutter="24">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="部件号:">
|
|
|
|
|
<el-input v-model="form.bujian" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="子件号:">
|
|
|
|
|
<el-input v-model="form.zijian" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<div style="float: right">
|
|
|
|
|
<el-button type="primary" icon="search" @click="searchFn()">查询</el-button>
|
|
|
|
|
<el-button icon="refreshLeft" @click="resetFn()">重置</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<div class="form-box">
|
|
|
|
|
<div v-if="isComponentOpen">
|
|
|
|
|
<div v-if="dataTree.length > 0" class="form-bom" ref="lineChart"></div>
|
|
|
|
|
<el-empty v-else description="数据为空"></el-empty>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="isSubOpen">
|
|
|
|
|
<avue-crud
|
|
|
|
|
:option="option"
|
|
|
|
|
:table-loading="loading"
|
|
|
|
|
:data="data"
|
|
|
|
|
v-model="form"
|
|
|
|
|
v-model:page="page"
|
|
|
|
|
ref="crud"
|
|
|
|
|
@row-del="rowDel"
|
|
|
|
|
@search-change="searchChange"
|
|
|
|
|
@search-reset="searchReset"
|
|
|
|
|
@selection-change="selectionChange"
|
|
|
|
|
@current-change="currentChange"
|
|
|
|
|
@size-change="sizeChange"
|
|
|
|
|
@refresh-change="refreshChange"
|
|
|
|
|
@on-load="onLoad"
|
|
|
|
|
>
|
|
|
|
|
<template #bpartCode="scope">
|
|
|
|
|
<el-button type="text" @click="linkToPartTree(scope.row)">{{
|
|
|
|
|
scope.row.bpartCode
|
|
|
|
|
}}</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</avue-crud>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</basic-container>
|
|
|
|
|
</template>
|
|
|
|
|
<script>
|
|
|
|
|
// import pieceChildQuery from './components/pieceChildQuery.vue'
|
|
|
|
|
// import sinterMoldNew from './components/sinterMoldNew.vue'
|
|
|
|
|
// import moldChild from './components/moldChild.vue'
|
|
|
|
|
import { getSinteringPart, getDsPartTree } from '@/api/processManagement/sinTerBOM';
|
|
|
|
|
export default {
|
|
|
|
|
components: {
|
|
|
|
|
// pieceChildQuery,
|
|
|
|
|
// sinterMoldNew,
|
|
|
|
|
// moldChild
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
activeName: '1',
|
|
|
|
|
form: {
|
|
|
|
|
bujian: '',
|
|
|
|
|
zijian: '',
|
|
|
|
|
},
|
|
|
|
|
option: {
|
|
|
|
|
height: 'auto',
|
|
|
|
|
calcHeight: 32,
|
|
|
|
|
tip: false,
|
|
|
|
|
simplePage: true,
|
|
|
|
|
searchShow: true,
|
|
|
|
|
searchMenuSpan: 6,
|
|
|
|
|
searchIcon: true,
|
|
|
|
|
searchIndex: 3,
|
|
|
|
|
tree: false,
|
|
|
|
|
border: true,
|
|
|
|
|
index: true,
|
|
|
|
|
selection: false,
|
|
|
|
|
viewBtn: false,
|
|
|
|
|
delBtn: false,
|
|
|
|
|
addBtn: false,
|
|
|
|
|
editBtn: false,
|
|
|
|
|
editBtnText: '修改',
|
|
|
|
|
addBtnIcon: ' ',
|
|
|
|
|
viewBtnIcon: ' ',
|
|
|
|
|
delBtnIcon: ' ',
|
|
|
|
|
editBtnIcon: ' ',
|
|
|
|
|
viewBtnText: '详情',
|
|
|
|
|
labelWidth: 120,
|
|
|
|
|
menuWidth: 100,
|
|
|
|
|
dialogWidth: 640,
|
|
|
|
|
dialogClickModal: false,
|
|
|
|
|
searchEnter: true,
|
|
|
|
|
excelBtn: false,
|
|
|
|
|
filterBtn: true,
|
|
|
|
|
searchShowBtn: false,
|
|
|
|
|
columnSort: true,
|
|
|
|
|
excelBtn: true,
|
|
|
|
|
columnSort: true,
|
|
|
|
|
showOverflowTooltip: true,
|
|
|
|
|
gridBtn: false,
|
|
|
|
|
searchLabelPosition: 'left',
|
|
|
|
|
searchGutter: 24,
|
|
|
|
|
searchSpan: 6,
|
|
|
|
|
menuAlign: 'left',
|
|
|
|
|
gridBtn: false,
|
|
|
|
|
searchMenuPosition: 'right',
|
|
|
|
|
addBtnIcon: ' ',
|
|
|
|
|
viewBtnIcon: ' ',
|
|
|
|
|
delBtnIcon: ' ',
|
|
|
|
|
editBtnIcon: ' ',
|
|
|
|
|
header: false,
|
|
|
|
|
menu: false,
|
|
|
|
|
align: 'center',
|
|
|
|
|
column: [
|
|
|
|
|
{
|
|
|
|
|
label: '部件号',
|
|
|
|
|
prop: 'bpartCode',
|
|
|
|
|
sortable: true,
|
|
|
|
|
filter: true,
|
|
|
|
|
span: 24,
|
|
|
|
|
search: false,
|
|
|
|
|
headerAlign: 'center',
|
|
|
|
|
align: 'center',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '子件号',
|
|
|
|
|
prop: 'zpartCode',
|
|
|
|
|
sortable: true,
|
|
|
|
|
filter: true,
|
|
|
|
|
span: 24,
|
|
|
|
|
search: false,
|
|
|
|
|
headerAlign: 'center',
|
|
|
|
|
align: 'center',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '子件名称',
|
|
|
|
|
prop: 'zpartName',
|
|
|
|
|
sortable: true,
|
|
|
|
|
filter: true,
|
|
|
|
|
span: 24,
|
|
|
|
|
search: false,
|
|
|
|
|
headerAlign: 'center',
|
|
|
|
|
align: 'center',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
isSubOpen: false,
|
|
|
|
|
isComponentOpen: false,
|
|
|
|
|
data: [],
|
|
|
|
|
dataTree: [],
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 点击子件中的部件号 跳转到部件树
|
|
|
|
|
linkToPartTree(row) {
|
|
|
|
|
this.isComponentOpen = true;
|
|
|
|
|
this.isSubOpen = false;
|
|
|
|
|
getDsPartTree({ partCode: row.bpartCode, zPartCode: '' }).then(res => {
|
|
|
|
|
let resData = res.data.data;
|
|
|
|
|
let arr = JSON.parse(JSON.stringify(resData).replace(/partName/g, 'name'));
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.createBarChart(arr);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
searchFn() {
|
|
|
|
|
if (this.form.zijian != '') {
|
|
|
|
|
this.isComponentOpen = false;
|
|
|
|
|
this.isSubOpen = true;
|
|
|
|
|
getSinteringPart({ bPartCode: this.form.bujian, zPartCode: this.form.zijian }).then(res => {
|
|
|
|
|
this.data = res.data.data;
|
|
|
|
|
});
|
|
|
|
|
} else if (this.form.bujian != '') {
|
|
|
|
|
this.isComponentOpen = true;
|
|
|
|
|
this.isSubOpen = false;
|
|
|
|
|
getDsPartTree({ partCode: this.form.bujian, zPartCode: this.form.zijian }).then(res => {
|
|
|
|
|
let resData = res.data.data;
|
|
|
|
|
this.dataTree = res.data.data;
|
|
|
|
|
let arr = JSON.parse(JSON.stringify(resData).replace(/partName/g, 'name'));
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.createBarChart(arr);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (this.form.zijian == '' && this.form.bujian == '') {
|
|
|
|
|
this.isSubOpen = false;
|
|
|
|
|
this.isComponentOpen = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 获取子件列表
|
|
|
|
|
|
|
|
|
|
resetFn() {
|
|
|
|
|
this.form.zijian = '';
|
|
|
|
|
this.form.bujian = '';
|
|
|
|
|
this.isSubOpen = false;
|
|
|
|
|
this.isComponentOpen = false;
|
|
|
|
|
},
|
|
|
|
|
getTextWidth(text, fontSize = 12) {
|
|
|
|
|
// 中文字符宽度≈fontSize,英文字符≈fontSize/2,加额外内边距
|
|
|
|
|
const cnChar = text.replace(/[a-zA-Z0-9]/g, '').length;
|
|
|
|
|
const enChar = text.length - cnChar;
|
|
|
|
|
return cnChar * fontSize + enChar * (fontSize / 2) + 20; // +20 内边距
|
|
|
|
|
},
|
|
|
|
|
// 图表
|
|
|
|
|
createBarChart(value) {
|
|
|
|
|
// 销毁旧的 ECharts 实例
|
|
|
|
|
if (this.mapBoxEchart) {
|
|
|
|
|
this.mapBoxEchart.dispose();
|
|
|
|
|
this.mapBoxEchart = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
if (!this.$refs.lineChart) return;
|
|
|
|
|
const chartWidth = this.calculateChartWidth(value);
|
|
|
|
|
this.$refs.lineChart.style.width = chartWidth + 'px';
|
|
|
|
|
const mapBoxEchart = this.$echarts.init(this.$refs.lineChart);
|
|
|
|
|
this.mapBoxEchart = mapBoxEchart;
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
triggerOn: 'mousemove',
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
roam: true,
|
|
|
|
|
type: 'tree',
|
|
|
|
|
data: value || [],
|
|
|
|
|
top: '10%',
|
|
|
|
|
left: '5%',
|
|
|
|
|
bottom: '10%',
|
|
|
|
|
right: '10%',
|
|
|
|
|
expandAndCollapse: false, // 允许展开收起
|
|
|
|
|
initialTreeDepth: 2, // 初始展开层级
|
|
|
|
|
animationDuration: 550,
|
|
|
|
|
animationDurationUpdate: 750,
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
symbol: 'roundRect',
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: '#284c89',
|
|
|
|
|
borderWidth: 2,
|
|
|
|
|
borderColor: '#fff',
|
|
|
|
|
borderRadius: 2,
|
|
|
|
|
},
|
|
|
|
|
// 节点间距配置(关键)
|
|
|
|
|
// nodePadding: [30, 100], // [上下间距,左右间距]
|
|
|
|
|
// layerPadding: 80, // 层级间距
|
|
|
|
|
// 动态计算节点大小
|
|
|
|
|
symbolSize: (val, params) => {
|
|
|
|
|
const nodeData = params?.data || params;
|
|
|
|
|
const nodeName = nodeData?.name || '工序';
|
|
|
|
|
const nodePartCode = nodeData?.partCode || '';
|
|
|
|
|
const nodeQuota = nodeData.quota ? nodeData.quota : '';
|
|
|
|
|
|
|
|
|
|
// 计算文本宽度
|
|
|
|
|
const nameWidth = this.getTextWidth(nodeName);
|
|
|
|
|
const codeWidth = this.getTextWidth(nodePartCode);
|
|
|
|
|
const quotaWidth = nodeQuota ? this.getTextWidth(`${nodeQuota}`) : 0;
|
|
|
|
|
|
|
|
|
|
const maxWidth = Math.max(nameWidth, codeWidth, quotaWidth, 60);
|
|
|
|
|
|
|
|
|
|
// 动态计算行数
|
|
|
|
|
let lineCount = 2;
|
|
|
|
|
if (nodeQuota !== '' && nodeQuota !== null) {
|
|
|
|
|
lineCount = 3;
|
|
|
|
|
}
|
|
|
|
|
const height = lineCount * 20 + 15;
|
|
|
|
|
|
|
|
|
|
return [Math.max(maxWidth, 60), height];
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
position: 'inside',
|
|
|
|
|
show: true,
|
|
|
|
|
align: 'center',
|
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
lineHeight: 18,
|
|
|
|
|
color: '#fff',
|
|
|
|
|
formatter: params => {
|
|
|
|
|
const nodeData = params.data || params;
|
|
|
|
|
nodeData.quota = nodeData.quota==-1?null:nodeData.quota
|
|
|
|
|
const quotaText = nodeData.quota != null ? `${nodeData.quota}` : '';
|
|
|
|
|
let result = `${nodeData.partCode}\n${nodeData.name}`;
|
|
|
|
|
if (quotaText) {
|
|
|
|
|
result += '\n' + quotaText;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
// 连线样式
|
|
|
|
|
lineStyle: {
|
|
|
|
|
color: '#ccc',
|
|
|
|
|
width: 1.5,
|
|
|
|
|
curveness: 0.5,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mapBoxEchart.setOption(option, true);
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
if (this.mapBoxEchart) {
|
|
|
|
|
this.mapBoxEchart.resize();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mapBoxEchart.on('dblclick', params => {
|
|
|
|
|
if (params.componentType === 'series' && params.seriesType === 'tree' && params.data) {
|
|
|
|
|
window.open(`${params.data.docLink}`, '_blank');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
// 计算图表所需宽度
|
|
|
|
|
calculateChartWidth(data) {
|
|
|
|
|
if (!data || data.length === 0) return 1000;
|
|
|
|
|
|
|
|
|
|
let maxChildren = 0;
|
|
|
|
|
let maxDepth = 0;
|
|
|
|
|
|
|
|
|
|
const traverse = (nodes, depth = 0) => {
|
|
|
|
|
if (!nodes || nodes.length === 0) return;
|
|
|
|
|
maxDepth = Math.max(maxDepth, depth);
|
|
|
|
|
|
|
|
|
|
nodes.forEach(node => {
|
|
|
|
|
if (node.children && node.children.length > 0) {
|
|
|
|
|
maxChildren = Math.max(maxChildren, node.children.length);
|
|
|
|
|
traverse(node.children, depth + 1);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
traverse(data);
|
|
|
|
|
|
|
|
|
|
// 根据子节点数量和层级计算宽度
|
|
|
|
|
// 每个节点约 200px,每层约 150px
|
|
|
|
|
const width = Math.max(
|
|
|
|
|
maxChildren * 200,
|
|
|
|
|
maxDepth * 150,
|
|
|
|
|
1000 // 最小宽度 1000px
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log('图表宽度计算:', { maxChildren, maxDepth, width });
|
|
|
|
|
return width;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.form-bom {
|
|
|
|
|
height: calc(100vh - 60px - 40px - 10px - 90px - 31px);
|
|
|
|
|
min-width: 100%;
|
|
|
|
|
background: #f0f2f5;
|
|
|
|
|
overflow: visible; // 让 ECharts 自由扩展
|
|
|
|
|
}
|
|
|
|
|
</style>
|