生产相关菜单

master
huangjinysf 3 months ago
parent 04c2bae570
commit b0168e4996

@ -0,0 +1,16 @@
<template>
<div class="empty-content">
<el-empty description="请选择功能进行分析" />
</div>
</template>
<script setup>
//
</script>
<style scoped>
.empty-content {
padding: 40px 0;
text-align: center;
}
</style>

@ -0,0 +1,323 @@
<template>
<div class="item-analysis-container">
<div class="item-select-section">
<el-form :model="analysisForm" label-width="100px">
<el-form-item label="项目名称">
<el-select
v-model="analysisForm.selectedItem"
placeholder="请选择要分析的项目"
style="width: 300px"
clearable
>
<el-option
v-for="item in itemList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-button
type="primary"
style="margin-left: 10px"
:disabled="!analysisForm.selectedItem"
@click="handleFetchData"
>
查询
</el-button>
<el-button
type="success"
style="margin-left: 10px"
:disabled="!analysisForm.selectedItem || workbenchData.length === 0"
@click="handleAnalyzeItem"
>
AI分析
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 机台不良分布数据表格 -->
<div v-if="workbenchData.length > 0" class="workbench-data">
<h3>机台不良分布 - {{ analysisForm.selectedItem }}</h3>
<el-table
:data="workbenchData"
border
style="width: 100%"
stripe
:default-sort="{ prop: 'bad_count', order: 'descending' }"
>
<el-table-column prop="workbench" label="机台号" width="100" sortable />
<el-table-column prop="bad_count" label="不良数量" width="120" sortable />
<el-table-column prop="bad_percentage" label="不良占比" width="120" sortable>
<template #default="scope">
{{ scope.row.bad_percentage }}%
</template>
</el-table-column>
<el-table-column label="不良占比可视化">
<template #default="scope">
<el-progress
:percentage="scope.row.bad_percentage"
:show-text="false"
:color="getProgressColor(scope.row.bad_percentage)"
/>
</template>
</el-table-column>
</el-table>
<div class="statistics-info">
<p>数据范围: {{ currentStartDate }} {{ currentEndDate }}</p>
<p>总计机台数: {{ workbenchData.length }}</p>
</div>
</div>
<!-- 分析结果显示区域 -->
<div v-if="analysisResult" class="analysis-result">
<h3>{{ analysisForm.selectedItem }} - 分析结果</h3>
<el-input
v-model="analysisResult"
type="textarea"
:rows="10"
readonly
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { AI_CONFIG, CURRENT_AI_MODEL } from "@/config/ai"
import { API_CONFIG } from "@/config/api"
const props = defineProps({
paretoData: {
type: Array,
default: () => []
},
selectedTimeRange: {
type: String,
default: '近1年'
}
})
const emit = defineEmits(['ai-analysis-complete'])
//
const analysisForm = reactive({
selectedItem: ''
})
//
const analysisResult = ref('')
//
const workbenchData = ref([])
//
const currentStartDate = ref('')
const currentEndDate = ref('')
//
const itemList = ref([
'漆膜厚度',
'击穿电压2(kv)',
'击穿电压5(kv)',
'击穿电压4(kv)',
'击穿电压3(kv)',
'回弹角(°)1',
'回弹角(°)2',
'击穿电压1(kv)',
'漆膜连续性(个)',
'最大外径',
'伸长率(%)1',
'导体f值',
'电阻值',
'预置伸长率',
'伸长率(%)2',
'平均单刮(N)',
'单向刮漆min(N)'
])
//
function getProgressColor(percentage) {
if (percentage >= 10) return '#f56c6c'; // -
if (percentage >= 5) return '#e6a23c'; // -
if (percentage >= 2) return '#409eff'; // -
return '#67c23a'; // 绿 -
}
//
async function handleFetchData() {
if (!analysisForm.selectedItem) {
emit('ai-analysis-complete', '请先选择要分析的项目')
return
}
//
const endDate = new Date();
const startDate = new Date();
//
if (props.selectedTimeRange === '近1年') {
startDate.setFullYear(endDate.getFullYear() - 1);
} else if (props.selectedTimeRange === '近2年') {
startDate.setFullYear(endDate.getFullYear() - 2);
} else if (props.selectedTimeRange === '近3年') {
startDate.setFullYear(endDate.getFullYear() - 3);
}
// YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const startDateStr = formatDate(startDate);
const endDateStr = formatDate(endDate);
//
currentStartDate.value = startDateStr;
currentEndDate.value = endDateStr;
// API
try {
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.WORKBENCH_BADNESS}?item_name=${encodeURIComponent(analysisForm.selectedItem)}&start_date=${startDateStr}&end_date=${endDateStr}`);
const data = await response.json();
if (data.code === 200 && data.data) {
workbenchData.value = data.data;
emit('ai-analysis-complete', `成功获取${analysisForm.selectedItem}的机台不良分布数据`);
} else {
workbenchData.value = [];
emit('ai-analysis-complete', data.message || '获取机台不良分布数据失败');
}
} catch (error) {
console.error('获取机台不良分布数据失败:', error);
workbenchData.value = [];
emit('ai-analysis-complete', '获取机台不良分布数据失败');
}
}
// AI
async function handleAnalyzeItem() {
if (!analysisForm.selectedItem || workbenchData.value.length === 0) {
emit('ai-analysis-complete', '请先选择项目并查询数据')
return
}
//
const selectedItemData = props.paretoData.find(item => item.item_name === analysisForm.selectedItem)
//
const prompt = `请对以下质检项目和机台不良分布进行详细分析:
项目名称: ${analysisForm.selectedItem}
数据范围: ${currentStartDate.value} ${currentEndDate.value}
帕累托分析数据:
${selectedItemData ?
`- 缺陷数量: ${selectedItemData.defect_count}\n- 缺陷占比: ${selectedItemData.defect_percentage}%\n- 累计占比: ${selectedItemData.cumulative_percentage}%` :
'该项目在当前帕累托分析数据中不存在'}
机台不良分布数据:
${workbenchData.value.slice(0, 5).map(item =>
`- 机台${item.workbench}: 不良数量${item.bad_count}, 不良占比${item.bad_percentage}%`
).join('\n')}
请从以下角度进行分析
1. 该项目缺陷对整体质量的影响
2. 产生缺陷的可能原因分析
3. 针对该项目的改进建议
4. 机台不良分布的规律分析
5. 该项目与其他项目的关联性分析`
try {
// AI
const currentAIConfig = AI_CONFIG[CURRENT_AI_MODEL]
// AIAPI
const response = await fetch(`${currentAIConfig.BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${currentAIConfig.API_KEY}`
},
body: JSON.stringify({
model: currentAIConfig.MODEL,
messages: [
{
role: 'system',
content: '你是一位专业的质量控制专家,擅长分析具体质检项目并提供针对性的改进建议。'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.3,
max_tokens: 1500
})
})
if (!response.ok) {
throw new Error(`API调用失败: ${response.status}`)
}
const data = await response.json()
const result = data.choices[0].message.content
analysisResult.value = result
//
emit('ai-analysis-complete', result)
} catch (error) {
console.error('项目分析失败:', error)
const errorMessage = `项目分析失败: ${error.message}\n\n请检查网络连接或稍后再试。`
analysisResult.value = errorMessage
//
emit('ai-analysis-complete', errorMessage)
}
}
</script>
<style scoped>
.item-analysis-container {
width: 100%;
padding: 20px;
}
.item-select-section {
margin-bottom: 20px;
padding: 15px;
background-color: #f5f7fa;
border-radius: 4px;
}
.analysis-result {
margin-top: 20px;
}
.analysis-result h3 {
margin-bottom: 15px;
color: #409eff;
}
.workbench-data {
margin-bottom: 20px;
}
.workbench-data h3 {
margin-bottom: 15px;
color: #409eff;
}
.statistics-info {
margin-top: 10px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.statistics-info p {
margin: 5px 0;
font-size: 14px;
}
</style>

@ -0,0 +1,210 @@
<template>
<div class="pareto-analysis-container">
<!-- 帕累托分析按钮 -->
<div style="margin-bottom: 10px;">
<el-button
type="success"
icon="ChatDotRound"
@click="handleAIAnalysis"
:loading="aiAnalysisLoading"
>
AI分析
</el-button>
</div>
<!-- 帕累托数据表格 -->
<el-table
v-if="paretoData.length > 0"
:data="paretoData"
border
style="width: 100%"
stripe
>
<el-table-column prop="item_name" label="检测项" width="180" />
<el-table-column prop="defect_count" label="缺陷数" width="100" />
<el-table-column prop="defect_percentage" label="缺陷百分比" width="120">
<template #default="scope">
{{ scope.row.defect_percentage }}%
</template>
</el-table-column>
<el-table-column prop="cumulative_percentage" label="累计百分比" width="120">
<template #default="scope">
{{ scope.row.cumulative_percentage }}%
</template>
</el-table-column>
<el-table-column label="缺陷占比">
<template #default="scope">
<el-progress
:percentage="scope.row.defect_percentage"
:show-text="false"
:color="getProgressColor(scope.row.defect_percentage)"
/>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div v-if="paretoData.length > 0" class="statistics-info">
<p>数据范围: {{ dateRange.start_date }} {{ dateRange.end_date }}</p>
<p>总计检测项: {{ paretoData.length }}</p>
</div>
<!-- AI分析结果对话框 -->
<el-dialog
v-model="aiDialogVisible"
title="AI分析结果"
width="60%"
:close-on-click-modal="false"
>
<div class="ai-analysis-content">
<el-input
v-model="aiAnalysisResult"
type="textarea"
:rows="15"
placeholder="AI分析结果将显示在这里"
readonly
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { AI_CONFIG, CURRENT_AI_MODEL } from "@/config/ai"
const props = defineProps({
selectedTimeRange: {
type: String,
default: '近1年'
},
paretoData: {
type: Array,
default: () => []
},
dateRange: {
type: Object,
default: () => ({
start_date: '',
end_date: ''
})
},
apiKey: {
type: String,
default: AI_CONFIG[CURRENT_AI_MODEL].API_KEY
}
})
const emit = defineEmits(['ai-analysis-complete'])
// AI
const aiDialogVisible = ref(false)
const aiAnalysisResult = ref("")
const aiAnalysisLoading = ref(false)
//
function getProgressColor(percentage) {
if (percentage >= 30) return '#f56c6c'; // -
if (percentage >= 15) return '#e6a23c'; // -
if (percentage >= 5) return '#409eff'; // -
return '#67c23a'; // 绿 -
}
// AI
async function handleAIAnalysis() {
if (props.paretoData.length === 0) {
emit('ai-analysis-complete', '请先获取帕累托分析数据')
return
}
aiAnalysisLoading.value = true
aiDialogVisible.value = true
aiAnalysisResult.value = '正在分析中,请稍候...'
try {
//
const prompt = `请分析以下帕累托分析数据,提供专业的质量控制见解:
数据范围: ${props.dateRange.start_date} ${props.dateRange.end_date}
检测项目总数: ${props.paretoData.length}
详细数据:
${props.paretoData.map(item =>
`- ${item.item_name}: 缺陷数 ${item.defect_count}, 缺陷占比 ${item.defect_percentage}%, 累计占比 ${item.cumulative_percentage}%`
).join('\n')}
请从以下角度进行分析
1. 主要缺陷项及其影响程度
2. 帕累托原理(80/20法则)在此数据中的体现
3. 针对高缺陷率项的改进建议
4. 质量控制重点优化方向`
// AI
const currentAIConfig = AI_CONFIG[CURRENT_AI_MODEL]
// AIAPI
const response = await fetch(`${currentAIConfig.BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${currentAIConfig.API_KEY}`
},
body: JSON.stringify({
model: currentAIConfig.MODEL,
messages: [
{
role: 'system',
content: '你是一位专业的质量控制专家,擅长分析缺陷数据并提供专业的改进建议。'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.1,
max_tokens: 2000
})
})
if (!response.ok) {
throw new Error(`API调用失败: ${response.status}`)
}
const data = await response.json()
const analysisResult = data.choices[0].message.content
aiAnalysisResult.value = analysisResult
//
emit('ai-analysis-complete', analysisResult)
} catch (error) {
console.error('AI分析失败:', error)
const errorMessage = `AI分析失败: ${error.message}\n\n请检查网络连接或稍后再试。`
aiAnalysisResult.value = errorMessage
//
emit('ai-analysis-complete', errorMessage)
} finally {
aiAnalysisLoading.value = false
}
}
</script>
<style scoped>
.pareto-analysis-container {
width: 100%;
}
.statistics-info {
margin-top: 10px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.statistics-info p {
margin: 5px 0;
font-size: 14px;
}
.ai-analysis-content {
padding: 10px 0;
}
</style>

@ -0,0 +1,223 @@
<template>
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="3">
<el-form :model="queryForm" label-width="80px">
<el-form-item label="数据范围">
<el-select v-model="queryForm.selectedTimeRange" placeholder="选择时间范围" style="width: 100%">
<el-option
v-for="item in timeRangeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
@click="handlePareto"
v-hasPermi="['warehouse:WmsImportResult:add']"
>帕累托分析</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
@click="handleItemAnalysis"
v-hasPermi="['warehouse:WmsImportResult:edit']"
>项目分析</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['warehouse:WmsImportResult:remove']"
>趋势分析</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
@click="handleExport"
v-hasPermi="['warehouse:WmsImportResult:export']"
>产品特征分析</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 动态内容显示区域 -->
<el-row class="mb8">
<el-col :span="24">
<!-- 根据currentComponent动态切换组件 -->
<component
:is="currentComponent"
v-if="currentComponent"
:selected-time-range="queryForm.selectedTimeRange"
:pareto-data="paretoData"
:date-range="dateRange"
@ai-analysis-complete="handleAIAnalysisComplete"
/>
</el-col>
</el-row>
<!-- 消息显示文本框 -->
<el-row>
<el-col :span="24">
<el-input
v-model="message"
type="textarea"
:rows="6"
placeholder="API返回的消息将显示在这里"
clearable
/>
</el-col>
</el-row>
</div>
</template>
<script setup name="WmsImportResult">
import { ref, reactive, shallowRef } from 'vue'
import { API_CONFIG } from "@/config/api"
import ParetoAnalysis from './ParetoAnalysis.vue'
import ItemAnalysis from './ItemAnalysis.vue'
import EmptyContent from './EmptyContent.vue'
const showSearch = ref(true)
const single = ref(true)
const multiple = ref(true)
const message = ref("")
const timeRangeOptions = ref([
{ value: "近1年", label: "近1年" },
{ value: "近2年", label: "近2年" },
{ value: "近3年", label: "近3年" }
])
const queryForm = ref({
selectedTimeRange: "近1年"
})
const paretoData = ref([])
const dateRange = ref({
start_date: '',
end_date: ''
})
//
const currentComponent = shallowRef(EmptyContent)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
model: null,
specification: null,
wireDisc: null,
totalNumber: null,
totalNetWeight: null,
totalGrossWeight: null,
type: 1,
warningValue: null,
},
rules: {
}
})
function handlePareto() {
//
const endDate = new Date();
const startDate = new Date();
if (queryForm.value.selectedTimeRange === '近1年') {
startDate.setFullYear(endDate.getFullYear() - 1);
} else if (queryForm.value.selectedTimeRange === '近2年') {
startDate.setFullYear(endDate.getFullYear() - 2);
} else if (queryForm.value.selectedTimeRange === '近3年') {
startDate.setFullYear(endDate.getFullYear() - 3);
}
// YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const startDateStr = formatDate(startDate);
const endDateStr = formatDate(endDate);
// console.log(startDateStr, endDateStr);
// API
fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.PARETO_ANALYSIS}?start_date=${startDateStr}&end_date=${endDateStr}`)
.then(response => response.json())
.then(data => {
message.value = data.message || `质检数据获取成功 (${queryForm.value.selectedTimeRange})`
//
if (data.code === 200 && data.data) {
paretoData.value = data.data;
dateRange.value = {
start_date: data.start_date,
end_date: data.end_date
};
//
currentComponent.value = ParetoAnalysis;
}
})
.catch(error => {
console.error('质检API调用失败:', error)
message.value = '质检API调用失败'
})
}
// AI
function handleAIAnalysisComplete(result) {
message.value = result;
}
//
function handleItemAnalysis() {
//
currentComponent.value = ItemAnalysis;
}
</script>
<style>
/* 在全局样式文件中添加 */
.el-dialog {
margin-top: 0 !important;
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
}
.statistics-info {
margin-top: 10px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.statistics-info p {
margin: 5px 0;
font-size: 14px;
}
.ai-analysis-content {
padding: 10px 0;
}
</style>
Loading…
Cancel
Save