|
@@ -0,0 +1,370 @@
|
|
|
+<template>
|
|
|
+ <ContentWrap v-loading="formLoading">
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ v-loading="formLoading"
|
|
|
+ style="margin-right: 4em; margin-left: 0.5em; margin-top: 1em"
|
|
|
+ label-width="130px"
|
|
|
+ >
|
|
|
+ <div class="base-expandable-content">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="领料单名称" prop="name">
|
|
|
+ <el-input type="text" v-model="formData.name" disabled/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="费用" prop="cost">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.cost"
|
|
|
+ placeholder="根据领用物料自动生成"
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="领用日期" prop="actualStartTime">
|
|
|
+ <el-date-picker
|
|
|
+ style="width: 150%"
|
|
|
+ v-model="formData.requisitionTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="x"
|
|
|
+ placeholder="实际保养开始时间"
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="领用人" prop="otherCost">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.responsiblePerson"
|
|
|
+ placeholder="请输入领用人"
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="16">
|
|
|
+ <el-form-item label="领用原因" prop="remark">
|
|
|
+ <el-input v-model="formData.reason" type="textarea" placeholder="请输入领用原因" disabled/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
+ <el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" disabled/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <!-- 搜索工作栏 -->
|
|
|
+ <el-form
|
|
|
+ class="-mb-15px"
|
|
|
+ :model="queryParams"
|
|
|
+ ref="queryFormRef"
|
|
|
+ :inline="true"
|
|
|
+ label-width="68px"
|
|
|
+ >
|
|
|
+ <el-form-item>
|
|
|
+ <el-button @click="openForm" type="warning">
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" />选择物料</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <ContentWrap>
|
|
|
+ <el-table v-loading="loading" :data="materialList" :stripe="true" :show-overflow-tooltip="true">
|
|
|
+ <!-- 添加序号列 -->
|
|
|
+ <el-table-column
|
|
|
+ type="index"
|
|
|
+ label="序号"
|
|
|
+ width="60"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column label="工厂" align="center" prop="factory" />
|
|
|
+ <el-table-column label="成本中心" align="center" prop="costCenter" />
|
|
|
+ <el-table-column label="物料编码" align="center" prop="materialCode" />
|
|
|
+ <el-table-column label="物料名称" align="center" prop="materialName" />
|
|
|
+ <el-table-column label="单价" align="center" prop="unitPrice" :formatter="erpPriceTableColumnFormatter"/>
|
|
|
+ <el-table-column label="库存数量" align="center" prop="quantity" :formatter="erpPriceTableColumnFormatter"/>
|
|
|
+ <el-table-column label="领用数量" align="center" prop="requisitionQuantity" :formatter="erpPriceTableColumnFormatter"/>
|
|
|
+ <el-table-column label="单位" align="center" prop="unit" />
|
|
|
+ </el-table>
|
|
|
+ </ContentWrap>
|
|
|
+
|
|
|
+ </ContentWrap>
|
|
|
+ <ContentWrap>
|
|
|
+ <el-form>
|
|
|
+ <el-form-item style="float: right">
|
|
|
+ <!-- <el-button @click="submitForm" type="primary" :disabled="formLoading">保 存</el-button> -->
|
|
|
+ <el-button @click="close">确 定</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap>
|
|
|
+ <SelectLocalStock ref="localStockFormRef" @choose="stockChoose" />
|
|
|
+</template>
|
|
|
+<script setup lang="ts">
|
|
|
+import * as UserApi from '@/api/system/user'
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
+import { ref } from 'vue'
|
|
|
+import { IotMaterialRequisitionApi, IotMaterialRequisitionVO } from '@/api/pms/iotmaterialrequisition'
|
|
|
+import { IotMaterialRequisitionDetailApi, IotMaterialRequisitionDetailVO } from '@/api/pms/iotmaterialrequisitiondetail'
|
|
|
+import { useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
+import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
|
|
|
+import SelectLocalStock from "@/views/pms/iotmaterialrequisition/SelectLocalStock.vue";
|
|
|
+import * as DeptApi from "@/api/system/dept";
|
|
|
+import {erpPriceTableColumnFormatter} from "@/utils";
|
|
|
+
|
|
|
+/** 物料领用 表单 */
|
|
|
+defineOptions({ name: 'IotMaterialRequisitionAdd' })
|
|
|
+
|
|
|
+const { t } = useI18n() // 国际化
|
|
|
+const message = useMessage() // 消息弹窗
|
|
|
+const { delView } = useTagsViewStore() // 视图操作
|
|
|
+const { currentRoute, push } = useRouter()
|
|
|
+const dept = ref() // 当前登录人所属部门对象
|
|
|
+const dialogTitle = ref('') // 弹窗的标题
|
|
|
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
|
|
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
|
|
+const materialList = ref<IotMaterialRequisitionDetailVO[]>([]) // 领用单明细
|
|
|
+const { params, name } = useRoute() // 查询参数
|
|
|
+const id = params.id
|
|
|
+
|
|
|
+const formData = ref({
|
|
|
+ id: undefined,
|
|
|
+ deptId: undefined,
|
|
|
+ name: '',
|
|
|
+ orderNumber: undefined,
|
|
|
+ responsiblePerson: undefined,
|
|
|
+ requisitionTime: undefined,
|
|
|
+ cost: undefined,
|
|
|
+ reason: undefined,
|
|
|
+ remark: undefined,
|
|
|
+ status: undefined,
|
|
|
+})
|
|
|
+
|
|
|
+const formRules = reactive({
|
|
|
+ name: [{ required: true, message: '工单名称不能为空', trigger: 'blur' }],
|
|
|
+ responsiblePerson: [{ required: true, message: '领用人不能为空', trigger: 'blur' }],
|
|
|
+})
|
|
|
+const formRef = ref() // 表单 Ref
|
|
|
+
|
|
|
+// 监听物料列表变化
|
|
|
+watch(
|
|
|
+ () => materialList.value,
|
|
|
+ () => {
|
|
|
+ calculateTotalCost()
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+const localStockFormRef = ref()
|
|
|
+const openForm = () => {
|
|
|
+ localStockFormRef.value.open()
|
|
|
+}
|
|
|
+
|
|
|
+// 计算保养金额
|
|
|
+const calculateTotalCost = () => {
|
|
|
+ // 物料总金额 = ∑(单价 * 消耗数量)
|
|
|
+ const materialTotal = materialList.value.reduce((sum, item) => {
|
|
|
+ const price = Number(item.unitPrice) || 0
|
|
|
+ const quantity = Number(item.requisitionQuantity) || 0
|
|
|
+ return sum + (price * quantity)
|
|
|
+ }, 0)
|
|
|
+
|
|
|
+ // 费用 = 物料总金额
|
|
|
+ formData.value.cost = (materialTotal).toFixed(2)
|
|
|
+}
|
|
|
+
|
|
|
+// 多选 物料
|
|
|
+const stockChoose = (selectedStocks) => {
|
|
|
+ // 转换数据结构(根据你的接口定义调整)
|
|
|
+ const newItems = selectedStocks.map(stock => ({
|
|
|
+ id: stock.id,
|
|
|
+ materialCode: stock.materialCode,
|
|
|
+ materialName: stock.materialName,
|
|
|
+ unit: stock.unit,
|
|
|
+ quantity: stock.quantity, // 库存数量
|
|
|
+ requisitionQuantity: stock.requisitionQuantity, // 领用数量
|
|
|
+ unitPrice: stock.unitPrice, // 单价
|
|
|
+ remark: null,
|
|
|
+ code: stock.materialCode // 移除操作需要
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 合并到现有列表(去重)
|
|
|
+ newItems.forEach(item => {
|
|
|
+ const exists = materialList.value.some(
|
|
|
+ existing => existing.materialCode === item.materialCode
|
|
|
+ )
|
|
|
+ if (!exists) {
|
|
|
+ materialList.value.push(item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleInput = (value, obj) => {
|
|
|
+ // 1. 过滤非法字符(只允许数字和小数点)
|
|
|
+ let filtered = value.replace(/[^\d.]/g, '')
|
|
|
+
|
|
|
+ // 2. 处理多个小数点的情况
|
|
|
+ filtered = filtered.replace(/\.{2,}/g, '.')
|
|
|
+
|
|
|
+ // 3. 限制小数点后最多两位
|
|
|
+ let decimalParts = filtered.split('.')
|
|
|
+ if (decimalParts.length > 1) {
|
|
|
+ decimalParts = decimalParts.slice(0, 2)
|
|
|
+ filtered = decimalParts.join('.')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 处理以小数点开头的情况(自动补0)
|
|
|
+ if (filtered.startsWith('.')) {
|
|
|
+ filtered = '0' + filtered
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 更新绑定值(同时处理连续输入多个0的情况)
|
|
|
+ formData.value[obj] = filtered.replace(/^0+(?=\d)/, '')
|
|
|
+}
|
|
|
+
|
|
|
+const queryParams = reactive({
|
|
|
+ requisitionId: id,
|
|
|
+})
|
|
|
+
|
|
|
+const close = () => {
|
|
|
+ delView(unref(currentRoute))
|
|
|
+ push({ name: 'IotMaterialRequisition', params:{}})
|
|
|
+}
|
|
|
+
|
|
|
+/** 提交表单 */
|
|
|
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
|
+const submitForm = async () => {
|
|
|
+ // 校验表单
|
|
|
+ await formRef.value.validate()
|
|
|
+ // 校验表格数据
|
|
|
+ const isValid = validateTableData()
|
|
|
+ if (!isValid) return
|
|
|
+ // 提交请求
|
|
|
+ formLoading.value = true
|
|
|
+ try {
|
|
|
+ const data = {
|
|
|
+ materialRequisition: formData.value,
|
|
|
+ materialReqDetails: materialList.value
|
|
|
+ }
|
|
|
+ await IotMaterialRequisitionApi.addMaterialRequisition(data)
|
|
|
+ message.success(t('common.createSuccess'))
|
|
|
+ close()
|
|
|
+ // 发送操作成功的事件
|
|
|
+ emit('success')
|
|
|
+ } finally {
|
|
|
+ formLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 校验表格数据 */
|
|
|
+const validateTableData = (): boolean => {
|
|
|
+ let isValid = true
|
|
|
+ const errorMessages: string[] = []
|
|
|
+
|
|
|
+ if (materialList.value.length === 0) {
|
|
|
+ errorMessages.push('请至少添加一条物料明细')
|
|
|
+ isValid = false
|
|
|
+ // 直接返回无需后续校验
|
|
|
+ message.error('请至少添加一条物料明细')
|
|
|
+ return isValid
|
|
|
+ }
|
|
|
+ return isValid
|
|
|
+}
|
|
|
+
|
|
|
+/** 重置表单 */
|
|
|
+const resetForm = () => {
|
|
|
+ formData.value = {
|
|
|
+ id: undefined,
|
|
|
+ status: undefined,
|
|
|
+ description: undefined,
|
|
|
+ pic: undefined,
|
|
|
+ remark: undefined,
|
|
|
+ deviceName: undefined,
|
|
|
+ processInstanceId: undefined,
|
|
|
+ auditStatus: undefined,
|
|
|
+ deptId: undefined
|
|
|
+ }
|
|
|
+ formRef.value?.resetFields()
|
|
|
+}
|
|
|
+onMounted(async () => {
|
|
|
+ materialList.value = []
|
|
|
+ const deptId = useUserStore().getUser.deptId
|
|
|
+ // 查询当前登录人所属部门名称
|
|
|
+ dept.value = await DeptApi.getDept(deptId)
|
|
|
+ formData.value.name = dept.value.name + ' - 领料单'
|
|
|
+ formData.value.deptId = deptId
|
|
|
+ try {
|
|
|
+ // 领料单主表数据
|
|
|
+ const requisition = await IotMaterialRequisitionApi.getIotMaterialRequisition(id);
|
|
|
+ formData.value = requisition;
|
|
|
+ // 查询领料单物料明细
|
|
|
+ const requisitionDetails = await IotMaterialRequisitionDetailApi.getIotMaterialRequisitionDetailList(queryParams);
|
|
|
+ materialList.value = []
|
|
|
+ if (Array.isArray(requisitionDetails)) {
|
|
|
+ materialList.value = requisitionDetails.map(item => ({
|
|
|
+ ...item,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('数据加载失败:', error)
|
|
|
+ message.error('数据加载失败,请重试')
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const handleDelete = async (materialCode: string) => {
|
|
|
+ try {
|
|
|
+ // 从 materialList 中移除对应物料
|
|
|
+ const index = materialList.value.findIndex(item => item.materialCode === materialCode);
|
|
|
+ if (index !== -1) {
|
|
|
+ materialList.value.splice(index, 1);
|
|
|
+ calculateTotalCost(); // 重新计算总费用
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('移除失败:', error);
|
|
|
+ message.error('移除失败');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+<style scoped>
|
|
|
+.base-expandable-content {
|
|
|
+ overflow: hidden; /* 隐藏溢出的内容 */
|
|
|
+ transition: max-height 0.3s ease; /* 平滑过渡效果 */
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-input-number .el-input__inner) {
|
|
|
+ text-align: left !important;
|
|
|
+ padding-left: 10px; /* 保持左侧间距 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 分组容器样式 */
|
|
|
+.form-group {
|
|
|
+ position: relative;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 20px 15px 10px;
|
|
|
+ margin-bottom: 18px;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分组标题样式 */
|
|
|
+.group-title {
|
|
|
+ position: absolute;
|
|
|
+ top: -10px;
|
|
|
+ left: 20px;
|
|
|
+ background: white;
|
|
|
+ padding: 0 8px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+</style>
|