heli-mes/mes-ui/mes-ui-admin-vue3/src/views/heli/xzdstoragelog/index.vue

686 lines
23 KiB
Vue
Raw Normal View History

<template>
<el-card class="hl-card">
<!-- <template #header>-->
<!-- <span>入出库单据生成</span>-->
<!-- </template>-->
<ContentWrap class="borderxx">
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item label="单据日期" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
class="!w-260px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="物料编号" prop="matCode">
<el-input
v-model="queryParams.matCode"
placeholder="物料编号"
clearable
@keyup.enter="handleQuery"
class="!w-260px"
/>
</el-form-item>
<el-form-item label="物料名称" prop="matName">
<el-input
v-model="queryParams.matName"
placeholder="物料名称"
clearable
@keyup.enter="handleQuery"
class="!w-260px"
/>
</el-form-item>
<el-form-item label="单据类型" prop="stockType">
<el-select
v-model="queryParams.stockType"
placeholder="请选择单据类型"
clearable
class="!w-260px"
>
<el-option
v-for="item in HeliStockTypeDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="单据状态" prop="isExport">
<el-select
v-model="queryParams.isExport"
placeholder="请选择状态"
clearable
class="!w-260px"
>
<el-option
v-for="item in HeliStorageIsExportDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery" type="primary">
<Icon icon="ep:search" class="mr-5px" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
重置
</el-button>
<el-checkbox style="margin-left: 18px" v-model="queryParams.isSameMat">
相同物料
</el-checkbox>
<el-button style="margin-left: 18px" @click="getMatCode()" type="primary" size="large">
获取编码
</el-button>
<el-button
style="margin-left: 18px"
@click="submitForm()"
type="primary"
size="large"
:disabled="!multipleSelection || multipleSelection.length === 0"
>
保存编码
</el-button>
<el-button
style="margin-left: 18px"
@click="generateBill()"
type="primary"
size="large"
:disabled="!multipleSelection || multipleSelection.length === 0"
>
单据生成
</el-button>
<el-button style="margin-left: 18px" @click="openPnForm()" type="success" size="large">
新增物料
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<div class="section-title">出入库单据信息</div>
<el-table
v-loading="loading"
:data="list"
:show-overflow-tooltip="true"
class="hl-table"
ref="multipleTable"
height="calc(100vh - 410px)"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" :selectable="checkSelectable" />
<el-table-column type="index" width="60" label="序号" align="center" fixed="left" />
<el-table-column
label="单据类型"
align="center"
prop="stockMode"
min-width="100"
fixed="left"
>
<template #default="scope">
<dict-tag :type="DICT_TYPE.HELI_STORAGE_TYPE" :value="scope.row.stockType" />
</template>
</el-table-column>
<el-table-column
label="单据状态"
align="center"
prop="stockMode"
min-width="100"
fixed="left"
>
<template #default="scope">
<el-tag v-if="scope.row.isExport == 0 || scope.row.isExport == null" type="danger">
未生成
</el-tag>
<el-tag v-else-if="scope.row.isExport == 1" type="success">已生成</el-tag>
<el-tag v-else type="success">其他</el-tag>
</template>
</el-table-column>
<el-table-column label="物料编码" align="center" prop="matCode" min-width="200">
<template #default="scope">
<template v-if="scope.row.goodsType == 2 || scope.row.goodsType == 1">
<el-form-item class="mb-0px!">
<el-select
v-model="scope.row.matId"
placeholder=""
style="width: 100%"
filterable
clearable
@focus="handleMatSelectFocus(scope.row)"
@change="(val) => handleMatIdChange(val, scope.row)"
>
<el-option
v-for="dict in scope.row.materialDOList"
:key="dict.id"
:label="dict.code + ' ' + dict.name + ' ' + dict.spec"
:value="dict.id"
/>
</el-select>
</el-form-item>
</template>
<template v-else>
{{ scope.row.matCode }}
</template>
</template>
</el-table-column>
<el-table-column label="价格类型" align="center" prop="priceType" min-width="190">
<template #default="scope">
<el-select v-model="scope.row.priceType" placeholder="请选择" class="!w-full" clearable>
<el-option label="1|临时暂估价" value="1|临时暂估价" />
<el-option label="3|实价" value="3|实价" />
</el-select>
</template>
</el-table-column>
<el-table-column label="发票编码" align="center" prop="incoiceCode" min-width="160">
<template #default="scope">
<el-input
v-model="scope.row.incoiceCode"
placeholder="请输入发票编码"
clearable
class="!w-full"
/>
</template>
</el-table-column>
<el-table-column label="物料名称" align="center" prop="matName" min-width="210" />
<el-table-column label="单价" align="center" prop="price" min-width="120" />
<el-table-column label="供应商简称" align="center" prop="brief" min-width="200" />
<el-table-column label="数量" align="center" prop="storageOkQty" min-width="140" />
<!-- <el-table-column label="单据类型" align="center" prop="stockMode" min-width="180" >-->
<!-- <template #default="scope">-->
<!-- <dict-tag :type="DICT_TYPE.HELI_STOCK_MODE" :value="scope.row.stockMode" />-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="规格型号" align="center" prop="matSpec" min-width="100" />
<el-table-column label="仓库" align="center" prop="whName" min-width="100" />
<!-- <el-table-column label="库区" align="center" prop="rgName" min-width="100" />-->
<el-table-column label="库区/库位" align="center" prop="pnName" min-width="140" />
<el-table-column label="单位" align="center" prop="matUnit" min-width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.HELI_MATERIAL_UNIT" :value="scope.row.matUnit" />
</template>
</el-table-column>
<el-table-column label="出入库单号" align="center" prop="codeNo" min-width="180" />
<el-table-column
label="入/出库日期"
align="center"
prop="createTime"
:formatter="dateFormatter1"
min-width="170"
/>
<el-table-column label="单据编号" align="center" prop="xzdStockNo" min-width="130" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</el-card>
<MaterialForm ref="materialOpenFormRef" @success="getList" />
</template>
<script setup lang="ts">
import * as StorageLogApi from '@/api/heli/xzdstoragelog'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import * as WarehouseApi from '@/api/heli/warehouse'
import routeParamsCache from '@/utils/routeParamsCache'
import { ElButton, ElTableColumn } from 'element-plus'
import { ref } from 'vue'
import { HeliStockTypeDict, HeliStorageStatusDict } from '@/api/heli/wmsstorage'
import { HeliStorageIsExportDict } from '@/api/heli/xzdstoragelog'
import { dateFormatter, dateFormatter1 } from '@/utils/formatTime'
import MaterialForm from '@/views/heli/material/MaterialForm.vue'
defineOptions({ name: 'Xzdstoragelog' })
const whList = ref([])
const multipleSelection = ref([])
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const exportLoading = ref(false) // 导出的加载中
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
matCode: undefined,
matName: undefined,
matType: undefined,
whId: undefined,
lotNo: undefined,
pnId: undefined,
stockNo: undefined,
stockType: undefined,
isExport: undefined,
createTime: [],
ids: []
})
const queryFormRef = ref() // 搜索的表单
const handleSelectionChange = (val) => {
// 更新 multipleSelection
multipleSelection.value = val
}
// 判断行是否可选择:已生成的单据不能选择
const checkSelectable = (row) => {
return row.isExport == 0 || row.isExport == null
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StorageLogApi.getStorageLogPage(queryParams)
list.value = data.list
total.value = data.total
// 价格类型默认'1|临时暂估价'
list.value.forEach((item) => {
if (!item.priceType) {
item.priceType = '1|临时暂估价'
}
})
} finally {
loading.value = false
}
}
const materialOpenFormRef = ref()
const openPnForm = () => {
materialOpenFormRef.value.open('create')
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.createTime = getDefaultDateRange()
handleQuery()
}
//仓库列表
const init_page_wh = async () => {
whList.value = await WarehouseApi.getSimpList()
}
// //库区列表
// const init_page_rg = (async ()=>{
// rgList.value = await RgApi.getSimpList()
// })
// //库位列表
// const init_page_pn = (async ()=>{
// pnList.value = await PnApi.getSimpList()
// })
const getMatCode = async () => {
try {
const selectedList = multipleSelection.value || [] // 安全获取数据
// 1. 检查空数据
if (!selectedList || selectedList.length === 0) {
message.error('请先选择数据')
return
}
loading.value = true
queryParams.ids = selectedList.map((item) => item.id)
// 只获取选中行的物料编码数据,不刷新整个页面
const data = await StorageLogApi.getStorageLogPage(queryParams)
// 只处理选中行,确保 Vue 响应式生效
data.list?.forEach((newRow) => {
// 只处理用户勾选的行
if (!queryParams.ids.includes(newRow.id)) return
const row = list.value.find((r) => r.id == newRow.id)
if (!row) return
// 默认价格类型
if (!row.priceType) {
row.priceType = '1|临时暂估价'
}
if (newRow.materialDOList && newRow.materialDOList.length > 0) {
// 更新 materialDOList
row.materialDOList = newRow.materialDOList
if (row.matId == null || row.matId === '') {
// matId 为空:物料编码 == 1 默认选中,> 1 不选择
if (newRow.materialDOList.length === 1) {
const mat = newRow.materialDOList[0]
row.matId = mat.id
row.matCode = mat.code
row.matName = mat.name
row._autoSelectMatId = true // 标记为自动选中
}
} else if (row._autoSelectMatId && newRow.materialDOList.length > 1) {
// 之前是自动选中的,现在变成多条,清空
row.matId = undefined
row.matCode = ''
row.matName = ''
row._autoSelectMatId = false
} else {
// matId 不为空:保持当前选中,检查是否仍在新列表中
const mat = newRow.materialDOList.find((m) => m.id == row.matId)
if (mat) {
row.matId = mat.id
row.matCode = mat.code
row.matName = mat.name
} else {
row.matId = undefined
row.matCode = ''
row.matName = ''
}
}
} else {
// 没有物料编码列表,清空
row.materialDOList = []
row.matId = undefined
row.matCode = ''
row.matName = ''
row._autoSelectMatId = false
}
})
queryParams.ids = []
message.success('获取成功')
} catch (error) {
console.error('获取失败:', error)
} finally {
loading.value = false
}
}
/** 点击物料编码下拉框时,请求当前行的物料编码列表 */
const handleMatSelectFocus = async (row) => {
// 避免重复请求
if (row._loadingMatList) return
row._loadingMatList = true
try {
queryParams.ids = [row.id]
const data = await StorageLogApi.getStorageLogPage(queryParams)
const newRow = data.list?.find((r) => r.id == row.id)
if (newRow && newRow.materialDOList) {
row.materialDOList = newRow.materialDOList
// matId 不为空:检查是否仍在新列表中,不在则清空
if (row.matId != null && row.matId !== '') {
const mat = newRow.materialDOList.find((m) => m.id == row.matId)
if (!mat) {
row.matId = undefined
row.matCode = ''
row.matName = ''
}
}
}
queryParams.ids = []
} catch (error) {
console.error('获取物料编码失败:', error)
} finally {
row._loadingMatList = false
}
}
const generateBill = async () => {
// 1. 检查是否选择了数据
if (multipleSelection.value.length === 0) {
message.warning('请选择要生成的数据')
return
}
// 2. 必须选择同一类型的单据
if (
multipleSelection.value.some((row) => row.stockType !== multipleSelection.value[0].stockType)
) {
message.warning('请选择同一单据类型的数据')
return
}
// 3. 检查物料编码根据goodsType判断
const rowWithoutMat = multipleSelection.value.find((row) => {
if (row.goodsType == 1 || row.goodsType == 2) {
return !row.matId
} else {
return !row.matCode
}
})
if (rowWithoutMat) {
const tableRowIndex = list.value.findIndex((r) => r.id === rowWithoutMat.id) + 1
message.error(`${tableRowIndex} )没有选择物料编码,请确认!`)
return
}
try {
// 4. 调用后端生成单据接口
await StorageLogApi.generateBill(multipleSelection.value)
message.success(`成功生成 ${multipleSelection.value.length} 条单据`)
// 5. 刷新列表,显示最新状态
await getList()
} catch (e: any) {
// 获取错误码和错误信息中的 ID 列表
let code = 0
let idStr = ''
if (typeof e === 'string') {
idStr = e
} else if (e.code) {
code = e.code
idStr = e.msg || ''
} else if (e.response?.data) {
code = e.response.data.code
idStr = e.response.data.msg || ''
}
// 移除 msg 中可能存在的中文后缀
idStr = idStr.replace('存在', '')
// 分割msg获取ID数组
const msg = idStr.split(',')
// 在list中找到对应ID的序号集合当前页的行号
const errorIndices = msg
.map((id) => {
const index = list.value.findIndex((row) => row.id === parseInt(id))
if (index === -1) return 0 // 未找到返回0
// 使用当前页的行号
return index + 1
})
.filter((index) => index > 0) // 过滤掉未找到的
// 转换为字符串用逗号分割errorIndices从小到大排序
errorIndices.sort((a, b) => a - b)
const errorIndicesStr = errorIndices.join(',')
// 根据不同的错误码显示不同的提示
if (code === 11115) {
message.error(`${errorIndicesStr} )没有选择物料编码,请确认!`)
} else if (code === 11111) {
message.error(`${errorIndicesStr} )存在已删除的数据,请刷新界面!`)
} else if (code === 111102) {
message.error(`${errorIndicesStr} )已生成入库单的记录,请刷新界面。`)
} else if (code === 11112) {
message.error(`${errorIndicesStr} )价格类型没有输入,请确认。`)
} else if (code === 11113) {
message.error(`${errorIndicesStr} )价格类型是'临时暂估价',发票编码不空,请确认。`)
} else if (code === 11114) {
message.error(`${errorIndicesStr} )价格类型是'3|实价',发票编码为空,请确认`)
} else {
message.error(`${errorIndicesStr} )存在异常数据,请刷新界面。`)
}
// 生成失败时,不清空多选状态,保留下拉框的值
return
}
}
const submitForm = async () => {
try {
const selectedList = multipleSelection.value || [] // 安全获取数据
// 1. 检查空数据
if (!selectedList || selectedList.length === 0) {
message.error('请先选择数据')
return
}
// 2.物料编码不能为空 提示 行(具体序列号)没有选择物料编码,请确认! 使用表格中的实际行号
// 根据goodsType判断goodsType为1或2时需要选择matId否则检查matCode
const rowWithoutMat = selectedList.find((row) => {
if (row.goodsType == 1 || row.goodsType == 2) {
return !row.matId
} else {
return !row.matCode
}
})
if (rowWithoutMat) {
const tableRowIndex = list.value.findIndex((r) => r.id === rowWithoutMat.id) + 1
message.error(`${tableRowIndex} )没有选择物料编码,请确认!`)
return
}
loading.value = true
// 5. 提交数据(添加超时处理)
const res = await Promise.race([
StorageLogApi.submitForm(selectedList),
new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), 30000))
])
message.success('保存成功')
// 不立即刷新列表,保持当前的下拉框选择状态
// 用户可以通过点击搜索按钮手动刷新
await getList() // 注释掉自动刷新
// emit('success')
} catch (e: any) {
console.log(e)
// 获取错误码和错误信息中的 ID 列表
let code = 0
let idStr = ''
if (typeof e === 'string') {
idStr = e
} else if (e.code) {
code = e.code
idStr = e.msg || ''
} else if (e.response?.data) {
code = e.response.data.code
idStr = e.response.data.msg || ''
}
// 分割msg获取ID数组
const msg = idStr.split(',')
// 在list中找到对应ID的序号集合当前页的行号
const errorIndices = msg
.map((id) => {
const index = list.value.findIndex((row) => row.id === parseInt(id))
if (index === -1) return 0 // 未找到返回0
// 使用当前页的行号
return index + 1
})
.filter((index) => index > 0) // 过滤掉未找到的
// 转换为字符串用逗号分割errorIndices从小到大排序
errorIndices.sort((a, b) => a - b)
const errorIndicesStr = errorIndices.join(',')
if (code === 10086) {
message.error(`${errorIndicesStr} )存在已删除的数据,请刷新界面!`)
} else if (code === 10087) {
message.error(`${errorIndicesStr} )已生成入库单的记录,请刷新界面。`)
} else if (code === 10088) {
message.error(`${errorIndicesStr} )没有选择物料编码,请确认!`)
} else {
message.error(`${errorIndicesStr} )存在异常数据,请刷新界面。`)
}
// console.error('获取失败:', error)
// message.error(`操作失败: ${error.message || "未知错误"}`);
} finally {
loading.value = false
}
}
/** 初始化 **/
const route = useRoute()
const routeValue = ref('')
onMounted(async () => {
let params = routeParamsCache.get(route.path)
routeValue.value = route.path
if (params) {
Object.assign(queryParams, params)
}
await init_page_wh()
// await init_page_rg()
// await init_page_pn()
await getList()
})
onBeforeUnmount(() => {
const plainParams = JSON.parse(JSON.stringify(queryParams))
routeParamsCache.set(routeValue.value, plainParams)
})
window.addEventListener('beforeunload', () => {
const plainParams = JSON.parse(JSON.stringify(queryParams))
routeParamsCache.set(routeValue.value, plainParams)
})
/** 初始化 **/
onMounted(() => {
// 设置默认日期范围当月1号到当天
queryParams.createTime = getDefaultDateRange()
// 自动加载数据
// getList()
})
/** 获取默认日期范围 */
const getDefaultDateRange = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const startDate = `${year}-${month}-01 00:00:00`
const endDate = `${year}-${month}-${day} 23:59:59`
return [startDate, endDate]
}
/** 物料编码变更处理 */
const handleMatIdChange = (val, row) => {
// 用户手动选择,清除自动选中标记
row._autoSelectMatId = false
if (val && queryParams.isSameMat) {
// 当勾选了"相同物料"选项时,更新所有选中行的物料编码
const selectedIds = multipleSelection.value.map((item) => item.id)
list.value.forEach((item) => {
if (selectedIds.includes(item.id)) {
// 更新物料 ID
item.matId = val
item._autoSelectMatId = false // 手动选择
// 同步更新物料编号和名称
const selectedMat = item.materialDOList?.find((m) => m.id === val)
if (selectedMat) {
item.matCode = selectedMat.code
item.matName = selectedMat.name
}
}
})
} else if (val) {
// 没有勾选"相同物料"时,只更新当前行
const selectedMat = row.materialDOList?.find((m) => m.id === val)
if (selectedMat) {
row.matCode = selectedMat.code
row.matName = selectedMat.name
}
}
}
</script>