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.

469 lines
17 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="'新增备件盘点单'" />
<!-- 盘点时间 -->
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title"><text class="required">*</text>盘点时间</text>
</view>
<view class="form-row-card datetime-picker-card">
<uni-datetime-picker
v-model="checkTime"
type="datetime"
placeholder="请选择盘点时间"
:clear-icon="false"
return-type="string"
/>
</view>
<!-- 生成来源 -->
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title"><text class="required">*</text>生成来源</text>
</view>
<view class="radio-group">
<view :class="['radio-item', sourceType === 1 ? 'radio-active' : '']" @click="switchSourceType(1)">
<text class="radio-text">按库存</text>
</view>
<view :class="['radio-item', sourceType === 2 ? 'radio-active' : '']" @click="switchSourceType(2)">
<text class="radio-text">按产品</text>
</view>
</view>
<!-- 按库存:仓库/库区/盘点项 -->
<template v-if="sourceType === 1">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">仓库/库区/盘点项</text>
</view>
<view class="select-row-card">
<view class="warehouse-area-rows">
<!-- 仓库(固定为备件仓,不可选) -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">仓库</text>
<view class="warehouse-area-dropdown">
<view class="dropdown-input">
<text class="dropdown-value">备件仓</text>
</view>
</view>
</view>
<!-- 库区 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown">
<view class="dropdown-input" @click="goSelectArea">
<text :class="['dropdown-value', { placeholder: !selectedAreas.length }]">
{{ selectedAreas.length ? selectedAreas.map(a => a.label).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow">▶</text>
</view>
</view>
</view>
<!-- 盘点项 -->
<view class="warehouse-area-row" v-if="selectedAreas.length">
<text class="warehouse-area-label">盘点项</text>
<view class="item-select-btn" @click="goSelectItems">
<text class="item-select-text">{{ items.length ? `已选${items.length}项` : '点击选择' }}</text>
<text class="dropdown-arrow">▼</text>
</view>
</view>
</view>
</view>
</template>
<!-- 按产品:产品/盘点项 -->
<template v-if="sourceType === 2">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title"><text class="required">*</text>产品/盘点项</text>
</view>
<view class="select-row-card">
<view class="warehouse-area-rows">
<!-- 产品 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">产品</text>
<view class="warehouse-area-dropdown" @click="goSelectProduct">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedProducts.length }]">
{{ selectedProducts.length ? selectedProducts.map(p => p.name).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow">▼</text>
</view>
</view>
</view>
<!-- 盘点项 -->
<view class="warehouse-area-row" v-if="selectedProducts.length">
<text class="warehouse-area-label">盘点项</text>
<view class="warehouse-area-dropdown" @click="goSelectItemsByProduct">
<view class="dropdown-input">
<text class="dropdown-value">{{ items.length ? `已选择 ${items.length} 项` : '请选择' }}</text>
<text class="dropdown-arrow">▼</text>
</view>
</view>
</view>
</view>
</view>
</template>
<!-- 已选盘点项列表 -->
<view class="section-title-bar" v-if="items.length">
<view class="section-bar-line"></view>
<text class="section-title">已选盘点项({{ items.length }}</text>
</view>
<view v-if="items.length" class="item-list">
<view v-for="(item, idx) in items" :key="idx" class="item-card">
<view class="item-header">
<text class="item-name">{{ textValue(item.productName) }}</text>
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
</view>
<view class="item-body">
<view class="item-row">
<text class="item-lbl">仓库</text>
<text class="item-val">{{ textValue(item.warehouseName) }}</text>
</view>
<view class="item-row">
<text class="item-lbl">库区</text>
<text class="item-val">{{ textValue(item.areaName) }}</text>
</view>
<view class="item-row">
<text class="item-lbl">账面数量</text>
<text class="item-val highlight">{{ textValue(item.stockCount) }}</text>
</view>
</view>
<view class="item-delete" @click="items.splice(idx, 1)">
<text class="item-delete-text">删除</text>
</view>
</view>
</view>
<view v-if="loadingProducts" class="hint">加载中...</view>
<!-- 备注 -->
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">备注</text>
</view>
<view class="form-row-card remark-card">
<textarea v-model="remark" class="remark-textarea" placeholder="请输入备注" :maxlength="200" auto-height />
</view>
<!-- 底部提交 -->
<view class="bottom-actions" v-if="items.length">
<view class="bottom-btn cancel-btn" @click="handleCancel">取消</view>
<view class="bottom-btn confirm-btn" @click="handleSubmit"></view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import NavBar from '@/components/common/NavBar.vue'
import UniDatetimePicker from '@dcloudio/uni-ui/lib/uni-datetime-picker/uni-datetime-picker.vue'
import { getWarehousePage } from '@/api/mes/moldget'
import { createCheck } from '@/api/mes/sparepartCheck'
// 盘点时间
const now = new Date()
const pad = (n) => String(n).padStart(2, '0')
const checkTime = ref(`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`)
// 生成来源 & 分类
const sourceType = ref(1)
const categoryType = ref(3)
function switchSourceType(type) {
sourceType.value = type
items.value = []
selectedProducts.value = []
selectedAreas.value = []
}
// 仓库
const warehouseOptions = ref([])
const selectedWarehouse = ref(null)
// 库区(多选)
const selectedAreas = ref([])
// 产品(按产品模式,多选)
const selectedProducts = ref([])
// 已选盘点项
const items = ref([])
const remark = ref('')
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
// 加载仓库(自动选定备件仓)
async function loadWarehouses() {
try {
const res = await getWarehousePage({ pageNo: 1, pageSize: 100, categoryType: 3 })
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : (Array.isArray(res?.data?.list) ? res.data.list : []))
warehouseOptions.value = data.map(w => ({ value: w.id, label: w.name || String(w.id) }))
if (warehouseOptions.value.length && !selectedWarehouse.value) {
selectedWarehouse.value = warehouseOptions.value[0]
}
} catch (e) {
console.error('loadWarehouses error', e)
}
}
// 跳转库区选择页
function goSelectArea() {
getApp().globalData._checkAreaSelectResult = null
getApp().globalData._checkAreaWarehouseId = selectedWarehouse.value?.value || ''
getApp().globalData._checkAreaSelected = selectedAreas.value.length
? JSON.parse(JSON.stringify(selectedAreas.value))
: null
uni.navigateTo({
url: `/pages_function/pages/sparepartCheck/areaSelect`
})
}
// 跳转盘点项选择页
function goSelectItems() {
if (!selectedAreas.value.length) return
getApp().globalData._checkItemSelectResult = null
getApp().globalData._checkItemSelected = items.value.length
? JSON.parse(JSON.stringify(items.value))
: null
const areaIds = selectedAreas.value.map(a => a.value).join(',')
uni.navigateTo({
url: `/pages_function/pages/sparepartCheck/itemSelect?warehouseId=${selectedWarehouse.value.value}&areaIds=${areaIds}`
})
}
// 选择产品(跳转产品多选页)
function goSelectProduct() {
getApp().globalData._checkProductSelectResult = null
// 传入当前已选产品,让选择页显示勾选状态
getApp().globalData._checkProductSelected = selectedProducts.value.length
? JSON.parse(JSON.stringify(selectedProducts.value))
: null
uni.navigateTo({
url: `/pages_function/pages/sparepartCheck/productSelect`
})
}
// 按产品选择盘点项跳转页面传多个productId
function goSelectItemsByProduct() {
if (!selectedProducts.value.length) return
getApp().globalData._checkItemSelectResult = null
getApp().globalData._checkItemSelected = items.value.length
? JSON.parse(JSON.stringify(items.value))
: null
const ids = selectedProducts.value.map(p => p.id).join(',')
uni.navigateTo({
url: `/pages_function/pages/sparepartCheck/itemSelectByProduct?productIds=${ids}`
})
}
// 提交
async function handleSubmit() {
if (!items.value.length) {
uni.showToast({ title: '请先选择盘点项', icon: 'none' })
return
}
const submitItems = items.value.map(item => ({
warehouseId: Number(item.warehouseId),
areaId: item.areaId ? Number(item.areaId) : undefined,
productId: Number(item.productId),
productBarCode: item.productBarCode || '',
productName: item.productName || '',
stockCount: item.stockCount != null ? Number(item.stockCount) : 0,
actualCount: null,
productPrice: item.productPrice != null ? Number(item.productPrice) : 0,
count: 0,
remark: ''
}))
try {
uni.showLoading({ title: '保存中...', mask: true })
await createCheck({
checkTime: checkTime.value,
sourceType: sourceType.value,
categoryType: categoryType.value,
checkStatus: 0,
remark: remark.value || '',
items: submitItems
})
uni.hideLoading()
uni.showToast({ title: '创建成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 1500)
} catch (e) {
uni.hideLoading()
const msg = e?.message || e?.data?.msg || '创建失败'
uni.showToast({ title: String(msg).substring(0, 50), icon: 'none' })
}
}
function handleCancel() { uni.navigateBack() }
onShow(() => {
loadWarehouses()
// 接收产品选择返回
const productResult = getApp().globalData?._checkProductSelectResult
if (productResult && Array.isArray(productResult)) {
selectedProducts.value = productResult.map(p => ({
id: p.id,
name: p.name || '备件'
}))
items.value = []
getApp().globalData._checkProductSelectResult = null
}
// 接收库区选择返回
const areaResult = getApp().globalData?._checkAreaSelectResult
if (areaResult && Array.isArray(areaResult)) {
selectedAreas.value = areaResult.map(a => ({
value: a.id,
label: a.name || '库区'
}))
items.value = []
getApp().globalData._checkAreaSelectResult = null
}
// 接收盘点项选择返回
const itemResult = getApp().globalData?._checkItemSelectResult
if (itemResult && Array.isArray(itemResult)) {
const existingKeys = new Set(items.value.map(i => `${i.productId || i.id}_${i.warehouseId || ''}_${i.areaId || ''}`))
const newItems = itemResult.filter(i => !existingKeys.has(`${i.productId || i.id}_${i.warehouseId || ''}_${i.areaId || ''}`))
if (newItems.length) {
items.value = [...items.value, ...newItems]
}
getApp().globalData._checkItemSelectResult = null
}
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f5f6f8;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
.section-title-bar {
display: flex; align-items: center; gap: 10rpx;
padding: 24rpx 24rpx 18rpx;
}
.section-bar-line { width: 6rpx; height: 32rpx; border-radius: 3rpx; background: #2563eb; flex-shrink: 0; }
.section-title { font-size: 30rpx; font-weight: 700; color: #1a1a1a; }
.required { color: #ef4444; }
.form-row-card {
display: flex; align-items: center; justify-content: space-between;
background: #fff; border-radius: 16rpx; padding: 26rpx 24rpx; margin: 0 24rpx 16rpx;
box-shadow: 0 4rpx 16rpx rgba(15,23,42,0.04);
font-size: 28rpx; color: #374151;
.placeholder { color: #9ca3af; }
}
.form-row-arrow { font-size: 20rpx; color: #999; }
.datetime-picker-card {
flex-direction: column;
align-items: stretch;
padding: 16rpx 24rpx;
:deep(.uni-date) {
width: 100%;
}
}
/* 单选组 */
.radio-group { display: flex; gap: 12rpx; padding: 0 24rpx 16rpx; }
.radio-item {
padding: 16rpx 36rpx;
border-radius: 12rpx;
background: #fff;
border: 2rpx solid #e5e7eb;
font-size: 26rpx;
color: #6b7280;
text-align: center;
&.radio-active {
background: #1f4b79;
border-color: #1f4b79;
.radio-text { color: #fff; }
}
}
.radio-text { font-size: 26rpx; color: #6b7280; }
/* 盘点项列表 */
.item-list { padding: 0 24rpx; }
.item-card {
background: #fff; border-radius: 14rpx; padding: 20rpx; margin-bottom: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(15,23,42,0.04);
position: relative;
}
.item-header { margin-bottom: 14rpx; padding-bottom: 14rpx; border-bottom: 1rpx solid #f0f0f0; }
.item-name { font-size: 28rpx; font-weight: 600; color: #1a1a1a; display: block; }
.item-code { font-size: 24rpx; color: #9ca3af; margin-top: 4rpx; display: block; }
.item-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx;
&:last-child { margin-bottom: 0; }
}
.item-lbl { font-size: 26rpx; color: #6b7280; }
.item-val { font-size: 26rpx; color: #374151; &.highlight { font-weight: 700; color: #1f4b79; } }
.item-delete { position: absolute; top: 20rpx; right: 20rpx; }
.item-delete-text { font-size: 24rpx; color: #dc2626; }
.remark-card { padding: 20rpx 24rpx; }
.remark-textarea { width: 100%; min-height: 120rpx; font-size: 27rpx; color: #374151; line-height: 1.6; box-sizing: border-box; }
.hint { padding: 120rpx 0; text-align: center; color: #94a3b8; font-size: 28rpx; }
.bottom-actions {
position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx;
padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom));
background: #fff; box-shadow: 0 -8rpx 24rpx rgba(15,23,42,0.06); z-index: 99;
}
.bottom-btn { flex: 1; height: 84rpx; line-height: 84rpx; text-align: center; border-radius: 16rpx; font-size: 30rpx; font-weight: 600;
&:active { opacity: 0.85; }
}
.cancel-btn { background: #eef2f7; color: #475569; }
.confirm-btn { background: #1f4b79; color: #fff; }
/* 下拉 */
.select-row-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin: 0 24rpx 16rpx; box-shadow: 0 4rpx 16rpx rgba(15,23,42,0.04); min-height: 0 !important; height: auto !important; }
.warehouse-area-rows { display: flex; flex-direction: column; gap: 16rpx; }
.warehouse-area-row { display: flex; align-items: center; }
.warehouse-area-label { width: 100rpx; font-size: 26rpx; color: #6b7280; flex-shrink: 0; }
.warehouse-area-dropdown { flex: 1; position: relative; }
.dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; padding: 0 20rpx; border: 1rpx solid #e0e0e0; border-radius: 10rpx; background: #f9fafb; }
.dropdown-value { flex: 1; font-size: 27rpx; color: #333; &.placeholder { color: #bbb; } }
.dropdown-arrow { font-size: 20rpx; color: #999; }
.dropdown-panel { position: absolute; top: 68rpx; left: 0; right: 0; z-index: 100; background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx; box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.1); overflow: hidden; }
.dropdown-scroll { max-height: 360rpx; overflow-y: auto; }
.dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0;
&:last-child { border-bottom: 0; }
&.active { background: #f0f5ff; }
}
.dropdown-item-text { font-size: 27rpx; color: #333; }
.dropdown-check { font-size: 28rpx; color: #2563eb; font-weight: 700; }
.dropdown-empty { padding: 32rpx; text-align: center; color: #999; font-size: 26rpx; }
/* 盘点项选择按钮 */
.item-select-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 64rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
background: #f9fafb;
}
.item-select-text {
font-size: 27rpx;
color: #333;
}
.item-select-btn .dropdown-arrow {
font-size: 20rpx;
color: #999;
}
</style>