add:生产下料同步
This commit is contained in:
408
pda/pda/src/views/ProductGroup.vue
Normal file
408
pda/pda/src/views/ProductGroup.vue
Normal file
@@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<van-nav-bar
|
||||
:title="t('productGroup.title')"
|
||||
left-arrow
|
||||
@click-left="router.push('/')"
|
||||
/>
|
||||
<div class="page-content">
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="workSectText"
|
||||
is-link
|
||||
readonly
|
||||
:label="t('productGroup.workSect')"
|
||||
:placeholder="t('productGroup.workSectPlaceholder')"
|
||||
:rules="[{ required: true, message: t('productGroup.workSectRequired') }]"
|
||||
name="workSect"
|
||||
@click="showWorkSectPicker = true"
|
||||
/>
|
||||
<van-field
|
||||
v-model="palletCode"
|
||||
:label="t('productGroup.palletCode')"
|
||||
:placeholder="t('productGroup.palletCodePlaceholder')"
|
||||
:rules="[{ required: true, message: t('productGroup.palletCodeRequired') }]"
|
||||
name="palletCode"
|
||||
clearable
|
||||
>
|
||||
<template #right-icon>
|
||||
<van-icon name="scan" class="scan-icon" @click="onScanPallet" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="workOrderText"
|
||||
is-link
|
||||
readonly
|
||||
:label="t('productGroup.workOrder')"
|
||||
:placeholder="t('productGroup.workOrderPlaceholder')"
|
||||
:rules="[{ required: true, message: t('productGroup.workOrderRequired') }]"
|
||||
name="workOrder"
|
||||
@click="onWorkOrderClick"
|
||||
/>
|
||||
<van-field
|
||||
v-model="loadPortText"
|
||||
is-link
|
||||
readonly
|
||||
:label="t('productGroup.loadPort')"
|
||||
:placeholder="t('productGroup.loadPortPlaceholder')"
|
||||
:rules="[{ required: true, message: t('productGroup.loadPortRequired') }]"
|
||||
name="loadPort"
|
||||
@click="onLoadPortClick"
|
||||
/>
|
||||
</van-form>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-header">
|
||||
<span class="detail-header-text">{{ t('productGroup.total', bomList.length) }}</span>
|
||||
</div>
|
||||
<div v-if="!bomList.length" class="detail-empty">
|
||||
<van-empty :description="t('productGroup.noData')" image="search" />
|
||||
</div>
|
||||
<div v-else class="card-list">
|
||||
<div
|
||||
v-for="(item, idx) in bomList"
|
||||
:key="idx"
|
||||
class="detail-card"
|
||||
>
|
||||
<div class="card-header">
|
||||
<span class="card-index">#{{ idx + 1 }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.materialCode') }}</span>
|
||||
<span class="card-value">{{ item.MaterialCode }}</span>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.materialName') }}</span>
|
||||
<span class="card-value">{{ item.MaterialName }}</span>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.bomQty') }}</span>
|
||||
<span class="card-value card-value--num">{{ item.BomQty }}</span>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.useBomQty') }}</span>
|
||||
<span class="card-value">{{ item.UseBomQty }}</span>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.planQty') }}</span>
|
||||
<span class="card-value card-value--num">{{ item.defaultQty }}</span>
|
||||
</div>
|
||||
<div class="card-row card-row--input">
|
||||
<span class="card-label">{{ t('productGroup.inputQty') }}</span>
|
||||
<van-field
|
||||
v-model="item.inputQty"
|
||||
type="number"
|
||||
class="card-input"
|
||||
:placeholder="t('productGroup.inputQtyPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-row">
|
||||
<span class="card-label">{{ t('productGroup.unit') }}</span>
|
||||
<span class="card-value">{{ item.Unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showWorkSectPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="workSectOptions"
|
||||
@confirm="onWorkSectConfirm"
|
||||
@cancel="showWorkSectPicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model:show="showWorkOrderPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="workOrderOptions"
|
||||
@confirm="onWorkOrderConfirm"
|
||||
@cancel="showWorkOrderPicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model:show="showLoadPortPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="loadPortOptions"
|
||||
@confirm="onLoadPortConfirm"
|
||||
@cancel="showLoadPortPicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<BottomButton :text="t('productGroup.confirm')" :loading="submitting" @click="onSubmit" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import { useI18n } from '@/i18n'
|
||||
import BottomButton from '@/components/BottomButton.vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const workSect = ref('')
|
||||
const workSectText = ref('')
|
||||
const palletCode = ref('')
|
||||
const selectedOrderCode = ref('')
|
||||
const workOrderText = ref('')
|
||||
const selectedLoadPort = ref('')
|
||||
const loadPortText = ref('')
|
||||
const bomList = ref([])
|
||||
const formRef = ref()
|
||||
const submitting = ref(false)
|
||||
|
||||
const orderList = ref([])
|
||||
const currentOrder = ref(null)
|
||||
|
||||
const showWorkSectPicker = ref(false)
|
||||
const showWorkOrderPicker = ref(false)
|
||||
const showLoadPortPicker = ref(false)
|
||||
|
||||
const workSectOptions = [
|
||||
{ text: 'work1', value: 'work1' },
|
||||
{ text: 'work2', value: 'work2' },
|
||||
]
|
||||
|
||||
const workOrderOptions = computed(() =>
|
||||
orderList.value.map((item) => ({
|
||||
text: item.OrderCode,
|
||||
value: item.OrderCode,
|
||||
}))
|
||||
)
|
||||
|
||||
const loadPortOptions = computed(() => {
|
||||
if (!currentOrder.value || !currentOrder.value.PortList) return []
|
||||
return currentOrder.value.PortList.map((p) => ({
|
||||
text: p.Port,
|
||||
value: p.Port,
|
||||
}))
|
||||
})
|
||||
|
||||
async function fetchWorkOrders() {
|
||||
try {
|
||||
const res = await request.get('/api/workOrder', { params: { status: 2 } })
|
||||
orderList.value = res.content || []
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
}
|
||||
}
|
||||
|
||||
function onWorkSectConfirm({ selectedOptions }) {
|
||||
const opt = selectedOptions[0]
|
||||
workSect.value = opt.value
|
||||
workSectText.value = opt.text
|
||||
showWorkSectPicker.value = false
|
||||
}
|
||||
|
||||
function onWorkOrderClick() {
|
||||
if (!workOrderOptions.value.length) {
|
||||
showToast({ message: t('productGroup.noOrder'), type: 'fail' })
|
||||
return
|
||||
}
|
||||
showWorkOrderPicker.value = true
|
||||
}
|
||||
|
||||
function onWorkOrderConfirm({ selectedOptions }) {
|
||||
const opt = selectedOptions[0]
|
||||
selectedOrderCode.value = opt.value
|
||||
workOrderText.value = opt.text
|
||||
currentOrder.value = orderList.value.find((o) => o.OrderCode === opt.value) || null
|
||||
selectedLoadPort.value = ''
|
||||
loadPortText.value = ''
|
||||
bomList.value = []
|
||||
showWorkOrderPicker.value = false
|
||||
}
|
||||
|
||||
function onLoadPortClick() {
|
||||
if (!currentOrder.value || !loadPortOptions.value.length) {
|
||||
showToast({ message: t('productGroup.noPort'), type: 'fail' })
|
||||
return
|
||||
}
|
||||
showLoadPortPicker.value = true
|
||||
}
|
||||
|
||||
function onLoadPortConfirm({ selectedOptions }) {
|
||||
const opt = selectedOptions[0]
|
||||
selectedLoadPort.value = opt.value
|
||||
loadPortText.value = opt.text
|
||||
filterBomList()
|
||||
showLoadPortPicker.value = false
|
||||
}
|
||||
|
||||
function filterBomList() {
|
||||
if (!currentOrder.value || !selectedLoadPort.value) {
|
||||
bomList.value = []
|
||||
return
|
||||
}
|
||||
const allBom = currentOrder.value.WorkOrderBomList || []
|
||||
bomList.value = allBom
|
||||
.filter((item) => item.LoadPort === selectedLoadPort.value)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
defaultQty: item.BomQty - item.UseBomQty,
|
||||
inputQty: String(item.BomQty - item.UseBomQty),
|
||||
}))
|
||||
}
|
||||
|
||||
function onScanPallet() {
|
||||
palletCode.value = 'PLT' + Date.now().toString().slice(-6)
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
if (!bomList.value.length) {
|
||||
showToast({ message: t('productGroup.noData'), type: 'fail' })
|
||||
return
|
||||
}
|
||||
const bomDataList = bomList.value.map((item) => ({
|
||||
id: item.Id,
|
||||
materialCode: item.MaterialCode,
|
||||
unit: item.Unit,
|
||||
useBomQty: Number(item.inputQty) || 0,
|
||||
}))
|
||||
submitting.value = true
|
||||
try {
|
||||
const res = await request.post('/api/productGroup', {
|
||||
workSect: workSect.value,
|
||||
workOrder: selectedOrderCode.value,
|
||||
palletCode: palletCode.value,
|
||||
loadPort: selectedLoadPort.value,
|
||||
bomDataList,
|
||||
})
|
||||
showToast({
|
||||
message: t('productGroup.submitSuccess'),
|
||||
type: 'success',
|
||||
})
|
||||
workSect.value = ''
|
||||
workSectText.value = ''
|
||||
palletCode.value = ''
|
||||
selectedOrderCode.value = ''
|
||||
workOrderText.value = ''
|
||||
selectedLoadPort.value = ''
|
||||
loadPortText.value = ''
|
||||
currentOrder.value = null
|
||||
bomList.value = []
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchWorkOrders()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.detail-header-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-empty {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--primary-gradient);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-index {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 4px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.card-row--input {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
flex-shrink: 0;
|
||||
width: 80px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-value--num {
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-input {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.card-input :deep(.van-field__control) {
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-input :deep(.van-field__body) {
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -34,3 +34,5 @@ CREATE TABLE IF NOT EXISTS `st_ivt_purchasedtl` (
|
||||
`instock_qty` DECIMAL(18,6) DEFAULT 0 COMMENT '已入库数量'
|
||||
)COMMENT='采购入库单分录信息表';
|
||||
|
||||
ALTER TABLE `wms_nlwq`.`st_ivt_purchasemst`
|
||||
ADD COLUMN `forwardZD` tinyint(1) NULL DEFAULT 0 COMMENT '0否 1是' AFTER `audit_msg`;
|
||||
Reference in New Issue
Block a user