Files
tiantian-system/app/pages/admin/supply/warehouse.vue
赵忠林 f9e1286ab1 refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
2026-04-09 07:35:34 +08:00

463 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
definePageMeta({ layout: 'admin' })
// 仓储统计
const warehouseStats = ref([
{ label: '物料种类', value: 5230, icon: 'fa-boxes', gradient: 'from-blue-500 to-cyan-500', change: '+126', up: true },
{ label: '库存总量', value: '8.5', unit: '万', icon: 'fa-warehouse', gradient: 'from-green-500 to-teal-500', change: '+1.2万', up: true },
{ label: '待入库', value: 45, icon: 'fa-arrow-down', gradient: 'from-orange-500 to-yellow-500', change: '+12', up: false },
{ label: '待出库', value: 28, icon: 'fa-truck', gradient: 'from-purple-500 to-pink-500', change: '-8', up: true },
])
// 库存列表
const inventoryList = ref([
{ code: 'MAT-001', name: '轴承组件 A型', category: '标准件', warehouse: 'A区', location: 'A-01-03', stock: 1500, safeStock: 500, unit: '套', status: 'normal' },
{ code: 'MAT-002', name: '铝合金板材', category: '原材料', warehouse: 'B区', location: 'B-02-05', stock: 320, safeStock: 200, unit: '张', status: 'normal' },
{ code: 'MAT-003', name: '液压缸体 B型', category: '半成品', warehouse: 'C区', location: 'C-01-02', stock: 80, safeStock: 100, unit: '件', status: 'low' },
{ code: 'MAT-004', name: '数控刀具套装', category: '工装', warehouse: 'D区', location: 'D-03-01', stock: 45, safeStock: 20, unit: '套', status: 'normal' },
{ code: 'MAT-005', name: '密封圈组件', category: '标准件', warehouse: 'A区', location: 'A-02-01', stock: 2800, safeStock: 1000, unit: '个', status: 'normal' },
{ code: 'MAT-006', name: '传动齿轮组 C型', category: '零部件', warehouse: 'C区', location: 'C-02-03', stock: 150, safeStock: 200, unit: '组', status: 'low' },
])
const statusMap: Record<string, { label: string; color: string; bg: string }> = {
normal: { label: '正常', color: 'text-green-600', bg: 'bg-green-100' },
low: { label: '偏低', color: 'text-orange-600', bg: 'bg-orange-100' },
out: { label: '缺货', color: 'text-red-600', bg: 'bg-red-100' },
over: { label: '超储', color: 'text-blue-600', bg: 'bg-blue-100' },
}
// 入库记录
const inboundRecords = ref([
{ id: 'IN-2026040901', material: '轴承组件 A型', quantity: 500, unit: '套', type: '采购入库', operator: '仓管员-张三', date: '2026-04-09 09:30', status: 'completed' },
{ id: 'IN-2026040802', material: '铝合金板材', quantity: 200, unit: '张', type: '采购入库', operator: '仓管员-李四', date: '2026-04-08 14:20', status: 'completed' },
{ id: 'IN-2026040801', material: '数控刀具套装', quantity: 20, unit: '套', type: '采购入库', operator: '仓管员-张三', date: '2026-04-08 10:15', status: 'completed' },
{ id: 'IN-2026040703', material: '工业润滑油', quantity: 50, unit: '桶', type: '采购入库', operator: '仓管员-王五', date: '2026-04-07 16:45', status: 'completed' },
])
// 出库记录
const outboundRecords = ref([
{ id: 'OUT-2026040901', material: '轴承组件 A型', quantity: 100, unit: '套', type: '生产领料', recipient: '1号车间', operator: '仓管员-张三', date: '2026-04-09 08:30', status: 'completed' },
{ id: 'OUT-2026040902', material: '密封圈组件', quantity: 200, unit: '个', type: '生产领料', recipient: '2号车间', operator: '仓管员-李四', date: '2026-04-09 10:20', status: 'completed' },
{ id: 'OUT-2026040801', material: '铝合金板材', quantity: 50, unit: '张', type: '生产领料', recipient: '3号车间', operator: '仓管员-王五', date: '2026-04-08 15:30', status: 'completed' },
])
const activeTab = ref('inventory')
const searchKeyword = ref('')
const filteredInventory = computed(() => {
return inventoryList.value.filter((item) => {
return !searchKeyword.value ||
item.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.code.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.category.toLowerCase().includes(searchKeyword.value.toLowerCase())
})
})
// 入库表单
const inboundVisible = ref(false)
const inboundForm = reactive({
material: '',
quantity: '',
unit: '',
type: 'purchase',
supplier: '',
remark: '',
})
function handleInbound() {
inboundVisible.value = false
message.success('入库登记成功')
}
</script>
<template>
<div class="warehouse-page">
<!-- 页面标题 -->
<div class="page-header mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-800">仓储物流</h2>
<p class="text-gray-500 mt-1">管理库存物料跟踪出入库记录</p>
</div>
<div class="flex gap-3">
<a-button @click="inboundVisible = true">
<template #icon><i class="fas fa-arrow-down mr-1"></i></template>
入库登记
</a-button>
<a-button type="primary">
<template #icon><i class="fas fa-arrow-up mr-1"></i></template>
出库登记
</a-button>
</div>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="stat in warehouseStats"
:key="stat.label"
class="glass rounded-2xl p-6 card-hover cursor-pointer"
>
<div class="flex items-center justify-between mb-4">
<div
class="w-12 h-12 rounded-xl flex items-center justify-center text-white"
:class="`bg-gradient-to-br ${stat.gradient}`"
>
<i :class="`fas ${stat.icon}`"></i>
</div>
<span
class="text-sm font-medium"
:class="stat.up ? 'text-green-500' : 'text-red-500'"
>
<i :class="stat.up ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ stat.change }}
</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">
{{ stat.value }}<span v-if="stat.unit" class="text-base text-gray-500 ml-1">{{ stat.unit }}</span>
</h3>
<p class="text-gray-500 text-sm">{{ stat.label }}</p>
</div>
</div>
<!-- Tab切换 -->
<div class="glass rounded-2xl p-4 mb-6">
<a-radio-group v-model:value="activeTab" button-style="solid">
<a-radio-button value="inventory">
<i class="fas fa-boxes mr-1"></i>库存查询
</a-radio-button>
<a-radio-button value="inbound">
<i class="fas fa-arrow-down mr-1"></i>入库记录
</a-radio-button>
<a-radio-button value="outbound">
<i class="fas fa-arrow-up mr-1"></i>出库记录
</a-radio-button>
</a-radio-group>
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索物料名称、编号..."
style="width: 280px; float: right"
allow-clear
/>
</div>
<!-- 库存列表 -->
<div v-show="activeTab === 'inventory'" class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-boxes text-blue-500 mr-2"></i>
库存列表
</h3>
<span class="text-sm text-gray-500"> {{ filteredInventory.length }} 种物料</span>
</div>
<a-table
:dataSource="filteredInventory"
:pagination="{ pageSize: 10 }"
rowKey="code"
:scroll="{ x: 1100 }"
>
<a-table-column title="物料编码" dataIndex="code" width="110" />
<a-table-column title="物料名称" dataIndex="name" width="160">
<template #default="{ text }">
<span class="font-medium">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="分类" dataIndex="category" width="100" />
<a-table-column title="仓库" dataIndex="warehouse" width="80" align="center" />
<a-table-column title="库位" dataIndex="location" width="100" align="center" />
<a-table-column title="库存量" dataIndex="stock" width="120" align="right">
<template #default="{ record }">
<span class="font-medium">{{ record.stock.toLocaleString() }} {{ record.unit }}</span>
</template>
</a-table-column>
<a-table-column title="安全库存" dataIndex="safeStock" width="100" align="right">
<template #default="{ record }">
<span class="text-gray-500">{{ record.safeStock }} {{ record.unit }}</span>
</template>
</a-table-column>
<a-table-column title="状态" dataIndex="status" width="100" align="center">
<template #default="{ text }">
<span
:class="statusMap[text]?.color"
class="px-2 py-1 rounded-lg text-sm font-medium"
:class="statusMap[text]?.bg"
>
{{ statusMap[text]?.label }}
</span>
</template>
</a-table-column>
<a-table-column title="操作" width="120" align="center" fixed="right">
<template #default>
<div class="flex items-center gap-2 justify-center">
<a-button type="link" size="small" title="详情">
<i class="fas fa-eye"></i>
</a-button>
<a-button type="link" size="small" title="调整">
<i class="fas fa-edit"></i>
</a-button>
</div>
</template>
</a-table-column>
</a-table>
</div>
<!-- 入库记录 -->
<div v-show="activeTab === 'inbound'" class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-arrow-down text-green-500 mr-2"></i>
入库记录
</h3>
</div>
<a-table
:dataSource="inboundRecords"
:pagination="{ pageSize: 10 }"
rowKey="id"
>
<a-table-column title="入库单号" dataIndex="id" width="140" />
<a-table-column title="物料名称" dataIndex="material" width="160">
<template #default="{ text }">
<span class="font-medium">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="数量" width="120" align="center">
<template #default="{ record }">
<span class="text-green-600 font-medium">+{{ record.quantity }} {{ record.unit }}</span>
</template>
</a-table-column>
<a-table-column title="入库类型" dataIndex="type" width="120">
<template #default="{ text }">
<a-tag :color="text === '采购入库' ? 'blue' : 'purple'">{{ text }}</a-tag>
</template>
</a-table-column>
<a-table-column title="操作员" dataIndex="operator" width="120" />
<a-table-column title="入库时间" dataIndex="date" width="160" />
<a-table-column title="状态" width="100" align="center">
<template #default>
<a-tag color="success">已完成</a-tag>
</template>
</a-table-column>
</a-table>
</div>
<!-- 出库记录 -->
<div v-show="activeTab === 'outbound'" class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-arrow-up text-orange-500 mr-2"></i>
出库记录
</h3>
</div>
<a-table
:dataSource="outboundRecords"
:pagination="{ pageSize: 10 }"
rowKey="id"
>
<a-table-column title="出库单号" dataIndex="id" width="140" />
<a-table-column title="物料名称" dataIndex="material" width="160">
<template #default="{ text }">
<span class="font-medium">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="数量" width="120" align="center">
<template #default="{ record }">
<span class="text-orange-600 font-medium">-{{ record.quantity }} {{ record.unit }}</span>
</template>
</a-table-column>
<a-table-column title="出库类型" dataIndex="type" width="120">
<template #default="{ text }">
<a-tag :color="text === '生产领料' ? 'orange' : 'cyan'">{{ text }}</a-tag>
</template>
</a-table-column>
<a-table-column title="领用部门" dataIndex="recipient" width="120" />
<a-table-column title="操作员" dataIndex="operator" width="120" />
<a-table-column title="出库时间" dataIndex="date" width="160" />
<a-table-column title="状态" width="100" align="center">
<template #default>
<a-tag color="success">已完成</a-tag>
</template>
</a-table-column>
</a-table>
</div>
<!-- 入库登记弹窗 -->
<a-modal
v-model:open="inboundVisible"
title="入库登记"
@ok="handleInbound"
ok-text="确认入库"
cancel-text="取消"
>
<a-form :model="inboundForm" layout="vertical">
<a-form-item label="物料" required>
<a-select v-model:value="inboundForm.material" placeholder="请选择物料">
<a-select-option v-for="item in inventoryList" :key="item.code" :value="item.name">
{{ item.name }} ({{ item.code }})
</a-select-option>
</a-select>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="数量" required>
<a-input-number v-model:value="inboundForm.quantity" style="width: 100%" :min="1" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="单位">
<a-input v-model:value="inboundForm.unit" placeholder="如:套、件、个" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="入库类型">
<a-radio-group v-model:value="inboundForm.type">
<a-radio value="purchase">采购入库</a-radio>
<a-radio value="return">退货入库</a-radio>
<a-radio value="transfer">调拨入库</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="供应商" v-if="inboundForm.type === 'purchase'">
<a-input v-model:value="inboundForm.supplier" placeholder="请输入供应商名称" />
</a-form-item>
<a-form-item label="备注">
<a-textarea v-model:value="inboundForm.remark" :rows="2" placeholder="请输入备注信息" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<style scoped>
.warehouse-page {
padding: 0;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.glass {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.grid {
display: grid;
}
.grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 1200px) {
.grid-cols-4 {
grid-template-columns: repeat(2, 1fr);
}
}
.mb-6 {
margin-bottom: 24px;
}
.mb-4 {
margin-bottom: 16px;
}
.mr-1 {
margin-right: 4px;
}
.mr-2 {
margin-right: 8px;
}
.ml-1 {
margin-left: 4px;
}
.text-2xl {
font-size: 24px;
}
.text-3xl {
font-size: 30px;
}
.text-lg {
font-size: 18px;
}
.text-sm {
font-size: 14px;
}
.text-base {
font-size: 16px;
}
.rounded-2xl {
border-radius: 16px;
}
.p-6 {
padding: 24px;
}
.p-4 {
padding: 16px;
}
.gap-6 {
gap: 24px;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.font-bold {
font-weight: 700;
}
.font-medium {
font-weight: 500;
}
.text-gray-800 {
color: #1f2937;
}
.text-gray-500 {
color: #6b7280;
}
.text-green-600 {
color: #16a34a;
}
.text-orange-600 {
color: #ea580c;
}
</style>