Files
my-mom-system/erp-frontend-vue/src/views/Sales/Order/index.vue
2026-02-27 23:50:25 +08:00

630 lines
18 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="page-container">
<!-- 搜索区域 -->
<el-card class="search-card" shadow="never">
<el-form :model="queryParams" :inline="true" @submit.prevent="handleQuery">
<el-form-item label="合同号">
<el-input
v-model="queryParams.contractNo"
placeholder="请输入合同号"
clearable
style="width: 140px"
/>
</el-form-item>
<el-form-item label="单据编码">
<el-input
v-model="queryParams.orderCode"
placeholder="请输入单据编码"
clearable
style="width: 140px"
/>
</el-form-item>
<el-form-item label="客户名称">
<el-input
v-model="queryParams.clientName"
placeholder="请输入客户名称"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="销售员">
<el-input
v-model="queryParams.salesmanName"
placeholder="请输入销售员"
clearable
style="width: 120px"
/>
</el-form-item>
<el-form-item label="销售类型">
<el-select
v-model="queryParams.salesType"
placeholder="全部"
clearable
style="width: 120px"
>
<el-option
v-for="item in salesTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="物料名称" v-show="viewMode === 'detail'">
<el-input
v-model="queryParams.itemName"
placeholder="请输入物料名称"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 240px"
@change="handleDateChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>搜索
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 工具栏与表格 -->
<el-card class="table-card" shadow="never">
<div class="toolbar">
<div class="toolbar-left">
<!-- 视图切换按钮当前是单据视图时显示"明细"按钮切换到明细视图 -->
<el-button
v-if="viewMode === 'document'"
@click="switchView('detail')"
>
明细
</el-button>
<el-button
v-else
@click="switchView('document')"
>
单据
</el-button>
<el-button @click="handleQueryAll">查询所有</el-button>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
<!-- 单据视图专有按钮 -->
<template v-if="viewMode === 'document'">
<el-button
type="danger"
:disabled="selectedRows.length === 0"
@click="handleBatchDelete"
>
<el-icon><Delete /></el-icon>删除
</el-button>
<el-button
type="primary"
:disabled="!canAudit"
@click="handleAudit"
>
<el-icon><Check /></el-icon>审核
</el-button>
<el-button
type="warning"
:disabled="!canUnaudit"
@click="handleUnaudit"
>
反审核
</el-button>
</template>
<el-button @click="handleExport">
<el-icon><Download /></el-icon>导出
</el-button>
</div>
</div>
<!-- 明细视图表格 -->
<el-table
v-if="viewMode === 'detail'"
v-loading="loading"
:data="detailTableData"
stripe
border
style="width: 100%"
:max-height="tableHeight"
>
<el-table-column type="index" label="序号" width="60" fixed="left" />
<el-table-column prop="contractNo" label="合同号" width="130" show-overflow-tooltip />
<el-table-column prop="orderCode" label="单据编码" width="140">
<template #default="{ row }">
<el-link type="primary" @click="handleView(row)">{{ row.orderCode }}</el-link>
</template>
</el-table-column>
<el-table-column prop="orderStatus" label="单据状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.orderStatus)">
{{ getStatusLabel(row.orderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="clientName" label="客户名称" width="180" show-overflow-tooltip />
<el-table-column prop="salesmanName" label="销售员" width="100" />
<el-table-column prop="itemName" label="物料名称" width="180" show-overflow-tooltip />
<el-table-column prop="itemCode" label="物料编码" width="120" />
<el-table-column prop="quantity" label="数量" width="100" align="right">
<template #default="{ row }">
{{ formatNumber(row.quantity) }}
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价" width="100" align="right">
<template #default="{ row }">
{{ formatCurrency(row.unitPrice) }}
</template>
</el-table-column>
<el-table-column prop="amount" label="金额" width="120" align="right">
<template #default="{ row }">
{{ formatCurrency(row.amount) }}
</template>
</el-table-column>
<el-table-column prop="deliveredQty" label="发货数量" width="100" align="right">
<template #default="{ row }">
{{ formatNumber(row.deliveredQty || 0) }}
</template>
</el-table-column>
<el-table-column prop="salesType" label="销售类型" width="100">
<template #default="{ row }">
{{ getSalesTypeLabel(row.salesType) }}
</template>
</el-table-column>
<el-table-column prop="orderDate" label="单据日期" width="110" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleProgress(row)">生产进度</el-button>
</template>
</el-table-column>
</el-table>
<!-- 单据视图表格 -->
<el-table
v-else
v-loading="loading"
:data="tableData"
stripe
border
style="width: 100%"
:max-height="tableHeight"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" fixed="left" />
<el-table-column prop="orderCode" label="单据编码" width="140">
<template #default="{ row }">
<el-link type="primary" @click="handleView(row)">{{ row.orderCode }}</el-link>
</template>
</el-table-column>
<el-table-column prop="orderDate" label="单据日期" width="110" />
<el-table-column prop="orderStatus" label="单据状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.orderStatus)">
{{ getStatusLabel(row.orderStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="bizType" label="业务类型" width="100">
<template #default="{ row }">
{{ getBizTypeLabel(row.bizType) }}
</template>
</el-table-column>
<el-table-column prop="clientName" label="销售客户" min-width="180" show-overflow-tooltip />
<el-table-column prop="salesmanName" label="销售员" width="100" />
<el-table-column prop="bizStatus" label="业务状态" width="90">
<template #default="{ row }">
{{ getBizStatusLabel(row.bizStatus) }}
</template>
</el-table-column>
<el-table-column prop="auditDate" label="审核日期" width="110" />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
<el-button
type="primary"
link
@click="handleEdit(row)"
:disabled="row.orderStatus === '审核'"
>
编辑
</el-button>
<el-popconfirm title="确认删除?" @confirm="handleDelete(row)">
<template #reference>
<el-button
type="danger"
link
:disabled="row.orderStatus === '审核'"
>
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[20, 50, 100, 200]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete, Check, Download } from '@element-plus/icons-vue'
import {
getSalesOrderList,
getSalesOrderDetailList,
deleteSalesOrder,
auditSalesOrder,
unauditSalesOrder,
exportSalesOrder,
ORDER_STATUS_MAP,
SALES_TYPE_OPTIONS,
BIZ_TYPE_OPTIONS,
BIZ_STATUS_OPTIONS,
type SalesOrder,
type SalesOrderDetailView,
type SalesOrderQuery
} from '@/api/salesOrder'
const router = useRouter()
// ============ 响应式状态 ============
/** 视图模式detail=明细视图document=单据视图 */
const viewMode = ref<'detail' | 'document'>('document')
/** 加载状态 */
const loading = ref(false)
/** 查询参数 */
const queryParams = reactive<SalesOrderQuery>({
orderCode: '',
contractNo: '',
clientName: '',
salesmanName: '',
salesType: '',
itemName: '',
beginOrderDate: '',
endOrderDate: '',
pageNum: 1,
pageSize: 100
})
/** 日期范围 */
const dateRange = ref<string[]>([])
/** 单据视图数据 */
const tableData = ref<SalesOrder[]>([])
/** 明细视图数据 */
const detailTableData = ref<SalesOrderDetailView[]>([])
/** 总条数 */
const total = ref(0)
/** 选中行(单据视图) */
const selectedRows = ref<SalesOrder[]>([])
/** 表格高度 */
const tableHeight = ref(500)
/** 销售类型选项 */
const salesTypeOptions = SALES_TYPE_OPTIONS
// ============ 计算属性 ============
/** 是否可以审核(选中了开立状态的订单) */
const canAudit = computed(() => {
return selectedRows.value.length > 0 &&
selectedRows.value.every(row => row.orderStatus === '开立')
})
/** 是否可以反审核(选中了审核状态的订单) */
const canUnaudit = computed(() => {
return selectedRows.value.length > 0 &&
selectedRows.value.every(row => row.orderStatus === '审核')
})
// ============ 方法 ============
/** 获取状态标签类型 */
function getStatusType(status: string): string {
return ORDER_STATUS_MAP[status]?.type || ''
}
/** 获取状态标签文本 */
function getStatusLabel(status: string): string {
return ORDER_STATUS_MAP[status]?.label || status || '-'
}
/** 获取销售类型标签 */
function getSalesTypeLabel(value: string): string {
const item = SALES_TYPE_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '-'
}
/** 获取业务类型标签 */
function getBizTypeLabel(value: string): string {
const item = BIZ_TYPE_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '销售订单'
}
/** 获取业务状态标签 */
function getBizStatusLabel(value: string): string {
const item = BIZ_STATUS_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '正常'
}
/** 格式化数字 */
function formatNumber(val: number | undefined): string {
if (val === undefined || val === null) return '-'
return val.toLocaleString()
}
/** 格式化金额 */
function formatCurrency(val: number | undefined): string {
if (val === undefined || val === null) return '-'
return '¥' + val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
/** 日期范围变化 */
function handleDateChange(val: string[] | null) {
if (val && val.length === 2) {
queryParams.beginOrderDate = val[0]
queryParams.endOrderDate = val[1]
} else {
queryParams.beginOrderDate = ''
queryParams.endOrderDate = ''
}
}
/** 加载数据 */
async function loadData() {
loading.value = true
try {
if (viewMode.value === 'detail') {
const res = await getSalesOrderDetailList(queryParams)
detailTableData.value = res.list
total.value = res.total
} else {
const res = await getSalesOrderList(queryParams)
tableData.value = res.list
total.value = res.total
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
}
}
/** 切换视图 */
function switchView(mode: 'detail' | 'document') {
viewMode.value = mode
selectedRows.value = []
queryParams.pageNum = 1
loadData()
}
/** 搜索 */
function handleQuery() {
queryParams.pageNum = 1
loadData()
}
/** 查询所有(清空条件) */
function handleQueryAll() {
queryParams.orderCode = ''
queryParams.contractNo = ''
queryParams.clientName = ''
queryParams.salesmanName = ''
queryParams.salesType = ''
queryParams.itemName = ''
queryParams.beginOrderDate = ''
queryParams.endOrderDate = ''
dateRange.value = []
queryParams.pageNum = 1
loadData()
}
/** 新增 */
function handleAdd() {
router.push('/sales/order/form')
}
/** 查看 */
function handleView(row: SalesOrder | SalesOrderDetailView) {
const id = 'orderId' in row ? row.orderId : (row as any).orderId
router.push(`/sales/order/view/${id}`)
}
/** 编辑 */
function handleEdit(row: SalesOrder) {
router.push(`/sales/order/form/${row.orderId}`)
}
/** 删除单个 */
async function handleDelete(row: SalesOrder) {
try {
await deleteSalesOrder(row.orderId!)
ElMessage.success('删除成功')
loadData()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}
/** 批量删除 */
async function handleBatchDelete() {
if (selectedRows.value.length === 0) return
try {
await ElMessageBox.confirm('确认删除选中的订单吗?', '警告', { type: 'warning' })
const ids = selectedRows.value.map(row => row.orderId!)
await deleteSalesOrder(ids)
ElMessage.success('删除成功')
selectedRows.value = []
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
/** 审核 */
async function handleAudit() {
if (selectedRows.value.length === 0) return
try {
await ElMessageBox.confirm('确认审核选中的订单吗?', '提示', { type: 'info' })
const ids = selectedRows.value.map(row => row.orderId!)
await auditSalesOrder(ids)
ElMessage.success('审核成功')
selectedRows.value = []
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('审核失败')
}
}
}
/** 反审核 */
async function handleUnaudit() {
if (selectedRows.value.length === 0) return
try {
await ElMessageBox.confirm('确认反审核选中的订单吗?', '提示', { type: 'warning' })
const ids = selectedRows.value.map(row => row.orderId!)
await unauditSalesOrder(ids)
ElMessage.success('反审核成功')
selectedRows.value = []
loadData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('反审核失败')
}
}
}
/** 导出 */
async function handleExport() {
try {
await ElMessageBox.confirm('是否确认导出销售订单数据?', '警告', { type: 'warning' })
const blob = await exportSalesOrder(queryParams)
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `销售订单_${new Date().toISOString().slice(0, 10)}.xlsx`
link.click()
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('导出失败')
}
}
}
/** 生产进度 */
function handleProgress(row: SalesOrderDetailView) {
router.push(`/sales/order/progress/${row.orderId}`)
}
/** 表格选择变化 */
function handleSelectionChange(rows: SalesOrder[]) {
selectedRows.value = rows
}
/** 分页大小变化 */
function handleSizeChange() {
queryParams.pageNum = 1
loadData()
}
/** 当前页变化 */
function handleCurrentChange() {
loadData()
}
/** 计算表格高度 */
function calcTableHeight() {
tableHeight.value = window.innerHeight - 300
}
// ============ 生命周期 ============
onMounted(() => {
loadData()
calcTableHeight()
window.addEventListener('resize', calcTableHeight)
})
</script>
<style scoped>
.page-container {
padding: 8px;
}
.search-card {
margin-bottom: 12px;
}
.search-card :deep(.el-card__body) {
padding: 12px 12px 0;
}
.search-card :deep(.el-form-item) {
margin-bottom: 12px;
margin-right: 12px;
}
.table-card :deep(.el-card__body) {
padding: 12px;
}
.toolbar {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.toolbar-left {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.pagination-wrapper {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
</style>