Merge remote-tracking branch 'origin/master'

master
suixy 4 months ago
commit f623416c34

@ -44,6 +44,8 @@ const openAiAssistantMenu = () => {
right: 10px;
width: 24px;
height: 24px;
cursor: pointer;
z-index:7000;
}
.el-message {

@ -93,6 +93,11 @@ export interface AiFormSettingForm extends BaseEntity {
*/
formPath?: string;
/**
* (12)
*/
formLoadType?: string;
/**
*
*/

@ -1,6 +1,7 @@
// hwmom-ui/src/api/dms/report/faultTrace.ts
import request from '@/utils/request';
import { FaultTraceQuery, FaultTraceVO } from './types';
import { RealtimeAlarmQuery, RealtimeAlarmVO } from './types';
export function listFaultTrace(query: FaultTraceQuery) {
@ -18,4 +19,21 @@ export function exportFaultTrace(query: FaultTraceQuery) {
data: query,
responseType: 'blob',
});
}
export function listRealtimeAlarm(query: RealtimeAlarmQuery) {
return request({
url: '/dms/report/realtimeAlarm/list',
method: 'get',
params: query,
});
}
export function exportRealtimeAlarm(query: RealtimeAlarmQuery) {
return request({
url: '/dms/report/realtimeAlarm/export',
method: 'post',
data: query,
responseType: 'blob',
});
}

@ -27,3 +27,33 @@ export interface FaultTraceQuery {
topResolutionTime: string | null;
faultType?: string | number;
}
export interface RealtimeAlarmQuery {
pageNum?: number;
pageSize?: number;
startDate?: string; // 'YYYY-MM-DD HH:mm:ss' or ISO
endDate?: string; // 'YYYY-MM-DD HH:mm:ss' or ISO
deviceModeId?: number;
machineCode?: string;
machineId?: number;
alarmLevelId?: number;
alarmTypeId?: number;
alarmStatus?: string; // 0未处理、1已处理、2已恢复
alarmMode?: string; // 1云端处理2终端上报
alarmInfoType?: string; // 1设备报警3离线报警
params?: Record<string, any>;
}
export interface RealtimeAlarmVO {
machineCode: string;
machineName: string;
deviceModeName: string;
alarmLevelName: string;
alarmTypeName: string;
alarmStatus: string; // 字典 alarm_status
alarmMode: string; // 字典 alarm_mode
alarmBeginTime: string;
alarmEndTime: string | null;
durationMinutes: string; // as string from backend
alarmContent: string;
}

@ -28,6 +28,51 @@ export interface ProcessProgressVO {
*
*/
isCompleted?: boolean;
/**
*
*/
isInProgress?: boolean;
/**
*
*/
planAmount?: number;
/**
*
*/
completeAmount?: number;
/**
*
*/
remainingAmount?: number;
/**
*
*/
processProgress?: number;
/**
*
*/
planBeginTime?: string;
/**
*
*/
planEndTime?: string;
/**
*
*/
realBeginTime?: string;
/**
*
*/
realEndTime?: string;
}
export interface WipTrackingReportVO {

@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
export const useSharedDataStore = defineStore('sharedData', {
state: () => ({
// 定义需要共享的数据
dynamicValue: null,
// 可以定义多个共享状态
otherData: '',
// 可以添加时间戳以便监听更新
lastUpdated: null
}),
actions: {
// 更新数据的方法
updateDynamicValue(value) {
this.dynamicValue = value
this.lastUpdated = new Date().toISOString()
},
// 清空数据
clearData() {
this.dynamicValue = null
this.otherData = ''
}
},
getters: {
// 可以添加计算属性
formattedValue() {
return this.dynamicValue ? JSON.stringify(this.dynamicValue) : '无数据'
}
}
})

@ -0,0 +1,115 @@
/**
*
* @param sourceObject
* @returns
*
* @description
* nullundefined('')
*
*/
export function filterEmptyValues<T extends object>(sourceObject: T): Partial<T> {
const result: Partial<T> = {};
// 遍历源对象的所有属性
Object.keys(sourceObject).forEach(key => {
const value = (sourceObject as any)[key];
// 检查值是否不为空
if (value !== null && value !== undefined && value !== '') {
// 如果是对象且不是数组,递归处理
if (typeof value === 'object' && !Array.isArray(value)) {
// 递归过滤嵌套对象
const filteredNestedObject = filterEmptyValues(value);
// 只有当过滤后的嵌套对象有属性时才添加
if (Object.keys(filteredNestedObject).length > 0) {
(result as any)[key] = filteredNestedObject;
}
} else {
// 对于非对象类型或数组,直接添加
(result as any)[key] = value;
}
}
});
return result;
}
/**
*
* @param sourceObject
* @param options
* @returns
*
* @description
*/
export function filterEmptyValuesAdvanced<T extends object>(
sourceObject: T,
options: {
// 是否将空数组视为空值
treatEmptyArrayAsEmpty?: boolean;
// 是否将空对象视为空值
treatEmptyObjectAsEmpty?: boolean;
// 是否将0视为空值
treatZeroAsEmpty?: boolean;
// 是否将false视为空值
treatFalseAsEmpty?: boolean;
} = {}
): Partial<T> {
const {
treatEmptyArrayAsEmpty = false,
treatEmptyObjectAsEmpty = false,
treatZeroAsEmpty = false,
treatFalseAsEmpty = false
} = options;
const result: Partial<T> = {};
Object.keys(sourceObject).forEach(key => {
const value = (sourceObject as any)[key];
// 基本空值检查
let isEmpty = value === null || value === undefined || value === '';
// 根据选项进行额外的空值检查
if (!isEmpty && treatZeroAsEmpty && value === 0) {
isEmpty = true;
}
if (!isEmpty && treatFalseAsEmpty && value === false) {
isEmpty = true;
}
if (!isEmpty && treatEmptyArrayAsEmpty && Array.isArray(value) && value.length === 0) {
isEmpty = true;
}
if (!isEmpty && treatEmptyObjectAsEmpty && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
isEmpty = true;
}
// 如果值不为空,处理并添加到结果中
if (!isEmpty) {
if (typeof value === 'object' && !Array.isArray(value)) {
const filteredNestedObject = filterEmptyValuesAdvanced(value, options);
if (Object.keys(filteredNestedObject).length > 0) {
(result as any)[key] = filteredNestedObject;
}
} else {
(result as any)[key] = value;
}
}
});
return result;
}
/**
*
* @param source
* @param target
* @returns
*/
export function copyNonEmptyValues<T extends object, U extends object>(source: T, target: U): U & Partial<T> {
const filteredValues = filterEmptyValues(source);
return Object.assign(target, filteredValues) as U & Partial<T>;
}

@ -17,3 +17,12 @@ export const getDefectAnalysisReport = (params: { startTime: string, endTime: st
params
});
};
// 新增获取进料检验效率IQC报表
export const getIncomingInspectionEfficiency = (params: { startTime: string, endTime: string }) => {
return request({
url: '/qms/report/incoming',
method: 'get',
params
});
};

@ -1,15 +1,32 @@
<template>
<div v-if="state.isShowSearch" class="ai-float-window ai-float-fullscreen">
<div v-if="state.isShowSearch" :class="['ai-float-window', currentSizeClass]"
:style="currentSize === 'custom' ? {
width: windowSize.width,
height: windowSize.height,
left: typeof windowPosition.x === 'string' ? 'auto' : windowPosition.x + 'px',
right: typeof windowPosition.x === 'string' ? '0' : 'auto',
top: windowPosition.y + 'px'
} : {}">
<div class="ai-float-main">
<div class="ai-float-left" v-if="isShowLeft">
<component :is="currentComponent" v-bind="currentProps" v-if="currentComponent"/>
</div>
<div class="ai-float-right" v-loading="aiLoading">
<div class="ai-float-header" @mousedown="onDragStart">
<div class="ai-float-header" @mousedown="startDrag">
<span>AI智能填报助手</span>
<!-- 将两个图标按钮放在一个容器内 -->
<div class="ai-float-header-actions">
<el-icon v-if="currentSize==='minimize'" class="ai-float-minimize" @click.stop="toggleSize">
<Plus />
</el-icon>
<el-icon v-if="currentSize!=='minimize'" class="ai-float-minimize" @click.stop="toggleSize">
<Minus />
</el-icon>
<el-icon class="ai-float-close" @click="close">
<Close/>
</el-icon>
</div>
</div>
<div class="ai-float-form-list ai-float-scroll">
<div class="ai-float-welcome">👋 欢迎使用AI智能填报助手</div>
@ -73,11 +90,11 @@
<div class="ai-select-list-title">{{ list.title }}</div>
<div class="ai-select-options">
<el-button v-for="opt in list.options.slice(0,4)" :key="opt" size="small"
@click="() => handleSelectOption(opt)"
:type="selectedOption === opt ? 'primary' : 'default'">{{ opt }}
@click="() => handleSelectOption(opt,lidx)"
:type="selectedOption === opt ? 'primary' : 'default'">{{ opt.fieldValue }}
</el-button>
<el-button v-if="list.options.length > 4" size="small" type="primary" link
@click="() => openSelectDialog(list)">更多
@click="() => openSelectDialog(list,lidx)">更多
</el-button>
</div>
</div>
@ -105,8 +122,9 @@
/>
<div class="ai-select-options">
<el-button v-for="opt in filteredSelectDialogOptions" :key="opt" size="small"
@click="() => handleSelectOption(opt)" :type="selectedOption === opt ? 'primary' : 'default'">
{{ opt }}
@click="() => handleSelectOption(opt)"
:type="selectedOption === opt ? 'primary' : 'default'">
{{ opt.fieldValue }}
</el-button>
</div>
</div>
@ -117,9 +135,22 @@
当前表单{{ aiFormSettingList.find(f => f.formSettingId === selectedFormSettingId)?.formName }}
</div>
<div class="form-info-tip">请根据下方示例填写内容AI将自动为您智能填报</div>
<div class="form-info-example">示例"申请10个物料A用于生产线1计划交付日期为2024-07-01"</div>
<div class="form-info-example">{{ aiFormSettingList.find(f => f.formSettingId === selectedFormSettingId)?.remark ?? '如申请10个物料A用于生产线1计划交付日期为2024-07-01'}}</div>
</div>
<div class="ai-float-input-row">
<el-button
@click="toggleRecording"
:class="['recording-btn', { 'recording': isRecording }]"
v-loading="audioAsrLoading"
:disabled="!canSend || aiThinking"
circle
type="primary"
>
<template #icon>
<el-icon v-if="isRecording"><VideoPause /></el-icon>
<el-icon v-else><Microphone /></el-icon>
</template>
</el-button>
<el-input
v-model="inputValue"
type="textarea"
@ -136,22 +167,19 @@
<el-button
class="ai-float-send-btn"
:disabled="!canSend || aiThinking"
icon="el-icon-s-promotion"
icon="Top"
@click="() => handleSend()"
circle
type="primary"
type="success"
/>
<el-button
@click="toggleRecording"
:class="{ 'recording': isRecording }"
v-loading="audioAsrLoading"
>
{{ isRecording ? '停止录音' : '开始录音' }}
</el-button>
</div>
</div>
</div>
</div>
<!-- 新增调整大小的句柄 -->
<div class="ai-float-resize-handle" @mousedown="startResize"></div>
</div>
</template>
@ -164,15 +192,25 @@ import {getAiFormSettingList, getAiFormSettingDetailList} from '@/api/ai/base/ai
import {AiFormSettingVO, AiFormSettingDetailVO} from '@/api/ai/base/aiFormSetting/types';
import {aiFillForm, recognizeSpeechByUrl, uploadFile} from '@/api/ai/skill/aiAssistant';
import router from "@/router"
import {useSharedDataStore} from '@/api/monitorData'
const sharedStore = useSharedDataStore()
const {proxy} = getCurrentInstance() as ComponentInternalInstance
const aiFormSettingList = ref<AiFormSettingVO[]>([])
const aiFormSettingDetailListMap = ref<Record<number, AiFormSettingDetailVO[]>>({});
const selectedFormSettingId = ref<number | null>(null)
const selectedForm = ref();
const formsLoading = ref(false)
const isShowLeft = ref(false);
const aiLoading = ref(false);
const currentSize = ref("menu");
const currentSizeClass = computed(() =>
`size-${currentSize.value}`
);
// reactivestate
const state = reactive({
@ -272,9 +310,12 @@ const getFormProps = (form: AiFormSettingVO) => {
}
const selectForm = async (aiFormSetting: AiFormSettingVO) => {
if( selectedFormSettingId.value === aiFormSetting.formSettingId) return;
selectedFormSettingId.value = aiFormSetting.formSettingId
selectedForm.value = aiFormSetting;
aiLoading.value = true
try {
resetAiAssistant();
if (!aiFormSettingDetailListMap.value[aiFormSetting.formSettingId]) {
const res = await getAiFormSettingDetailList(
{formSettingId: aiFormSetting.formSettingId, settingFlag: '1'})
@ -288,6 +329,10 @@ const selectForm = async (aiFormSetting: AiFormSettingVO) => {
console.log(aiFormSettingDetailListMap.value[aiFormSetting.formSettingId])
}
const resetAiAssistant = () =>{
chatList.value = [];
}
// function selectForm(aiFormSetting: AiFormSettingVO) {
// selectedFormSettingId.value = aiFormSetting.formSettingId
// if(!aiFormSettingDetailListMap[aiFormSetting.formSettingId]){
@ -306,11 +351,14 @@ watch(selectedFormSettingId, async (formSettingId) => {
try {
if (formPageMap.value[formSettingId].props && formPageMap.value[formSettingId].props.dialogVisibleVariable && formPageMap.value[formSettingId].props.dialogVisibleVariable !== '') {
isShowLeft.value = false
router.push("/" + formPageMap.value[formSettingId].props.formPath)
currentSize.value = "menu";
router.push({path: "/" + formPageMap.value[formSettingId].props.formPath, query: {from: "ai"}})
// proxy.$store.commit('aiFormSettingId', formSettingId)
// proxy.$store.commit('aiFormSetting', formPageMap.value[formSettingId])
} else {
isShowLeft.value = true
currentSize.value = "form";
const mod = await formPageMap.value[formSettingId].loader()
console.log(formPageMap.value[formSettingId].props)
currentComponent.value = mod.default
@ -330,6 +378,7 @@ watch(selectedFormSettingId, async (formSettingId) => {
}
})
//
defineExpose({
openSearch
@ -342,7 +391,50 @@ watch(state, (newVal) => {
}
});
const selectLists = ref([]);
// fieldType1,使
const type1Elements = ref([]);
// fieldType2使
const type2Elements = ref([]);
// fieldType2
const selectedType2Elements = ref([]);
// fieldType1
const processData = (data) => {
type1Elements.value = [];
type2Elements.value = [];
let title = "";
data.forEach((subArray, subArrayIndex) => {
if (Array.isArray(subArray)) {
subArray.forEach(item => {
if (item.fieldType === '3' || item.fieldType === 3) {//title
if (title == '') title = item.fieldValue;
}
if (item.fieldType === '1' || item.fieldType === 1) {//
type1Elements.value.push({
...item,
subArrayIndex
});
}
if (item.fieldType === '2' || item.fieldType === 2) {//
type2Elements.value.push({
...item,
subArrayIndex
});
}
});
}
});
selectLists.value.push({title: title, options: type2Elements.value, formOptions: type1Elements.value});
};
const sendForm = ref({});
const handleSend = async (selectedText?: string) => {
if ((!canSend.value || aiThinking.value) && !selectedText) return
const userMsg = selectedText || inputValue.value
@ -361,38 +453,144 @@ const handleSend = async (selectedText?: string) => {
platformId: 1
}
// currentProps.value = {completeAmount:10,productionTime:16.6};
// chatList.value.push({
// text: ',:',
// from: 'ai',
// type: 'select',
// selectLists: selectLists.value
// })
//
// sharedStore.updateDynamicValue({
// message: {deptId:100},
// timestamp: new Date().toLocaleString()
// })
// return;
const response = await aiFillForm(params)
currentProps.value = response;
sendForm.value = response;
inputValue.value = ''
aiThinking.value = false
traverseJSON();
if (selectedForm.value && selectedForm.value.dialogVisibleVariable && selectedForm.value.dialogVisibleVariable !== '') {
//使
sharedStore.updateDynamicValue({
message: sendForm.value,
timestamp: new Date().toLocaleString()
})
} else {
//使
currentProps.value = sendForm.value;
}
// AI+
chatList.value.push({
text: '操作成功,请选择相关信息:',
from: 'ai',
type: 'select',
selectLists: [
{title: '物料信息', options: ['物料A', '物料B', '物料C', '物料D', '物料E', '物料F']},
{title: 'BOM信息', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
]
})
// chatList.value.push({
// text: '',
// from: 'ai',
// type: 'select',
// selectLists: [
// {title: '', options: ['A', 'B', 'C', 'D', 'E', 'F']},
// {title: 'BOM', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
// ]
// })
if (selectLists.value.length > 0) {
chatList.value.push({
text: '操作成功,请选择相关信息:',
from: 'ai',
type: 'select',
selectLists: selectLists.value
})
} else {
chatList.value.push({
text: '操作成功',
from: 'ai',
})
}
scrollToBottom()
console.log(1)
console.log(response)
console.log(2)
} catch (error) {
proxy?.$modal.msgError(error);
console.log("Error:" + error)
chatList.value.push({
text: '请重试:'+error,
from: 'ai',
})
} finally {
aiThinking.value = false
inputValue.value = ''
aiLoading.value = false
}
}
// JSON
function traverseJSON() {
//
if (Array.isArray(sendForm.value)) {
console.log('这是一个数组,包含 ' + sendForm.value.length + ' 个元素:');
//
// obj.forEach((item, index) => {
// console.log(' '.repeat(indent) + ' ' + index + ':');
// });
} else {
// key
for (const key in sendForm.value) {
// key
if (sendForm.value.hasOwnProperty(key)) {
const value = sendForm.value[key];
// value
if (Array.isArray(value)) {
// selectLists: [
// {title: '', options: ['A', 'B', 'C', 'D', 'E', 'F']},
// {title: 'BOM', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
// ]
sendForm.value[key] = '';
if (value.length == 1) {
const firstValue = value[0];
firstValue.forEach(item => {
if(item.fieldType ==='1' || item.fieldType===1){
sendForm.value[key] = item.fieldValue
}
});
// let selectList = {title: value[0].relateTableDesc, options: value}
// selectLists.value.push(selectList);
}else if (value.length > 1) {
processData(value)
}
//
// value.forEach((item, index) => {
// console.log(' '.repeat(indent + 1) + ' ' + index + ':');
// });
}
// value
// else if (value !== null && typeof value === 'object') {
// console.log(' '.repeat(indent) + ':');
// traverseJSON(value, indent + 1);
// }
//
// else {
// console.log(' '.repeat(indent) + ':', value);
// }
}
}
}
}
//
const isRecording = ref(false);
let mediaRecorder = null
@ -418,8 +616,6 @@ const toggleRecording = () => {
//
async function startRecording() {
alert('您的浏览器不支持录音功能1')
proxy.$modal.msgError('您的浏览器不支持录音功能')
try {
//
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
@ -529,8 +725,8 @@ function stopRecording() {
console.error('处理录音文件失败:', error)
proxy.$modal.msgError('处理录音文件失败,请重试', error)
// addLog(`: ${error.message}`)
}finally{
} finally {
audioAsrLoading.value = false;
}
}
}
@ -699,13 +895,11 @@ async function uploadAudioFile(blob, fileName) {
if (response.code === 200) {
// addLog('')
// alert(response.data.url)
const response1 = await recognizeSpeechByUrl(response.data.url);
if (response1.code === 200) {
console.log("--" + response1);
inputValue.value += response1.data.text
// recognitionResult.value = response1.data.text || ''
// alert(recognitionResult.value)
// addLog(': ' + recognitionResult.value)
}
} else {
@ -767,6 +961,7 @@ type ChatMsg = { text: string, from: 'user' | 'ai', type?: 'text' | 'select', se
const chatList = ref<ChatMsg[]>([])
const selectDialog = ref(false)
const selectDialogList = ref<ChatSelectList | null>(null)
const selectDialogListIndex = ref();
const aiThinking = ref(false)
const chatListRef = ref<HTMLElement | null>(null)
const selectedOption = ref('')
@ -776,61 +971,48 @@ const iframeRef = ref<HTMLIFrameElement | null>(null)
const selectDialogFilterText = ref('')
const filteredSelectDialogOptions = computed(() =>
selectDialogList.value
? selectDialogList.value.options.filter(opt => opt.includes(selectDialogFilterText.value))
? selectDialogList.value.options.filter(opt => {
// opt访
// labelvalue
const fieldValue = typeof opt === 'string' ? opt :
opt.fieldValue
return fieldValue.toLowerCase().includes(selectDialogFilterText.value.toLowerCase());
})
: []
)
//
const floatStyle = reactive({
left: '',
top: '0px',
width: '440px',
height: '100vh',
zIndex: 9999,
position: 'fixed' as const,
})
//
onMounted(() => {
floatStyle.left = (window.innerWidth - 440) + 'px';
floatStyle.top = '0px';
//
// loadForms()
//
// const testData = [[{"fieldKey":"relateTableDesc","fieldValue":"1","fieldType":"3"},
// {"fieldKey":"deptName","fieldValue":"1","fieldType":"2"},
// {"fieldKey":"deptId","fieldValue":105,"fieldType":"1"}],
// [{"fieldKey":"relateTableDesc","fieldValue":null,"fieldType":"3"},
// {"fieldKey":"deptName","fieldValue":"2","fieldType":"2"},
// {"fieldKey":"deptId","fieldValue":"1952572898270183426","fieldType":"1"}]
// ]
//
//
// processData(testData);
//
// const testData1 = [
// [{"fieldKey":"relateTableDesc","fieldValue":"2","fieldType":"3"},
// {"fieldKey":"deptName","fieldValue":"11","fieldType":"2"},
// {"fieldKey":"deptId","fieldValue":1051,"fieldType":"1"}],
// [{"fieldKey":"relateTableDesc","fieldValue":null,"fieldType":"3"},
// {"fieldKey":"deptName","fieldValue":"22","fieldType":"2"},
// {"fieldKey":"deptId","fieldValue":"19525728982701834261","fieldType":"1"}]
// ]
// processData(testData1);
})
let drag = false
let offsetX = 0
let offsetY = 0
function onDragStart(e: MouseEvent) {
drag = true
offsetX = e.clientX - parseInt(floatStyle.left)
offsetY = e.clientY - parseInt(floatStyle.top)
document.addEventListener('mousemove', onDragging)
document.addEventListener('mouseup', onDragEnd)
}
function onDragging(e: MouseEvent) {
if (!drag) return
let newLeft = e.clientX - offsetX
let newTop = e.clientY - offsetY
//
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - 440))
newTop = Math.max(0, Math.min(newTop, window.innerHeight - 100))
floatStyle.left = newLeft + 'px'
floatStyle.top = newTop + 'px'
}
function onDragEnd() {
drag = false
document.removeEventListener('mousemove', onDragging)
document.removeEventListener('mouseup', onDragEnd)
}
onBeforeUnmount(() => {
document.removeEventListener('mousemove', onDragging)
document.removeEventListener('mouseup', onDragEnd)
})
//
@ -865,22 +1047,40 @@ function scrollToBottom() {
// <script setup>
function handleSelectOption(opt: string) {
function handleSelectOption(opt, index) {
if (index === undefined || index === null) {
index = selectDialogListIndex.value
}
let selectList = selectLists.value[index];
selectedOption.value = opt
let selectFormOptions = selectList.formOptions;
const subArray = selectFormOptions[opt.subArrayIndex];
const newValueJson = {[subArray.fieldKey]: subArray.fieldValue}
sharedStore.updateDynamicValue({
message: newValueJson,
timestamp: new Date().toLocaleString()
})
// currentProps.value= newValueJson;
}
function openSelectDialog(list: ChatSelectList) {
function openSelectDialog(list: ChatSelectList,listIndex:number) {
selectDialogList.value = list
selectDialog.value = true
selectDialogListIndex.value = listIndex;
}
function formatMsg(text: string) {
// <br>XSS
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>')
if (text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>')
}
}
function onInputKeydown(e: KeyboardEvent) {
@ -895,19 +1095,172 @@ function close() {
state.isShowSearch = false
}
//
const closeSearch = () => {
state.isShowSearch = false;
};
//
const isDragging = ref(false);
const isResizing = ref(false);
const windowPosition = ref({ x: 'auto', y: 0 });
const isSmallSize = ref(false);
const windowSize = ref({ width: '24%', height: '100vh' });
const startPos = ref({ x: 0, y: 0 });
const startSize = ref({ width: 0, height: 0 });
const isCustomPosition = ref(false); //
//
function startDrag(e: MouseEvent) {
if (isResizing.value) return;
isDragging.value = true;
//
const windowElement = document.querySelector('.ai-float-window') as HTMLElement;
if (windowElement) {
const rect = windowElement.getBoundingClientRect();
startPos.value = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
//
isCustomPosition.value = true;
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', stopDrag);
}
function onDrag(e: MouseEvent) {
if (!isDragging.value) return;
const newX = e.clientX - startPos.value.x;
const newY = e.clientY - startPos.value.y;
//
const maxX = window.innerWidth - 200; // 200px
const maxY = window.innerHeight - 100; // 100px
windowPosition.value = {
x: Math.max(0, Math.min(newX, maxX)),
y: Math.max(0, Math.min(newY, maxY))
};
//
if (currentSize.value === 'menu' || currentSize.value === 'form' || currentSize.value === 'minimize') {
currentSize.value = 'custom';
}
}
function stopDrag() {
isDragging.value = false;
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
}
// ... existing code ...
//
function startResize(e: MouseEvent) {
isResizing.value = true;
startPos.value = { x: e.clientX, y: e.clientY };
startSize.value = {
width: window.innerWidth * parseFloat(windowSize.value.width) / 100,
height: window.innerHeight * parseFloat(windowSize.value.height) / 100
};
document.addEventListener('mousemove', onResize);
document.addEventListener('mouseup', stopResize);
e.preventDefault(); //
}
function onResize(e: MouseEvent) {
if (!isResizing.value) return;
const deltaX = e.clientX - startPos.value.x;
const deltaY = e.clientY - startPos.value.y;
//
let newWidth = startSize.value.width + deltaX;
let newHeight = startSize.value.height + deltaY;
newWidth = Math.max(200, newWidth); // 200px
newHeight = Math.max(300, newHeight); // 300px
//
windowSize.value = {
width: `${(newWidth / window.innerWidth) * 100}%`,
height: `${(newHeight / window.innerHeight) * 100}%`
};
// 使
if (currentSize.value !== 'custom') {
currentSize.value = 'custom';
}
}
function stopResize() {
isResizing.value = false;
document.removeEventListener('mousemove', onResize);
document.removeEventListener('mouseup', stopResize);
}
//
watch(currentSize, (newSize) => {
if (newSize === 'menu') {
windowSize.value = { width: '24%', height: '100vh' };
windowPosition.value = { x: 'auto', y: 0 };
} else if (newSize === 'form') {
windowSize.value = { width: '80%', height: '100vh' };
windowPosition.value = { x: 'auto', y: 0 };
} else if(newSize === 'minimize'){
windowSize.value = { width: '24%', height: '360px' };
windowPosition.value = { x: 'auto', y: 0 };
}
// const windowSize = computed(() => {
// if (isSmallSize.value) {
// return {
// width: '100px',
// height: '100px',
// top: 'auto',
// bottom: '20px',
// left: 'auto',
// right: '20px',
// borderRadius: '8px',
// overflow: 'hidden'
// }
// }else{
// return {
// width: '24%', height: '100vh'
// }
// }
// return {}
// })
});
//
function toggleSize() {
if(currentSize.value === 'minimize'){
if (!selectedForm.value) {//form
currentSize.value = 'menu';
}else{
if(selectedForm.value.dialogVisibleVariable && selectedForm.value.dialogVisibleVariable !== ''){
currentSize.value = 'menu'
} else{
currentSize.value = 'form'
}
}
}else{
currentSize.value = 'minimize'
}
}
</script>
<style scoped lang="less">
.el-message {
z-index:8006 !important;
}
.ai-float-fullscreen {
position: fixed;
left: 0;
@ -929,13 +1282,121 @@ const closeSearch = () => {
user-select: none;
display: flex;
flex-direction: column;
height: 100vh;
width: 60vw;
height: 99vh;
max-height:99vh;
right: 0;
left: auto;
top: 0;
position: fixed;
z-index: 7005;
//
&.size-custom {
right: v-bind('isCustomPosition ? "auto" : "0"');
left: v-bind('isCustomPosition ? windowPosition.x + "px" : "auto"');
top: v-bind('isCustomPosition ? windowPosition.y + "px" : "0"');
width: v-bind('windowSize.width');
height: v-bind('windowSize.height');
}
&.size-minimized {
right: auto !important;
left: v-bind('windowPosition.x + "px"') !important;
top: v-bind('windowPosition.y + "px"') !important;
width: v-bind('windowSize.width + "px"') !important;
height: v-bind('windowSize.height + "px"') !important;
border-radius: 20px;
padding: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
}
//
.size-minimized .ai-float-main,
.size-minimized .ai-float-form-list,
.size-minimized .ai-float-header > div:first-child {
display: none;
}
//
.size-minimized .ai-float-header {
padding: 0;
width: 100%;
height: 100%;
border-bottom: none;
cursor: default;
display: flex;
align-items: center;
justify-content: center;
}
/* 确保头部右侧按钮组正确显示 */
.ai-float-header-actions {
display: flex;
align-items: center;
}
//
.ai-float-minimize {
font-size: 16px;
cursor: pointer;
color: #888;
transition: color 0.2s;
margin-right: 12px;
line-height: 1;
}
.size-form {
width: 80vw;
}
.size-menu {
width: 24%; //80vw30%al-float-rightflex
}
.size-minimize {
width: 24%;
height:360px;
}
.ai-float-minimize:hover {
color: #409eff;
}
//
.size-minimized .ai-float-close {
display: none;
}
// 使
.ai-float-resize-handle {
position: absolute;
right: 4px;
bottom: 4px;
width: 16px;
height: 16px;
border-radius: 2px;
cursor: se-resize;
opacity: 0.6;
transition: opacity 0.2s;
z-index: 10;
// 使CSS
&::after {
content: '';
position: absolute;
right: -2px;
bottom: -2px;
width: 0;
height: 0;
border-style: solid;
border-width: 0 8px 8px 0;
border-color: transparent #fff transparent transparent;
}
}
.ai-float-resize-handle:hover {
opacity: 1;
}
.ai-float-main {
@ -945,7 +1406,6 @@ const closeSearch = () => {
.ai-float-left {
flex: 7 7 0;
min-width: 400px;
background: #f7f8fa;
border-right: 1px solid #e0e7ef;
overflow: auto;
@ -955,11 +1415,13 @@ const closeSearch = () => {
.ai-float-right {
flex: 3 3 0;
min-width: 260px;
width: 500px;
height: 100%;
display: flex;
flex-direction: column;
}
//
.ai-float-header {
display: flex;
align-items: center;
@ -971,6 +1433,12 @@ const closeSearch = () => {
border-bottom: 1px solid #f0f0f0;
}
//
.ai-float-header-actions {
display: flex;
align-items: center;
}
.ai-float-welcome {
font-size: 16px;
color: #409eff;
@ -1201,5 +1669,14 @@ const closeSearch = () => {
background: linear-gradient(135deg, #e0e7ef 0%, #d0eaff 100%);
color: #409eff;
}
.recording-btn {
margin-bottom: 4px;
}
.recording-btn.recording {
color: #f56c6c;
}
</style>

@ -52,7 +52,11 @@
</template>
</el-table-column>
<el-table-column label="路径" align="center" prop="formPath" :show-overflow-tooltip="true"/>
<el-table-column label="弹窗变量" align="center" prop="dialogVisibleVariable" :show-overflow-tooltip="true"/>
<el-table-column label="加载类型" align="center" prop="formLoadType" :show-overflow-tooltip="true">
<template #default="scope">
<dict-tag :options="ai_form_setting_load_type" :value="scope.row.formLoadType"/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-tooltip content="修改" placement="top">
@ -70,7 +74,7 @@
</el-card>
<!-- 设置弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="60%" top="5vh" append-to-body>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="80vw" top="5vh" append-to-body>
<el-form :model="form" ref="aiFormSettingFormRef" :rules="rules" label-width="100px">
<el-row>
<el-col :span="12">
@ -115,8 +119,15 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="弹窗变量" prop="dialogVisibleVariable">
<el-input v-model="form.dialogVisibleVariable" placeholder="请输入表单弹窗变量名称"/>
<el-form-item label="加载类型" prop="formLoadType">
<el-select v-model="form.formLoadType" placeholder="请选择加载类型" clearable>
<el-option
v-for="dict in ai_form_setting_load_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
@ -131,9 +142,9 @@
<el-table :data="form.aiFormSettingDetailList" border height="40vh" v-loading="columnLoading">
<el-table-column label="字段名称" prop="fieldName"/>
<el-table-column label="字段注释" prop="fieldDesc" type="textarea">
<el-table-column label="字段注释" prop="fieldDesc" >
<template #default="scope">
<el-input v-model="scope.row.fieldDesc"/>
<el-input v-model="scope.row.fieldDesc" type="textarea"/>
</template>
</el-table-column>
<el-table-column label="表单prop" prop="propName">
@ -149,6 +160,43 @@
</el-radio-group>
</template>
</el-table-column>
<el-table-column label="字段类型">
<template #default="scope">
<el-select v-model="scope.row.fieldType" placeholder="请选择字段类型" clearable>
<el-option
v-for="dict in ai_form_setting_field_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="字段样式">
<template #default="scope">
<el-select v-model="scope.row.fieldStyle" placeholder="请选择字段样式" clearable>
<el-option
v-for="dict in ai_form_setting_field_style"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="获取字段" prop="relateTableFields" >
<template #default="scope">
<el-input v-model="scope.row.relateTableFields" type="textarea" placeholder="filedType是1的代表赋值filedType是2的代表显示例:[{fieldName:'dept_id',formProp:'deptId',fieldType:'1'},{fieldName:'dept_name',formProp:'deptName',fieldType:'2'}]"/>
</template>
</el-table-column>
<el-table-column label="过滤条件" prop="relateFilterCondition" >
<template #default="scope">
<el-input v-model="scope.row.relateFilterCondition" type="textarea" placeholder="例:[{fieldKey:'delFlag',fieldValue:'0',conditionSymbol:'='}]"/>
</template>
</el-table-column>
</el-table>
<template #footer>
@ -197,8 +245,8 @@ import {listAiFormSetting, getAiFormSetting, delAiFormSetting, addAiFormSetting,
import {AiFormSettingForm,AiFormSettingVO,AiFormSettingDetailVO,AiFormSettingQuery} from '@/api/ai/base/aiFormSetting/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
ai_form_setting_flag,ai_form_setting_client_type
} = toRefs<any>(proxy?.useDict('ai_form_setting_flag','ai_form_setting_client_type'));
ai_form_setting_flag,ai_form_setting_client_type,ai_form_setting_field_type,ai_form_setting_field_style,ai_form_setting_load_type
} = toRefs<any>(proxy?.useDict('ai_form_setting_flag','ai_form_setting_client_type','ai_form_setting_field_type','ai_form_setting_field_style','ai_form_setting_load_type'));
const aiFormSettingList = ref<AiFormSettingVO[]>([]);
const loading = ref(false);
@ -229,6 +277,7 @@ const initFormData: AiFormSettingForm = {
tableName: undefined,
formName: undefined,
formPath: undefined,
formLoadType:undefined,
dialogVisibleVariable: undefined,
subTableName: undefined,
subTableFkName: undefined,
@ -266,6 +315,9 @@ const data = reactive<PageData<AiFormSettingForm, AiFormSettingQuery>>({
clientType: [
{ required: true, message: "客户端类型不能为空", trigger: "change" }
],
loadType: [
{ required: true, message: "加载类型不能为空", trigger: "change" }
],
}
});

@ -1,18 +1,21 @@
<template>
<div class="p-2">
<el-form :model="form" label-width="100px" class="dataset-form" style="margin-bottom: 0;">
<div class="form-row">
<el-form-item label="数据集名称">
<el-input v-model="form.name" placeholder="请输入数据集名称" />
<el-input v-model="form.name" placeholder="请输入数据集名称"/>
</el-form-item>
<el-form-item label="数据源">
<el-select v-model="form.datasource" placeholder="请选择数据源" style="width: 240px;">
<el-option v-for="ds in datasourceList" :key="ds.value" :label="ds.label" :value="ds.value" />
<el-option v-for="ds in datasourceList" :key="ds.value" :label="ds.label" :value="ds.value"/>
</el-select>
</el-form-item>
<el-form-item label="是否分页">
<el-switch v-model="form.pagination" />
<el-switch v-model="form.pagination"/>
<el-tooltip content="开启分页后,查询结果将按页返回,适用于大数据量场景。" placement="top">
<el-icon style="margin-left:4px;cursor:pointer;"><QuestionFilled /></el-icon>
<el-icon style="margin-left:4px;cursor:pointer;">
<QuestionFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
</div>
@ -26,7 +29,11 @@
style="flex:1"
/>
<el-button type="primary" @click="showAiDialog = true" style="margin-left:8px;">
<svg width="20" height="20" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" fill="#409EFF"/><text x="50%" y="60%" text-anchor="middle" fill="#fff" font-size="400" font-family="Arial" dy=".3em">AI</text></svg>
<svg width="20" height="20" viewBox="0 0 1024 1024">
<circle cx="512" cy="512" r="512" fill="#409EFF"/>
<text x="50%" y="60%" text-anchor="middle" fill="#fff" font-size="400" font-family="Arial" dy=".3em">AI
</text>
</svg>
</el-button>
</div>
<div class="sql-desc">
@ -43,7 +50,9 @@
<el-tabs v-model="activeTab" style="margin: 16px 0 0 10px;">
<el-tab-pane label="列表字段" name="fields">
<div style="margin-bottom:8px;display:flex;align-items:center;">
<el-button type="danger" size="small" :disabled="!multipleSelection.length" @click="batchRemoveFields"></el-button>
<el-button type="danger" size="small" :disabled="!multipleSelection.length" @click="batchRemoveFields">
批量删除
</el-button>
</div>
<el-table
:data="fields"
@ -52,34 +61,34 @@
ref="fieldsTableRef"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column type="selection" width="50"/>
<el-table-column prop="name" label="字段名" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.name" size="small" />
<el-input v-model="scope.row.name" size="small"/>
</template>
</el-table-column>
<el-table-column prop="text" label="字段文本" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.text" size="small" />
<el-input v-model="scope.row.text" size="small"/>
</template>
</el-table-column>
<el-table-column prop="type" label="字段类型" min-width="100">
<template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar" />
<el-option label="数值" value="int" />
<el-option label="日期" value="date" />
<el-option label="文本" value="varchar"/>
<el-option label="数值" value="int"/>
<el-option label="日期" value="date"/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="order" label="排序" min-width="80">
<template #default="scope">
<el-input-number v-model="scope.row.order" :min="1" size="small" style="width:80px" />
<el-input-number v-model="scope.row.order" :min="1" size="small" style="width:80px"/>
</template>
</el-table-column>
<el-table-column prop="dictCode" label="字典编码" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.dictCode" size="small" />
<el-input v-model="scope.row.dictCode" size="small"/>
</template>
</el-table-column>
</el-table>
@ -87,7 +96,9 @@
<el-tab-pane label="参数字段" name="params">
<div style="margin-bottom:8px;display:flex;align-items:center;">
<el-button type="primary" size="small" @click="addParam"></el-button>
<el-button type="danger" size="small" :disabled="!multipleParamSelection.length" @click="batchRemoveParams" style="margin-left:8px;">批量删除</el-button>
<el-button type="danger" size="small" :disabled="!multipleParamSelection.length" @click="batchRemoveParams"
style="margin-left:8px;">批量删除
</el-button>
</div>
<el-table
:data="params"
@ -96,29 +107,29 @@
ref="paramsTableRef"
@selection-change="handleParamSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column type="selection" width="50"/>
<el-table-column prop="param" label="参数" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.param" size="small" />
<el-input v-model="scope.row.param" size="small"/>
</template>
</el-table-column>
<el-table-column prop="text" label="参数文本" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.text" size="small" />
<el-input v-model="scope.row.text" size="small"/>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" min-width="100">
<template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar" />
<el-option label="数值" value="int" />
<el-option label="日期" value="date" />
<el-option label="文本" value="varchar"/>
<el-option label="数值" value="int"/>
<el-option label="日期" value="date"/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="defaultValue" label="默认值" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.defaultValue" size="small" />
<el-input v-model="scope.row.defaultValue" size="small"/>
</template>
</el-table-column>
</el-table>
@ -126,10 +137,11 @@
<el-tab-pane label="数据预览" name="preview">
<div style="display:flex;align-items:center;margin-bottom:8px;">
<el-button type="primary" size="small" @click="fetchPreviewData"></el-button>
<span style="margin-left:8px;padding:2px 14px;background:#ecf5ff;color:#409EFF;font-weight:bold;border-radius:4px;font-size:14px;">仅预览前10条数据</span>
<span
style="margin-left:8px;padding:2px 14px;background:#ecf5ff;color:#409EFF;font-weight:bold;border-radius:4px;font-size:14px;">仅预览前10条数据</span>
</div>
<el-table :data="previewData" style="width: 100%" border v-if="previewData.length">
<el-table-column v-for="col in previewColumns" :key="col.prop" :prop="col.prop" :label="col.label" />
<el-table-column v-for="col in previewColumns" :key="col.prop" :prop="col.prop" :label="col.label"/>
</el-table>
<div v-else style="color:#888;text-align:center;padding:24px 0;">暂无数据</div>
</el-tab-pane>
@ -148,7 +160,7 @@
placeholder="请输入需求描述查询年龄大于35的用户信息包括所在部门的名称"
style="margin-bottom:12px;"
/>
<el-button type="primary" @click="generateSql" :loading="aiLoading">生成SQL</el-button>
<el-button type="primary" @click="getSql" :loading="aiLoading">生成SQL</el-button>
<el-input
v-model="aiSql"
type="textarea"
@ -161,13 +173,18 @@
<el-button type="primary" @click="replaceSql">SQL</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import {aiSqlAsk} from "@/api/ai/skill/aiChat/index";
import {ref, reactive, watch} from 'vue'
import {ElMessage} from 'element-plus'
import {QuestionFilled} from '@element-plus/icons-vue'
import {generateSql} from "@/api/ai/skill/aiChat/index";
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
const form = reactive({
name: '',
@ -176,8 +193,8 @@ const form = reactive({
pagination: true
})
const datasourceList = ref([
{ label: '数据源1', value: 'ds1' },
{ label: '数据源2', value: 'ds2' }
{label: '数据源1', value: 'ds1'},
{label: '数据源2', value: 'ds2'}
])
const showAiDialog = ref(false)
@ -196,7 +213,7 @@ const paramsTableRef = ref()
const previewData = ref<any[]>([])
const previewColumns = ref<any[]>([])
const generateSql = async () => {
const getSql = async () => {
if (!aiPrompt.value.trim() || aiLoading.value) return
aiLoading.value = true
@ -208,23 +225,26 @@ const generateSql = async () => {
// return;
try {
const response = await aiSqlAsk({prompt:aiPrompt.value})
let sql = JSON.stringify(response)
const response = await generateSql({text: aiPrompt.value,modelId:1,platformId:1})
// sql = sql.replaceAll("\"","");
// sql = sql.substring(String.prototype.toLowerCase(sql).indexOf("select"));
//
if ((sql.startsWith("\"") && sql.endsWith("\"")) ||
(sql.startsWith("'") && sql.endsWith("'"))) {
sql = sql.substring(1, sql.length - 1);
}
// if ((sql.startsWith("\"") && sql.endsWith("\"")) ||
// (sql.startsWith("'") && sql.endsWith("'"))) {
// sql = sql.substring(1, sql.length - 1);
// }
aiSql.value = response;
aiSql.value = sql;
console.log(1)
console.log(response)
console.log(2)
} catch (error) {
console.log("Error:"+error)
proxy?.$modal.msgError(error);
console.log("Error:" + error)
} finally {
aiLoading.value = false
}
@ -235,39 +255,47 @@ function replaceSql() {
form.sql = aiSql.value
showAiDialog.value = false
}
function parseSql() {
if (!form.sql) return ElMessage.warning('请先填写SQL')
fields.value = [
{ name: 'id', text: '编号', type: 'int', order: 1, dictCode: '' },
{ name: 'name', text: '名称', type: 'varchar', order: 2, dictCode: '' }
{name: 'id', text: '编号', type: 'int', order: 1, dictCode: ''},
{name: 'name', text: '名称', type: 'varchar', order: 2, dictCode: ''}
]
params.value = [
{ param: 'year', text: '年份', type: 'int', defaultValue: 2023 }
{param: 'year', text: '年份', type: 'int', defaultValue: 2023}
]
ElMessage.success('解析成功')
}
function handleSelectionChange(val: any[]) {
multipleSelection.value = val
}
function batchRemoveFields() {
if (!multipleSelection.value.length) return
fields.value = fields.value.filter(row => !multipleSelection.value.includes(row))
multipleSelection.value = []
}
function handleParamSelectionChange(val: any[]) {
multipleParamSelection.value = val
}
function batchRemoveParams() {
if (!multipleParamSelection.value.length) return
params.value = params.value.filter(row => !multipleParamSelection.value.includes(row))
multipleParamSelection.value = []
}
function addParam() {
params.value.push({ param: '', text: '', type: '', defaultValue: '' })
params.value.push({param: '', text: '', type: '', defaultValue: ''})
}
function onSave() {
ElMessage.success('保存成功')
}
function onClose() {
ElMessage.info('已关闭')
}
@ -280,9 +308,9 @@ function fetchPreviewData() {
return
}
//
previewColumns.value = fields.value.map(f => ({ prop: f.name, label: f.text }))
previewColumns.value = fields.value.map(f => ({prop: f.name, label: f.text}))
// 10
previewData.value = Array.from({ length: 10 }, (_, i) => {
previewData.value = Array.from({length: 10}, (_, i) => {
const row: any = {}
fields.value.forEach(f => {
if (f.type === 'int') row[f.name] = i + 1
@ -309,15 +337,18 @@ watch(activeTab, (val) => {
padding: 16px 24px 0 24px;
border-radius: 8px;
}
.form-row {
display: flex;
gap: 24px;
align-items: center;
margin-bottom: 0;
}
.sql-form-item {
margin-bottom: 0;
}
.sql-desc {
color: #888;
font-size: 12px;
@ -326,13 +357,16 @@ watch(activeTab, (val) => {
align-items: flex-start;
justify-content: space-between;
}
.sql-tips {
flex: 1;
}
.sql-tips div {
line-height: 1.5;
margin-bottom: 2px;
}
.sql-desc .el-button {
margin-left: 8px;
padding: 0 10px;

@ -19,9 +19,9 @@
<!-- <el-form-item label="设备模型" prop="deviceModeId">-->
<!-- <el-input v-model="queryParams.deviceModeId" placeholder="输入设备模型ID" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="设备编号" prop="machineCode">-->
<!-- <el-input v-model="queryParams.machineCode" placeholder="输入设备编号" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<el-form-item label="设备编号" prop="machineCode">
<el-input v-model="queryParams.machineCode" placeholder="输入设备编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="故障类型" prop="faultType">
<el-select v-model="queryParams.faultType" placeholder="选择故障类型" clearable>
<el-option label="外部故障" value="1" />
@ -52,8 +52,11 @@
<el-table-column label="设备类型" prop="deviceType" min-width="100" />
<el-table-column label="设备编号" prop="machineCode" min-width="120" />
<el-table-column label="故障类型" prop="faultType" min-width="100" >
<!-- <template #default="scope">-->
<!-- <dict-tag :options="activity_fault_type" :value="scope.row.faultType"/>-->
<!-- </template>-->
<template #default="scope">
<dict-tag :options="activity_fault_type" :value="scope.row.faultType"/>
<span>{{ formatFaultType(scope.row.faultType) }}</span>
</template>
</el-table-column>
<el-table-column label="故障次数" prop="faultCount" min-width="90" align="right" />
@ -129,11 +132,11 @@ const debugFaultType = (faultType: any) => {
console.log('=== 前端字典调试 ===');
console.log('faultType原始值:', JSON.stringify(faultType), '类型:', typeof faultType);
console.log('字典选项:', activity_fault_type.value);
//
const matchedDict = activity_fault_type.value.find(item => item.value === faultType);
console.log('匹配的字典项:', matchedDict);
return ''; //
};
@ -151,7 +154,7 @@ const getList = async () => {
console.log('第一条记录完整数据:', list.value[0]);
}
} finally {
loading.value = false;
}
@ -175,6 +178,21 @@ const resetQuery = () => {
getList();
};
//
const formatFaultType = (faultType: any) => {
switch (faultType) {
case 1:
case '1':
return '外部故障';
case 2:
case '2':
return '内部故障';
default:
return '其他';
}
};
const handleExport = async () => {
proxy?.download('/dms/report/faultTrace/export', { ...queryParams }, `fault_trace_${new Date().getTime()}.xlsx`);
};

@ -0,0 +1,288 @@
<template>
<div class="mes-report-container realtime-alarm-report">
<!-- 查询条件卡片 -->
<el-card class="query-card" shadow="hover">
<template #header>
<div class="card-header">
<span>设备故障实时报警现场响应专用</span>
<div class="header-actions">
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="resetQuery" :disabled="loading">
<el-icon><Refresh /></el-icon>
重置
</el-button>
<el-button type="success" @click="handleExport" :disabled="loading || total===0">
<el-icon><Download /></el-icon>
导出Excel
</el-button>
</div>
</div>
</template>
<el-form :model="queryParams" label-width="110px" inline>
<el-form-item label="时间范围">
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="loading"
/>
</el-form-item>
<!-- <el-form-item label="设备编号">
<el-input v-model="queryParams.machineCode" placeholder="支持模糊查询" clearable :disabled="loading" />
</el-form-item>
<el-form-item label="报警级别">
<el-select v-model="queryParams.alarmLevelId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarmLevels" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警类型">
<el-select v-model="queryParams.alarmTypeId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarmTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警状态">
<el-select v-model="queryParams.alarmStatus" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarm_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警方式">
<el-select v-model="queryParams.alarmMode" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarm_mode" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="设备型号">
<el-select v-model="queryParams.deviceModeId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="dm in deviceModeOptions" :key="dm.deviceModeId" :label="dm.deviceModeName" :value="dm.deviceModeId" />
</el-select>
</el-form-item>-->
</el-form>
</el-card>
<!-- 概览统计卡片 -->
<div class="overview-grid">
<el-card class="overview-card" shadow="never">
<div class="stat">
<div class="stat-value">{{ total }}</div>
<div class="stat-label">报警总数</div>
</div>
<div ref="pieRef" class="chart" />
</el-card>
<el-card class="overview-card" shadow="never">
<div ref="lineRef" class="chart" />
</el-card>
<el-card class="overview-card" shadow="never">
<div class="legend">
<div class="legend-item" v-for="lvl in alarmLevelStats" :key="lvl.name">
<span class="dot" :style="{ background: lvl.color }"></span>
<span class="name">{{ lvl.name }}</span>
<span class="value">{{ lvl.value }}</span>
</div>
</div>
</el-card>
</div>
<!-- 数据表格 -->
<el-card class="table-card" shadow="never">
<el-table v-loading="loading" :data="list" stripe height="480">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="machineCode" label="设备编号" width="140" align="center" />
<el-table-column prop="machineName" label="设备名称" width="160" align="center" />
<el-table-column prop="deviceModeName" label="设备型号" width="140" align="center" />
<el-table-column prop="alarmBeginTime" label="故障发生时间" width="180" align="center" />
<el-table-column prop="alarmContent" label="故障现象描述" min-width="220" align="left" />
<el-table-column prop="alarmLevelName" label="故障等级" width="120" align="center" />
<el-table-column prop="alarmTypeName" label="报警类型" width="120" align="center" />
<el-table-column label="当前状态" width="120" align="center">
<template #default="scope">
<dict-tag :options="alarm_status" :value="scope.row.alarmStatus" />
</template>
</el-table-column>
<el-table-column label="报警方式" width="120" align="center">
<template #default="scope">
<dict-tag :options="alarm_mode" :value="scope.row.alarmMode" />
</template>
</el-table-column>
<el-table-column prop="alarmEndTime" label="结束时间" width="180" align="center" />
<el-table-column prop="durationMinutes" label="持续时长(分钟)" width="140" align="center" />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup lang="ts" name="RealtimeAlarmReport">
import { onMounted, ref, getCurrentInstance, nextTick, toRefs } from 'vue';
import * as echarts from 'echarts';
import { listRealtimeAlarm, exportRealtimeAlarm } from '@/api/dms/report';
import { RealtimeAlarmQuery, RealtimeAlarmVO } from '@/api/dms/report/types';
import { getDmsDeviceModeList } from '@/api/dms/deviceMode';
const { proxy } = getCurrentInstance() as any;
const loading = ref(false);
const list = ref<RealtimeAlarmVO[]>([]);
const total = ref(0);
const dateRange = ref<string[]>([]);
const queryParams = ref<RealtimeAlarmQuery>({
pageNum: 1,
pageSize: 10,
startDate: undefined,
endDate: undefined,
deviceModeId: undefined,
machineCode: undefined,
machineId: undefined,
alarmLevelId: undefined,
alarmTypeId: undefined,
alarmStatus: undefined,
alarmMode: undefined,
alarmInfoType: undefined,
});
//
const { alarm_status } = toRefs<any>(proxy?.useDict('alarm_status'));
const { alarm_mode } = toRefs<any>(proxy?.useDict('alarm_mode'));
//
const alarmLevels = ref<Array<{ label: string; value: string | number }>>([]);
const alarmTypes = ref<Array<{ label: string; value: string | number }>>([]);
//
const deviceModeOptions = ref<Array<{ deviceModeId: number; deviceModeName: string }>>([]);
//
const pieRef = ref<HTMLDivElement | null>(null);
const lineRef = ref<HTMLDivElement | null>(null);
let pieChart: echarts.ECharts | null = null;
let lineChart: echarts.ECharts | null = null;
function initCharts() {
nextTick(() => {
if (pieRef.value) {
pieChart = echarts.init(pieRef.value);
const levelGroup = groupBy(list.value, x => x.alarmLevelName || '未设定');
const pieData = Object.entries(levelGroup).map(([name, arr]) => ({ name, value: (arr as any[]).length }));
pieChart.setOption({
title: { text: '报警级别分布', left: 'center' },
tooltip: { trigger: 'item' },
series: [{ type: 'pie', radius: ['40%', '70%'], data: pieData }]
});
}
if (lineRef.value) {
lineChart = echarts.init(lineRef.value);
const byHour = groupBy(list.value, x => (x.alarmBeginTime || '').substring(0, 13));
const categories = Object.keys(byHour).sort();
const values = categories.map(k => (byHour[k] as any[]).length);
lineChart.setOption({
title: { text: '小时报警趋势', left: 'center' },
xAxis: { type: 'category', data: categories },
yAxis: { type: 'value' },
tooltip: { trigger: 'axis' },
series: [{ type: 'line', data: values, smooth: true }]
});
}
});
}
function groupBy<T>(arr: T[], keySelector: (x: T) => string) {
return arr.reduce((acc: any, cur: any) => {
const key = keySelector(cur) || '-';
acc[key] = acc[key] || [];
acc[key].push(cur);
return acc;
}, {} as Record<string, T[]>);
}
async function getDeviceModeOptions() {
const res = await getDmsDeviceModeList({});
deviceModeOptions.value = (res?.data || []).map((x: any) => ({ deviceModeId: x.deviceModeId, deviceModeName: x.deviceModeName }));
}
async function getList() {
loading.value = true;
try {
if (dateRange.value?.length === 2) {
queryParams.value.startDate = dateRange.value[0];
queryParams.value.endDate = dateRange.value[1];
} else {
queryParams.value.startDate = undefined;
queryParams.value.endDate = undefined;
}
const res = await listRealtimeAlarm(queryParams.value);
list.value = res.rows || [];
total.value = res.total || 0;
initCharts();
} finally {
loading.value = false;
}
}
function handleSearch() {
queryParams.value.pageNum = 1;
getList();
}
function resetQuery() {
dateRange.value = [];
queryParams.value = { pageNum: 1, pageSize: 10 };
getList();
}
function handleExport() {
proxy?.download('/dms/report/realtimeAlarm/export', { ...queryParams.value }, `realtime_alarm_${new Date().getTime()}.xlsx`);
}
onMounted(async () => {
await getDeviceModeOptions();
await getList();
});
</script>
<style scoped>
.realtime-alarm-report {
display: flex;
flex-direction: column;
gap: 12px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-actions {
display: flex;
gap: 8px;
}
.query-card {
border-radius: 8px;
}
.overview-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.overview-card {
height: 240px;
}
.chart {
width: 100%;
height: 180px;
}
.legend {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
}
.legend-item { display: flex; align-items: center; gap: 8px; }
.legend-item .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--el-color-primary); }
.stat { display: flex; align-items: baseline; gap: 12px; padding: 4px 12px; }
.stat-value { font-size: 28px; font-weight: 700; }
.stat-label { color: var(--el-text-color-secondary); }
.table-card { border-radius: 8px; }
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 18,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 18;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 17,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 17;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 2,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 2;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -76,7 +76,11 @@
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
@ -96,6 +100,14 @@ import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);

@ -0,0 +1,232 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="机台名称" prop="machineId">
<el-select v-model="queryParams.machineId" placeholder="请选择机台名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in machineInfoList" :key="item.machineId" :label="item.machineName" :value="item.machineId" />
</el-select>
</el-form-item>
<el-form-item label="班次名称" prop="shiftId">
<el-select v-model="queryParams.shiftId" placeholder="请选择班次名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in shiftList" :key="item.shiftId" :label="item.shiftName" :value="item.shiftId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
<el-form-item label="计划编号" prop="planCode">
<el-input v-model="queryParams.planCode" placeholder="请输入计划编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" :data="reportList">
<el-table-column label="派工单" align="center" prop="dispatchCode" v-if="columns[0].visible" />
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[1].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[2].visible" />
<el-table-column label="物料名称" align="center" prop="materialName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序名称" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="报工数量" align="center" prop="completeAmount" v-if="columns[5].visible" />
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportWorkHour" lang="ts">
import { getCurrentInstance, ref, watch, onMounted } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import { listWorkHourReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
//
const processInfoList = ref([]);
const machineInfoList = ref([]);
const shiftList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '派工单', visible: true },
{ key: 1, label: '班组', visible: true },
{ key: 2, label: '工位', visible: true },
{ key: 3, label: '物料名称', visible: true },
{ key: 4, label: '工序名称', visible: true },
{ key: 5, label: '报工数量', visible: true },
{ key: 6, label: '标准工时(h)', visible: true },
{ key: 7, label: '报工工时(h)', visible: true },
{ key: 8, label: '生产效率', visible: true },
{ key: 9, label: '派工单状态', visible: true },
{ key: 10, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 17,
machineId: undefined,
shiftId: undefined,
classTeamId: undefined,
planCode: '',
materialName: '',
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getProdBaseMachineInfoListSelect = async () => {
const res = await getProdBaseMachineInfoList({ processId: queryParams.value.processId });
machineInfoList.value = res.data;
};
const getShiftSelect = async () => {
const res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listWorkHourReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 17;
queryParams.value.machineId = undefined;
queryParams.value.shiftId = undefined;
queryParams.value.classTeamId = undefined;
queryParams.value.planCode = '';
queryParams.value.materialName = '';
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/workHourReport/export', { ...queryParams.value }, `工时报表_${new Date().getTime()}.xlsx`);
};
//
watch(
() => queryParams.value.processId,
async () => {
loading.value = true;
await getProdBaseMachineInfoListSelect();
await getList();
loading.value = false;
}
);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getProdBaseMachineInfoListSelect();
await getShiftSelect();
await getClassTeamSelect();
await getList();
});
</script>

@ -0,0 +1,232 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="机台名称" prop="machineId">
<el-select v-model="queryParams.machineId" placeholder="请选择机台名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in machineInfoList" :key="item.machineId" :label="item.machineName" :value="item.machineId" />
</el-select>
</el-form-item>
<el-form-item label="班次名称" prop="shiftId">
<el-select v-model="queryParams.shiftId" placeholder="请选择班次名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in shiftList" :key="item.shiftId" :label="item.shiftName" :value="item.shiftId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
<el-form-item label="计划编号" prop="planCode">
<el-input v-model="queryParams.planCode" placeholder="请输入计划编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" :data="reportList">
<el-table-column label="派工单" align="center" prop="dispatchCode" v-if="columns[0].visible" />
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[1].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[2].visible" />
<el-table-column label="物料名称" align="center" prop="materialName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序名称" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="报工数量" align="center" prop="completeAmount" v-if="columns[5].visible" />
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportWorkHour" lang="ts">
import { getCurrentInstance, ref, watch, onMounted } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import { listWorkHourReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
//
const processInfoList = ref([]);
const machineInfoList = ref([]);
const shiftList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '派工单', visible: true },
{ key: 1, label: '班组', visible: true },
{ key: 2, label: '工位', visible: true },
{ key: 3, label: '物料名称', visible: true },
{ key: 4, label: '工序名称', visible: true },
{ key: 5, label: '报工数量', visible: true },
{ key: 6, label: '标准工时(h)', visible: true },
{ key: 7, label: '报工工时(h)', visible: true },
{ key: 8, label: '生产效率', visible: true },
{ key: 9, label: '派工单状态', visible: true },
{ key: 10, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 2,
machineId: undefined,
shiftId: undefined,
classTeamId: undefined,
planCode: '',
materialName: '',
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getProdBaseMachineInfoListSelect = async () => {
const res = await getProdBaseMachineInfoList({ processId: queryParams.value.processId });
machineInfoList.value = res.data;
};
const getShiftSelect = async () => {
const res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listWorkHourReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 2;
queryParams.value.machineId = undefined;
queryParams.value.shiftId = undefined;
queryParams.value.classTeamId = undefined;
queryParams.value.planCode = '';
queryParams.value.materialName = '';
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/workHourReport/export', { ...queryParams.value }, `工时报表_${new Date().getTime()}.xlsx`);
};
//
watch(
() => queryParams.value.processId,
async () => {
loading.value = true;
await getProdBaseMachineInfoListSelect();
await getList();
loading.value = false;
}
);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getProdBaseMachineInfoListSelect();
await getShiftSelect();
await getClassTeamSelect();
await getList();
});
</script>

@ -3,27 +3,28 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<!-- <el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<!-- <el-form-item label="日期范围" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item label="生产订单号" prop="orderCode">
<el-input
v-model="queryParams.orderCode"
placeholder="请输入生产订单号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="生产订单号" prop="orderCode">
<el-input
v-model="queryParams.orderCode"
placeholder="请输入生产订单号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>-->
<!--
<el-form-item label="物料编号" prop="materialCode">
<el-input
v-model="queryParams.materialCode"
@ -31,7 +32,8 @@
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>-->
</el-form-item>
-->
<el-form-item label="物料名称" prop="materialName">
<el-input
@ -41,8 +43,8 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<!-- <el-form-item label="进度状态" prop="progressStatus">
<!--
<el-form-item label="进度状态" prop="progressStatus">
<el-select v-model="queryParams.progressStatus" placeholder="请选择进度状态" clearable @keyup.enter="handleQuery">
<el-option label="正常" value="正常" />
<el-option label="延期" value="延期" />
@ -59,6 +61,32 @@
</transition>
<!-- 统计图表卡片 -->
<!-- <el-row :gutter="10" class="mb-[10px]">
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">进度状态分布</span>
</template>
<div ref="statusChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">整体进度分布</span>
</template>
<div ref="progressChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never">
<template #header>
<span class="font-bold">工序完成率统计</span>
</template>
<div ref="processStatsChartRef" style="width: 100%; height: 300px"></div>
</el-card>
</el-col>
</el-row>-->
<el-row :gutter="10" class="mb-[10px]">
<el-col :span="12">
<el-card shadow="never">
@ -78,6 +106,7 @@
</el-col>
</el-row>
<!-- 工序进度可视化 -->
<el-card shadow="never" class="mb-[10px]">
<template #header>
@ -89,9 +118,9 @@
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<!-- <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:prodReport:export']"></el-button>
</el-col>
</el-col>-->
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
@ -107,7 +136,11 @@
<el-table-column label="计划开工时间" align="center" prop="planBeginTime" v-if="columns[7].visible" width="150" />
<el-table-column label="实际开工时间" align="center" prop="realBeginTime" v-if="columns[8].visible" width="150" />
<el-table-column label="计划完工时间" align="center" prop="planEndTime" v-if="columns[9].visible" width="150" />
<el-table-column label="整体进度" align="center" prop="overallProgress" v-if="columns[10].visible" width="100">
<el-table-column label="当前时间" align="center" prop="currentTime" v-if="columns[10].visible" width="150" />
<el-table-column label="总工序数" align="center" prop="totalProcessCount" v-if="columns[11].visible" width="100" />
<el-table-column label="在制工序" align="center" prop="wipProcesses" v-if="columns[12].visible" width="150" show-overflow-tooltip />
<el-table-column label="剩余工序" align="center" prop="remainingProcesses" v-if="columns[13].visible" width="150" show-overflow-tooltip />
<el-table-column label="整体进度" align="center" prop="overallProgress" v-if="columns[14].visible" width="120">
<template #default="scope">
<el-progress
:percentage="parseFloat(scope.row.overallProgress)"
@ -116,23 +149,42 @@
/>
</template>
</el-table-column>
<el-table-column label="进度状态" align="center" prop="progressStatus" v-if="columns[11].visible" width="100">
<el-table-column label="进度状态" align="center" prop="progressStatus" v-if="columns[15].visible" width="100">
<template #default="scope">
<el-tag :type="scope.row.progressStatus === '正常' ? 'success' : 'danger'">
{{ scope.row.progressStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="工序进度" align="center" v-if="columns[12].visible" width="300">
<el-table-column label="工序进度" align="center" v-if="columns[16].visible" width="350">
<template #default="scope">
<div class="process-progress-container">
<!-- <div
v-for="process in scope.row.processProgressList"
:key="process.processId"
class="process-step"
:class="{
'completed': process.isCompleted === 1,
'in-progress': process.isInProgress === 1,
'pending': process.isCompleted !== 1 && process.isInProgress !== 1
}"
:title="`${process.processName} - ${process.statusDesc}${process.processProgress ? ' (' + process.processProgress + '%)' : ''}`"
>-->
<div
v-for="process in scope.row.processProgressList"
:key="process.processId"
class="process-step"
:class="{ 'completed': process.isCompleted, 'pending': !process.isCompleted }"
:class="{
'completed': process.isCompleted === 1,
'in-progress': process.isInProgress === 1,
'pending': process.isCompleted !== 1 && process.isInProgress !== 1
}"
:title="`${process.processName}`"
>
{{ process.processName }}
<span class="process-name">{{ process.processName }}</span>
<!-- <span v-if="process.processProgress && process.processProgress > 0" class="process-percentage">
{{ process.processProgress }}%
</span>-->
</div>
</div>
</template>
@ -165,14 +217,16 @@ const dateRange = ref<string[]>(['', '']);
const statusChartRef = ref<HTMLDivElement | null>(null);
const progressChartRef = ref<HTMLDivElement | null>(null);
const processChartRef = ref<HTMLDivElement | null>(null);
const processStatsChartRef = ref<HTMLDivElement | null>(null);
let statusChart: echarts.ECharts | null = null;
let progressChart: echarts.ECharts | null = null;
let processChart: echarts.ECharts | null = null;
let processStatsChart: echarts.ECharts | null = null;
//
const columns = ref([
{ key: 0, label: '生产订单号', visible: true },
{ key: 1, label: '物料编号', visible: true },
{ key: 1, label: '物料编号', visible: false },
{ key: 2, label: '物料名称', visible: true },
{ key: 3, label: '规格型号', visible: true },
{ key: 4, label: '计划总数量', visible: true },
@ -181,9 +235,13 @@ const columns = ref([
{ key: 7, label: '计划开工时间', visible: true },
{ key: 8, label: '实际开工时间', visible: true },
{ key: 9, label: '计划完工时间', visible: true },
{ key: 10, label: '整体进度', visible: true },
{ key: 11, label: '进度状态', visible: true },
{ key: 12, label: '工序进度', visible: true }
{ key: 10, label: '当前时间', visible: false },
{ key: 11, label: '总工序数', visible: true },
{ key: 12, label: '在制工序', visible: true },
{ key: 13, label: '剩余工序', visible: true },
{ key: 14, label: '整体进度', visible: true },
{ key: 15, label: '进度状态', visible: true },
{ key: 16, label: '工序进度', visible: true }
]);
const queryParams = ref<WipTrackingReportQuery>({
@ -277,6 +335,9 @@ function initCharts() {
if (processChartRef.value) {
processChart = echarts.init(processChartRef.value);
}
if (processStatsChartRef.value) {
processStatsChart = echarts.init(processStatsChartRef.value);
}
}
/** 更新图表数据 */
@ -284,6 +345,7 @@ function updateCharts() {
updateStatusChart();
updateProgressChart();
updateProcessChart();
updateProcessStatsChart();
}
/** 更新进度状态分布图 */
@ -299,7 +361,8 @@ function updateStatusChart() {
const option = {
title: {
text: '进度状态分布',
left: 'center'
left: 'center',
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
@ -347,7 +410,8 @@ function updateProgressChart() {
const option = {
title: {
text: '整体进度分布',
left: 'center'
left: 'center',
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'axis',
@ -381,6 +445,59 @@ function updateProgressChart() {
progressChart.setOption(option);
}
/** 更新工序完成率统计图 */
function updateProcessStatsChart() {
if (!processStatsChart || !reportList.value.length) return;
const processStats = {
'已完成': 0,
'进行中': 0,
'未开始': 0
};
reportList.value.forEach(item => {
if (item.processProgressList) {
item.processProgressList.forEach(process => {
if (process.isCompleted) {
processStats['已完成']++;
} else if (process.isInProgress) {
processStats['进行中']++;
} else {
processStats['未开始']++;
}
});
}
});
const option = {
title: {
text: '工序完成率统计',
left: 'center',
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '工序状态',
type: 'pie',
radius: ['40%', '70%'],
data: Object.entries(processStats).map(([name, value]) => ({ name, value })),
itemStyle: {
color: function(params: any) {
const colors = ['#722ed1', '#52c41a', '#d9d9d9'];
return colors[params.dataIndex];
}
}
}
]
};
processStatsChart.setOption(option);
}
/** 更新工序进度可视化图 */
function updateProcessChart() {
if (!processChart || !reportList.value.length) return;
@ -399,13 +516,14 @@ function updateProcessChart() {
const option = {
title: {
text: '工序进度可视化前10个订单',
left: 'center'
left: 'center',
textStyle: { fontSize: 14 }
},
tooltip: {
trigger: 'axis',
formatter: function(params: any) {
const data = params[0].data.value;
return `订单号: ${data[0]}<br/>
return `
进度: ${data[1]}%<br/>
状态: ${data[2]}<br/>
在制工序: ${data[3]}<br/>
@ -451,6 +569,7 @@ function handleResize() {
statusChart?.resize();
progressChart?.resize();
processChart?.resize();
processStatsChart?.resize();
}
onMounted(() => {
@ -466,6 +585,7 @@ onBeforeUnmount(() => {
statusChart?.dispose();
progressChart?.dispose();
processChart?.dispose();
processStatsChart?.dispose();
});
</script>
@ -488,19 +608,34 @@ onBeforeUnmount(() => {
}
.process-step {
padding: 4px 8px;
border-radius: 4px;
padding: 6px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid;
transition: all 0.3s ease;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
min-width: 60px;
position: relative;
}
.process-step.completed {
background-color: #722ed1;
color: white;
border-color: #722ed1;
box-shadow: 0 2px 8px rgba(114, 46, 209, 0.3);
}
.process-step.in-progress {
background-color: #52c41a;
color: white;
border-color: #52c41a;
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
animation: pulse 2s infinite;
}
.process-step.pending {
@ -511,6 +646,30 @@ onBeforeUnmount(() => {
.process-step:hover {
transform: scale(1.05);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.process-name {
font-weight: 600;
margin-bottom: 2px;
}
.process-percentage {
font-size: 10px;
opacity: 0.9;
font-weight: 400;
}
/* 进行中工序的脉动动画 */
@keyframes pulse {
0% {
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
50% {
box-shadow: 0 2px 12px rgba(82, 196, 26, 0.6);
}
100% {
box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
}
}
</style>

@ -0,0 +1,133 @@
<template>
<div class="p-4">
<el-card shadow="hover" class="mb-4">
<el-form :inline="true" :model="queryParams">
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
@change="handleDateChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" v-loading="loading">
<template #header>
<div class="text-center text-xl font-bold">进料检验报表</div>
</template>
<el-table :data="tableData" border style="width: 100%" :height="tableHeight">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column prop="inspectionNo" label="检验单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="purchaseOrderNo" label="采购单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="inspectionDate" label="检验日期" width="120" />
<el-table-column prop="inspectionStartTime" label="开始时间" width="160" />
<el-table-column prop="inspectionEndTime" label="结束时间" width="160" />
<el-table-column prop="materialCode" label="物料编码" min-width="120" show-overflow-tooltip />
<el-table-column prop="materialName" label="物料名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="materialSpec" label="规格型号" min-width="140" show-overflow-tooltip />
<el-table-column prop="materialUnit" label="单位" width="80" />
<el-table-column prop="supplierName" label="供应商" min-width="160" show-overflow-tooltip />
<el-table-column prop="inspectionQty" label="检验数量" width="110" align="right" />
<el-table-column prop="qualifiedQty" label="合格数" width="100" align="right" />
<el-table-column prop="unqualifiedQty" label="不合格数" width="110" align="right" />
<el-table-column prop="inspectionResult" label="检验结果" width="100" />
<el-table-column prop="inspectionTypeName" label="检验类型" width="120" />
<el-table-column prop="inspectorName" label="检验人" width="100" />
<el-table-column prop="items" label="检验项目" min-width="180" show-overflow-tooltip />
<el-table-column prop="methods" label="检验方法" min-width="180" show-overflow-tooltip />
<el-table-column prop="unqualifiedItems" label="不合格项" min-width="200" show-overflow-tooltip />
<el-table-column prop="returnReason" label="退货原因" min-width="160" show-overflow-tooltip />
<el-table-column prop="unqualifiedSummary" label="不合格汇总" min-width="220" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, onBeforeUnmount } from 'vue';
import dayjs from 'dayjs';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import { getIncomingInspectionEfficiency } from '@/api/qms/report';
dayjs.extend(weekOfYear);
//
const loading = ref(false);
const dateRange = ref<[string, string] | null>(null);
const queryParams = ref({ startTime: '', endTime: '' });
const tableData = ref<any[]>([]);
//
const windowHeight = ref(window.innerHeight);
const tableHeight = computed(() => {
return windowHeight.value - 280; //
});
const resizeHandler = () => {
windowHeight.value = window.innerHeight;
};
//
const handleDateChange = (val: any) => {
if (val && Array.isArray(val)) {
queryParams.value.startTime = val[0];
queryParams.value.endTime = val[1];
} else {
queryParams.value.startTime = '';
queryParams.value.endTime = '';
}
};
const resetQuery = () => {
//
const start = dayjs().startOf('week').format('YYYY-MM-DD');
const end = dayjs().endOf('week').format('YYYY-MM-DD');
dateRange.value = [start, end] as [string, string];
handleDateChange(dateRange.value);
handleQuery();
};
const handleQuery = async () => {
loading.value = true;
try {
const res: any = await getIncomingInspectionEfficiency(queryParams.value);
if (res && res.code === 200) {
tableData.value = res.data || [];
} else {
tableData.value = [];
}
} catch (e) {
console.error('获取进料检验效率报表失败', e);
tableData.value = [];
} finally {
loading.value = false;
}
};
onMounted(() => {
//
resetQuery();
window.addEventListener('resize', resizeHandler);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
});
</script>
<style scoped>
.el-form-item {
margin-bottom: 0;
}
</style>

@ -324,6 +324,10 @@ import { treeselect } from '@/api/system/dept';
import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import { useSharedDataStore } from '@/api/monitorData'
import { filterEmptyValues, copyNonEmptyValues } from '@/api/objectUtil';
const sharedStore = useSharedDataStore()
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -672,14 +676,55 @@ const resetForm = () => {
form.value.id = undefined;
form.value.status = '1';
};
onMounted(() => {
getTreeSelect(); //
getList(); //
proxy?.getConfigKey('sys.user.initPassword').then((response) => {
initPassword.value = response.data;
});
getSourceInfo();
});
const getSourceInfo = () =>{
const from = proxy.$route.query.from;
if(form && from==="ai"){
handleAdd();
}
// previousRoute.value = localStorage.getItem("previousRoute");
// if(previousRoute.value && previousRoute.value == "ai"){
// alert(1)
// localStorage.removeItem("previousRoute");
//
// }
}
//
const sharedData = computed(() => sharedStore.dynamicValue)
//
watch(
() => sharedStore.dynamicValue,
(newValue, oldValue) => {
if (newValue !== oldValue) {
ElMessage.info('收到新数据更新')
console.log('数据更新:', newValue)
//
// handleDataUpdate(newValue)
copyNonEmptyValues(newValue.message,form.value);
if(form.value.deptId && form.value.deptId!==''){
handleDeptChange(form.value.deptId)
}
}
},
{ deep: true } //
)
async function handleDeptChange(value: number | string) {
const response = await optionselect(value);
postOptions.value = response.data;

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -166,10 +159,10 @@ const getList = async () => {
const res = await getInventoryDifference(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -182,8 +175,8 @@ const calculateStats = () => {
noDifference: reportList.value.filter(item => item.differenceType === '无差异').length,
profit: reportList.value.filter(item => item.differenceType === '盘盈').length,
loss: reportList.value.filter(item => item.differenceType === '盘亏').length,
avgRate: reportList.value.length > 0
? reportList.value.reduce((sum, item) => sum + (item.differenceRate || 0), 0) / reportList.value.length
avgRate: reportList.value.length > 0
? reportList.value.reduce((sum, item) => sum + (item.differenceRate || 0), 0) / reportList.value.length
: 0
};
};
@ -384,4 +377,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -102,7 +95,7 @@ const getList = async () => {
const res = await getInventoryTrendAnalysis(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
nextTick(() => {
updateChart();
@ -243,4 +236,4 @@ onUnmounted(() => {
.el-card {
margin-bottom: 20px;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -156,10 +149,10 @@ const getList = async () => {
const res = await getInventoryTurnover(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -365,4 +358,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -105,7 +98,7 @@ const getList = async () => {
const res = await getReturnReasonAnalysis(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
nextTick(() => {
updateCharts();
@ -255,4 +248,4 @@ onUnmounted(() => {
.el-card {
margin-bottom: 20px;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -87,7 +80,7 @@
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="物料大类" align="center" prop="materialCategoryName" />
<el-table-column label="当前库存数量" align="center" prop="currentInventoryQty" />
<el-table-column label="安全库存值" align="center" prop="safeStockAmount" />
<!-- <el-table-column label="安全库存值" align="center" prop="safeStockAmount" />-->
<el-table-column label="最小库存值" align="center" prop="minStockAmount" />
<el-table-column label="最大库存值" align="center" prop="maxStockAmount" />
<el-table-column label="预警状态" align="center" prop="alertStatus">
@ -146,10 +139,10 @@ const getList = async () => {
const res = await getSafetyStockAlert(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateChart();
@ -318,4 +311,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -147,10 +140,10 @@ const getList = async () => {
const res = await getStagnantInventory(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -356,4 +349,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

Loading…
Cancel
Save