Files
mom-web/src/views/erp/sale/order/index.vue
2026-03-06 14:46:40 +08:00

484 lines
15 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="mobile-sale-order">
<!-- 顶部操作栏 -->
<div class="mobile-header">
<div class="mobile-header__search">
<el-input
v-model="queryParams.no"
placeholder="搜索订单单号"
clearable
@keyup.enter="handleQuery"
:prefix-icon="Search"
/>
</div>
<div class="mobile-header__actions">
<el-button :icon="Filter" circle @click="filterVisible = true" />
<el-button
type="primary"
:icon="Plus"
circle
@click="openForm('create')"
v-hasPermi="['erp:sale-order:create']"
/>
</div>
</div>
<!-- 卡片列表 -->
<div class="mobile-list" v-loading="loading">
<div v-if="list.length === 0 && !loading" class="mobile-empty">
<el-empty description="暂无销售订单" />
</div>
<div
v-for="item in list"
:key="item.id"
class="mobile-card"
@click="handleCardClick(item)"
>
<div class="mobile-card__header">
<span class="mobile-card__no">{{ item.no }}</span>
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="item.status" />
</div>
<div class="mobile-card__body">
<div class="mobile-card__row">
<span class="mobile-card__label">客户</span>
<span class="mobile-card__value">{{ item.customerName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">产品</span>
<span class="mobile-card__value mobile-card__value--ellipsis">{{ item.productNames || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">订单时间</span>
<span class="mobile-card__value">{{ formatDate2(item.orderTime) }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">创建人</span>
<span class="mobile-card__value">{{ item.creatorName || '-' }}</span>
</div>
<div class="mobile-card__nums">
<div class="mobile-card__num-item">
<div class="mobile-card__num-val">{{ erpCountInputFormatter(item.totalCount) }}</div>
<div class="mobile-card__num-label">总数量</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val">{{ erpCountInputFormatter(item.outCount) }}</div>
<div class="mobile-card__num-label">出库</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val">{{ erpCountInputFormatter(item.returnCount) }}</div>
<div class="mobile-card__num-label">退货</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val mobile-card__num-val--price">¥{{ erpPriceInputFormatter(item.totalPrice) }}</div>
<div class="mobile-card__num-label">含税金额</div>
</div>
</div>
</div>
<div class="mobile-card__footer">
<el-button size="small" @click.stop="openForm('detail', item.id)" v-hasPermi="['erp:sale-order:query']">详情</el-button>
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:sale-order:update']" :disabled="item.status === 20 || item.hasApprovalRecords">编辑</el-button>
<el-button size="small" type="success" @click.stop="handleSubmitApproval(item.id)" v-hasPermi="['erp:sale-order:submit-approval']" v-if="item.status === 10 && !item.hasApprovalRecords">提交审批</el-button>
<el-button size="small" type="info" @click.stop="handleViewApproval(item.id)" v-hasPermi="['erp:sale-order:query-approval']" v-if="item.hasApprovalRecords">审批记录</el-button>
<el-button size="small" type="primary" @click.stop="handleProcessApproval(item.id)" v-hasPermi="['erp:approval-record:process']" v-if="item.hasApprovalRecords && item.status !== 20">处理审批</el-button>
<el-button size="small" type="primary" @click.stop="handleUpdateStatus(item.id, 20)" v-hasPermi="['erp:sale-order:update-status']" v-if="item.status === 10">审批</el-button>
<el-button size="small" type="danger" @click.stop="handleUpdateStatus(item.id, 10)" v-hasPermi="['erp:sale-order:update-status']" v-if="item.status === 20">反审批</el-button>
<el-button size="small" type="danger" @click.stop="handleDelete([item.id])" v-hasPermi="['erp:sale-order:delete']" :disabled="item.hasApprovalRecords">删除</el-button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="mobile-pagination" v-if="total > 0">
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:page-sizes="[10, 20]"
layout="total, prev, pager, next"
:pager-count="5"
@pagination="getList"
/>
</div>
<!-- 筛选抽屉 -->
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="70%">
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
<el-form-item label="产品" prop="productId">
<el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" style="width:100%">
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="客户" prop="customerId">
<el-select v-model="queryParams.customerId" clearable filterable placeholder="请选择客户" style="width:100%">
<el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="订单时间" prop="orderTime">
<el-date-picker
v-model="queryParams.orderTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
style="width:100%"
/>
</el-form-item>
<el-form-item label="创建人" prop="creator">
<el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" style="width:100%">
<el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%">
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_AUDIT_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="出库状态" prop="outStatus">
<el-select v-model="queryParams.outStatus" placeholder="请选择" clearable style="width:100%">
<el-option label="未出库" value="0" />
<el-option label="部分出库" value="1" />
<el-option label="全部出库" value="2" />
</el-select>
</el-form-item>
<el-form-item label="退货状态" prop="returnStatus">
<el-select v-model="queryParams.returnStatus" placeholder="请选择" clearable style="width:100%">
<el-option label="未退货" value="0" />
<el-option label="部分退货" value="1" />
<el-option label="全部退货" value="2" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
</template>
</el-drawer>
<!-- 表单弹窗添加/修改 -->
<SaleOrderForm ref="formRef" @success="getList" />
<!-- 提交审批对话框 -->
<SubmitApprovalDialog ref="submitApprovalDialogRef" @success="getList" />
<!-- 审批记录对话框 -->
<ApprovalRecordsDialog ref="approvalDialogRef" />
<!-- 处理审批对话框 -->
<ProcessApprovalDialog ref="processApprovalDialogRef" @success="getList" />
</div>
</template>
<script setup lang="ts">
import { Search, Filter, Plus } from '@element-plus/icons-vue'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
import { SaleOrderApi, SaleOrderVO } from '@/api/erp/sale/order'
import SaleOrderForm from './SaleOrderForm.vue'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { UserVO } from '@/api/system/user'
import * as UserApi from '@/api/system/user'
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
import { SubmitApprovalDialog, ApprovalRecordsDialog, ProcessApprovalDialog } from '@/components/Approval'
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval/index'
/** ERP 销售订单列表 */
defineOptions({ name: 'ErpSaleOrder' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<SaleOrderVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const filterVisible = ref(false) // 筛选抽屉
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
no: undefined,
customerId: undefined,
productId: undefined,
orderTime: [],
status: undefined,
remark: undefined,
creator: undefined,
outStatus: undefined,
returnStatus: undefined
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const productList = ref<ProductVO[]>([]) // 产品列表
const customerList = ref<CustomerVO[]>([]) // 客户列表
const userList = ref<UserVO[]>([]) // 用户列表
const formatDate2 = (date: any) => {
if (!date) return '-'
return formatDate(new Date(date), 'YYYY-MM-DD')
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SaleOrderApi.getSaleOrderPage(queryParams)
// 为每个销售订单检查是否有审批记录
const listWithApprovalStatus = await Promise.all(
data.list.map(async (item) => {
try {
const records = await ApprovalRecordApi.getApprovalRecordListByBiz(item.id,'erp_sale_order')
return {
...item,
hasApprovalRecords: records && records.length > 0
}
} catch (error) {
console.error('获取审批记录失败', error)
return {
...item,
hasApprovalRecords: false
}
}
})
)
list.value = listWithApprovalStatus
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 筛选确认 */
const handleFilterConfirm = () => {
filterVisible.value = false
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 卡片点击 */
const handleCardClick = (row: SaleOrderVO) => {
if (row.status === 20) {
openForm('detail', row.id)
} else if (row.status === 10) {
openForm('update', row.id)
}
}
/** 提交审批 */
const submitApprovalDialogRef = ref()
const handleSubmitApproval = (id: number) => {
submitApprovalDialogRef.value.open(String(id), 'erp_sale_order')
}
/** 查看审批记录 */
const approvalDialogRef = ref()
const handleViewApproval = (id: number) => {
approvalDialogRef.value.open(String(id), 'erp_sale_order')
}
/** 处理审批 */
const processApprovalDialogRef = ref()
const handleProcessApproval = (id: number) => {
processApprovalDialogRef.value.open(String(id), 'erp_sale_order')
}
/** 删除按钮操作 */
const handleDelete = async (ids: number[]) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await SaleOrderApi.deleteSaleOrder(ids)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 审批/反审批操作 */
const handleUpdateStatus = async (id: number, status: number) => {
try {
// 审批的二次确认
await message.confirm(`确定${status === 20 ? '审批' : '反审批'}该订单吗?`)
// 发起审批
await SaleOrderApi.updateSaleOrderStatus(id, status)
message.success(`${status === 20 ? '审批' : '反审批'}成功`)
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await SaleOrderApi.exportSaleOrder(queryParams)
download.excel(data, '销售订单.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await getList()
// 加载产品、仓库列表、客户
productList.value = await ProductApi.getProductSimpleList()
customerList.value = await CustomerApi.getCustomerSimpleList()
userList.value = await UserApi.getSimpleUserList()
})
</script>
<style lang="scss" scoped>
.mobile-sale-order {
padding: 12px;
background: #f5f5f5;
min-height: 100vh;
}
.mobile-header {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 12px;
&__search {
flex: 1;
}
&__actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
}
.mobile-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-empty {
padding: 40px 0;
}
.mobile-card {
background: #fff;
border-radius: 10px;
padding: 14px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
&__no {
font-weight: 600;
font-size: 15px;
color: #303133;
}
&__body {
font-size: 13px;
}
&__row {
display: flex;
justify-content: space-between;
padding: 3px 0;
}
&__label {
color: #909399;
flex-shrink: 0;
margin-right: 12px;
}
&__value {
color: #606266;
text-align: right;
&--ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
}
&__nums {
display: flex;
justify-content: space-around;
margin-top: 10px;
padding: 10px 0;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
&__num-item {
text-align: center;
}
&__num-val {
font-size: 15px;
font-weight: 600;
color: #303133;
&--price {
color: #e6a23c;
}
}
&__num-label {
font-size: 11px;
color: #909399;
margin-top: 2px;
}
&__footer {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
}
.mobile-pagination {
margin-top: 12px;
display: flex;
justify-content: center;
:deep(.el-pagination) {
flex-wrap: wrap;
justify-content: center;
}
}
</style>