You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
14 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>