Commit 20ecc0a1 by 杨子

feat(盘点): 新增盘点执行功能及相关组件

refactor(出库单): 将"出库人"和"出库时间"改为"操作人"和"操作时间"

style(盘点任务): 移除不必要的表格列和注释

docs(首页): 重写首页为数据看板样式

chore(登录): 移除登录后设备初始化延迟逻辑
parent a6b68aed
import request from '@/utils/request'
/**
* 盘点检查接口
* @param {Object} data - 请求参数
* @param {Long} data.taskId - 盘点任务ID
* @param {Array<String>} data.rfidList - 扫描的RFID列表
* @param {Array<String>} data.batchNoList - 批次号列表
* @param {Long} data.checkerId - 盘点员ID
* @param {String} data.checkerName - 盘点员姓名
* @returns {Promise}
*/
export function inventoryCheck(data) {
return request({
url: '/ware/inventoryCheck/check',
method: 'post',
data
})
}
/**
* 二维码批次号盘点检查接口
* @param {Object} data - 请求参数
* @param {Long} data.taskId - 盘点任务ID
* @param {Array<String>} data.batchNoList - 批次号列表
* @param {Long} data.checkerId - 盘点员ID
* @param {String} data.checkerName - 盘点员姓名
* @returns {Promise}
*/
export function inventoryCheckToBatchNo(data) {
return request({
url: '/ware/inventoryCheck/checkToBatchNo',
method: 'post',
data
})
}
\ No newline at end of file
<template>
<div class="app-container flex justify-center items-center">
<h1>欢迎来到{{settings.title}}</h1>
<div class="app-container">
<!-- 统计概览卡片 -->
<div class="stats-container grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">总库存</p>
<el-statistic :value="statistics.totalInventory" :precision="0" suffix="件" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-blue-100 text-blue-600 p-3 rounded-full">
<el-icon :size="32">
<Goods />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">待处理任务</p>
<el-statistic :value="statistics.pendingTasks" :precision="0" suffix="个" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-red-100 text-red-600 p-3 rounded-full">
<el-icon :size="32">
<Clock />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">今日入库</p>
<el-statistic :value="statistics.todayInbound" :precision="0" suffix="件" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-green-100 text-green-600 p-3 rounded-full">
<el-icon :size="32">
<Plus />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">今日出库</p>
<el-statistic :value="statistics.todayOutbound" :precision="0" suffix="件" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-purple-100 text-purple-600 p-3 rounded-full">
<el-icon :size="32">
<Minus />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">物资总数</p>
<el-statistic :value="statistics.totalMaterials" :precision="0" suffix="种" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-cyan-100 text-cyan-600 p-3 rounded-full">
<el-icon :size="32">
<List />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">仓库总数</p>
<el-statistic :value="statistics.totalWarehouses" :precision="0" suffix="个" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-orange-100 text-orange-600 p-3 rounded-full">
<el-icon :size="32">
<Box />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">供应商总数</p>
<el-statistic :value="statistics.totalSuppliers" :precision="0" suffix="家" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-teal-100 text-teal-600 p-3 rounded-full">
<el-icon :size="32">
<UserFilled />
</el-icon>
</div>
</div>
</el-card>
<el-card shadow="hover" class="stat-card">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">客户总数</p>
<el-statistic :value="statistics.totalCustomers" :precision="0" suffix="家" class="text-2xl font-bold" />
</div>
<div class="stat-icon bg-pink-100 text-pink-600 p-3 rounded-full">
<el-icon :size="32">
<UserFilled />
</el-icon>
</div>
</div>
</el-card>
</div>
<!-- 库存统计图表 -->
<div class="mb-6">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>库存统计分析</span>
</div>
</template>
<div class="inventory-charts grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 库存分类分布 -->
<div class="chart-container">
<h3 class="chart-title mb-2">库存分类分布</h3>
<div id="inventoryCategoryChart" ref="inventoryCategoryChart" class="chart" style="height: 350px;"></div>
</div>
<!-- 仓库库存占比 -->
<div class="chart-container">
<h3 class="chart-title mb-2">仓库库存占比</h3>
<div id="warehouseInventoryChart" ref="warehouseInventoryChart" class="chart" style="height: 350px;"></div>
</div>
<!-- 库存趋势 -->
<div class="chart-container lg:col-span-2">
<h3 class="chart-title mb-2">近30天库存趋势</h3>
<div id="inventoryTrendChart" ref="inventoryTrendChart" class="chart" style="height: 350px;"></div>
</div>
</div>
</el-card>
</div>
<!-- 快速操作 -->
<div class="mb-6">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>快速操作</span>
</div>
</template>
<div class="quick-actions grid grid-cols-2 md:grid-cols-4 gap-4">
<el-button type="primary" class="action-btn" @click="handleQuickAction('inventory')">
<el-icon class="mr-2">
<DocumentChecked />
</el-icon>
新增盘点任务
</el-button>
<el-button type="success" class="action-btn" @click="handleQuickAction('inbound')">
<el-icon class="mr-2">
<DocumentAdd />
</el-icon>
新增入库单
</el-button>
<el-button type="warning" class="action-btn" @click="handleQuickAction('outbound')">
<el-icon class="mr-2">
<DocumentRemove />
</el-icon>
新增出库单
</el-button>
<el-button type="info" class="action-btn" @click="handleQuickAction('query')">
<el-icon class="mr-2">
<Search />
</el-icon>
库存查询
</el-button>
</div>
</el-card>
</div>
<!-- 库存预警 -->
<div class="mb-6">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>库存预警</span>
<el-button type="text" size="small" class="text-primary">查看所有</el-button>
</div>
</template>
<div class="alert-list">
<el-table :data="inventoryAlerts" stripe style="width: 100%">
<el-table-column label="预警类型" align="center" prop="alertType">
<template #default="scope">
<dict-tag :options="alarm_type" :value="scope.row.alertType" />
</template>
</el-table-column>
<el-table-column label="物资" align="center" prop="material.materialName" />
<el-table-column label="仓库/库区/货架" align="center" prop="warehouseId" width="120">
<template #default="scope">
<span
v-if="scope.row.warehouse?.warehouseName || scope.row.area?.areaName || scope.row.location?.locationName">
{{ scope.row.warehouse?.warehouseName || '-' }}/
{{ scope.row.area?.areaName || '-' }}/
{{ scope.row.location?.locationName || '-' }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="当前值" align="center" prop="currentValue" />
<el-table-column label="阈值" align="center" prop="thresholdValue" />
<el-table-column label="预警时间" align="center" prop="alertTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.alertTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="预警级别" align="center" prop="alertLevel">
<template #default="scope">
<dict-tag :options="alert_level" :value="scope.row.alertLevel" />
</template>
</el-table-column>
<el-table-column label="预警信息" align="center" prop="alertMessage" width="260" />
<el-table-column label="处理状态" align="center" prop="alertStatus">
<template #default="scope">
<dict-tag :options="alert_status" :value="scope.row.alertStatus" />
</template>
</el-table-column>
<el-table-column label="处理人" align="center" prop="handlerId" width="180">
<template #default="scope">
<span v-if="scope.row.handler">{{ scope.row.handler?.nickName || '-' }} | {{
scope.row.handler?.phonenumber || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="处理时间" align="center" prop="handleTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.handleTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="处理结果" align="center" prop="handleResult" width="120" />
<el-table-column label="操作" width="100" fixed="right">
<template #default>
<el-button type="primary" size="small">处理</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</div>
<!-- 最近动态和任务列表 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 最近动态 -->
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>最近动态</span>
<el-button type="text" size="small" class="text-primary">查看更多</el-button>
</div>
</template>
<div class="activity-list space-y-4">
<div v-for="(activity, index) in recentActivities" :key="index" class="activity-item flex">
<div class="activity-icon mr-3 mt-1">
<el-icon :size="18" :class="getActivityIconClass(activity.type)">
<component :is="getActivityIcon(activity.type)" />
</el-icon>
</div>
<div class="activity-content">
<p>{{ activity.content }}</p>
<p class="text-xs text-gray-500">{{ activity.time }}</p>
</div>
</div>
</div>
</el-card>
<!-- 任务列表 -->
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>待处理任务</span>
<el-button type="text" size="small" class="text-primary">查看更多</el-button>
</div>
</template>
<el-table :data="pendingTasks" stripe style="width: 100%">
<el-table-column prop="name" label="任务名称" width="200" />
<el-table-column prop="type" label="任务类型" width="120">
<template #default="scope">
<el-tag :type="getTaskTypeColor(scope.row.type)">
{{ scope.row.type }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="warehouse" label="仓库" width="120" />
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="deadline" label="截止时间" width="160" />
<el-table-column label="操作" width="100" fixed="right">
<template #default>
<el-button type="primary" size="small">处理</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import settings from '@/settings'
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import {
Goods,
Clock,
Plus,
Minus,
DocumentChecked,
DocumentAdd,
DocumentRemove,
Search,
List,
Box,
UserFilled,
Warning,
Bell,
CircleCheck,
CirclePlus,
} from '@element-plus/icons-vue'
// 统计数据
const statistics = ref({
totalInventory: 12568,
pendingTasks: 23,
todayInbound: 345,
todayOutbound: 289,
totalMaterials: 896,
totalWarehouses: 4,
totalSuppliers: 156,
totalCustomers: 234
})
// 图表引用
const inventoryCategoryChart = ref(null)
const warehouseInventoryChart = ref(null)
const inventoryTrendChart = ref(null)
// 模拟图表数据
const categoryData = [
{ name: '电子元件', value: 3568 },
{ name: '包装材料', value: 2890 },
{ name: '原材料', value: 2450 },
{ name: '成品', value: 2230 },
{ name: '工具设备', value: 1430 }
]
const warehouseData = [
{ name: '主仓库', value: 5230 },
{ name: '分仓库A', value: 3120 },
{ name: '分仓库B', value: 2890 },
{ name: '临时仓库', value: 1328 }
]
const trendData = {
dates: [],
inbound: [],
outbound: [],
inventory: []
}
// 生成30天的日期和数据
for (let i = 29; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
const dateStr = `${date.getMonth() + 1}/${date.getDate()}`
trendData.dates.push(dateStr)
// 生成随机的入库和出库数据
const inbound = Math.floor(Math.random() * 100) + 50
const outbound = Math.floor(Math.random() * 80) + 30
trendData.inbound.push(inbound)
trendData.outbound.push(outbound)
// 计算库存趋势
if (i === 29) {
trendData.inventory.push(statistics.value.totalInventory - (inbound - outbound) * 29)
} else {
const prevInventory = trendData.inventory[trendData.inventory.length - 1]
trendData.inventory.push(prevInventory + inbound - outbound)
}
}
// 初始化库存分类分布图表
const initInventoryCategoryChart = () => {
if (inventoryCategoryChart.value) {
const chart = echarts.init(inventoryCategoryChart.value)
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 10,
data: categoryData.map(item => item.name)
},
series: [
{
name: '库存分类',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: categoryData
}
]
}
chart.setOption(option)
// 监听窗口大小变化,自动调整图表
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 初始化仓库库存占比图表
const initWarehouseInventoryChart = () => {
if (warehouseInventoryChart.value) {
const chart = echarts.init(warehouseInventoryChart.value)
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '仓库库存',
type: 'pie',
radius: '60%',
center: ['50%', '60%'],
data: warehouseData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
formatter: '{b}\n{c}\n({d}%)'
}
}
]
}
chart.setOption(option)
// 监听窗口大小变化,自动调整图表
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 初始化库存趋势图表
const initInventoryTrendChart = () => {
if (inventoryTrendChart.value) {
const chart = echarts.init(inventoryTrendChart.value)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['入库', '出库', '库存']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: trendData.dates
}
],
yAxis: [
{
type: 'value',
name: '数量(件)',
position: 'left',
axisLabel: {
formatter: '{value}'
}
}
],
series: [
{
name: '入库',
type: 'bar',
data: trendData.inbound,
itemStyle: {
color: '#52c41a'
}
},
{
name: '出库',
type: 'bar',
data: trendData.outbound,
itemStyle: {
color: '#ff4d4f'
}
},
{
name: '库存',
type: 'line',
yAxisIndex: 0,
data: trendData.inventory,
smooth: true,
itemStyle: {
color: '#1890ff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(24, 144, 255, 0.3)'
},
{
offset: 1,
color: 'rgba(24, 144, 255, 0.05)'
}
])
}
}
]
}
chart.setOption(option)
// 监听窗口大小变化,自动调整图表
window.addEventListener('resize', () => {
chart.resize()
})
}
}
// 最近动态数据
const recentActivities = ref([
{ id: 1, content: '用户张三创建了新的盘点任务 #20251230001', time: '2025-12-30 14:32', type: 'task' },
{ id: 2, content: '入库单 #RK20251230005 已完成审批', time: '2025-12-30 13:45', type: 'inbound' },
{ id: 3, content: '出库单 #CK20251230008 已完成发货', time: '2025-12-30 11:20', type: 'outbound' },
{ id: 4, content: '仓库A的库存预警:商品SKU-001库存不足', time: '2025-12-30 10:15', type: 'alert' },
{ id: 5, content: '用户李四更新了仓库B的容量设置', time: '2025-12-30 09:30', type: 'setting' },
{ id: 6, content: '调拨单 #DB20251229001 已完成执行', time: '2025-12-29 16:45', type: 'transfer' },
{ id: 7, content: '新品SKU-999已成功导入系统', time: '2025-12-29 14:20', type: 'material' },
{ id: 8, content: '客户ABC有限公司的订单已生成出库单', time: '2025-12-29 11:10', type: 'customer' },
{ id: 9, content: '供应商XYZ的原材料已到货,等待入库', time: '2025-12-29 10:05', type: 'supplier' },
{ id: 10, content: '盘点任务 #PD20251228001 已完成,差异已处理', time: '2025-12-28 17:30', type: 'inventory' }
])
// 待处理任务数据
const pendingTasks = ref([
{ id: 1, name: '年度大盘点', type: '盘点任务', warehouse: '主仓库', createTime: '2025-12-30', deadline: '2025-12-31', priority: 'high' },
{ id: 2, name: '季度库存抽查', type: '盘点任务', warehouse: '分仓库A', createTime: '2025-12-29', deadline: '2025-12-30', priority: 'medium' },
{ id: 3, name: '新品入库', type: '入库任务', warehouse: '分仓库B', createTime: '2025-12-30', deadline: '2025-12-30', priority: 'high' },
{ id: 4, name: '客户订单发货', type: '出库任务', warehouse: '主仓库', createTime: '2025-12-30', deadline: '2025-12-30', priority: 'high' },
{ id: 5, name: '库存调拨', type: '调拨任务', warehouse: '临时仓库', createTime: '2025-12-29', deadline: '2025-12-30', priority: 'medium' },
{ id: 6, name: '月度库存分析报告', type: '报告任务', warehouse: '所有仓库', createTime: '2025-12-28', deadline: '2025-12-31', priority: 'low' },
{ id: 7, name: '供应商ABC原材料入库', type: '入库任务', warehouse: '主仓库', createTime: '2025-12-30', deadline: '2025-12-30', priority: 'medium' },
{ id: 8, name: '过期商品清理', type: '盘点任务', warehouse: '临时仓库', createTime: '2025-12-29', deadline: '2025-12-30', priority: 'medium' }
])
// 库存预警数据
const inventoryAlerts = ref([
{ id: 1, materialName: 'SKU-001 电子元件', warehouse: '主仓库', currentStock: 15, minStock: 50, status: '预警', time: '2025-12-30 10:15' },
{ id: 2, materialName: 'SKU-002 包装材料', warehouse: '分仓库A', currentStock: 23, minStock: 100, status: '预警', time: '2025-12-29 16:30' },
{ id: 3, materialName: 'SKU-003 原材料', warehouse: '分仓库B', currentStock: 8, minStock: 30, status: '紧急', time: '2025-12-30 09:45' },
{ id: 4, materialName: 'SKU-004 成品', warehouse: '主仓库', currentStock: 120, maxStock: 100, status: '超量', time: '2025-12-29 14:20' }
])
// 快速操作处理函数
const handleQuickAction = (actionType) => {
switch (actionType) {
case 'inventory':
ElMessage.info('跳转到新增盘点任务页面')
// 这里可以添加路由跳转逻辑
break
case 'inbound':
ElMessage.info('跳转到新增入库单页面')
// 这里可以添加路由跳转逻辑
break
case 'outbound':
ElMessage.info('跳转到新增出库单页面')
// 这里可以添加路由跳转逻辑
break
case 'query':
ElMessage.info('跳转到库存查询页面')
// 这里可以添加路由跳转逻辑
break
default:
break
}
}
// 获取任务类型颜色
const getTaskTypeColor = (type) => {
switch (type) {
case '盘点任务':
return 'primary'
case '入库任务':
return 'success'
case '出库任务':
return 'warning'
case '调拨任务':
return 'info'
case '报告任务':
return 'secondary'
default:
return 'default'
}
}
// 获取活动图标
const getActivityIcon = (type) => {
switch (type) {
case 'task':
return DocumentChecked
case 'inbound':
return CirclePlus
case 'outbound':
return Minus
case 'alert':
return Warning
case 'setting':
return Clock
case 'transfer':
return Bell
case 'material':
return List
case 'customer':
return Box
case 'supplier':
return UserFilled
case 'inventory':
return CircleCheck
default:
return ''
}
}
// 获取活动图标颜色类
const getActivityIconClass = (type) => {
switch (type) {
case 'task':
return 'text-blue-500'
case 'inbound':
return 'text-green-500'
case 'outbound':
return 'text-orange-500'
case 'alert':
return 'text-red-500'
case 'setting':
return 'text-gray-500'
case 'transfer':
return 'text-purple-500'
case 'material':
return 'text-cyan-500'
case 'customer':
return 'text-pink-500'
case 'supplier':
return 'text-teal-500'
case 'inventory':
return 'text-indigo-500'
default:
return 'text-gray-400'
}
}
// 组件挂载后初始化图表
onMounted(() => {
initInventoryCategoryChart()
initWarehouseInventoryChart()
initInventoryTrendChart()
})
</script>
<style scoped>
.app-container {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
margin-bottom: 30px;
}
.page-header h1 {
font-size: 28px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.stats-container {
margin-bottom: 24px;
}
.stat-card {
border-radius: 8px;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.stat-icon {
font-size: 24px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.quick-actions {
margin-top: 16px;
}
.action-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100px;
border-radius: 8px;
transition: all 0.3s ease;
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
.action-btn .el-icon {
font-size: 24px;
margin-bottom: 8px;
}
.activity-list {
margin-top: 16px;
}
.activity-item {
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-content p:first-child {
margin-bottom: 4px;
font-size: 14px;
}
.activity-content p:last-child {
font-size: 12px;
color: #909399;
}
/* 图表样式 */
.chart-container {
margin-bottom: 20px;
}
.chart-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.chart {
border-radius: 8px;
overflow: hidden;
}
/* 响应式设计 */
@media (max-width: 768px) {
.app-container {
padding: 10px;
}
.page-header h1 {
font-size: 24px;
}
.action-btn {
height: 80px;
}
}
</style>
......@@ -176,14 +176,6 @@ function handleLogin() {
return acc;
}, {});
router.push({ path: redirect.value || "/", query: otherQueryParams });
setTimeout(() => {
if (
(userStore.userInfo && userStore.userInfo.admin) ||
loginForm.value.username === "admin"
) {
useDeviceInit();
}
}, 1000)
}).catch(() => {
loading.value = false;
// 重新获取验证码
......
......@@ -9,7 +9,6 @@
<el-descriptions-item label="任务名称">{{ taskInfo.taskName }}</el-descriptions-item>
<el-descriptions-item label="总盘点数">{{ taskInfo.totalItems }}</el-descriptions-item>
<el-descriptions-item label="已盘数">{{ taskInfo.countedItems }}</el-descriptions-item>
<!-- <el-descriptions-item label="盘点设备">{{ taskInfo.inventoryDevice }}</el-descriptions-item> -->
<el-descriptions-item label="备注" :span="2">{{ taskInfo.remark }}</el-descriptions-item>
</el-descriptions>
</div>
......@@ -39,7 +38,6 @@
<dict-tag :options="check_status" :value="scope.row.checkStatus" />
</template>
</el-table-column>
<el-table-column label="盘点员ID" align="center" prop="checkerId" />
<el-table-column label="盘点员姓名" align="center" prop="checkerName" />
<el-table-column label="盘点时间" align="center" prop="checkTime" width="180">
<template #default="scope">
......@@ -47,8 +45,6 @@
</template>
</el-table-column>
<el-table-column label="RFID标签" align="center" prop="rfidTag" />
<el-table-column label="是否盲区" align="center" prop="isBlindZone" />
<el-table-column label="质量状态" align="center" prop="qualityStatus" />
<el-table-column label="备注" align="center" prop="remark" />
</el-table>
</div>
......
<template>
<el-dialog v-model="visible" title="执行盘点" fullscreen @close="handleClose">
<div class="flex">
<div class="flex-grow w-0">
<!-- 任务基础信息 -->
<div v-if="taskInfo" class="task-info">
<el-divider content-position="left">任务信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="任务编号">{{ taskInfo.taskNo }}</el-descriptions-item>
<el-descriptions-item label="任务名称">{{ taskInfo.taskName }}</el-descriptions-item>
<el-descriptions-item label="仓库">{{ taskInfo.warehouseName }}</el-descriptions-item>
<el-descriptions-item label="总盘点数">{{ taskInfo.totalItems || 0 }}</el-descriptions-item>
<el-descriptions-item label="已盘数">{{ taskInfo.countedItems || 0 }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 任务明细信息 -->
<div class="detail-info mt-4">
<el-divider content-position="left">任务明细信息</el-divider>
<el-table :data="taskDetailList" height="400px">
<el-table-column label="物资ID" align="center" prop="materialId" />
<el-table-column label="仓库名称" align="center" prop="warehouseName" />
<el-table-column label="库区名称" align="center" prop="areaName" />
<el-table-column label="货架名称" align="center" prop="locationName" />
<el-table-column label="批次号" align="center" prop="batchNo" width="120" />
<el-table-column label="系统数量" align="center" prop="systemQuantity" />
<el-table-column label="实际数量" align="center" prop="actualQuantity" />
<el-table-column label="差异数量" align="center" prop="differenceQuantity" />
<el-table-column label="差异率" align="center" prop="differenceRate" />
<el-table-column label="差异原因" align="center" prop="differenceReason" width="120">
<template #default="scope">
<dict-tag :options="diff_type" :value="scope.row.differenceReason" />
</template>
</el-table-column>
<el-table-column label="盘点状态" align="center" prop="checkStatus" width="120">
<template #default="scope">
<dict-tag :options="check_status" :value="scope.row.checkStatus" />
</template>
</el-table-column>
<el-table-column label="盘点员姓名" align="center" prop="checkerName" />
<el-table-column label="盘点时间" align="center" prop="checkTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.checkTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="RFID标签" align="center" prop="rfidTag" />
</el-table>
</div>
</div>
<div class="w-[400px]">
<el-form ref="executeFormRef" :model="form" label-width="120px" @submit.prevent>
<!-- 隐藏的任务ID -->
<el-form-item label="" class="hidden">
<el-input v-model="form.taskId" type="hidden" />
</el-form-item>
<!-- 选择盘点方式 -->
<el-form-item label="盘点方式">
<el-radio-group v-model="form.checkType" size="large">
<el-radio label="batchNo">二维码</el-radio>
<el-radio label="rfidTag">RFID标签</el-radio>
</el-radio-group>
</el-form-item>
<!-- 选择操作模式 -->
<el-form-item label="操作模式">
<el-radio-group v-model="form.operateMode" size="large">
<el-radio label="single">单个</el-radio>
<el-radio label="batch">批量</el-radio>
</el-radio-group>
</el-form-item>
<!-- 二维码批次号 -->
<el-form-item label="二维码批次号" v-if="form.checkType === 'batchNo'">
<div style="display: flex; flex-direction: column; gap: 10px; " class="w-full">
<!-- 单个模式 -->
<el-input v-if="form.operateMode === 'single'" v-model="form.singleBatchNo"
placeholder="请输入单个二维码批次号" @keyup.enter="handleCheck"/>
<!-- 批量模式 -->
<el-input v-else v-model="form.batchNoInput" type="textarea" :rows="4"
placeholder="请输入二维码批次号,多行输入支持批量操作" @keyup.enter.ctrl="handleCheck" />
<div style="text-align: right;">
<el-button type="primary" @click="handleCheck"
v-if="form.operateMode === 'batch'">扫描/查询</el-button>
</div>
</div>
</el-form-item>
<!-- RFID标签编号 -->
<el-form-item label="RFID标签编号" v-if="form.checkType === 'rfidTag'">
<div style="display: flex; flex-direction: column; gap: 10px;" class="w-full">
<!-- 单个模式 -->
<el-input v-if="form.operateMode === 'single'" v-model="form.singleRfidTag"
placeholder="请输入单个RFID标签编号" @keyup.enter="handleCheck" />
<!-- 批量模式 -->
<el-input v-else v-model="form.rfidTagInput" type="textarea" :rows="4"
placeholder="请输入RFID标签编号,多行输入支持批量操作" @keyup.enter.ctrl="handleCheck" />
<div style="text-align: right;">
<el-button type="primary" @click="handleCheck"
v-if="form.operateMode === 'batch'">扫描/查询</el-button>
</div>
</div>
</el-form-item>
</el-form>
<!-- 盘点结果展示 -->
<div v-if="inventoryResult" class="inventory-result">
<el-divider content-position="left">盘点结果</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="总盘点数">{{ inventoryResult.totalItems }}</el-descriptions-item>
<el-descriptions-item label="已盘数">{{ inventoryResult.countedItems }}</el-descriptions-item>
<el-descriptions-item label="盘盈数">{{ inventoryResult.surplusItems }}</el-descriptions-item>
<el-descriptions-item label="盘亏数">{{ inventoryResult.deficitItems }}</el-descriptions-item>
<el-descriptions-item label="盘点进度">
<el-progress :percentage="(inventoryResult.countedItems / inventoryResult.totalItems * 100).toFixed(0)" />
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="InventoryExecuteForm">
import { ref, reactive, defineEmits, getCurrentInstance, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { inventoryCheck, inventoryCheckToBatchNo } from '@/api/ware/wmsInventoryCheck'
import { getWmsInventoryTask } from '@/api/ware/wmsInventoryTask'
import { listWmsInventoryTaskDetail } from "@/api/ware/wmsInventoryTaskDetail"
import useUserStore from '@/store/modules/user'
import { listWmsInventoryResult } from "@/api/ware/wmsInventoryResult"
const userStore = useUserStore()
const emit = defineEmits(['close', 'executeSuccess'])
// 获取当前实例
const { proxy } = getCurrentInstance()
const { check_status, diff_type } = proxy.useDict('check_status', 'diff_type')
// 表单引用
const executeFormRef = ref(null)
// 对话框可见性
const visible = ref(false)
// 表单数据
const form = reactive({
taskId: null, // 盘点任务ID
checkType: 'batchNo', // 默认选择二维码
operateMode: 'single', // 默认选择单个操作
batchNoInput: '', // 批量批次号输入
rfidTagInput: '', // 批量RFID标签输入
singleBatchNo: '', // 单个批次号输入
singleRfidTag: '' // 单个RFID标签输入
})
// 盘点结果
const inventoryResult = ref(null)
// 任务信息
const taskInfo = ref(null)
// 任务明细列表
const taskDetailList = ref([])
// 处理盘点查询
async function handleCheck() {
// 获取当前登录用户信息
const checkerId = userStore.id
const checkerName = userStore.nickName
let requestParams = {
taskId: form.taskId,
checkerId,
checkerName
}
try {
let result
if (form.checkType === 'batchNo') {
let batchNoList = []
if (form.operateMode === 'single') {
// 单个模式
if (!form.singleBatchNo.trim()) {
ElMessage.warning('请输入二维码批次号')
return
}
batchNoList = [form.singleBatchNo.trim()]
} else {
// 批量模式
batchNoList = form.batchNoInput
.split('\n')
.map(item => item.trim())
.filter(item => item)
if (batchNoList.length === 0) {
ElMessage.warning('请输入二维码批次号')
return
}
}
requestParams.batchNoList = batchNoList
result = await inventoryCheckToBatchNo(requestParams)
} else {
let rfidList = []
if (form.operateMode === 'single') {
// 单个模式
if (!form.singleRfidTag.trim()) {
ElMessage.warning('请输入RFID标签编号')
return
}
rfidList = [form.singleRfidTag.trim()]
} else {
// 批量模式
rfidList = form.rfidTagInput
.split('\n')
.map(item => item.trim())
.filter(item => item)
if (rfidList.length === 0) {
ElMessage.warning('请输入RFID标签编号')
return
}
}
requestParams.rfidList = rfidList
result = await inventoryCheck(requestParams)
}
if (result.code === 200) {
ElMessage.success('查询成功')
// 触发执行成功事件
emit('executeSuccess', result.data)
await getTaskInfo()
await loadTaskDetail()
await getInventoryResult()
// 单个模式下清空输入框
if (form.operateMode === 'single') {
if (form.checkType === 'batchNo') {
form.singleBatchNo = ''
} else {
form.singleRfidTag = ''
}
}else{
form.batchNoInput = ''
form.rfidTagInput = ''
}
} else {
ElMessage.error(result.msg || '查询失败')
}
} catch (error) {
ElMessage.error('查询异常,请稍后重试')
console.error('盘点查询失败:', error)
}
}
// 打开对话框
async function open(taskId = null) {
visible.value = true
form.taskId = taskId
resetForm()
// 获取任务信息
if (taskId) {
await getTaskInfo()
await loadTaskDetail()
await getInventoryResult()
}
}
async function getTaskInfo() {
try {
const result = await getWmsInventoryTask(form.taskId)
if (result.code === 200) {
taskInfo.value = result.data
} else {
ElMessage.error(result.msg || '获取任务信息失败')
}
} catch (error) {
ElMessage.error('获取任务信息异常,请稍后重试')
console.error('获取任务信息失败:', error)
}
}
// 加载任务明细数据
async function loadTaskDetail() {
const queryParams = {
pageNum: 1,
pageSize: 1000, // 加载所有明细
taskId: form.taskId
}
try {
const result = await listWmsInventoryTaskDetail(queryParams)
if (result.code === 200) {
taskDetailList.value = result.rows
} else {
ElMessage.error(result.msg || '获取任务明细失败')
}
} catch (error) {
ElMessage.error('获取任务明细异常,请稍后重试')
console.error('获取任务明细失败:', error)
}
}
async function getInventoryResult() {
try {
const result = await listWmsInventoryResult({
taskId: form.taskId
})
if (result.code === 200) {
inventoryResult.value = result.rows[0] || {}
} else {
ElMessage.error(result.msg || '获取盘点结果失败')
}
} catch (error) {
ElMessage.error('获取盘点结果异常,请稍后重试')
console.error('获取盘点结果失败:', error)
}
}
// 关闭对话框
function handleClose() {
visible.value = false
resetForm()
emit('close')
}
// 重置表单
function resetForm() {
form.batchNoInput = ''
form.rfidTagInput = ''
form.singleBatchNo = ''
form.singleRfidTag = ''
inventoryResult.value = null
taskInfo.value = null
executeFormRef.value?.resetFields()
}
// 暴露方法
defineExpose({
open
})
</script>
<style scoped>
.task-info {
margin-bottom: 20px;
}
.inventory-result {
margin-top: 20px;
}
.dialog-footer {
text-align: right;
}
/* 隐藏样式 */
.hidden {
display: none;
}
</style>
\ No newline at end of file
......@@ -41,6 +41,8 @@
v-hasPermi="['ware:wmsInventoryTask:detail']">详情</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-hasPermi="['ware:wmsInventoryTask:remove']">删除</el-button>
<el-button link type="primary" icon="Scan" @click="handleExecute(scope.row)"
v-hasPermi="['ware:wmsInventoryTask:execute']">执行盘点</el-button>
</template>
</el-table-column>
</el-table>
......@@ -68,6 +70,9 @@
</div>
</template>
</el-dialog>
<!-- 执行盘点组件 -->
<InventoryExecuteForm ref="inventoryExecuteFormRef" @executeSuccess="getList" />
</div>
</template>
......@@ -75,6 +80,7 @@
import { listWmsInventoryTask, getWmsInventoryTask, delWmsInventoryTask, addWmsInventoryTask, updateWmsInventoryTask } from "@/api/ware/wmsInventoryTask"
import DetailInfo from "./components/DetailInfo.vue"
import AddForm from "./components/AddForm.vue"
import InventoryExecuteForm from "./components/InventoryExecuteForm.vue"
const { proxy } = getCurrentInstance()
......@@ -89,6 +95,9 @@ const total = ref(0)
const title = ref("")
const detailOpen = ref(false)
// 执行盘点对话框
const inventoryExecuteFormRef = ref(null)
const data = reactive({
form: {},
detailForm: {},
......@@ -252,5 +261,11 @@ function handleDetailClose() {
detailForm.value = {}
}
/** 执行盘点按钮操作 */
function handleExecute(row) {
// 传递任务ID到执行盘点组件
inventoryExecuteFormRef.value?.open(row.taskId)
}
getList()
</script>
......@@ -29,10 +29,10 @@
<el-descriptions-item label="总金额">
{{ orderDetail.totalAmount }}
</el-descriptions-item>
<el-descriptions-item label="出库人">
<el-descriptions-item label="操作人">
{{ orderDetail.nickName || orderDetail.applicantName }}
</el-descriptions-item>
<el-descriptions-item label="出库时间">
<el-descriptions-item label="操作时间">
{{ orderDetail.applyTime }}
</el-descriptions-item>
<el-descriptions-item label="预计发货日期">
......@@ -263,11 +263,11 @@ function handlePrint() {
<span>${orderData.totalAmount}</span>
</div>
<div class="info-row">
<span class="info-label">出库人:</span>
<span class="info-label">操作人:</span>
<span>${orderData.applicantUser?.nickName || orderData.nickName || orderData.applicantName || ''}</span>
</div>
<div class="info-row">
<span class="info-label">出库时间:</span>
<span class="info-label">操作时间:</span>
<span>${orderData.applyTime ? new Date(orderData.applyTime).toLocaleDateString() : ''}</span>
</div>
<div class="info-row">
......
......@@ -83,7 +83,7 @@
<dict-tag :options="out_order_status" :value="scope.row.orderStatus" />
</template>
</el-table-column>
<el-table-column label="出库人" align="center" prop="applicantUser.nickName" />
<el-table-column label="操作人" align="center" prop="applicantUser.nickName" />
<el-table-column label="操作时间" align="center" prop="applyTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
......@@ -165,7 +165,7 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出库人" prop="applicantId">
<el-form-item label="操作人" prop="applicantId">
<el-input v-model="form.applicantName" placeholder="请输入申请人" />
</el-form-item>
</el-col>
......@@ -870,7 +870,7 @@ function handlePrint(row) {
<span>${orderData.totalAmount}</span>
</div>
<div class="info-row">
<span class="info-label">出库人:</span>
<span class="info-label">操作人:</span>
<span>${orderData.applicantUser?.nickName || orderData.applicantName || orderData.applicantId || ''}</span>
</div>
<div class="info-row">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment