|
|
<template>
|
|
|
<view class="page-container">
|
|
|
<NavBar :title="pageTitle" />
|
|
|
|
|
|
<scroll-view scroll-y class="detail-scroll">
|
|
|
<view class="content-section">
|
|
|
<view class="section-card">
|
|
|
<view class="section-header">
|
|
|
<view class="section-icon">
|
|
|
<uni-icons type="compose" size="24" color="#1f7cff"></uni-icons>
|
|
|
</view>
|
|
|
<text class="section-title">调拨信息</text>
|
|
|
</view>
|
|
|
|
|
|
<view class="form-field">
|
|
|
<text class="form-label">调拨时间<text class="required-star">*</text></text>
|
|
|
<picker mode="date" :value="moveDate" @change="handleDateChange">
|
|
|
<view class="select-field">
|
|
|
<text :class="['select-text', moveDate ? '' : 'placeholder']">{{ moveDate || '请选择调拨时间' }}</text>
|
|
|
<uni-icons type="calendar" size="18" color="#9ca3af"></uni-icons>
|
|
|
</view>
|
|
|
</picker>
|
|
|
</view>
|
|
|
|
|
|
|
|
|
<view class="form-field">
|
|
|
<text class="form-label">备注</text>
|
|
|
<textarea v-model="remark" class="form-textarea" placeholder="请输入备注信息" placeholder-class="placeholder-text" maxlength="500" />
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="section-card">
|
|
|
<view class="section-header list-header">
|
|
|
<view class="section-title-wrap">
|
|
|
<view class="section-icon">
|
|
|
<uni-icons type="list" size="24" color="#1f7cff"></uni-icons>
|
|
|
</view>
|
|
|
<text class="section-title">调拨清单({{ itemList.length }})</text>
|
|
|
</view>
|
|
|
<view class="add-product-btn" @click="handleAddItem">
|
|
|
<uni-icons type="plusempty" size="16" color="#1f7cff"></uni-icons>
|
|
|
<text>添加{{ itemLabel }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-if="itemList.length" class="summary-strip">
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-value">{{ itemList.length }}</text>
|
|
|
<text class="summary-label">{{ itemLabel }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-value">{{ totalCount }}</text>
|
|
|
<text class="summary-label">总数量</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-if="itemList.length" class="item-list">
|
|
|
<view v-for="(item, index) in itemList" :key="item._key || index" class="item-card" @click="editItem(index)">
|
|
|
<view class="item-info">
|
|
|
<view class="item-header">
|
|
|
<view class="item-title-wrap">
|
|
|
<text class="item-name">{{ textValue(item.productName) }}</text>
|
|
|
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
|
|
|
</view>
|
|
|
<view class="delete-btn" @click.stop="removeItem(index)">
|
|
|
<uni-icons type="trash" size="18" color="#ef4444"></uni-icons>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="info-grid">
|
|
|
<view class="info-cell info-cell-wide">
|
|
|
<text class="info-label">调出</text>
|
|
|
<text class="info-value">{{ textValue(item.fromWarehouseName) }} / {{ textValue(item.fromAreaName) }}</text>
|
|
|
</view>
|
|
|
<view class="info-cell info-cell-wide">
|
|
|
<text class="info-label">调入</text>
|
|
|
<text class="info-value">{{ textValue(item.toWarehouseName) }} / {{ textValue(item.toAreaName) }}</text>
|
|
|
</view>
|
|
|
<view class="info-cell">
|
|
|
<text class="info-label">数量</text>
|
|
|
<text class="info-value highlight">{{ textValue(item.count) }}{{ textUnit(item.productUnitName) }}</text>
|
|
|
</view>
|
|
|
<view class="info-cell">
|
|
|
<text class="info-label">库存</text>
|
|
|
<text class="info-value">{{ textValue(item.stockCount) }}{{ textUnit(item.productUnitName) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-else class="empty-card" @click="handleAddItem">
|
|
|
<uni-icons type="plusempty" size="30" color="#94a3b8"></uni-icons>
|
|
|
<text>请添加调拨{{ itemLabel }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
|
|
|
<view class="action-bar">
|
|
|
<view class="action-btn back-btn" @click="handleCancel">取消</view>
|
|
|
<view class="action-btn submit-btn" @click="handleSubmit">确认调拨</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { computed, ref } from 'vue'
|
|
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
import { createMaterialMove } from '@/api/mes/materialMove'
|
|
|
|
|
|
const itemList = ref([])
|
|
|
const moveDate = ref(formatDate(new Date()))
|
|
|
const categoryType = ref(2)
|
|
|
const remark = ref('')
|
|
|
|
|
|
const categoryNameMap = {
|
|
|
1: '产品',
|
|
|
2: '物料',
|
|
|
3: '备件'
|
|
|
}
|
|
|
const itemLabel = computed(() => categoryNameMap[Number(categoryType.value)] || '物料')
|
|
|
const pageTitle = computed(() => '新增' + itemLabel.value + '库存调拨')
|
|
|
const totalCount = computed(() => itemList.value.reduce((sum, item) => sum + (Number(item.count) || 0), 0))
|
|
|
|
|
|
function formatDate(date) {
|
|
|
const y = date.getFullYear()
|
|
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
const d = String(date.getDate()).padStart(2, '0')
|
|
|
return `${y}-${m}-${d}`
|
|
|
}
|
|
|
function textValue(value) {
|
|
|
if (value === 0) return '0'
|
|
|
if (value === null || value === undefined) return '-'
|
|
|
const text = String(value).trim()
|
|
|
return text || '-'
|
|
|
}
|
|
|
function textUnit(value) {
|
|
|
if (value === null || value === undefined) return ''
|
|
|
return String(value).trim()
|
|
|
}
|
|
|
function handleDateChange(event) {
|
|
|
moveDate.value = event.detail.value
|
|
|
}
|
|
|
function handleAddItem() {
|
|
|
const gd = getApp().globalData
|
|
|
gd._materialMoveItems = [...itemList.value]
|
|
|
gd._materialMoveEditIndex = null
|
|
|
gd._materialMoveEditItem = null
|
|
|
gd._materialMoveCategoryType = categoryType.value
|
|
|
uni.navigateTo({ url: `/pages_function/pages/materialMove/productConfirm?categoryType=${categoryType.value}` })
|
|
|
}
|
|
|
function editItem(index) {
|
|
|
const item = itemList.value[index]
|
|
|
if (!item) return
|
|
|
const gd = getApp().globalData
|
|
|
gd._materialMoveItems = [...itemList.value]
|
|
|
gd._materialMoveEditIndex = index
|
|
|
gd._materialMoveEditItem = JSON.parse(JSON.stringify(item))
|
|
|
gd._materialMoveCategoryType = categoryType.value
|
|
|
uni.navigateTo({ url: `/pages_function/pages/materialMove/productConfirm?mode=edit&categoryType=${categoryType.value}` })
|
|
|
}
|
|
|
function removeItem(index) {
|
|
|
itemList.value.splice(index, 1)
|
|
|
getApp().globalData._materialMoveItems = [...itemList.value]
|
|
|
}
|
|
|
function handleCancel() {
|
|
|
const gd = getApp().globalData
|
|
|
gd._materialMoveItems = []
|
|
|
gd._materialMoveEditIndex = null
|
|
|
gd._materialMoveEditItem = null
|
|
|
uni.navigateBack()
|
|
|
}
|
|
|
function validateForm() {
|
|
|
if (!moveDate.value) return '请选择调拨时间'
|
|
|
if (!itemList.value.length) return '请先添加' + itemLabel.value
|
|
|
const invalid = itemList.value.find((item) => !item.productId || !item.fromWarehouseId || !item.fromAreaId || !item.toWarehouseId || !item.toAreaId || !Number(item.count))
|
|
|
if (invalid) return '请完善调拨明细'
|
|
|
const sameArea = itemList.value.find((item) => String(item.fromAreaId) === String(item.toAreaId))
|
|
|
if (sameArea) return '调出库区和调入库区不能相同'
|
|
|
const overStock = itemList.value.find((item) => Number(item.count) > Number(item.stockCount))
|
|
|
if (overStock) return '调拨数量不能大于库存'
|
|
|
return ''
|
|
|
}
|
|
|
function buildMoveTime() {
|
|
|
const now = new Date()
|
|
|
const [y, m, d] = moveDate.value.split('-').map(Number)
|
|
|
return new Date(y, m - 1, d, now.getHours(), now.getMinutes(), now.getSeconds()).getTime()
|
|
|
}
|
|
|
async function handleSubmit() {
|
|
|
const error = validateForm()
|
|
|
if (error) {
|
|
|
uni.showToast({ title: error, icon: 'none' })
|
|
|
return
|
|
|
}
|
|
|
const data = {
|
|
|
moveTime: buildMoveTime(),
|
|
|
categoryType: categoryType.value,
|
|
|
status: 0,
|
|
|
totalCount: totalCount.value,
|
|
|
totalPrice: 0,
|
|
|
remark: remark.value || '',
|
|
|
items: itemList.value.map((item) => ({
|
|
|
productId: item.productId,
|
|
|
productName: item.productName,
|
|
|
productBarCode: item.productBarCode,
|
|
|
productUnitName: item.productUnitName,
|
|
|
fromWarehouseId: item.fromWarehouseId,
|
|
|
fromAreaId: item.fromAreaId,
|
|
|
toWarehouseId: item.toWarehouseId,
|
|
|
toAreaId: item.toAreaId,
|
|
|
stockCount: item.stockCount,
|
|
|
count: Number(item.count),
|
|
|
remark: item.remark || ''
|
|
|
}))
|
|
|
}
|
|
|
uni.showLoading({ title: '提交中...', mask: true })
|
|
|
try {
|
|
|
await createMaterialMove(data)
|
|
|
uni.hideLoading()
|
|
|
getApp().globalData._materialMoveItems = []
|
|
|
uni.showToast({ title: '调拨单已创建', icon: 'success' })
|
|
|
setTimeout(() => uni.navigateBack(), 1000)
|
|
|
} catch (e) {
|
|
|
uni.hideLoading()
|
|
|
const msg = e?.message || e?.data?.msg || '保存失败'
|
|
|
uni.showToast({ title: String(msg).slice(0, 50), icon: 'none' })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
onLoad((options) => {
|
|
|
const gd = getApp().globalData || {}
|
|
|
categoryType.value = Number(options?.categoryType || gd._materialMoveCategoryType || 2)
|
|
|
gd._materialMoveCategoryType = categoryType.value
|
|
|
})
|
|
|
onShow(() => {
|
|
|
const gd = getApp().globalData || {}
|
|
|
const items = gd._materialMoveItems
|
|
|
if (Array.isArray(items)) itemList.value = [...items]
|
|
|
if (gd._materialMoveCategoryType) categoryType.value = Number(gd._materialMoveCategoryType)
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.page-container { min-height: 100vh; background: #f5f7fb; }
|
|
|
.detail-scroll { height: calc(100vh - 116rpx); }
|
|
|
.content-section { padding: 20rpx 24rpx 28rpx; }
|
|
|
.section-card { background: #ffffff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1rpx solid #eef2f7; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
|
|
|
.section-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 22rpx; padding-bottom: 18rpx; border-bottom: 1rpx solid #f1f5f9; }
|
|
|
.section-icon { width: 40rpx; height: 40rpx; border-radius: 10rpx; background: #eff6ff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
|
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
|
|
|
.section-title-wrap { display: flex; align-items: center; gap: 12rpx; min-width: 0; }
|
|
|
.list-header { justify-content: space-between; gap: 16rpx; }
|
|
|
.form-field { display: flex; flex-direction: column; gap: 12rpx; }
|
|
|
.form-field + .form-field { margin-top: 24rpx; }
|
|
|
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
|
|
|
.required-star { color: #ef4444; font-size: 28rpx; margin-left: 4rpx; }
|
|
|
.select-field { display: flex; align-items: center; justify-content: space-between; padding: 0 24rpx; height: 76rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; }
|
|
|
.select-text { flex: 1; min-width: 0; font-size: 28rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.placeholder, .placeholder-text { color: #9ca3af; }
|
|
|
.form-textarea { width: 100%; min-height: 120rpx; background: #f8fafc; border-radius: 14rpx; padding: 18rpx 24rpx; font-size: 28rpx; color: #374151; box-sizing: border-box; }
|
|
|
.add-product-btn { height: 60rpx; padding: 0 18rpx; border-radius: 999rpx; border: 1rpx solid #bfdbfe; background: #eff6ff; color: #1f7cff; font-size: 24rpx; font-weight: 600; display: flex; align-items: center; gap: 8rpx; flex-shrink: 0; }
|
|
|
.summary-strip { display: grid; grid-template-columns: 1fr 1fr; background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
|
|
|
.summary-item { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6rpx; padding: 16rpx 8rpx; border-right: 1rpx solid #eef2f7; }
|
|
|
.summary-item:last-child { border-right: 0; }
|
|
|
.summary-value { max-width: 100%; font-size: 30rpx; font-weight: 700; color: #1f4b79; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.summary-label { font-size: 22rpx; color: #8a94a6; }
|
|
|
.item-list { display: flex; flex-direction: column; gap: 18rpx; }
|
|
|
.item-card { padding: 20rpx; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 18rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
|
|
|
.item-info { min-width: 0; }
|
|
|
.item-header { display: flex; align-items: flex-start; gap: 12rpx; margin-bottom: 14rpx; }
|
|
|
.item-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
|
|
|
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.item-code { font-size: 24rpx; color: #94a3b8; }
|
|
|
.delete-btn { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #fef2f2; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
|
|
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
|
|
.info-cell-wide { grid-column: 1 / -1; }
|
|
|
.info-label { font-size: 22rpx; color: #9ca3af; }
|
|
|
.info-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.info-value.highlight { color: #1f4b79; font-weight: 700; }
|
|
|
.empty-card { min-height: 220rpx; border: 2rpx dashed #d7dde8; border-radius: 18rpx; background: #f8fafc; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14rpx; color: #94a3b8; font-size: 27rpx; }
|
|
|
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06); z-index: 99; }
|
|
|
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
|
|
|
.back-btn { background: #eef2f7; color: #475569; }
|
|
|
.submit-btn { background: #1f4b79; color: #ffffff; }
|
|
|
</style>
|