黄伟杰 3 days ago
commit ba1bdb3164

@ -717,6 +717,13 @@
"navigationStyle": "custom"
}
},
{
"path": "sparepartCheck/detail",
"style": {
"navigationBarTitleText": "备件盘点详情",
"navigationStyle": "custom"
}
},
{
"path": "sparepartCheck/itemSelect",
"style": {
@ -768,6 +775,13 @@
"navigationStyle": "custom"
}
},
{
"path": "materialCheck/detail",
"style": {
"navigationBarTitleText": "物料盘点详情",
"navigationStyle": "custom"
}
},
{
"path": "materialCheck/itemSelect",
"style": {

@ -2,151 +2,153 @@
<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>
<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="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>
<view class="form-field">
<text class="form-label">盘点时间<text class="required-star">*</text></text>
<uni-datetime-picker
v-model="checkTime"
type="datetime"
placeholder="请选择盘点时间"
:clear-icon="false"
return-type="string"
/>
</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 class="form-field">
<text class="form-label">生成来源<text class="required-star">*</text></text>
<view class="segment-control">
<view :class="['segment-item', sourceType === 1 ? 'active' : '']" @click="switchSourceType(1)"></view>
<view :class="['segment-item', sourceType === 2 ? 'active' : '']" @click="switchSourceType(2)"></view>
</view>
</view>
<view class="form-field">
<text class="form-label">备注</text>
<textarea v-model="remark" class="form-textarea" placeholder="请输入备注" placeholder-class="placeholder-text" maxlength="200" />
</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">产品与盘点项</text>
</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 }]">
<template v-if="sourceType === 1">
<view class="form-field">
<text class="form-label">仓库</text>
<view class="select-field">
<text class="select-text">原料仓</text>
</view>
</view>
<view class="form-field">
<text class="form-label">库区</text>
<view class="select-field" @click="goSelectArea">
<text :class="['select-text', selectedAreas.length ? '' : 'placeholder']">
{{ selectedAreas.length ? selectedAreas.map(a => a.label).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow"></text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</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 v-if="selectedAreas.length" class="select-items-card" @click="goSelectItems">
<view class="select-items-main">
<text class="select-items-title">盘点项</text>
<text class="select-items-desc">{{ items.length ? '已选' + items.length + '项' : '点击选择' }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
</view>
</view>
</template>
</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 }]">
<template v-else>
<view class="form-field">
<text class="form-label">产品</text>
<view class="select-field" @click="goSelectProduct">
<text :class="['select-text', selectedProducts.length ? '' : 'placeholder']">
{{ selectedProducts.length ? selectedProducts.map(p => p.name).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow"></text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</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 v-if="selectedProducts.length" class="select-items-card" @click="goSelectItemsByProduct">
<view class="select-items-main">
<text class="select-items-title">盘点项</text>
<text class="select-items-desc">{{ items.length ? '已选' + items.length + '项' : '点击选择' }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
</view>
</view>
</template>
</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 v-if="items.length" class="summary-strip">
<view class="summary-item" v-if="sourceType === 2">
<text class="summary-value">{{ selectedProducts.length }}</text>
<text class="summary-label">产品</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ items.length }}</text>
<text class="summary-label">盘点项</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ totalStockCount }}</text>
<text class="summary-label">账面数量</text>
</view>
</view>
<view class="item-row">
<text class="item-lbl">账面数量</text>
<text class="item-val highlight">{{ textValue(item.stockCount) }}</text>
<view v-if="items.length" class="item-list">
<view v-for="(item, idx) in items" :key="idx" class="item-card">
<view class="item-info">
<view class="item-header">
<text class="item-name">{{ textValue(item.productName) }}</text>
<view class="delete-btn" @click="items.splice(idx, 1)">
<uni-icons type="trash" size="18" color="#ef4444"></uni-icons>
</view>
</view>
<view class="info-grid">
<view class="info-cell">
<text class="info-label">编码</text>
<text class="info-value">{{ textValue(item.productBarCode) }}</text>
</view>
<view class="info-cell">
<text class="info-label">仓库</text>
<text class="info-value">{{ textValue(item.warehouseName) }}</text>
</view>
<view class="info-cell">
<text class="info-label">库区</text>
<text class="info-value">{{ textValue(item.areaName) }}</text>
</view>
<view class="info-cell">
<text class="info-label">账面数量</text>
<text class="info-value highlight">{{ textValue(item.stockCount) }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="item-delete" @click="items.splice(idx, 1)">
<text class="item-delete-text">删除</text>
</view>
</view>
</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>
</scroll-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 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 { ref } from 'vue'
import { ref, computed } 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'
@ -169,13 +171,13 @@ function switchSourceType(type) {
const warehouseOptions = ref([])
const selectedWarehouse = ref(null)
const selectedAreas = ref([])
const selectedProducts = ref([])
const items = ref([])
const remark = ref('')
const totalStockCount = computed(() => items.value.reduce((sum, item) => sum + (Number(item.stockCount) || 0), 0))
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
@ -202,9 +204,7 @@ function goSelectArea() {
getApp().globalData._materialCheckAreaSelected = selectedAreas.value.length
? JSON.parse(JSON.stringify(selectedAreas.value))
: null
uni.navigateTo({
url: `/pages_function/pages/materialCheck/areaSelect`
})
uni.navigateTo({ url: `/pages_function/pages/materialCheck/areaSelect` })
}
function goSelectItems() {
@ -214,9 +214,7 @@ function goSelectItems() {
? JSON.parse(JSON.stringify(items.value))
: null
const areaIds = selectedAreas.value.map(a => a.value).join(',')
uni.navigateTo({
url: `/pages_function/pages/materialCheck/itemSelect?warehouseId=${selectedWarehouse.value.value}&areaIds=${areaIds}`
})
uni.navigateTo({ url: `/pages_function/pages/materialCheck/itemSelect?warehouseId=${selectedWarehouse.value.value}&areaIds=${areaIds}` })
}
function goSelectProduct() {
@ -224,9 +222,7 @@ function goSelectProduct() {
getApp().globalData._materialCheckProductSelected = selectedProducts.value.length
? JSON.parse(JSON.stringify(selectedProducts.value))
: null
uni.navigateTo({
url: `/pages_function/pages/materialCheck/productSelect`
})
uni.navigateTo({ url: `/pages_function/pages/materialCheck/productSelect` })
}
function goSelectItemsByProduct() {
@ -236,9 +232,7 @@ function goSelectItemsByProduct() {
? JSON.parse(JSON.stringify(items.value))
: null
const ids = selectedProducts.value.map(p => p.id).join(',')
uni.navigateTo({
url: `/pages_function/pages/materialCheck/itemSelectByProduct?productIds=${ids}`
})
uni.navigateTo({ url: `/pages_function/pages/materialCheck/itemSelectByProduct?productIds=${ids}` })
}
async function handleSubmit() {
@ -246,7 +240,6 @@ async function handleSubmit() {
uni.showToast({ title: '请先选择盘点项', icon: 'none' })
return
}
const submitItems = items.value.map(item => ({
warehouseId: Number(item.warehouseId),
areaId: item.areaId ? Number(item.areaId) : undefined,
@ -259,7 +252,6 @@ async function handleSubmit() {
count: 0,
remark: ''
}))
try {
uni.showLoading({ title: '保存中...', mask: true })
await createMaterialCheck({
@ -286,19 +278,13 @@ onShow(() => {
loadWarehouses()
const productResult = getApp().globalData?._materialCheckProductSelectResult
if (productResult && Array.isArray(productResult)) {
selectedProducts.value = productResult.map(p => ({
id: p.id,
name: p.name || '物料'
}))
selectedProducts.value = productResult.map(p => ({ id: p.id, name: p.name || '物料' }))
items.value = []
getApp().globalData._materialCheckProductSelectResult = null
}
const areaResult = getApp().globalData?._materialCheckAreaSelectResult
if (areaResult && Array.isArray(areaResult)) {
selectedAreas.value = areaResult.map(a => ({
value: a.id,
label: a.name || '库区'
}))
selectedAreas.value = areaResult.map(a => ({ value: a.id, label: a.name || '库区' }))
items.value = []
getApp().globalData._materialCheckAreaSelectResult = null
}
@ -306,102 +292,56 @@ onShow(() => {
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]
}
if (newItems.length) { items.value = [...items.value, ...newItems] }
getApp().globalData._materialCheckItemSelectResult = 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; }
}
.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; }
.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; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0;
&:last-child { border-bottom: 0; }
&.active { background: #f0f5ff; }
}
.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; }
.page-container { min-height: 100vh; background: #f5f7fb; }
.detail-scroll { height: calc(100vh - 172rpx); }
.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; min-height: 76rpx; padding: 0 24rpx; 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; }
.segment-control { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 6rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 16rpx; }
.segment-item { height: 68rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 27rpx; color: #64748b; }
.segment-item.active { background: #1f4b79; color: #ffffff; font-weight: 600; box-shadow: 0 6rpx 16rpx rgba(31, 75, 121, 0.18); }
.select-items-card { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding: 20rpx 22rpx; margin: 18rpx 0; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 16rpx; }
.select-items-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
.select-items-title { font-size: 28rpx; font-weight: 600; color: #1f2937; }
.select-items-desc { font-size: 24rpx; color: #64748b; }
.summary-strip { display: grid; grid-template-columns: repeat(2, 1fr); background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
.summary-strip .summary-item:first-child:nth-last-child(3), .summary-strip .summary-item:first-child:nth-last-child(3) ~ .summary-item { width: auto; }
.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: center; gap: 12rpx; margin-bottom: 14rpx; }
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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-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: 600; }
.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>

@ -0,0 +1,219 @@
<template>
<view class="page-container">
<NavBar :title="'物料盘点详情'" />
<scroll-view scroll-y class="detail-scroll">
<view class="content-section">
<view v-if="detail" class="section-card">
<view class="section-header">
<view class="section-icon">
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">盘点信息</text>
</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">单据号</text>
<text class="info-value">{{ textValue(detail.no) }}</text>
</view>
<view class="info-row">
<text class="info-label">盘点时间</text>
<text class="info-value">{{ formatDateTime(detail.checkTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">仓库</text>
<text class="info-value">{{ textValue(getWarehouseNames()) }}</text>
</view>
<view class="info-row">
<text class="info-label">来源方式</text>
<text class="info-value">{{ detail.sourceType === 1 ? '按库存' : '按产品' }}</text>
</view>
<view class="info-row">
<text class="info-label">盘点状态</text>
<text :class="['info-value', 'status-tag', checkStatusClass(detail.checkStatus)]">{{ checkStatusText(detail.checkStatus) }}</text>
</view>
<view class="info-row">
<text class="info-label">创建人</text>
<text class="info-value">{{ textValue(detail.creatorName || detail.creator) }}</text>
</view>
<view class="info-row" v-if="detail.remark">
<text class="info-label">备注</text>
<text class="info-value">{{ textValue(detail.remark) }}</text>
</view>
</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">盘点项</text>
</view>
<text v-if="items.length" class="item-count">{{ items.length }} </text>
</view>
<view v-if="loading" class="hint">...</view>
<view v-else-if="items.length" class="item-list">
<view v-for="(item, idx) in items" :key="idx" class="item-card">
<view class="item-header">
<view class="item-index">{{ idx + 1 }}</view>
<view class="item-title-wrap">
<text class="item-name">{{ textValue(item.productName) }}</text>
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
</view>
</view>
<view class="info-grid">
<view class="info-cell">
<text class="cell-label">仓库</text>
<text class="cell-value">{{ textValue(item.warehouseName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">库区</text>
<text class="cell-value">{{ textValue(item.areaName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">账面数量</text>
<text class="cell-value highlight">{{ textValue(item.stockCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">实盘数量</text>
<text class="cell-value highlight">{{ textValue(item.actualCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">差异</text>
<text :class="['cell-value', 'diff-value', diffClass(item)]">{{ getDiffText(item) }}</text>
</view>
</view>
</view>
</view>
<view v-else class="hint">暂无盘点项</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import NavBar from '@/components/common/NavBar.vue'
import { getMaterialCheckDetail } from '@/api/mes/materialCheck'
const detail = ref(null)
const items = ref([])
const loading = ref(false)
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
function formatDateTime(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
const [y, m, d, h = 0, M = 0, s = 0] = value
const p = (n) => String(n).padStart(2, '0')
return `${y}-${p(m)}-${p(d)} ${p(h)}:${p(M)}:${p(s)}`
}
const d = new Date(Number(value))
if (Number.isNaN(d.getTime())) return String(value)
const p = (n) => String(n).padStart(2, '0')
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
}
function getWarehouseNames() {
return [...new Set(items.value.map(i => i.warehouseName).filter(Boolean))].join('、')
}
function getDiff(item) {
const a = item.actualCount != null ? Number(item.actualCount) : null
if (a == null) return null
return a - (Number(item.stockCount) || 0)
}
function getDiffText(item) {
const d = getDiff(item)
if (d == null) return '-'
return `${d > 0 ? '+' : ''}${d}`
}
function diffClass(item) {
const d = getDiff(item)
if (d > 0) return 'positive'
if (d < 0) return 'negative'
return 'zero'
}
function checkStatusText(s) {
const m = { 0: '未盘点', 1: '已盘点', 2: '已提交', 10: '待审核', 20: '已审核' }
return m[s] || '-'
}
function checkStatusClass(s) {
const m = { 0: 'status-pending', 1: 'status-done', 2: 'status-submitted', 10: 'status-auditing', 20: 'status-passed' }
return m[s] || ''
}
async function loadDetail(id) {
loading.value = true
try {
const res = await getMaterialCheckDetail(id)
const data = res?.data || res
detail.value = data
items.value = Array.isArray(data?.items) ? data.items : []
} catch (e) {
uni.showToast({ title: '加载详情失败', icon: 'none' })
} finally {
loading.value = false
}
}
onLoad((options) => {
const id = options?.id
if (!id) { uni.showToast({ title: '缺少单据ID', icon: 'none' }); return }
loadDetail(id)
})
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f7fb; }
.detail-scroll { height: calc(100vh - 120rpx); }
.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; }
.item-count { font-size: 24rpx; color: #1f4b79; font-weight: 600; }
.info-list { display: flex; flex-direction: column; gap: 14rpx; }
.info-row { display: flex; justify-content: space-between; gap: 20rpx; }
.info-label { width: 150rpx; font-size: 25rpx; color: #94a3b8; flex-shrink: 0; }
.info-value { flex: 1; text-align: right; font-size: 27rpx; color: #334155; line-height: 1.5; }
.status-tag { font-weight: 600; }
.status-pending { color: #f59e0b; }
.status-done { color: #3b82f6; }
.status-submitted { color: #8b5cf6; }
.status-auditing { color: #f59e0b; }
.status-passed { color: #16a34a; }
.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-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 18rpx; }
.item-index { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #1f4b79; color: #fff; font-size: 26rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.item-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
.item-name { font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-code { font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 18rpx; background: #f8fafc; border: 1rpx solid #eef2f7; border-radius: 16rpx; }
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
.cell-label { font-size: 22rpx; color: #9ca3af; }
.cell-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-value.highlight { color: #1f4b79; font-weight: 600; }
.diff-value.positive { color: #16a34a; font-weight: 700; }
.diff-value.negative { color: #dc2626; font-weight: 700; }
.diff-value.zero { color: #64748b; font-weight: 700; }
.hint { padding: 60rpx 0; text-align: center; color: #94a3b8; font-size: 26rpx; }
</style>

@ -518,8 +518,8 @@ function clearSearchTimer() {
}
}
onShow(() => {
loadMaterialWarehouse()
onShow(async () => {
await loadMaterialWarehouse()
fetchList(true)
})

@ -3,7 +3,7 @@
<NavBar :title="t('moldPressureNet.moduleName')">
<template #right>
<view class="nav-right-btn" @click="goHistory">
<uni-icons type="calendar" size="22" color="#22486e"></uni-icons>
<uni-icons type="calendar" size="22" color="#ffffff"></uni-icons>
</view>
</template>
</NavBar>
@ -399,7 +399,7 @@ function getCurrentTime() {
<style lang="scss" scoped>
.page-container { min-height: 100vh; background-color: #f5f7fb; }
.nav-right-btn { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; background: #ffffff; border-radius: 50%; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08); }
.nav-right-btn { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; }
.detail-scroll { height: calc(100vh - 172rpx); }
.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); }

@ -4,7 +4,7 @@
<NavBar :title="t('moldOperate.tabDown')">
<template #right>
<view class="nav-right-btn" @click="goToHistory">
<uni-icons type="calendar" size="22" color="#22486e"></uni-icons>
<uni-icons type="calendar" size="22" color="#ffffff"></uni-icons>
</view>
</template>
</NavBar>
@ -585,9 +585,6 @@ onShow(async () => {
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border-radius: 50%;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
}
.page-container {

@ -4,7 +4,7 @@
<NavBar :title="t('moldOperate.tabUp')">
<template #right>
<view class="nav-right-btn" @click="goToHistory">
<uni-icons type="calendar" size="22" color="#22486e"></uni-icons>
<uni-icons type="calendar" size="22" color="#ffffff"></uni-icons>
</view>
</template>
</NavBar>
@ -531,9 +531,6 @@ onShow(async () => {
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border-radius: 50%;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
}
/* ====== 操作按钮区 ====== */

@ -2,162 +2,159 @@
<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>
<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="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>
<view class="form-field">
<text class="form-label">盘点时间<text class="required-star">*</text></text>
<uni-datetime-picker
v-model="checkTime"
type="datetime"
placeholder="请选择盘点时间"
:clear-icon="false"
return-type="string"
/>
</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 class="form-field">
<text class="form-label">生成来源<text class="required-star">*</text></text>
<view class="segment-control">
<view :class="['segment-item', sourceType === 1 ? 'active' : '']" @click="switchSourceType(1)"></view>
<view :class="['segment-item', sourceType === 2 ? 'active' : '']" @click="switchSourceType(2)"></view>
</view>
</view>
<view class="form-field">
<text class="form-label">备注</text>
<textarea v-model="remark" class="form-textarea" placeholder="请输入备注" placeholder-class="placeholder-text" maxlength="200" />
</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">产品与盘点项</text>
</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 }]">
<template v-if="sourceType === 1">
<view class="form-field">
<text class="form-label">仓库</text>
<view class="select-field">
<text class="select-text">备件仓</text>
</view>
</view>
<view class="form-field">
<text class="form-label">库区</text>
<view class="select-field" @click="goSelectArea">
<text :class="['select-text', selectedAreas.length ? '' : 'placeholder']">
{{ selectedAreas.length ? selectedAreas.map(a => a.label).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow"></text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</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 v-if="selectedAreas.length" class="select-items-card" @click="goSelectItems">
<view class="select-items-main">
<text class="select-items-title">盘点项</text>
<text class="select-items-desc">{{ items.length ? '已选' + items.length + '项' : '点击选择' }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
</view>
</view>
</template>
</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 }]">
<template v-else>
<view class="form-field">
<text class="form-label">产品</text>
<view class="select-field" @click="goSelectProduct">
<text :class="['select-text', selectedProducts.length ? '' : 'placeholder']">
{{ selectedProducts.length ? selectedProducts.map(p => p.name).join('、') : '请选择' }}
</text>
<text class="dropdown-arrow"></text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</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 v-if="selectedProducts.length" class="select-items-card" @click="goSelectItemsByProduct">
<view class="select-items-main">
<text class="select-items-title">盘点项</text>
<text class="select-items-desc">{{ items.length ? '已选' + items.length + '项' : '点击选择' }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
</view>
</view>
</template>
</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 v-if="items.length" class="summary-strip">
<view class="summary-item" v-if="sourceType === 2">
<text class="summary-value">{{ selectedProducts.length }}</text>
<text class="summary-label">产品</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ items.length }}</text>
<text class="summary-label">盘点项</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ totalStockCount }}</text>
<text class="summary-label">账面数量</text>
</view>
</view>
<view class="item-row">
<text class="item-lbl">账面数量</text>
<text class="item-val highlight">{{ textValue(item.stockCount) }}</text>
<view v-if="items.length" class="item-list">
<view v-for="(item, idx) in items" :key="idx" class="item-card">
<view class="item-info">
<view class="item-header">
<text class="item-name">{{ textValue(item.productName) }}</text>
<view class="delete-btn" @click="items.splice(idx, 1)">
<uni-icons type="trash" size="18" color="#ef4444"></uni-icons>
</view>
</view>
<view class="info-grid">
<view class="info-cell">
<text class="info-label">编码</text>
<text class="info-value">{{ textValue(item.productBarCode) }}</text>
</view>
<view class="info-cell">
<text class="info-label">仓库</text>
<text class="info-value">{{ textValue(item.warehouseName) }}</text>
</view>
<view class="info-cell">
<text class="info-label">库区</text>
<text class="info-value">{{ textValue(item.areaName) }}</text>
</view>
<view class="info-cell">
<text class="info-label">账面数量</text>
<text class="info-value highlight">{{ textValue(item.stockCount) }}</text>
</view>
</view>
</view>
</view>
</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>
</scroll-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 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 { ref } from 'vue'
import { ref, computed } 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'
@ -194,6 +191,8 @@ const selectedProducts = ref([])
const items = ref([])
const remark = ref('')
const totalStockCount = computed(() => items.value.reduce((sum, item) => sum + (Number(item.stockCount) || 0), 0))
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
@ -252,6 +251,12 @@ function goSelectProduct() {
})
}
function removeProduct(product) {
selectedProducts.value = selectedProducts.value.filter(p => String(p.id) !== String(product.id))
const productIds = new Set(selectedProducts.value.map(p => String(p.id)))
items.value = items.value.filter(item => productIds.has(String(item.productId)))
}
// productId
function goSelectItemsByProduct() {
if (!selectedProducts.value.length) return
@ -343,126 +348,55 @@ onShow(() => {
</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;
}
.page-container { min-height: 100vh; background: #f5f7fb; }
.detail-scroll { height: calc(100vh - 172rpx); }
.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; min-height: 76rpx; padding: 0 24rpx; 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; }
.segment-control { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 6rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 16rpx; }
.segment-item { height: 68rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 27rpx; color: #64748b; }
.segment-item.active { background: #1f4b79; color: #ffffff; font-weight: 600; box-shadow: 0 6rpx 16rpx rgba(31, 75, 121, 0.18); }
.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; justify-content: center; gap: 8rpx; flex-shrink: 0; }
.block-btn { height: 76rpx; border-radius: 14rpx; margin-bottom: 18rpx; }
.selected-products { display: flex; flex-wrap: wrap; gap: 12rpx; margin-bottom: 18rpx; }
.product-chip { max-width: 100%; display: flex; align-items: center; gap: 8rpx; padding: 10rpx 16rpx; border-radius: 999rpx; background: #eff6ff; color: #1f4b79; border: 1rpx solid #bfdbfe; }
.product-chip-text { max-width: 500rpx; font-size: 24rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-items-card { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding: 20rpx 22rpx; margin: 18rpx 0; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 16rpx; }
.select-items-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
.select-items-title { font-size: 28rpx; font-weight: 600; color: #1f2937; }
.select-items-desc { font-size: 24rpx; color: #64748b; }
.summary-strip { display: grid; grid-template-columns: repeat(2, 1fr); background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
.summary-strip .summary-item:first-child:nth-last-child(3), .summary-strip .summary-item:first-child:nth-last-child(3) ~ .summary-item { width: auto; }
.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: center; gap: 12rpx; margin-bottom: 14rpx; }
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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-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: 600; }
.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; margin-top: 20rpx; }
.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>

@ -0,0 +1,225 @@
<template>
<view class="page-container">
<NavBar :title="'备件盘点详情'" />
<scroll-view scroll-y class="detail-scroll">
<view class="content-section">
<!-- 盘点信息 -->
<view v-if="detail" class="section-card">
<view class="section-header">
<view class="section-icon">
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">盘点信息</text>
</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">单据号</text>
<text class="info-value">{{ textValue(detail.no) }}</text>
</view>
<view class="info-row">
<text class="info-label">盘点时间</text>
<text class="info-value">{{ formatDateTime(detail.checkTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">仓库</text>
<text class="info-value">{{ textValue(getWarehouseNames()) }}</text>
</view>
<view class="info-row">
<text class="info-label">来源方式</text>
<text class="info-value">{{ detail.sourceType === 1 ? '按库存' : '按产品' }}</text>
</view>
<view class="info-row">
<text class="info-label">盘点状态</text>
<text :class="['info-value', 'status-tag', checkStatusClass(detail.checkStatus)]">{{ checkStatusText(detail.checkStatus) }}</text>
</view>
<view class="info-row">
<text class="info-label">创建人</text>
<text class="info-value">{{ textValue(detail.creatorName || detail.creator) }}</text>
</view>
<view class="info-row" v-if="detail.remark">
<text class="info-label">备注</text>
<text class="info-value">{{ textValue(detail.remark) }}</text>
</view>
</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">盘点项</text>
</view>
<text v-if="items.length" class="item-count">{{ items.length }} </text>
</view>
<view v-if="loading" class="hint">...</view>
<view v-else-if="items.length" class="item-list">
<view v-for="(item, idx) in items" :key="idx" class="item-card">
<view class="item-header">
<view class="item-index">{{ idx + 1 }}</view>
<view class="item-title-wrap">
<text class="item-name">{{ textValue(item.productName) }}</text>
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
</view>
</view>
<view class="info-grid">
<view class="info-cell">
<text class="cell-label">仓库</text>
<text class="cell-value">{{ textValue(item.warehouseName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">库区</text>
<text class="cell-value">{{ textValue(item.areaName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">账面数量</text>
<text class="cell-value highlight">{{ textValue(item.stockCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">实盘数量</text>
<text class="cell-value highlight">{{ textValue(item.actualCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
</view>
<view class="info-cell">
<text class="cell-label">差异</text>
<text :class="['cell-value', 'diff-value', diffClass(item)]">{{ getDiffText(item) }}</text>
</view>
</view>
</view>
</view>
<view v-else class="hint">暂无盘点项</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import NavBar from '@/components/common/NavBar.vue'
import { getSparepartCheckDetail } from '@/api/mes/sparepartCheck'
const detail = ref(null)
const items = ref([])
const loading = ref(false)
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
function formatDateTime(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
const [year, month, day, hour = 0, minute = 0, second = 0] = value
const pad = (n) => String(n).padStart(2, '0')
return `${year}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
}
const date = new Date(Number(value))
if (Number.isNaN(date.getTime())) return String(value)
const pad = (n) => String(n).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
function getWarehouseNames() {
return [...new Set(items.value.map((item) => item.warehouseName).filter(Boolean))].join('、')
}
function getDiff(item) {
const actual = item.actualCount != null ? Number(item.actualCount) : null
if (actual == null) return null
return actual - (Number(item.stockCount) || 0)
}
function getDiffText(item) {
const diff = getDiff(item)
if (diff == null) return '-'
return `${diff > 0 ? '+' : ''}${diff}`
}
function diffClass(item) {
const diff = getDiff(item)
if (diff > 0) return 'positive'
if (diff < 0) return 'negative'
return 'zero'
}
function checkStatusText(status) {
const map = { 0: '未盘点', 1: '已盘点', 2: '已提交', 10: '待审核', 20: '已审核' }
return map[status] || '-'
}
function checkStatusClass(status) {
const map = { 0: 'status-pending', 1: 'status-done', 2: 'status-submitted', 10: 'status-auditing', 20: 'status-passed' }
return map[status] || ''
}
async function loadDetail(id) {
loading.value = true
try {
const res = await getSparepartCheckDetail(id)
const data = res?.data || res
detail.value = data
items.value = Array.isArray(data?.items) ? data.items : []
} catch (e) {
uni.showToast({ title: '加载详情失败', icon: 'none' })
} finally {
loading.value = false
}
}
onLoad((options) => {
const id = options?.id
if (!id) {
uni.showToast({ title: '缺少单据ID', icon: 'none' })
return
}
loadDetail(id)
})
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f7fb; }
.detail-scroll { height: calc(100vh - 120rpx); }
.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; }
.item-count { font-size: 24rpx; color: #1f4b79; font-weight: 600; }
.info-list { display: flex; flex-direction: column; gap: 14rpx; }
.info-row { display: flex; justify-content: space-between; gap: 20rpx; }
.info-label { width: 150rpx; font-size: 25rpx; color: #94a3b8; flex-shrink: 0; }
.info-value { flex: 1; text-align: right; font-size: 27rpx; color: #334155; line-height: 1.5; }
.status-tag { font-weight: 600; }
.status-pending { color: #f59e0b; }
.status-done { color: #3b82f6; }
.status-submitted { color: #8b5cf6; }
.status-auditing { color: #f59e0b; }
.status-passed { color: #16a34a; }
.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-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 18rpx; }
.item-index { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #1f4b79; color: #fff; font-size: 26rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.item-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
.item-name { font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-code { font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 18rpx; background: #f8fafc; border: 1rpx solid #eef2f7; border-radius: 16rpx; }
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
.cell-label { font-size: 22rpx; color: #9ca3af; }
.cell-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-value.highlight { color: #1f4b79; font-weight: 600; }
.diff-value.positive { color: #16a34a; font-weight: 700; }
.diff-value.negative { color: #dc2626; font-weight: 700; }
.diff-value.zero { color: #64748b; font-weight: 700; }
.hint { padding: 60rpx 0; text-align: center; color: #94a3b8; font-size: 26rpx; }
</style>

@ -555,8 +555,8 @@ function clearSearchTimer() {
}
}
onShow(() => {
loadSparepartWarehouse()
onShow(async () => {
await loadSparepartWarehouse()
fetchList(true)
})

Loading…
Cancel
Save