Files
tiantian-system/app/pages/warehouse.vue
赵忠林 ecdc1cc986 feat(collaboration): 添加完整协同办公模块及设备管理和文档协同页面
- 新增现代化企业协同办公系统,包含概览仪表板、项目管理、任务看板和文档协同
- 使用Vue 3、TypeScript、Nuxt.js及Ant Design Vue实现前端结构和交互
- 设计响应式布局、渐变背景及毛玻璃视觉效果,优化移动端体验
- 创建设备管理页面,实现设备台账、巡检、维修和报警管理
- 新建文档协同页面,支持文档搜索、筛选、分类显示及多种视图切换
- 配置协同办公导航及布局文件,完善模块化和组件化架构
- 修复管理页语法错误,完善演示数据和统计信息展示
- 提供新建、导入、分享、重命名和删除文档等核心操作功能
- 添加设备健康度及维修记录展示模块,方便车间设备管理和维护
- 更新.gitignore忽略输出目录,提升项目环境整洁性
2026-04-09 12:13:35 +08:00

329 lines
17 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.

<template>
<div class="flex min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
<aside class="w-64 fixed h-full text-white flex flex-col sidebar">
<div class="p-6 border-b border-white/10">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center">
<BlockOutlined class="text-xl" />
</div>
<div>
<h1 class="font-bold text-lg">DEMO演示系统</h1>
<p class="text-xs text-white/70">ERP 管理平台</p>
</div>
</div>
</div>
<nav class="flex-1 py-6 px-3">
<div class="space-y-1">
<NuxtLink to="/" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<HomeOutlined class="text-base" /><span>工作台</span>
</NuxtLink>
<NuxtLink to="/device" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<SettingOutlined class="text-base" /><span>设备管理</span>
</NuxtLink>
<NuxtLink to="/procurement" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<ShoppingCartOutlined class="text-base" /><span>采购管理</span>
</NuxtLink>
<NuxtLink to="/warehouse" class="sidebar-item active flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<InboxOutlined class="text-base" /><span>仓储物流</span>
</NuxtLink>
<NuxtLink to="/finance" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<WalletOutlined class="text-base" /><span>财务管理</span>
</NuxtLink>
<NuxtLink to="/hr" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<TeamOutlined class="text-base" /><span>人力资源</span>
</NuxtLink>
<NuxtLink to="/office" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<ProjectOutlined class="text-base" /><span>协同办公</span>
</NuxtLink>
</div>
<div class="mt-8 pt-6 border-t border-white/10">
<p class="px-4 text-xs text-white/50 mb-3">个人</p>
<a href="#" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<UserOutlined class="text-base" /><span>个人信息</span>
</a>
<a href="#" class="sidebar-item flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer">
<SettingOutlined class="text-base" /><span>系统设置</span>
</a>
</div>
</nav>
<div class="p-4 border-t border-white/10">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center"><UserOutlined /></div>
<div class="flex-1">
<p class="font-medium">管理员</p>
<p class="text-xs text-white/70">超级管理员</p>
</div>
<button class="text-white/70 hover:text-white"><LogoutOutlined /></button>
</div>
</div>
</aside>
<main class="flex-1 ml-64">
<header class="bg-white/85 backdrop-blur-xl sticky top-0 z-50 px-8 py-4 border-b border-white/30">
<div class="flex items-center justify-between">
<div class="relative w-96">
<a-input v-model:value="searchKeyword" placeholder="搜索物料名称、编码、位置..." size="large">
<template #prefix><SearchOutlined class="text-gray-400" /></template>
</a-input>
</div>
<div class="flex items-center gap-6">
<button class="text-gray-500 hover:text-purple-600 transition-colors">
<FullscreenOutlined class="text-lg" />
</button>
<a-badge count="2" :offset="[-2, 2]">
<BellOutlined class="text-gray-500 hover:text-purple-600 transition-colors text-lg cursor-pointer" />
</a-badge>
<div class="flex items-center gap-3 pl-6 border-l border-gray-200">
<a-avatar class="bg-gradient-to-br from-purple-500 to-pink-500">A</a-avatar>
<div>
<p class="font-medium text-gray-800">Admin</p>
<p class="text-xs text-gray-500">超级管理员</p>
</div>
</div>
</div>
</div>
</header>
<div class="p-8">
<div class="mb-8">
<div class="flex items-center gap-3 mb-2">
<NuxtLink to="/" class="text-gray-500 hover:text-purple-600 transition-colors">
<ArrowLeftOutlined />
</NuxtLink>
<h2 class="text-3xl font-bold text-gray-800">仓储物流</h2>
</div>
<p class="text-gray-500">管理物料入库出库库存盘点及物流配送</p>
</div>
<!-- 数据概览 -->
<div class="grid grid-cols-4 gap-6 mb-8">
<div class="stat-card blue bg-white rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
<InboxOutlined class="text-blue-600 text-xl" />
</div>
<span class="text-green-500 text-sm font-medium flex items-center gap-1"><ArrowUpOutlined /> 6%</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">5,230</h3>
<p class="text-gray-500 text-sm">库存物料种类</p>
</div>
<div class="stat-card green bg-white rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center">
<ImportOutlined class="text-green-600 text-xl" />
</div>
<span class="text-green-500 text-sm font-medium flex items-center gap-1"><ArrowUpOutlined /> 15%</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">48</h3>
<p class="text-gray-500 text-sm">本月入库单</p>
</div>
<div class="stat-card orange bg-white rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-orange-100 rounded-xl flex items-center justify-center">
<ExportOutlined class="text-orange-600 text-xl" />
</div>
<span class="text-green-500 text-sm font-medium flex items-center gap-1"><ArrowUpOutlined /> 9%</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">62</h3>
<p class="text-gray-500 text-sm">本月出库单</p>
</div>
<div class="stat-card red bg-white rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-red-100 rounded-xl flex items-center justify-center">
<WarningOutlined class="text-red-600 text-xl" />
</div>
<span class="text-red-500 text-sm font-medium">需补货</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">12</h3>
<p class="text-gray-500 text-sm">库存预警</p>
</div>
</div>
<!-- 操作栏 -->
<div class="bg-white/85 backdrop-blur-xl rounded-2xl p-4 mb-8 shadow-sm">
<div class="flex items-center justify-between">
<div class="flex gap-3">
<a-button type="primary" class="bg-gradient-to-r from-purple-600 to-purple-700 border-0">
<PlusOutlined />新建入库单
</a-button>
<a-button><ExportOutlined class="text-orange-500" />新建出库单</a-button>
<a-button><AuditOutlined class="text-blue-500" />库存盘点</a-button>
</div>
<div class="flex gap-3">
<a-button><FilterOutlined class="text-gray-500" />筛选</a-button>
<a-button><ExportOutlined class="text-green-500" />导出报表</a-button>
</div>
</div>
</div>
<!-- 标签页 -->
<div class="bg-white/85 backdrop-blur-xl rounded-2xl p-2 mb-8 inline-flex shadow-sm">
<a-segmented v-model:value="activeTab" :options="tabOptions" />
</div>
<!-- 库存列表 -->
<div class="bg-white/85 backdrop-blur-xl rounded-2xl overflow-hidden mb-8 shadow-sm">
<div class="p-6 border-b border-gray-100">
<div class="flex items-center justify-between">
<h3 class="font-bold text-lg text-gray-800 flex items-center gap-2">
<UnorderedListOutlined class="text-purple-500" />
物料库存列表
<a-tag color="purple">5,230</a-tag>
</h3>
<a-input-search v-model:value="listSearchKeyword" placeholder="搜索物料编码、名称..." class="w-64" />
</div>
</div>
<a-table :dataSource="stockData" :columns="columns" :pagination="false">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'material'">
<div>
<p class="font-medium text-gray-800">{{ record.name }}</p>
<p class="text-xs text-gray-500">{{ record.code }}</p>
</div>
</template>
<template v-if="column.key === 'stock'">
<span :class="record.qty < record.minQty ? 'text-red-600 font-bold' : 'text-gray-800'">
{{ record.qty }} {{ record.unit }}
</span>
</template>
<template v-if="column.key === 'warning'">
<a-tag v-if="record.qty < record.minQty" color="error"><WarningOutlined /> 库存不足</a-tag>
<a-tag v-else color="success"><CheckCircleOutlined /> 正常</a-tag>
</template>
<template v-if="column.key === 'action'">
<div class="flex items-center justify-center gap-2">
<a-button type="text" size="small" class="text-blue-600"><EyeOutlined /></a-button>
<a-button type="text" size="small" class="text-green-600"><ImportOutlined /></a-button>
<a-button type="text" size="small" class="text-orange-600"><ExportOutlined /></a-button>
</div>
</template>
</template>
</a-table>
<div class="p-6 border-t border-gray-100">
<div class="flex items-center justify-between">
<p class="text-sm text-gray-500">显示 1-6 5,230 </p>
<a-pagination v-model:current="currentPage" :total="5230" :pageSize="6" show-less-items />
</div>
</div>
</div>
<!-- 底部 -->
<div class="grid grid-cols-2 gap-6">
<!-- 出入库趋势 -->
<div class="bg-white/85 backdrop-blur-xl rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-6">
<h3 class="font-bold text-lg text-gray-800 flex items-center gap-2">
<LineChartOutlined class="text-purple-500" />出入库趋势
</h3>
</div>
<div class="space-y-4">
<div v-for="trend in stockTrends" :key="trend.month">
<div class="flex items-center justify-between mb-1">
<span class="text-sm text-gray-600">{{ trend.month }}</span>
<span class="text-sm font-medium text-gray-800">入库 {{ trend.inQty }} / 出库 {{ trend.outQty }}</span>
</div>
<div class="flex gap-1 h-3">
<div class="rounded-full bg-blue-400" :style="{ width: trend.inPercent + '%' }"></div>
<div class="rounded-full bg-orange-400" :style="{ width: trend.outPercent + '%' }"></div>
</div>
</div>
<div class="flex gap-4 text-xs text-gray-500 pt-2">
<span class="flex items-center gap-1"><span class="w-3 h-3 rounded-full bg-blue-400 inline-block"></span>入库</span>
<span class="flex items-center gap-1"><span class="w-3 h-3 rounded-full bg-orange-400 inline-block"></span>出库</span>
</div>
</div>
</div>
<!-- 库存预警 -->
<div class="bg-white/85 backdrop-blur-xl rounded-2xl p-6 card-hover shadow-sm">
<div class="flex items-center justify-between mb-6">
<h3 class="font-bold text-lg text-gray-800 flex items-center gap-2">
<WarningOutlined class="text-red-500" />库存预警
</h3>
<a-badge count="12" />
</div>
<div class="space-y-3">
<div v-for="w in warnings" :key="w.code" class="flex items-center gap-4 p-3 bg-red-50 rounded-xl border border-red-100">
<div class="w-10 h-10 bg-red-100 rounded-xl flex items-center justify-center flex-shrink-0">
<WarningOutlined class="text-red-600" />
</div>
<div class="flex-1">
<p class="font-medium text-gray-800">{{ w.name }}</p>
<p class="text-xs text-gray-500">当前库存{{ w.current }} / 最低要求{{ w.min }}</p>
</div>
<a-button type="primary" size="small" danger>申请补货</a-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import {
HomeOutlined, SettingOutlined, ShoppingCartOutlined, InboxOutlined, WalletOutlined,
TeamOutlined, ProjectOutlined, UserOutlined, LogoutOutlined, SearchOutlined,
FullscreenOutlined, BellOutlined, ArrowLeftOutlined, ArrowUpOutlined,
PlusOutlined, ExportOutlined, ImportOutlined, FilterOutlined, UnorderedListOutlined,
CheckCircleOutlined, WarningOutlined, EyeOutlined, LineChartOutlined,
BlockOutlined, AuditOutlined,
} from '@ant-design/icons-vue'
definePageMeta({ layout: 'blank' })
const searchKeyword = ref('')
const listSearchKeyword = ref('')
const currentPage = ref(1)
const activeTab = ref('库存管理')
const tabOptions = ['库存管理', '入库管理', '出库管理', '库存盘点', '物流配送']
const columns = [
{ title: '物料信息', key: 'material' },
{ title: '类别', dataIndex: 'category', key: 'category' },
{ title: '存放位置', dataIndex: 'location', key: 'location' },
{ title: '当前库存', key: 'stock' },
{ title: '单价', dataIndex: 'price', key: 'price' },
{ title: '库存状态', key: 'warning' },
{ title: '操作', key: 'action', align: 'center' },
]
const stockData = [
{ key: '1', name: '304不锈钢板 2mm', code: 'WL-STL-001', category: '原材料', location: 'A区-01-03', qty: 850, minQty: 500, unit: 'kg', price: '¥38/kg' },
{ key: '2', name: '铝合金型材 6061', code: 'WL-ALU-008', category: '原材料', location: 'A区-02-01', qty: 120, minQty: 200, unit: 'kg', price: '¥45/kg' },
{ key: '3', name: '标准六角螺栓 M8', code: 'WL-BLT-023', category: '标准件', location: 'B区-05-12', qty: 5800, minQty: 1000, unit: '个', price: '¥0.5/个' },
{ key: '4', name: 'ABS工程塑料颗粒', code: 'WL-PLT-006', category: '原材料', location: 'C区-03-08', qty: 380, minQty: 400, unit: 'kg', price: '¥28/kg' },
{ key: '5', name: '密封圈 O型 20mm', code: 'WL-SL-047', category: '耗材', location: 'B区-08-04', qty: 1200, minQty: 500, unit: '个', price: '¥2.5/个' },
{ key: '6', name: '导轨润滑油脂', code: 'WL-OIL-012', category: '耗材', location: 'D区-01-02', qty: 45, minQty: 50, unit: 'kg', price: '¥85/kg' },
]
const stockTrends = [
{ month: '1月', inQty: 380, outQty: 320, inPercent: 55, outPercent: 45 },
{ month: '2月', inQty: 290, outQty: 260, inPercent: 52, outPercent: 48 },
{ month: '3月', inQty: 420, outQty: 390, inPercent: 51, outPercent: 49 },
{ month: '4月', inQty: 480, outQty: 620, inPercent: 43, outPercent: 57 },
]
const warnings = [
{ code: 'WL-ALU-008', name: '铝合金型材 6061', current: '120kg', min: '200kg' },
{ code: 'WL-PLT-006', name: 'ABS工程塑料颗粒', current: '380kg', min: '400kg' },
{ code: 'WL-OIL-012', name: '导轨润滑油脂', current: '45kg', min: '50kg' },
]
</script>
<style scoped>
.sidebar { background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); }
.sidebar-item { color: white; }
.sidebar-item:hover, .sidebar-item.active { background: rgba(255,255,255,0.2); }
.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); }
.stat-card { position: relative; overflow: hidden; }
.stat-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; }
.stat-card.blue::before { background: linear-gradient(90deg, #667eea, #764ba2); }
.stat-card.green::before { background: linear-gradient(90deg, #11998e, #38ef7d); }
.stat-card.orange::before { background: linear-gradient(90deg, #f093fb, #f5576c); }
.stat-card.red::before { background: linear-gradient(90deg, #f5576c, #ff4d4f); }
</style>