154 lines
5.6 KiB
Vue
154 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, onUnmounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useAppStore } from '@/stores/app'
|
|
import { addNotification } from '@/shared/notification'
|
|
import { useInventory } from '@/modules/inventory/composables/useInventory'
|
|
|
|
import InventorySidebar from './components/InventorySidebar.vue'
|
|
import DashboardTab from './components/DashboardTab.vue'
|
|
import ProductsTab from './components/ProductsTab.vue'
|
|
import MaterialsTab from './components/MaterialsTab.vue'
|
|
import InventoryTab from './components/InventoryTab.vue'
|
|
import PurchaseOrdersTab from './components/PurchaseOrdersTab.vue'
|
|
import SalesOrdersTab from './components/SalesOrdersTab.vue'
|
|
import SuppliersTab from './components/SuppliersTab.vue'
|
|
import CustomersTab from './components/CustomersTab.vue'
|
|
import FinanceTab from './components/FinanceTab.vue'
|
|
import MovementsTab from './components/MovementsTab.vue'
|
|
|
|
const store = useAppStore()
|
|
const router = useRouter()
|
|
const {
|
|
state,
|
|
activeMenu,
|
|
checkBackendHealth,
|
|
loadDashboard,
|
|
destroyPickers
|
|
} = useInventory()
|
|
|
|
onMounted(() => {
|
|
if (!store.user) {
|
|
router.push('/login')
|
|
return
|
|
}
|
|
checkBackendHealth().then(() => {
|
|
if (!state.backendDbReady) {
|
|
addNotification(state.backendDbMessage || '业务服务不可用', 'warning')
|
|
return
|
|
}
|
|
loadDashboard()
|
|
})
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
destroyPickers()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page-container">
|
|
<div class="page-header">
|
|
<h1>进销存管理</h1>
|
|
<p>库存、采购、销售管理</p>
|
|
</div>
|
|
|
|
<div class="inventory-layout">
|
|
<aside class="inventory-sidebar-wrapper">
|
|
<InventorySidebar />
|
|
</aside>
|
|
|
|
<section class="inventory-content">
|
|
<div class="inventory-content-header">
|
|
<div class="inventory-breadcrumb">
|
|
<span>{{ activeMenu?.group?.title || '进销存' }}</span>
|
|
<span class="sep">/</span>
|
|
<span class="current">{{ activeMenu?.item?.label || '' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!state.backendDbReady" class="inline-alert inline-alert-warning">
|
|
<div class="inline-alert-title">提示</div>
|
|
<div class="inline-alert-message">{{ state.backendDbMessage }}</div>
|
|
</div>
|
|
|
|
<div v-if="state.loading" class="loading-state">
|
|
<div class="spinner"></div>
|
|
<span>加载中...</span>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<DashboardTab v-if="state.activeTab === 'dashboard'" />
|
|
<ProductsTab v-else-if="state.activeTab === 'products'" />
|
|
<MaterialsTab v-else-if="state.activeTab === 'materials'" />
|
|
<InventoryTab v-else-if="state.activeTab === 'inventory'" />
|
|
<PurchaseOrdersTab v-else-if="state.activeTab === 'purchases'" />
|
|
<SalesOrdersTab v-else-if="state.activeTab === 'sales_orders'" />
|
|
<SuppliersTab v-else-if="state.activeTab === 'suppliers'" />
|
|
<CustomersTab v-else-if="state.activeTab === 'customers'" />
|
|
<FinanceTab v-else-if="state.activeTab === 'finance'" />
|
|
<MovementsTab v-else-if="state.activeTab === 'movements'" />
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page-container { padding: 0; }
|
|
.page-header { margin-bottom: var(--space-6); }
|
|
.page-header h1 { font-size: var(--text-2xl); font-weight: var(--font-bold); color: var(--text-primary); margin: 0; }
|
|
.page-header p { color: var(--text-tertiary); margin: var(--space-1) 0 0; font-size: var(--text-sm); }
|
|
|
|
.inventory-layout { display: flex; gap: var(--space-6); min-height: calc(100vh - 200px); }
|
|
.inventory-sidebar-wrapper { width: 200px; flex-shrink: 0; }
|
|
.inventory-content { flex: 1; min-width: 0; }
|
|
|
|
.inventory-content-header { margin-bottom: var(--space-4); }
|
|
.inventory-breadcrumb { font-size: var(--text-sm); color: var(--text-tertiary); }
|
|
.inventory-breadcrumb .sep { margin: 0 var(--space-2); }
|
|
.inventory-breadcrumb .current { color: var(--text-primary); font-weight: var(--font-medium); }
|
|
|
|
.inline-alert { padding: var(--space-4); border-radius: var(--radius-md); margin-bottom: var(--space-4); }
|
|
.inline-alert-warning { background: var(--warning-bg); border: 1px solid var(--warning); }
|
|
.inline-alert-title { font-weight: var(--font-semibold); margin-bottom: var(--space-1); }
|
|
.inline-alert-message { font-size: var(--text-sm); }
|
|
|
|
.loading-state { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-8); justify-content: center; color: var(--text-tertiary); }
|
|
.spinner { width: 20px; height: 20px; border: 2px solid var(--border-default); border-top-color: var(--primary-500); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.inventory-sidebar-wrapper :deep(.el-menu) {
|
|
border-right: none;
|
|
background: transparent;
|
|
}
|
|
.inventory-sidebar-wrapper :deep(.sidebar-group-title) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--space-3) var(--space-4);
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-tertiary);
|
|
text-transform: uppercase;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.inventory-sidebar-wrapper :deep(.el-menu-item) {
|
|
height: 36px;
|
|
line-height: 36px;
|
|
font-size: var(--text-sm);
|
|
margin: 2px var(--space-2);
|
|
border-radius: var(--radius-md);
|
|
padding: 0 var(--space-4);
|
|
}
|
|
.inventory-sidebar-wrapper :deep(.el-menu-item.is-active) {
|
|
background: var(--primary-50);
|
|
color: var(--primary-600);
|
|
font-weight: var(--font-medium);
|
|
}
|
|
.inventory-sidebar-wrapper :deep(.el-menu-item:hover) {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
</style>
|