This commit is contained in:
2026-03-15 22:04:28 +08:00
parent 48853a988d
commit 66f2bd2daa
6 changed files with 721 additions and 35 deletions
+235 -20
View File
@@ -1474,9 +1474,17 @@ const InventoryView = {
activeTab: 'dashboard',
dashboard: null,
financeSummary: null,
financePeriod: {
year: new Date().getFullYear(),
quarter: ''
},
financeTransactions: [],
receivables: [],
payables: [],
customerFinanceStatement: [],
supplierFinanceStatement: [],
customerProductStatement: [],
supplierProductStatement: [],
products: [],
suppliers: [],
customers: [],
@@ -1512,6 +1520,17 @@ const InventoryView = {
}
};
const loadWarehouses = async () => {
state.loading = true;
try {
state.warehouses = await apiRequest('/api/warehouses');
} catch (e) {
handleApiError(e, '加载仓库');
} finally {
state.loading = false;
}
};
const loadSuppliers = async () => {
state.loading = true;
try {
@@ -1559,16 +1578,39 @@ const InventoryView = {
const loadFinance = async () => {
state.loading = true;
try {
const [summary, transactions, receivables, payables] = await Promise.all([
apiRequest('/api/finance/summary'),
apiRequest('/api/finance/transactions?status=confirmed&limit=20'),
apiRequest('/api/finance/receivables?limit=20'),
apiRequest('/api/finance/payables?limit=20')
const selectedYear = Number(state.financePeriod.year) || new Date().getFullYear();
const selectedQuarter = state.financePeriod.quarter ? Number(state.financePeriod.quarter) : null;
const periodQuery = selectedQuarter
? `year=${selectedYear}&quarter=${selectedQuarter}`
: `year=${selectedYear}`;
const [
summary,
transactions,
receivables,
payables,
customerStatement,
supplierStatement,
customerProductStatement,
supplierProductStatement
] = await Promise.all([
apiRequest(`/api/finance/summary?${periodQuery}`),
apiRequest(`/api/finance/transactions?status=confirmed&limit=20&${periodQuery}`),
apiRequest(`/api/finance/receivables?limit=20&${periodQuery}`),
apiRequest(`/api/finance/payables?limit=20&${periodQuery}`),
apiRequest(`/api/finance/partner-statement/customer?${periodQuery}`),
apiRequest(`/api/finance/partner-statement/supplier?${periodQuery}`),
apiRequest(`/api/finance/partner-product-statement/customer?${periodQuery}`),
apiRequest(`/api/finance/partner-product-statement/supplier?${periodQuery}`)
]);
state.financeSummary = summary;
state.financeTransactions = transactions;
state.receivables = receivables;
state.payables = payables;
state.customerFinanceStatement = customerStatement.items || [];
state.supplierFinanceStatement = supplierStatement.items || [];
state.customerProductStatement = customerProductStatement.items || [];
state.supplierProductStatement = supplierProductStatement.items || [];
} catch (e) {
handleApiError(e, '加载财务数据');
} finally {
@@ -1576,6 +1618,12 @@ const InventoryView = {
}
};
const refreshFinanceByPeriod = () => {
if (state.activeTab === 'finance') {
loadFinance();
}
};
const switchTab = (tab) => {
state.activeTab = tab;
switch (tab) {
@@ -1589,13 +1637,26 @@ const InventoryView = {
}
};
const openModal = (type, item = null) => {
const openModal = async (type, item = null) => {
state.modalType = type;
state.editingItem = item;
if (item) {
state.form = { ...item };
} else {
state.form = {};
if (type === 'stockIn' || type === 'stockOut') {
if (!state.products.length) {
await loadProducts();
}
if (!state.warehouses.length) {
await loadWarehouses();
}
state.form = {
product_id: state.products[0]?.id || null,
warehouse_id: state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
quantity: 1
};
}
}
state.showModal = true;
};
@@ -1759,7 +1820,8 @@ const InventoryView = {
saveCustomer,
deleteCustomer,
stockIn,
stockOut
stockOut,
refreshFinanceByPeriod
};
},
template: `
@@ -1967,6 +2029,27 @@ const InventoryView = {
</div>
<div v-else-if="state.activeTab === 'finance'">
<div class="table-container" style="margin-bottom: 16px;">
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
<div>
<label style="margin-right:8px;">年份</label>
<input v-model.number="state.financePeriod.year" type="number" min="2000" max="2100" class="form-input" style="width:120px; display:inline-block;" />
</div>
<div>
<label style="margin-right:8px;">季度</label>
<select v-model="state.financePeriod.quarter" class="form-input" style="width:140px; display:inline-block;">
<option value="">全年</option>
<option value="1">Q1</option>
<option value="2">Q2</option>
<option value="3">Q3</option>
<option value="4">Q4</option>
</select>
</div>
<button class="btn btn-primary" @click="refreshFinanceByPeriod">刷新统计</button>
<span style="color:var(--text-secondary);">统计周期:{{ state.financeSummary?.period_label || '-' }}</span>
</div>
</div>
<div class="dashboard-grid">
<div class="stat-card">
<div class="stat-icon">🧾</div>
@@ -1985,19 +2068,135 @@ const InventoryView = {
<div class="stat-card">
<div class="stat-icon">💵</div>
<div class="stat-content">
<div class="stat-value">{{ formatCurrency(state.financeSummary?.monthly_receipt_total || 0) }}</div>
<div class="stat-label">本月收款</div>
<div class="stat-value">{{ formatCurrency(state.financeSummary?.period_receipt_total || 0) }}</div>
<div class="stat-label">周期收款</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏦</div>
<div class="stat-content">
<div class="stat-value">{{ formatCurrency(state.financeSummary?.monthly_payment_total || 0) }}</div>
<div class="stat-label">本月付款</div>
<div class="stat-value">{{ formatCurrency(state.financeSummary?.period_payment_total || 0) }}</div>
<div class="stat-label">周期付款</div>
</div>
</div>
</div>
<div class="table-container" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px;">客户账款(周期)</h3>
<table class="data-table">
<thead>
<tr>
<th>客户</th>
<th>订单数</th>
<th>流水数</th>
<th>订单金额</th>
<th>订单已收</th>
<th>实收流水</th>
<th>应收余额</th>
</tr>
</thead>
<tbody>
<tr v-for="item in state.customerFinanceStatement" :key="'customer-' + item.partner_id">
<td>{{ item.partner_name }}</td>
<td>{{ item.order_count }}</td>
<td>{{ item.transaction_count }}</td>
<td>{{ formatCurrency(item.order_total) }}</td>
<td>{{ formatCurrency(item.settled_total) }}</td>
<td>{{ formatCurrency(item.transaction_total) }}</td>
<td>{{ formatCurrency(item.outstanding_total) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px;">供应商账款(周期)</h3>
<table class="data-table">
<thead>
<tr>
<th>供应商</th>
<th>订单数</th>
<th>流水数</th>
<th>订单金额</th>
<th>订单已付</th>
<th>实付流水</th>
<th>应付余额</th>
</tr>
</thead>
<tbody>
<tr v-for="item in state.supplierFinanceStatement" :key="'supplier-' + item.partner_id">
<td>{{ item.partner_name }}</td>
<td>{{ item.order_count }}</td>
<td>{{ item.transaction_count }}</td>
<td>{{ formatCurrency(item.order_total) }}</td>
<td>{{ formatCurrency(item.settled_total) }}</td>
<td>{{ formatCurrency(item.transaction_total) }}</td>
<td>{{ formatCurrency(item.outstanding_total) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px;">客户-商品追溯(周期)</h3>
<table class="data-table">
<thead>
<tr>
<th>客户</th>
<th>SKU</th>
<th>商品</th>
<th>订单数</th>
<th>数量</th>
<th>订单金额</th>
<th>已结款</th>
<th>未结款</th>
</tr>
</thead>
<tbody>
<tr v-for="item in state.customerProductStatement" :key="'customer-product-' + item.partner_id + '-' + item.product_id">
<td>{{ item.partner_name }}</td>
<td>{{ item.product_sku || '-' }}</td>
<td>{{ item.product_name }}</td>
<td>{{ item.order_count }}</td>
<td>{{ formatNumber(item.order_quantity) }}</td>
<td>{{ formatCurrency(item.order_amount) }}</td>
<td>{{ formatCurrency(item.settled_amount) }}</td>
<td>{{ formatCurrency(item.outstanding_amount) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px;">供应商-商品追溯(周期)</h3>
<table class="data-table">
<thead>
<tr>
<th>供应商</th>
<th>SKU</th>
<th>商品</th>
<th>订单数</th>
<th>数量</th>
<th>订单金额</th>
<th>已结款</th>
<th>未结款</th>
</tr>
</thead>
<tbody>
<tr v-for="item in state.supplierProductStatement" :key="'supplier-product-' + item.partner_id + '-' + item.product_id">
<td>{{ item.partner_name }}</td>
<td>{{ item.product_sku || '-' }}</td>
<td>{{ item.product_name }}</td>
<td>{{ item.order_count }}</td>
<td>{{ formatNumber(item.order_quantity) }}</td>
<td>{{ formatCurrency(item.order_amount) }}</td>
<td>{{ formatCurrency(item.settled_amount) }}</td>
<td>{{ formatCurrency(item.outstanding_amount) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" style="margin-top: 16px;">
<h3 style="margin-bottom: 12px;">最近财务流水</h3>
<table class="data-table">
@@ -2039,7 +2238,7 @@ const InventoryView = {
</thead>
<tbody>
<tr v-for="movement in state.movements" :key="movement.id">
<td>{{ movement.product_name }}</td>
<td>{{ movement.product_name }}{{ movement.product_sku ? ' (' + movement.product_sku + ')' : '' }}</td>
<td>
<span :class="['badge', movement.movement_type === 'in' ? 'badge-success' : movement.movement_type === 'out' ? 'badge-error' : 'badge-warning']">
{{ movement.movement_type === 'in' ? '入库' : movement.movement_type === 'out' ? '出库' : '调整' }}
@@ -2158,12 +2357,20 @@ const InventoryView = {
<!-- 入库表单 -->
<form v-else-if="state.modalType === 'stockIn'" @submit.prevent="stockIn">
<div class="form-group">
<label class="form-label">产品ID *</label>
<input v-model.number="state.form.product_id" type="number" class="form-input" required placeholder="产品ID" />
<label class="form-label">产品 *</label>
<select v-model.number="state.form.product_id" class="form-input" required>
<option v-for="product in state.products" :key="'stockin-product-' + product.id" :value="product.id">
{{ product.sku }} - {{ product.name }}(ID: {{ product.id }})
</option>
</select>
</div>
<div class="form-group">
<label class="form-label">仓库ID *</label>
<input v-model.number="state.form.warehouse_id" type="number" class="form-input" required placeholder="仓库ID (默认1)" />
<label class="form-label">仓库 *</label>
<select v-model.number="state.form.warehouse_id" class="form-input" required>
<option v-for="warehouse in state.warehouses" :key="'stockin-warehouse-' + warehouse.id" :value="warehouse.id">
{{ warehouse.name }}{{ warehouse.code ? ' (' + warehouse.code + ')' : '' }}{{ warehouse.is_default ? ' [默认]' : '' }}(ID: {{ warehouse.id }})
</option>
</select>
</div>
<div class="form-group">
<label class="form-label">数量 *</label>
@@ -2186,12 +2393,20 @@ const InventoryView = {
<!-- 出库表单 -->
<form v-else-if="state.modalType === 'stockOut'" @submit.prevent="stockOut">
<div class="form-group">
<label class="form-label">产品ID *</label>
<input v-model.number="state.form.product_id" type="number" class="form-input" required placeholder="产品ID" />
<label class="form-label">产品 *</label>
<select v-model.number="state.form.product_id" class="form-input" required>
<option v-for="product in state.products" :key="'stockout-product-' + product.id" :value="product.id">
{{ product.sku }} - {{ product.name }}(ID: {{ product.id }})
</option>
</select>
</div>
<div class="form-group">
<label class="form-label">仓库ID *</label>
<input v-model.number="state.form.warehouse_id" type="number" class="form-input" required placeholder="仓库ID (默认1)" />
<label class="form-label">仓库 *</label>
<select v-model.number="state.form.warehouse_id" class="form-input" required>
<option v-for="warehouse in state.warehouses" :key="'stockout-warehouse-' + warehouse.id" :value="warehouse.id">
{{ warehouse.name }}{{ warehouse.code ? ' (' + warehouse.code + ')' : '' }}{{ warehouse.is_default ? ' [默认]' : '' }}(ID: {{ warehouse.id }})
</option>
</select>
</div>
<div class="form-group">
<label class="form-label">数量 *</label>