优化前端,使用组件Element
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { handleApiError } from '@/shared/notification'
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
loadFinance,
|
||||
refreshFinanceByPeriod
|
||||
} = useInventory()
|
||||
|
||||
const periodYear = ref(state.financePeriod.year)
|
||||
const periodQuarter = ref(state.financePeriod.quarter)
|
||||
|
||||
const quarterOptions = [
|
||||
{ label: '全年', value: '' },
|
||||
{ label: 'Q1', value: '1' },
|
||||
{ label: 'Q2', value: '2' },
|
||||
{ label: 'Q3', value: '3' },
|
||||
{ label: 'Q4', value: '4' }
|
||||
]
|
||||
|
||||
const periodLabel = computed(() => {
|
||||
if (!periodQuarter.value) return `${periodYear.value}年 全年`
|
||||
return `${periodYear.value}年 Q${periodQuarter.value}`
|
||||
})
|
||||
|
||||
function refreshFinance() {
|
||||
state.financePeriod.year = periodYear.value
|
||||
state.financePeriod.quarter = periodQuarter.value
|
||||
loadFinance()
|
||||
}
|
||||
|
||||
const totalReceivable = computed(() => {
|
||||
return state.financeSummary?.total_receivable ?? 0
|
||||
})
|
||||
|
||||
const totalPayable = computed(() => {
|
||||
return state.financeSummary?.total_payable ?? 0
|
||||
})
|
||||
|
||||
const periodReceived = computed(() => {
|
||||
return state.financeSummary?.period_received ?? 0
|
||||
})
|
||||
|
||||
const periodPaid = computed(() => {
|
||||
return state.financeSummary?.period_paid ?? 0
|
||||
})
|
||||
|
||||
function getTransactionTypeLabel(type: string): string {
|
||||
return type === 'receipt' ? '收款' : '付款'
|
||||
}
|
||||
|
||||
function getTransactionTypeTag(type: string): string {
|
||||
return type === 'receipt' ? 'success' : 'warning'
|
||||
}
|
||||
|
||||
function getTransactionStatusLabel(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
confirmed: '已确认',
|
||||
pending: '待确认'
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
refreshFinance()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<span style="font-weight: 600;">统计周期:</span>
|
||||
<el-input-number
|
||||
v-model="periodYear"
|
||||
:min="2020"
|
||||
:max="2099"
|
||||
style="width: 120px;"
|
||||
/>
|
||||
<span>年</span>
|
||||
<el-select v-model="periodQuarter" placeholder="选择季度" style="width: 120px;">
|
||||
<el-option
|
||||
v-for="q in quarterOptions"
|
||||
:key="q.value"
|
||||
:label="q.label"
|
||||
:value="q.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="refreshFinance">刷新统计</el-button>
|
||||
<el-tag type="info" size="large">{{ periodLabel }}</el-tag>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应收总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #409eff;">
|
||||
{{ formatCurrency(totalReceivable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应付总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #e6a23c;">
|
||||
{{ formatCurrency(totalPayable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期收款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #67c23a;">
|
||||
{{ formatCurrency(periodReceived) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期付款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #f56c6c;">
|
||||
{{ formatCurrency(periodPaid) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已收">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实收流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.receivable_balance) }}</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.customerFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已付">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实付流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.payable_balance) }}</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.supplierFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.customerProductStatement.length === 0" description="暂无数据" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.supplierProductStatement.length === 0" description="暂无数据" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">最近财务流水</span>
|
||||
</template>
|
||||
<el-table :data="state.financeTransactions" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="order_no" label="单号" />
|
||||
<el-table-column label="类型">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTransactionTypeTag(row.type)" size="small">
|
||||
{{ getTransactionTypeLabel(row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="partner_name" label="往来方" />
|
||||
<el-table-column label="金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="small">
|
||||
{{ getTransactionStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="日期">
|
||||
<template #default="{ row }">{{ formatDate(row.transaction_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.financeTransactions.length === 0" description="暂无数据" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,538 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
getSalesOrderStatusLabel,
|
||||
loadProductionOrders,
|
||||
loadFinishedProducts,
|
||||
loadCustomers,
|
||||
loadMaterials,
|
||||
loadInventory,
|
||||
loadMovements
|
||||
} = useInventory()
|
||||
|
||||
const showModal = ref(false)
|
||||
const editingItem = ref<any>(null)
|
||||
const form = ref<any>({})
|
||||
const moldItems = ref<any[]>([])
|
||||
const productionWarehouseId = ref<number | null>(null)
|
||||
|
||||
const showConsumptionModal = ref(false)
|
||||
const consumptionItems = ref<any[]>([])
|
||||
const consumedMaterials = ref<any[]>([])
|
||||
|
||||
function openCreateOrder() {
|
||||
editingItem.value = null
|
||||
form.value = {
|
||||
customer_id: null,
|
||||
delivery_date: '',
|
||||
remark: ''
|
||||
}
|
||||
moldItems.value = []
|
||||
consumedMaterials.value = []
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function addMoldItem() {
|
||||
moldItems.value.push({
|
||||
mold_mode: 'new',
|
||||
mold_sku: '',
|
||||
mold_name: '',
|
||||
mold_id: null,
|
||||
quantity: 1,
|
||||
unit_price: 0,
|
||||
remark: ''
|
||||
})
|
||||
}
|
||||
|
||||
function removeMoldItem(index: number) {
|
||||
moldItems.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const orderTotalAmount = computed(() => {
|
||||
return moldItems.value.reduce((sum: number, item: any) => {
|
||||
return sum + (item.unit_price || 0) * (item.quantity || 0)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
async function editOrder(order: any) {
|
||||
await loadCustomers()
|
||||
await loadFinishedProducts()
|
||||
await loadMaterials()
|
||||
try {
|
||||
const detail = await apiRequest(`/api/sales-orders/${order.id}`)
|
||||
editingItem.value = order
|
||||
form.value = {
|
||||
customer_id: detail.customer_id || order.customer_id || null,
|
||||
delivery_date: detail.delivery_date || order.delivery_date || '',
|
||||
remark: detail.remark || order.remark || ''
|
||||
}
|
||||
moldItems.value = (detail.items || []).map((item: any) => ({
|
||||
mold_mode: item.mold_id ? 'existing' : 'new',
|
||||
mold_sku: item.sku || '',
|
||||
mold_name: item.name || '',
|
||||
mold_id: item.mold_id || null,
|
||||
quantity: item.quantity || 1,
|
||||
unit_price: item.unit_price || 0,
|
||||
remark: item.remark || ''
|
||||
}))
|
||||
try {
|
||||
const consumptionData = await apiRequest(`/api/sales-orders/${order.id}/consume-materials`)
|
||||
consumedMaterials.value = (consumptionData.items || []).map((item: any) => ({
|
||||
material_id: item.material_id,
|
||||
material_name: item.material_name || item.material_sku || '',
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price || 0,
|
||||
remark: item.remark || ''
|
||||
}))
|
||||
} catch {
|
||||
consumedMaterials.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
handleApiError(e, '加载销售订单详情')
|
||||
}
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
async function saveSalesOrder() {
|
||||
if (!form.value.customer_id) {
|
||||
addNotification('请选择客户', 'warning')
|
||||
return
|
||||
}
|
||||
if (!moldItems.value || moldItems.value.length === 0) {
|
||||
addNotification('请至少添加一个模具', 'warning')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
customer_id: form.value.customer_id,
|
||||
delivery_date: form.value.delivery_date || null,
|
||||
remark: form.value.remark || '',
|
||||
items: moldItems.value.map((item: any) => ({
|
||||
mold_mode: item.mold_mode,
|
||||
mold_sku: item.mold_sku || '',
|
||||
mold_name: item.mold_name || '',
|
||||
mold_id: item.mold_id || null,
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price || 0,
|
||||
remark: item.remark || ''
|
||||
}))
|
||||
}
|
||||
if (editingItem.value) {
|
||||
await apiRequest(`/api/sales-orders/${editingItem.value.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
addNotification('销售订单更新成功', 'success')
|
||||
} else {
|
||||
await apiRequest('/api/sales-orders', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
addNotification('销售订单创建成功', 'success')
|
||||
}
|
||||
showModal.value = false
|
||||
editingItem.value = null
|
||||
form.value = {}
|
||||
moldItems.value = []
|
||||
consumedMaterials.value = []
|
||||
loadProductionOrders()
|
||||
loadFinishedProducts()
|
||||
loadInventory()
|
||||
loadMovements()
|
||||
} catch (e) {
|
||||
handleApiError(e, '保存销售订单')
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteOrder(id: number) {
|
||||
if (!confirm('确定要删除这个销售订单吗?')) return
|
||||
try {
|
||||
await apiRequest(`/api/sales-orders/${id}`, { method: 'DELETE' })
|
||||
addNotification('销售订单已删除', 'success')
|
||||
loadProductionOrders()
|
||||
loadInventory()
|
||||
loadMovements()
|
||||
} catch (e) {
|
||||
handleApiError(e, '删除销售订单')
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStatus(orderId: number, status: string) {
|
||||
const label = status === 'delivered' ? '已交付' : '已收款'
|
||||
if (!confirm(`确认标记为${label}?`)) return
|
||||
try {
|
||||
await apiRequest(`/api/sales-orders/${orderId}/status`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status })
|
||||
})
|
||||
addNotification(`已标记为${label}`, 'success')
|
||||
loadProductionOrders()
|
||||
loadMovements()
|
||||
} catch (e) {
|
||||
handleApiError(e, '更新状态')
|
||||
}
|
||||
}
|
||||
|
||||
function openConsumptionModal() {
|
||||
consumptionItems.value = []
|
||||
showConsumptionModal.value = true
|
||||
}
|
||||
|
||||
function addConsumptionItem() {
|
||||
consumptionItems.value.push({
|
||||
material_id: state.materials[0]?.id || null,
|
||||
quantity: 1,
|
||||
remark: ''
|
||||
})
|
||||
}
|
||||
|
||||
function removeConsumptionItem(index: number) {
|
||||
consumptionItems.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const consumptionTotalAmount = computed(() => {
|
||||
return consumptionItems.value.reduce((sum: number, item: any) => {
|
||||
const material = state.materials.find((m: any) => m.id === item.material_id)
|
||||
const unitPrice = material?.cost_price || 0
|
||||
return sum + unitPrice * (item.quantity || 0)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
async function saveConsumption() {
|
||||
if (!editingItem.value) return
|
||||
if (!consumptionItems.value || consumptionItems.value.length === 0) {
|
||||
addNotification('请至少添加一个消耗物料', 'warning')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
items: consumptionItems.value.map((item: any) => ({
|
||||
material_id: item.material_id,
|
||||
quantity: item.quantity,
|
||||
unit_price: state.materials.find((m: any) => m.id === item.material_id)?.cost_price || 0,
|
||||
remark: item.remark || ''
|
||||
}))
|
||||
}
|
||||
await apiRequest(`/api/sales-orders/${editingItem.value.id}/consume-materials`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
addNotification('物料消耗记录保存成功', 'success')
|
||||
showConsumptionModal.value = false
|
||||
try {
|
||||
const consumptionData = await apiRequest(`/api/sales-orders/${editingItem.value.id}/consume-materials`)
|
||||
consumedMaterials.value = (consumptionData.items || []).map((item: any) => ({
|
||||
material_id: item.material_id,
|
||||
material_name: item.material_name || item.material_sku || '',
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price || 0,
|
||||
remark: item.remark || ''
|
||||
}))
|
||||
} catch {
|
||||
consumedMaterials.value = []
|
||||
}
|
||||
loadInventory()
|
||||
loadMovements()
|
||||
} catch (e) {
|
||||
handleApiError(e, '保存物料消耗')
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showModal.value = false
|
||||
editingItem.value = null
|
||||
form.value = {}
|
||||
moldItems.value = []
|
||||
consumedMaterials.value = []
|
||||
}
|
||||
|
||||
function closeConsumptionModal() {
|
||||
showConsumptionModal.value = false
|
||||
consumptionItems.value = []
|
||||
}
|
||||
|
||||
async function initData() {
|
||||
await loadProductionOrders()
|
||||
await loadCustomers()
|
||||
await loadMaterials()
|
||||
await loadFinishedProducts()
|
||||
}
|
||||
|
||||
initData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 12px; align-items: center;">
|
||||
<el-select
|
||||
v-model="productionWarehouseId"
|
||||
placeholder="选择仓库"
|
||||
style="width: 200px;"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button @click="loadProductionOrders">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateOrder">新增销售订单</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.productionOrders" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="order_no" label="销售单" />
|
||||
<el-table-column prop="customer_name" label="客户" />
|
||||
<el-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.total_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交付日期">
|
||||
<template #default="{ row }">{{ formatDate(row.delivery_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单创建">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际交付">
|
||||
<template #default="{ row }">{{ row.delivered_at ? formatDateTime(row.delivered_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际收款">
|
||||
<template #default="{ row }">{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'paid' ? 'success' : row.status === 'delivered' ? 'warning' : 'info'">
|
||||
{{ getSalesOrderStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240">
|
||||
<template #default="{ row }">
|
||||
<el-dropdown style="margin-right: 8px;">
|
||||
<el-button size="small">
|
||||
状态 ▾
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="updateStatus(row.id, 'delivered')">已交付</el-dropdown-item>
|
||||
<el-dropdown-item @click="updateStatus(row.id, 'paid')">已收款</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="row.status === 'paid'"
|
||||
@click="editOrder(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="deleteOrder(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-empty v-if="!state.loading && state.productionOrders.length === 0" description="暂无销售订单" />
|
||||
</el-table>
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '销售订单'"
|
||||
width="900px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="客户" required>
|
||||
<el-select v-model="form.customer_id" placeholder="请选择客户" style="width:100%">
|
||||
<el-option
|
||||
v-for="customer in state.customers"
|
||||
:key="customer.id"
|
||||
:label="customer.name"
|
||||
:value="customer.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="交付日期">
|
||||
<el-date-picker
|
||||
v-model="form.delivery_date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width:100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">模具明细</span>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<el-button v-if="editingItem" type="warning" size="small" @click="openConsumptionModal">+ 消耗物料</el-button>
|
||||
<el-button type="primary" size="small" @click="addMoldItem">+ 添加模具</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="moldItems" border size="small">
|
||||
<el-table-column label="模式" width="80">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.mold_mode" size="small" style="width:100%">
|
||||
<el-option label="新模" value="new" />
|
||||
<el-option label="改模" value="existing" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具SKU" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-if="line.mold_mode === 'new'" v-model="line.mold_sku" size="small" placeholder="模具SKU" />
|
||||
<el-select v-else v-model="line.mold_id" placeholder="选择模具" size="small" style="width:100%">
|
||||
<el-option
|
||||
v-for="product in state.finishedProducts"
|
||||
:key="product.id"
|
||||
:label="`${product.sku} - ${product.name}`"
|
||||
:value="product.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具名称" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input
|
||||
v-if="line.mold_mode === 'new'"
|
||||
v-model="line.mold_name"
|
||||
size="small"
|
||||
placeholder="模具名称"
|
||||
/>
|
||||
<span v-else style="padding-left: 8px;">
|
||||
{{ state.finishedProducts.find((p: any) => p.id === line.mold_id)?.name || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="100">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeMoldItem($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
订单总金额:{{ formatCurrency(orderTotalAmount) }}
|
||||
</div>
|
||||
|
||||
<template v-if="editingItem && consumedMaterials.length > 0">
|
||||
<el-divider content-position="left">已消耗物料</el-divider>
|
||||
<el-table :data="consumedMaterials" border size="small">
|
||||
<el-table-column label="物料">
|
||||
<template #default="{ row: item }">{{ item.material_name }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量">
|
||||
<template #default="{ row: item }">{{ item.quantity }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价">
|
||||
<template #default="{ row: item }">{{ formatCurrency(item.unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注">
|
||||
<template #default="{ row: item }">{{ item.remark || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveSalesOrder">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showConsumptionModal"
|
||||
title="物料消耗"
|
||||
width="800px"
|
||||
@closed="closeConsumptionModal"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">消耗物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addConsumptionItem">+ 添加物料</el-button>
|
||||
</div>
|
||||
<el-table :data="consumptionItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number
|
||||
:model-value="state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0"
|
||||
disabled
|
||||
size="small"
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeConsumptionItem($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
消耗总金额:{{ formatCurrency(consumptionTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showConsumptionModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveConsumption">保存消耗</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { reactive, ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
import { formatCurrency, formatNumber, formatDateTime, formatDate } from '@/shared/utils'
|
||||
|
||||
Reference in New Issue
Block a user