feat(oa/erp): 添加订单管理台账功能

- 在合同订单页面添加订单名称链接跳转到订单台账
- 新增订单管理台账页面,包含合同信息、发货信息、回款信息展示
dev
Yangk 1 week ago
parent 8a2fe85238
commit 6da9914114

@ -140,6 +140,12 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/oa/erp/projectLedger/index.vue'),
name: 'ProjectLedger',
meta: { title: '项目台账', activeMenu: '/oa/erp/projectInfo' }
},
{
path: 'orderLedger/:projectId',
component: () => import('@/views/oa/erp/orderLedger/index.vue'),
name: 'OrderLedger',
meta: { title: '订单管理台账', activeMenu: '/contract/contractInfo/contractOrder' }
}
]
},

@ -36,11 +36,11 @@
<el-option v-for="dict in project_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<!-- <el-form-item label="激活标识" prop="activeFlag">-->
<!-- <el-select v-model="queryParams.activeFlag" placeholder="请选择激活标识" clearable>-->
<!-- <el-option v-for="dict in active_flag" :key="dict.value" :label="dict.label" :value="dict.value" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="激活标识" prop="activeFlag">-->
<!-- <el-select v-model="queryParams.activeFlag" placeholder="请选择激活标识" clearable>-->
<!-- <el-option v-for="dict in active_flag" :key="dict.value" :label="dict.label" :value="dict.value" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
@ -51,41 +51,41 @@
</transition>
<el-card shadow="never">
<!-- <template #header>-->
<!-- <el-row :gutter="10" class="mb8">-->
<!-- <el-col :span="1.5">-->
<!-- <el-dropdown trigger="click" @command="handleAdd">-->
<!-- <el-button type="primary" plain icon="Plus" v-hasPermi="['oa/erp:projectInfo:add']">-->
<!-- 新增-->
<!-- <el-icon class="el-icon&#45;&#45;right">-->
<!-- <arrow-down />-->
<!-- </el-icon>-->
<!-- </el-button>-->
<!-- <template #dropdown>-->
<!-- <el-dropdown-menu>-->
<!-- <el-dropdown-item v-for="dict in project_category" :key="dict.value" :command="dict.value">-->
<!-- {{ dict.label }}-->
<!-- </el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </template>-->
<!-- </el-dropdown>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa/erp:projectInfo:edit']"-->
<!-- >修改-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['oa/erp:projectInfo:remove']"-->
<!-- >删除-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['oa/erp:projectInfo:export']"> </el-button>-->
<!-- </el-col>-->
<!-- <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList"></right-toolbar>-->
<!-- </el-row>-->
<!-- </template>-->
<!-- <template #header>-->
<!-- <el-row :gutter="10" class="mb8">-->
<!-- <el-col :span="1.5">-->
<!-- <el-dropdown trigger="click" @command="handleAdd">-->
<!-- <el-button type="primary" plain icon="Plus" v-hasPermi="['oa/erp:projectInfo:add']">-->
<!-- 新增-->
<!-- <el-icon class="el-icon&#45;&#45;right">-->
<!-- <arrow-down />-->
<!-- </el-icon>-->
<!-- </el-button>-->
<!-- <template #dropdown>-->
<!-- <el-dropdown-menu>-->
<!-- <el-dropdown-item v-for="dict in project_category" :key="dict.value" :command="dict.value">-->
<!-- {{ dict.label }}-->
<!-- </el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </template>-->
<!-- </el-dropdown>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa/erp:projectInfo:edit']"-->
<!-- >修改-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['oa/erp:projectInfo:remove']"-->
<!-- >删除-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['oa/erp:projectInfo:export']"> </el-button>-->
<!-- </el-col>-->
<!-- <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList"></right-toolbar>-->
<!-- </el-row>-->
<!-- </template>-->
<el-table v-loading="loading" border :data="projectInfoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
@ -96,13 +96,19 @@
</template>
</el-table-column>
<el-table-column label="订单编号" align="center" prop="projectCode" width="140" v-if="columns[3].visible">
<!-- <template #default="scope">-->
<!-- <el-link type="primary" underline @click="handleLedger(scope.row)">-->
<!-- {{ scope.row.projectCode }}-->
<!-- </el-link>-->
<!-- </template>-->
<!-- <template #default="scope">-->
<!-- <el-link type="primary" underline @click="handleLedger(scope.row)">-->
<!-- {{ scope.row.projectCode }}-->
<!-- </el-link>-->
<!-- </template>-->
</el-table-column>
<el-table-column label="订单名称" align="center" prop="projectName" width="200" v-if="columns[4].visible">
<template #default="scope">
<el-link type="primary" underline @click="handleOrderLedger(scope.row)">
{{ scope.row.projectName }}
</el-link>
</template>
</el-table-column>
<el-table-column label="订单名称" align="center" prop="projectName" width="200" v-if="columns[4].visible" />
<el-table-column label="合同编号" align="center" prop="contractCode" width="140" v-if="columns[28].visible" />
<el-table-column label="合同名称" align="center" prop="contractName" width="200" v-if="columns[29].visible" />
<el-table-column label="业务方向" align="center" prop="businessDirection" width="100" v-if="columns[5].visible">
@ -343,6 +349,13 @@ const handleLedger = (row: ProjectInfoVO) => {
router.push(`/oa/erp/projectLedger/${_projectId}`);
};
/** 订单管理台账 */
const handleOrderLedger = (row: ProjectInfoVO) => {
const _projectId = row?.projectId;
if (!_projectId) return;
router.push(`/oa/erp/orderLedger/${_projectId}`);
};
/** 删除按钮操作 */
const handleDelete = async (row?: ProjectInfoVO) => {
const _projectIds = row?.projectId || ids.value;

@ -0,0 +1,381 @@
<template>
<div class="order-ledger-container">
<el-card shadow="never" class="main-card">
<template #header>
<div class="card-header">
<span class="header-title">订单管理台账</span>
<el-button size="default" type="primary" icon="ArrowLeft" @click="handleBack"></el-button>
</div>
</template>
<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="custom-tabs">
<!-- 合同信息 Tab -->
<el-tab-pane label="合同信息" name="contract">
<div v-loading="loadingContract" class="tab-content">
<el-descriptions :column="3" border v-if="contractInfo" class="order-descriptions">
<el-descriptions-item label="合同编号" :span="1">
<span class="value-text value-highlight">{{ contractInfo?.contractCode || '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="合同名称" :span="2">
<span class="value-text value-highlight">{{ contractInfo?.contractName || '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="客户合同编号">{{ contractInfo?.customerContractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="内部合同号">{{ contractInfo?.internalContractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="外部合同号">{{ contractInfo?.externalContractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="订单号">{{ contractInfo?.orderContractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="项目号">{{ contractInfo?.projectContractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="业务方向">
<dict-tag :options="business_direction" :value="contractInfo?.businessDirection" />
</el-descriptions-item>
<el-descriptions-item label="合同大类">
<dict-tag :options="contract_category" :value="contractInfo?.contractCategory" />
</el-descriptions-item>
<el-descriptions-item label="合同类型">
<dict-tag :options="contract_type" :value="contractInfo?.contractType" />
</el-descriptions-item>
<el-descriptions-item label="合同状态">
<dict-tag :options="contract_status" :value="contractInfo?.contractStatus" />
</el-descriptions-item>
<el-descriptions-item label="合同时间">{{ contractInfo?.contractDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="合同总价" :span="1">
<span class="value-text value-amount">
{{ contractInfo?.totalPrice ? formatNumber(contractInfo.totalPrice) + ' 元' : '-' }}
</span>
</el-descriptions-item>
<el-descriptions-item label="付款方式">{{ contractInfo?.paymentMethod || '-' }}</el-descriptions-item>
<el-descriptions-item label="交货地点">{{ contractInfo?.deliveryLocation || '-' }}</el-descriptions-item>
<el-descriptions-item label="运输方式">{{ contractInfo?.shipMethod || '-' }}</el-descriptions-item>
<el-descriptions-item label="交付启动期限">{{
contractInfo?.deliveryStart ? contractInfo.deliveryStart + ' 天' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="质保期">{{
contractInfo?.warrantyPeriod ? contractInfo.warrantyPeriod + ' 天' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="质保期描述">{{ contractInfo?.warrantyPeriodDescription || '-' }}</el-descriptions-item>
<el-descriptions-item label="签订地点">{{ contractInfo?.signingPlace || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="3">{{ contractInfo?.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<el-empty v-else description="暂无合同信息" class="empty-state" />
</div>
</el-tab-pane>
<!-- 发货信息 Tab -->
<el-tab-pane label="发货信息" name="shipping">
<div v-loading="loadingShipping" class="tab-content">
<el-table :data="shippingList" border stripe size="default" class="data-table">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="发货单号" prop="shippingCode" width="150" align="center" />
<el-table-column label="发货方式" prop="shippingMode" width="120" align="center">
<template #default="scope">
<dict-tag :options="shipping_mode" :value="scope.row.shippingMode" />
</template>
</el-table-column>
<el-table-column label="客户名称" prop="customerName" width="180" align="center" show-overflow-tooltip />
<el-table-column label="收货地址" prop="shippingAddress" width="200" align="center" show-overflow-tooltip />
<el-table-column label="收货联系人" prop="receiverName" width="100" align="center" />
<el-table-column label="收货电话" prop="receiverPhone" width="130" align="center" />
<!-- <el-table-column label="物流公司" prop="logisticsCompany" width="130" align="center" />-->
<!-- <el-table-column label="运单号" prop="trackingNo" width="150" align="center" />-->
<el-table-column label="发货状态" prop="shippingStatus" width="100" align="center">
<template #default="scope">
<dict-tag :options="shipping_status" :value="scope.row.shippingStatus" />
</template>
</el-table-column>
<el-table-column label="计划到货时间" prop="planArrivalTime" width="120" align="center">
<template #default="scope">
<span>{{ parseTime(scope.row.planArrivalTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="实际发货时间" prop="shippingTime" width="120" align="center">
<template #default="scope">
<span>{{ parseTime(scope.row.shippingTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="发货说明" prop="directions" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" align="center" fixed="right" width="100">
<template #default="scope">
<el-tooltip content="查看发货单明细" placement="top">
<el-button link type="primary" icon="View" @click="handleViewShipping(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loadingShipping && shippingList.length === 0" description="暂无发货信息" class="empty-state" />
<pagination
v-show="shippingTotal > 0"
:total="shippingTotal"
v-model:page="shippingQuery.pageNum"
v-model:limit="shippingQuery.pageSize"
@pagination="loadShippingList"
class="pagination-wrapper"
/>
</div>
</el-tab-pane>
<!-- 回款信息 Tab -->
<el-tab-pane label="回款信息" name="payment">
<div v-loading="loadingPayment" class="tab-content">
<el-table :data="planStageList" border stripe size="default" class="data-table">
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="回款阶段" width="220" align="center">
<template #default="scope">
{{ getPaymentStageName(scope.row.collectionStage) }}
</template>
</el-table-column>
<el-table-column label="预计回款比例(%)" width="160" align="center" prop="repaymentRate" />
<el-table-column label="预计回款时间" width="150" align="center">
<template #default="scope">
<span>{{ parseTime(scope.row.repaymentTime, '{y}-{m}-{d}') || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="200" prop="remark" show-overflow-tooltip />
</el-table>
<el-empty v-if="!loadingPayment && planStageList.length === 0" description="暂无回款信息" class="empty-state" />
</div>
</el-tab-pane>
<!-- 开票信息 Tab -->
<el-tab-pane label="开票信息" name="invoice">
<div class="tab-content">
<el-empty description="暂无开票信息,功能开发中" class="empty-state" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup lang="ts" name="OrderLedger">
import { computed, reactive, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getContractOrder } from '@/api/oa/erp/contractOrder';
import { getContractInfo } from '@/api/oa/erp/contractInfo';
import { ContractInfoVO } from '@/api/oa/erp/contractInfo/types';
import { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
import { listWmsShippingBill } from '@/api/wms/wmsShippingBill';
import { WmsShippingBillVO } from '@/api/wms/wmsShippingBill/types';
import { getErpProjectPlanStageList } from '@/api/oa/erp/erpProjectPlanStage';
import { ErpProjectPlanStageForm } from '@/api/oa/erp/erpProjectPlanStage/types';
import { getBasePaymentStageList } from '@/api/oa/base/paymentStage';
import { PaymentStageVO } from '@/api/oa/base/paymentStage/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
const { business_direction, contract_category, contract_type, contract_status, shipping_mode, shipping_status } = toRefs<any>(
proxy?.useDict('business_direction', 'contract_category', 'contract_type', 'contract_status', 'shipping_mode', 'shipping_status')
);
const projectId = computed(() => route.params.projectId as string | number | undefined);
const orderInfo = ref<ProjectInfoVO | null>(null);
const contractInfo = ref<ContractInfoVO | null>(null);
const shippingList = ref<WmsShippingBillVO[]>([]);
const planStageList = ref<ErpProjectPlanStageForm[]>([]);
const paymentStageList = ref<PaymentStageVO[]>([]);
const activeTab = ref('contract');
const loadedTabs = ref<Set<string>>(new Set(['contract']));
const loadingContract = ref(false);
const loadingShipping = ref(false);
const loadingPayment = ref(false);
const shippingTotal = ref(0);
const shippingQuery = reactive({ pageNum: 1, pageSize: 10 });
/** 格式化数字 */
const formatNumber = (num: number) => {
return num?.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
/** 返回 */
const handleBack = () => {
proxy?.$tab.closePage(route);
router.back();
};
/** 查看发货单明细 */
const handleViewShipping = (row: WmsShippingBillVO) => {
router.push({ path: '/shipping/wmsShippingBill/edit', query: { type: 'view', id: row.shippingBillId as string } });
};
/** 加载订单基本信息和合同信息 */
const loadOrderAndContract = async () => {
if (!projectId.value) return;
loadingContract.value = true;
try {
//
const orderRes = await getContractOrder(projectId.value);
orderInfo.value = orderRes.data;
// contractId
if (orderRes.data?.contractId) {
const contractRes = await getContractInfo(orderRes.data.contractId);
contractInfo.value = contractRes.data;
}
} finally {
loadingContract.value = false;
}
};
/** 加载发货信息 */
const loadShippingList = async () => {
if (!orderInfo.value?.contractId) {
shippingList.value = [];
shippingTotal.value = 0;
return;
}
loadingShipping.value = true;
try {
const res: any = await listWmsShippingBill({
...shippingQuery,
contractId: orderInfo.value.contractId
});
shippingList.value = res.rows || [];
shippingTotal.value = res.total || 0;
} finally {
loadingShipping.value = false;
}
};
/** 加载回款阶段基础数据 */
const loadPaymentStageBaseList = async () => {
try {
const res = await getBasePaymentStageList({ pageNum: 1, pageSize: 9999 });
paymentStageList.value = (res.data || []).map((item: any) => ({
...item,
paymentStageId: item.paymentStageId != null ? String(item.paymentStageId) : item.paymentStageId
}));
} catch (error) {
console.error('查询回款阶段列表失败:', error);
paymentStageList.value = [];
}
};
/** 加载回款信息(项目阶段计划) */
const loadPlanStageList = async () => {
if (!projectId.value) {
planStageList.value = [];
return;
}
loadingPayment.value = true;
try {
const res = await getErpProjectPlanStageList({ projectId: projectId.value });
planStageList.value = (res.data || []).map((item: any) => ({
...item,
collectionStage: item.collectionStage || '',
repaymentRate: item.repaymentRate || undefined,
repaymentTime: item.repaymentTime || undefined,
remark: item.remark || ''
}));
} catch (error) {
console.error('查询回款信息失败:', error);
planStageList.value = [];
} finally {
loadingPayment.value = false;
}
};
/** 获取回款阶段名称 */
const getPaymentStageName = (stageId: string | number) => {
if (!stageId) return '-';
const stage = paymentStageList.value.find((s) => String(s.paymentStageId) === String(stageId));
return stage?.paymentMethod || '-';
};
/** Tab 切换 */
const handleTabChange = (tabName: string) => {
if (!loadedTabs.value.has(tabName)) {
loadedTabs.value.add(tabName);
switch (tabName) {
case 'shipping':
shippingQuery.pageNum = 1;
loadShippingList();
break;
case 'payment':
loadPlanStageList();
break;
}
}
};
watch(
() => projectId.value,
() => {
if (projectId.value) {
loadedTabs.value.clear();
loadedTabs.value.add('contract');
loadPaymentStageBaseList();
loadOrderAndContract();
}
},
{ immediate: true }
);
</script>
<style scoped lang="scss">
.order-ledger-container {
padding: 16px;
.main-card {
:deep(.el-card__header) {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
:deep(.el-card__body) {
padding: 0;
}
}
.custom-tabs {
:deep(.el-tabs__content) {
padding: 20px;
}
}
.tab-content {
padding: 0;
}
.order-descriptions {
.value-text {
&.value-highlight {
font-weight: 600;
color: #409eff;
}
&.value-amount {
font-weight: 600;
color: #e6a23c;
}
}
}
.data-table {
:deep(.el-table__header) {
th {
font-weight: 600;
}
}
}
.empty-state {
padding: 40px 0;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
}
</style>
Loading…
Cancel
Save