优化
This commit is contained in:
@@ -87,3 +87,26 @@ export function uploadMaterialPhotos(data: FormData) {
|
||||
export function catchPhoto(data: FormData) {
|
||||
return http.post(`${BASE_URL}/import/catch`, data)
|
||||
}
|
||||
|
||||
/* 批次导入结果类型 */
|
||||
export interface BatchImportResp {
|
||||
importKey: string
|
||||
totalRows: number
|
||||
validRows: number
|
||||
duplicateRows: number
|
||||
}
|
||||
|
||||
/** @desc 下载批次导入模板 */
|
||||
export function downloadBatchImportTemplate() {
|
||||
return http.download(`${BASE_URL}/batch/import/template`)
|
||||
}
|
||||
|
||||
/** @desc 解析批次导入数据 */
|
||||
export function parseBatchImport(data: FormData) {
|
||||
return http.post(`${BASE_URL}/batch/import/parse`, data)
|
||||
}
|
||||
|
||||
/** @desc 批次导入 */
|
||||
export function batchImport(data: any) {
|
||||
return http.post(`${BASE_URL}/batch/import`, data)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface WeighManageResp {
|
||||
materialSpec: string
|
||||
unitWeight: number
|
||||
photoUrl: string
|
||||
batch: string
|
||||
materialProcess: string
|
||||
matchResult: string
|
||||
downFloatRatio: string
|
||||
|
||||
163
src/views/material/BatchImport.vue
Normal file
163
src/views/material/BatchImport.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="批次导入"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
ok-text="确认导入"
|
||||
cancel-text="取消导入"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" size="large" auto-label-width>
|
||||
<a-alert v-if="!form.disabled" style="margin-bottom: 15px">
|
||||
请按照模板要求填写数据,填写完毕后,请先上传并进行解析。
|
||||
<template #action>
|
||||
<a-link @click="downloadTemplate">
|
||||
<template #icon><GiSvgIcon name="file-excel" :size="16" /></template>
|
||||
<template #default>下载模板</template>
|
||||
</a-link>
|
||||
</template>
|
||||
</a-alert>
|
||||
<fieldset>
|
||||
<legend>1.解析数据</legend>
|
||||
<div class="file-box">
|
||||
<a-upload
|
||||
draggable
|
||||
:custom-request="handleUpload"
|
||||
:limit="1"
|
||||
:show-retry-butto="false"
|
||||
:show-cancel-button="false" tip="仅支持xls、xlsx格式"
|
||||
:file-list="uploadFile"
|
||||
accept=".xls, .xlsx, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="dataResult.importKey">
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="总计行数" :value="dataResult.totalRows" />
|
||||
<a-statistic title="正常行数" :value="dataResult.validRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="file-box">
|
||||
<a-space size="large">
|
||||
<a-statistic title="已存在批次" :value="dataResult.duplicateRows" />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message, type RequestOption } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import {
|
||||
type BatchImportResp,
|
||||
downloadBatchImportTemplate,
|
||||
batchImport,
|
||||
parseBatchImport,
|
||||
} from '@/apis/material/materialInfo'
|
||||
import { useDownload, useResetReactive } from '@/hooks'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
||||
const visible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadFile = ref([])
|
||||
|
||||
const [form, resetForm] = useResetReactive({
|
||||
importKey: '',
|
||||
})
|
||||
|
||||
const dataResult = ref<BatchImportResp>({
|
||||
importKey: '',
|
||||
totalRows: 0,
|
||||
validRows: 0,
|
||||
duplicateRows: 0,
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
dataResult.value.importKey = ''
|
||||
uploadFile.value = []
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
useDownload(() => downloadBatchImportTemplate())
|
||||
}
|
||||
|
||||
const handleUpload = (options: RequestOption) => {
|
||||
const controller = new AbortController();
|
||||
(async function requestWrap() {
|
||||
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
||||
onProgress(20)
|
||||
const formData = new FormData()
|
||||
formData.append(name as string, fileItem.file as Blob)
|
||||
try {
|
||||
const res = await parseBatchImport(formData)
|
||||
dataResult.value = res.data
|
||||
Message.success('上传解析成功')
|
||||
onSuccess(res)
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
}
|
||||
})()
|
||||
return {
|
||||
abort() {
|
||||
controller.abort()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
if (!dataResult.value.importKey) {
|
||||
Message.warning('请先上传文件,解析导入数据')
|
||||
return false
|
||||
}
|
||||
form.importKey = dataResult.value.importKey
|
||||
const res = await batchImport(form)
|
||||
Message.success(`导入成功! 表格总数${res.data.totalRows}, 修改${res.data.updateRows}`)
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const onOpen = () => {
|
||||
reset()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
fieldset {
|
||||
padding: 15px 15px 0 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
color: rgb(var(--gray-10));
|
||||
padding: 2px 5px 2px 5px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.file-box {
|
||||
margin-bottom: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -78,6 +78,19 @@ const columns: ColumnItem[] = reactive([
|
||||
allowSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '物料流程编码',
|
||||
field: 'materialProcess',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '批次',
|
||||
field: 'batch',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '灯光等级',
|
||||
field: 'lightLevel',
|
||||
@@ -90,13 +103,6 @@ const columns: ColumnItem[] = reactive([
|
||||
},
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: '物料流程编码',
|
||||
field: 'materialProcess',
|
||||
type: 'input',
|
||||
span: 24,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '物料直径',
|
||||
field: 'materialSpec',
|
||||
@@ -120,7 +126,7 @@ const columns: ColumnItem[] = reactive([
|
||||
type: 'image',
|
||||
savePath: 'material/',
|
||||
span: 24,
|
||||
// required: true,
|
||||
required: true,
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
@@ -44,7 +44,11 @@
|
||||
</a-button>
|
||||
<a-button v-permission="['admin:materialInfo:import']" @click="onPhotosImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>照片批量导入</template>
|
||||
<template #default>照片导入</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['admin:materialInfo:import']" @click="onBatchImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>批次导入</template>
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
@@ -77,6 +81,7 @@
|
||||
<MaterialInfoAddModal ref="MaterialInfoAddModalRef" @save-success="search" />
|
||||
<MaterialInfoImportDrawer ref="MaterialInfoImportDrawerRef" @save-success="search" />
|
||||
<PhotosImport ref="PhotosImportRef" @save-success="search"/>
|
||||
<BatchImport ref="BatchImportRef" @save-success="search"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -84,6 +89,7 @@
|
||||
import MaterialInfoAddModal from './MaterialInfoAddModal.vue'
|
||||
import MaterialInfoImportDrawer from './MaterialInfoImportDrawer.vue'
|
||||
import PhotosImport from '@/views/material/PhotosImport.vue';
|
||||
import BatchImport from '@/views/material/BatchImport.vue';
|
||||
import { type MaterialInfoQuery, type MaterialInfoResp, deleteMaterialInfo, exportMaterialInfo, listMaterialInfo } from '@/apis/material/materialInfo'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useDownload, useTable } from '@/hooks'
|
||||
@@ -121,8 +127,9 @@ const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '物料编码', dataIndex: 'encoding', slotName: 'encoding' },
|
||||
{ title: '物料单位重量(g)', dataIndex: 'unitWeight', slotName: 'unitWeight' },
|
||||
{ title: '物料品类', dataIndex: 'typeName' },
|
||||
{ title: '物料直径', dataIndex: 'materialSpec', slotName: 'materialSpec' },
|
||||
{ title: '物料颜色', dataIndex: 'color', slotName: 'color' },
|
||||
{ title: '批次', dataIndex: 'batch' },
|
||||
{ title: '物料直径', dataIndex: 'materialSpec', slotName: 'materialSpec', show: false },
|
||||
{ title: '物料颜色', dataIndex: 'color', slotName: 'color', show: false },
|
||||
{ title: '物料流程', dataIndex: 'materialProcess', slotName: 'materialProcess' },
|
||||
{ title: '灯光等级', dataIndex: 'lightLevel', slotName: 'lightLevel' },
|
||||
{ title: '物料照片', dataIndex: 'photoUrl', slotName: 'photoUrl', width: 120, align: 'center' },
|
||||
@@ -196,6 +203,12 @@ const PhotosImportRef = ref<InstanceType<typeof PhotosImport>>()
|
||||
const onPhotosImport = () => {
|
||||
PhotosImportRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const BatchImportRef = ref<InstanceType<typeof BatchImport>>()
|
||||
const onBatchImport = () => {
|
||||
BatchImportRef.value?.onOpen()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -238,4 +251,4 @@ const onPhotosImport = () => {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -32,13 +32,13 @@
|
||||
<a-form :model="formData" layout="vertical">
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
<a-form-item label="物料名称">
|
||||
<a-input v-model="formData.materialName" placeholder="物料名称" disabled />
|
||||
<a-form-item label="批次">
|
||||
<a-input v-model="formData.batch" placeholder="批次" disabled />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<a-form-item label="物料直径">
|
||||
<a-input v-model="formData.materialSpec" placeholder="物料直径" disabled />
|
||||
<a-form-item label="物料名称">
|
||||
<a-input v-model="formData.materialName" placeholder="物料名称" disabled />
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,6 +134,10 @@
|
||||
<label>输入数量:</label>
|
||||
<a-input-number :min="1" mode="button" v-model="inputQuantity" placeholder="请输入数量" @change="calculateWeight" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>当前数量:</label>
|
||||
<a-input v-model="calculateNumber" placeholder="-" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
@@ -185,8 +189,8 @@
|
||||
<h4>称重列表</h4>
|
||||
</div>
|
||||
<a-table :columns="columns" :data="weighingList" bordered>
|
||||
<template #image="{ record }">
|
||||
<a-image width="60" :src="record.image"/>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image width="60" :src="record.imgUrl"/>
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
@@ -255,20 +259,9 @@
|
||||
v-if="activeStep === 1"
|
||||
type="primary"
|
||||
class="next-button"
|
||||
@click="compareHandle"
|
||||
>
|
||||
开始比对
|
||||
</a-button>
|
||||
<!-- 下一步按钮 -->
|
||||
<a-button
|
||||
v-if="activeStep === 1"
|
||||
type="primary"
|
||||
class="next-button"
|
||||
:disabled="compareMatchResult !== 'success'"
|
||||
style="margin-left: 12px"
|
||||
@click="handleNext"
|
||||
>
|
||||
下一步
|
||||
开始比对
|
||||
</a-button>
|
||||
<!-- 完成按钮 -->
|
||||
<a-button
|
||||
@@ -313,6 +306,7 @@ const formData = reactive({
|
||||
encoding: '', // 物料编码
|
||||
materialName: '', // 物料名称
|
||||
materialSpec: '', // 物料直径
|
||||
batch: '', // 批次
|
||||
materialProcess: '', // 物料流程
|
||||
unitWeight: 0, // 重量
|
||||
photoUrl: '', // 样图URL
|
||||
@@ -572,8 +566,9 @@ const workOrderResp = ref<WorkOrderResp>({
|
||||
|
||||
// 称重登记页面数据
|
||||
const inputQuantity = ref()
|
||||
const calculateNumber = ref()
|
||||
// todo
|
||||
const ahDeviceWeight = ref('')
|
||||
const ahDeviceWeight = ref('6')
|
||||
const calculatedWeight = ref('')
|
||||
const weighingCount = ref(1)
|
||||
|
||||
@@ -592,7 +587,7 @@ const columns = ref<TableInstanceColumns[]>([
|
||||
{ title: '数量', dataIndex: 'quantity', key: 'quantity', className: 'green-bg',},
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight', className: 'green-bg',},
|
||||
{ title: '标准重量(g)', dataIndex: 'calculatedWeight', key: 'calculatedWeight',},
|
||||
{ title: '抓图', dataIndex: 'image', key: 'image',slotName: 'image',},
|
||||
{ title: '抓图', dataIndex: 'imgUrl', key: 'imgUrl',slotName: 'imgUrl',},
|
||||
{ title: '操作', dataIndex: 'action', key: 'action', slotName: 'action',},
|
||||
])
|
||||
|
||||
@@ -679,47 +674,14 @@ const fetchMaterialData = async (code: string) => {
|
||||
formData.materialProcess = res.data?.materialProcess || ''
|
||||
formData.unitWeight = res.data?.unitWeight || 0
|
||||
formData.photoUrl = res.data?.photoUrl || ''
|
||||
formData.weightRange = (res.data?.downFloatRatio || '-') + '% ~ ' + (res.data?.upFloatRatio || '-') + '%'
|
||||
formData.batch = res.data?.batch || ''
|
||||
formData.weightRange = (res.data?.downFloatRatio ?? '-') + '% ~ ' + (res.data?.upFloatRatio ?? '-') + '%'
|
||||
}
|
||||
if(res.data && res.data.id) {
|
||||
await brightness(res.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
const compareHandle = async () => {
|
||||
try {
|
||||
const materialCode = formData.inputMaterialCode?.trim()
|
||||
if (!materialCode) {
|
||||
Message.error('请先扫描物料编码')
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.materialProcess || formData.materialProcess === '') {
|
||||
Message.error('未找到物料流程,无法对比')
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用后端接口获取比对结果 // todo
|
||||
const res = await vmSend(materialCode);
|
||||
if (res.data) {
|
||||
compareMatchResult.value = res.data
|
||||
if (res.data === 'success') {
|
||||
Message.success('比对成功')
|
||||
} else {
|
||||
Message.error('比对失败')
|
||||
}
|
||||
} else {
|
||||
// 比对失败,提示错误
|
||||
Message.error('比对数据异常')
|
||||
}
|
||||
// compareMatchResult.value = 'success'
|
||||
} catch (error) {
|
||||
console.error('比对失败:', error)
|
||||
Message.error('相机异常,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理下一步
|
||||
const handleNext = async () => {
|
||||
// 步骤2:显示确认弹框
|
||||
@@ -763,9 +725,48 @@ const handleNext = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他情况(步骤1且已成功比对,或步骤0):直接进入下一步
|
||||
activeStep.value++
|
||||
// 进入称重页面的操作已在watch监听器中处理
|
||||
if (activeStep.value === 1) {
|
||||
try {
|
||||
const materialCode = formData.inputMaterialCode?.trim()
|
||||
if (!materialCode) {
|
||||
Message.error('请先扫描物料编码')
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.batch || formData.batch === '') {
|
||||
Message.error('该物料没有批次,无法对比')
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.materialProcess || formData.materialProcess === '') {
|
||||
Message.error('未找到物料流程,无法对比')
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用后端接口获取比对结果 // todo
|
||||
// const res = await vmSend(materialCode);
|
||||
const res = {
|
||||
data: 'success',
|
||||
};
|
||||
if (res.data) {
|
||||
compareMatchResult.value = res.data
|
||||
if (res.data === 'success') {
|
||||
Message.success('比对成功')
|
||||
activeStep.value++
|
||||
} else {
|
||||
Message.error('比对失败')
|
||||
}
|
||||
} else {
|
||||
// 比对失败,提示错误
|
||||
Message.error('比对数据异常')
|
||||
}
|
||||
// compareMatchResult.value = 'success'
|
||||
} catch (error) {
|
||||
console.error('比对失败:', error)
|
||||
Message.error('相机异常,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 处理上一步
|
||||
@@ -807,7 +808,7 @@ const handleBackToFirst = () => {
|
||||
// 重置称重登记页面数据
|
||||
inputQuantity.value = ''
|
||||
// todo
|
||||
ahDeviceWeight.value = ''
|
||||
ahDeviceWeight.value = '6'
|
||||
calculatedWeight.value = ''
|
||||
weighingCount.value = 1
|
||||
// 清空称重列表
|
||||
@@ -885,7 +886,7 @@ const handleConfirm = async () => {
|
||||
quantity: inputQuantity.value,
|
||||
weight: ahDeviceWeight.value,
|
||||
calculatedWeight: calculatedWeight.value,
|
||||
image: '加载中...', // 先显示加载状态
|
||||
imgUrl: '加载中...', // 先显示加载状态
|
||||
}
|
||||
|
||||
// 先添加到列表,让用户能立即看到记录
|
||||
@@ -894,7 +895,7 @@ const handleConfirm = async () => {
|
||||
// 重置输入(让用户能继续输入)
|
||||
inputQuantity.value = ''
|
||||
// todo
|
||||
ahDeviceWeight.value = ''
|
||||
ahDeviceWeight.value = '6'
|
||||
calculatedWeight.value = ''
|
||||
weighingCount.value = weighingList.value.length + 1
|
||||
|
||||
@@ -902,7 +903,7 @@ const handleConfirm = async () => {
|
||||
const imageUrl = await captureImage(newItem.key + '_' + newItem.materialId);
|
||||
const addedItem = weighingList.value.find((item) => item.key === newItem.key)
|
||||
if (addedItem) {
|
||||
addedItem.image = imageUrl || '抓图失败'
|
||||
addedItem.imgUrl = imageUrl || '抓图失败'
|
||||
// 触发视图更新
|
||||
weighingList.value = [...weighingList.value]
|
||||
}
|
||||
@@ -945,6 +946,13 @@ const establishWebSocket = () => {
|
||||
try {
|
||||
if (event.data) {
|
||||
ahDeviceWeight.value = event.data
|
||||
const weight = Number.parseFloat(ahDeviceWeight.value.toString())
|
||||
const unitWeight = formData.unitWeight
|
||||
if (!isNaN(weight) && !isNaN(unitWeight) && unitWeight > 0) {
|
||||
calculateNumber.value = Math.floor(weight / unitWeight)
|
||||
} else {
|
||||
calculateNumber.value = ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket消息解析失败:', error)
|
||||
@@ -1377,4 +1385,17 @@ onUnmounted(() => {
|
||||
font-size: 14px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* 禁用输入框样式 - 黑色加粗字体 */
|
||||
:deep(.arco-input-wrapper.arco-input-disabled) {
|
||||
background-color: #ffffff !important;
|
||||
border-color: #d9d9d9 !important;
|
||||
}
|
||||
|
||||
:deep(.arco-input-wrapper.arco-input-disabled .arco-input) {
|
||||
color: #000000 !important;
|
||||
background-color: transparent !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #000000 !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user