feat(approve): 新增已审批订单查看功能

- 新增已审批订单列表页面,支持多条件查询和分页
- 实现审批详情抽屉展示,集成审批流程组件
- 添加订单详情和项目详情抽屉查看功能
- 优化审批相关组件样式和交互体验
- 后端增加审批节点信息查询接口和逻辑处理
- 数据库层面支持根据审批记录筛选订单数据
dev_1.0.0
chenhao 2025-11-21 10:47:00 +08:00
parent 993722e4a0
commit 281fa1f4e5
18 changed files with 353 additions and 27 deletions

View File

@ -0,0 +1,9 @@
import request from "@/utils/request";
export function listOrder(query) {
return request({
url: '/project/order/vue/approve/log/list',
method: 'GET',
params: query
})
}

View File

@ -13,7 +13,8 @@ export function listProject(query) {
export function getProject(id) {
return request({
url: '/sip/project/vue/' + id,
method: 'get'
method: 'get',
needLoading:true
})
}

View File

@ -13,7 +13,8 @@ export function listOrder(query) {
export function getOrder(id) {
return request({
url: '/project/order/vue/' + id,
method: 'get'
method: 'get',
needLoading:true
})
}

View File

@ -345,20 +345,35 @@ export default {
</script>
<style scoped>
.order-info-display .el-form-item {
margin-bottom: 5px;
/* Make form item labels bold to match table headers in other components */
.order-info-display ::v-deep .el-form-item__label {
font-weight: bold;
font-size: 13px; /* Ensure labels also have consistent font size */
color: #606266; /* Ensure labels also have consistent font color */
}
.order-info-display .el-form-item {
margin-bottom: 0; /* Reduced margin for a denser look */
display: flex;
align-items: center;
}
/* Adjust span line-height for better text appearance */
.order-info-display span {
color: #606266;
line-height: 36px;
line-height: 1.5; /* Use a relative line-height instead of a fixed pixel value */
font-size: 13px; /* Apply consistent font size for values */
}
/* Adjust padding and borders for a cleaner, more table-like feel */
.col-item-border {
border: 1px solid ;
padding: 3px;
margin-bottom: -1px; /* 消除边框重叠 */
margin-right: -1px; /* 消除边框重叠 */
border: 1px solid #e0e0e0;
padding: 8px; /* Standardized padding */
margin-bottom: -1px; /* Collapse borders */
margin-right: -1px; /* Collapse borders */
}
.el-row {
margin-bottom: -1px; /* 消除el-col之间的间隙 */
margin-bottom: -1px;
}
</style>

View File

@ -0,0 +1,214 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="合同编号" prop="orderCode">
<el-input
v-model="queryParams.orderCode"
placeholder="请输入合同编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="客户名称" prop="customerName">
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="汇智负责人" prop="dutyName">
<el-input
v-model="queryParams.dutyName"
placeholder="请输入汇智负责人"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="orderList">
<el-table-column label="合同编号" align="center" prop="orderCode" />
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" />
<el-table-column label="汇智负责人" align="center" prop="dutyName" />
<el-table-column label="审批节点" align="center" prop="approveNode">
<template slot-scope="scope">
<span>{{ scope.row.approveNode || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleApprove(scope.row)"
>查看详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 审批弹窗 -->
<el-drawer
title="已审批最新详情"
:visible.sync="approveDialogVisible"
custom-class="approve-dialog"
size="80%"
direction="rtl"
append-to-body
destroy-on-close
>
<approve-dialog
v-if="approveDialogVisible"
ref="approveDialog"
:order-id="currentOrderId"
@close="closeApproveDialog"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="approveDialogVisible = false"> </el-button>
<el-button type="danger" @click="handleReject"> </el-button>
<el-button type="primary" @click="handleApproveConfirm"> </el-button>
</div>
</el-drawer>
</div>
</template>
<script>
import { listOrder } from "@/api/approve/order/orderLog";
import ApproveDialog from '../order/Approve.vue';
export default {
name: "ApprovedOrder",
components: {
ApproveDialog
},
data() {
return {
// ...data...
approveDialogVisible: false,
currentOrderId: null,
//
loading: true,
//
showSearch: true,
//
total: 0,
//
orderList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: null,
projectName: null,
projectCode: null,
customerName: null,
dutyName: null,
approveNode: null,
},
};
},
created() {
this.getList();
},
methods: {
/** 查询订单列表 */
getList() {
this.loading = true;
listOrder(this.queryParams).then(response => {
this.orderList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
resetForm(formName) {
if (this.$refs[formName]) {
this.$refs[formName].resetFields();
}
},
/** 审批按钮操作 */
handleApprove(row) {
this.currentOrderId = row.id;
this.approveDialogVisible = true;
},
/** 关闭审批弹窗 */
closeApproveDialog() {
this.approveDialogVisible = false;
this.currentOrderId = null;
this.getList(); //
},
/** 同意按钮操作 */
handleApproveConfirm() {
if (this.$refs.approveDialog) {
this.$refs.approveDialog.handleApprove();
}
},
/** 驳回按钮操作 */
handleReject() {
if (this.$refs.approveDialog) {
this.$refs.approveDialog.handleReject();
}
}
}
};
</script>
<style scoped>
::v-deep .el-drawer__body {
overflow-y: auto;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="approve-dialog-content">
<div style="display: flex;flex-direction: row-reverse;">
<el-button
v-if="dataLoaded"
@ -535,6 +535,9 @@ export default {
</script>
<style scoped>
.approve-dialog-content {
height: 100%;
}
.approve-container {
position: relative;
}
@ -571,6 +574,7 @@ export default {
/* Tab样式 */
.approve-tabs {
margin-top: 20px;
margin-left: 10px;
}
/* 流转过程容器 */

View File

@ -318,11 +318,12 @@ export default {
<style scoped>
.config-info-container {
font-family: 'Arial', sans-serif;
padding: 20px;
}
h3 {
font-size: 14px; /* Adjusted to match form item labels */
font-weight: bold;
color: #333;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
@ -336,11 +337,14 @@ h3 {
table-layout: fixed;
}
.product-table th, .product-table td {
.product-table th,
.product-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
word-wrap: break-word;
font-size: 13px; /* Smaller font size for table content */
color: #606266; /* Consistent text color */
}
/* Column widths for alignment */

View File

@ -66,7 +66,11 @@
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" />
<el-table-column label="汇智负责人" align="center" prop="dutyName" />
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="审批节点" align="center" prop="approveNode">
<template slot-scope="scope">
<span>{{ scope.row.approveNode || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button

View File

@ -92,7 +92,7 @@ import { getDicts } from "@/api/system/dict/data";
import CheckoutDialog from './CheckoutDialog.vue';
import OuterDetailDialog from './OuterDetailDialog.vue';
import OrderDetailDrawer from "@/views/project/order/OrderDetailDrawer.vue";
import OrderInfo from "@/components/order/OrderInfo.vue";
import OrderInfo from "@/components/order/OrderInfoDisplay.vue";
export default {
name: "ExecutionEdit",

View File

@ -81,16 +81,12 @@
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="合同编号" align="center" prop="orderCode" width="200">
<template slot-scope="scope">
<router-link :to="'/project/order-view/' + scope.row.id" class="link-type">
<span>{{ scope.row.orderCode }}</span>
</router-link>
<span class="link-type" @click="handleViewOrder(scope.row)">{{ scope.row.orderCode }}</span>
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="300" :show-overflow-tooltip="true">
<template slot-scope="scope">
<router-link :to="'/sip/project-view/' + scope.row.projectId" class="link-type">
<span>{{ scope.row.projectName }}</span>
</router-link>
<a @click="handleViewProject(scope.row)" class="link-type">{{ scope.row.projectName }}</a>
</template>
</el-table-column>
<el-table-column label="出库状态" align="center" prop="outerStatus">
@ -186,15 +182,27 @@
</div>
</el-dialog>
<!-- 项目详情抽屉 -->
<project-detail-drawer :visible.sync="projectDrawerVisible" :project-id="currentProjectId" />
<!-- 订单详情抽屉 -->
<order-detail-drawer :visible.sync="orderDrawerVisible" :order-id="currentOrderId" title="订单详情" />
</div>
</template>
<script>
import { listExecution, recallExecution, downloadSignFile } from "@/api/inventory/execution";
import { getToken } from "@/utils/auth";
import OrderDetailDrawer from '../../project/order/OrderDetailDrawer.vue';
import ProjectDetailDrawer from '../../project/info/ProjectDetailDrawer.vue';
export default {
name: "Execution",
components: {
ProjectDetailDrawer,
OrderDetailDrawer
},
dicts: ['execution_outer_status', 'execution_delivery_status', 'execution_sign_status'],
data() {
return {
@ -208,6 +216,11 @@ export default {
executionList: [],
//
dateRange: [],
// --- ---
projectDrawerVisible: false,
orderDrawerVisible: false,
currentProjectId: null,
currentOrderId: null,
//
queryParams: {
pageNum: 1,
@ -307,6 +320,16 @@ export default {
this.getList();
this.$modal.msgSuccess("撤单成功");
}).catch(() => {});
},
/** 查看项目详情 */
handleViewProject(row) {
this.currentProjectId = row.projectId;
this.projectDrawerVisible = true;
},
/** 查看订单详情 */
handleViewOrder(row) {
this.currentOrderId = row.id;
this.orderDrawerVisible = true;
}
}
};

View File

@ -196,7 +196,7 @@
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
@click="handleView(scope.row)"
v-hasPermi="['sip:project:edit']"
>项目详情</el-button>
<el-button
@ -336,14 +336,14 @@ export default {
const yys = this.dict.type.bg_yys || [];
const hysy = this.dict.type.bg_hysy || [];
const combined = [...yys, ...hysy];
const uniqueMap = new Map();
combined.forEach(item => {
if (!uniqueMap.has(item.value)) {
uniqueMap.set(item.value, item);
}
});
return Array.from(uniqueMap.values());
}
},
@ -456,4 +456,4 @@ export default {
},
}
};
</script>
</script>

View File

@ -7,7 +7,6 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.sip.domain.ProjectOrderFileLog;
import com.ruoyi.sip.domain.ProjectOrderInfo;
import com.ruoyi.sip.flowable.domain.Todo;
@ -59,6 +58,18 @@ public class VueProjectOrderInfoController extends BaseController {
projectOrderInfo.setApprove(ShiroUtils.getUserId().toString());
startPage();
List<ProjectOrderInfo> list = projectOrderInfoService.selectProjectOrderInfoList(projectOrderInfo);
clearPage();
todoService.fillOrderApproveNode(list);
return getDataTable(list);
}
@RequiresPermissions(value = {"project:order:list", "project:order:approve"}, logical = Logical.OR)
@GetMapping("/approve/log/list")
public TableDataInfo listApproveLog(ProjectOrderInfo projectOrderInfo) {
projectOrderInfo.setApproveLog(ShiroUtils.getUserId().toString());
startPage();
List<ProjectOrderInfo> list = projectOrderInfoService.selectProjectOrderInfoList(projectOrderInfo);
clearPage();
todoService.fillOrderApproveNode(list);
return getDataTable(list);
}

View File

@ -249,6 +249,8 @@ public class ProjectOrderInfo extends BaseEntity {
// @Excel(name = "供货商")
private String supplier;
private String approve;
private String approveLog;
private String approveNode;
/**
*

View File

@ -3,6 +3,7 @@ package com.ruoyi.sip.flowable.mapper;
import com.ruoyi.sip.flowable.domain.Todo;
import com.sun.xml.internal.bind.v2.TODO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -79,5 +80,7 @@ public interface TodoMapper
Todo selectLastApproveTodo(@Param("businessKey") String businessKey,@Param("taskName") String taskName);
List<Todo> listTodoByBusinessKeyAndProcessKey(@Param("businessKeys") List<String> businessKeyList, @Param("processKeys") List<String> list);
// List<Todo> listApprove(WorkOrderStatisticsRequestDto dto);
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.sip.flowable.service;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.sip.domain.ProjectOrderInfo;
import com.ruoyi.sip.flowable.domain.Todo;
import org.flowable.engine.runtime.ProcessInstance;
@ -125,4 +126,6 @@ public interface TodoService
void deleteTodoByBusinessKey(String businessKey);
Todo selectLastApproveTodo(String businessKey,String taskName);
void fillOrderApproveNode(List<ProjectOrderInfo> list);
}

View File

@ -26,6 +26,7 @@ import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -64,7 +65,10 @@ public class TodoServiceImpl implements TodoService {
@Autowired
private IProjectOrderInfoService projectOrderInfoService;
@Value("${process.definition.orderApproveOnline}")
private String orderOnlineFlowKey;
@Value("${process.definition.orderApproveOffline}")
private String orderOfflineFlowKey;
/**
*
*
@ -324,6 +328,19 @@ public class TodoServiceImpl implements TodoService {
return todoMapper.selectLastApproveTodo(businessKey,taskName);
}
@Override
public void fillOrderApproveNode(List<ProjectOrderInfo> list) {
List<String> businessKeyList = list.stream().map(ProjectOrderInfo::getOrderCode).collect(Collectors.toList());
List<Todo> todoList = todoMapper.listTodoByBusinessKeyAndProcessKey(businessKeyList, Arrays.asList(orderOnlineFlowKey, orderOfflineFlowKey));
Map<String, String> approveUserNameMap = todoList.stream()
.collect(Collectors.groupingBy(Todo::getBusinessKey,
Collectors.mapping(Todo::getApproveUserName,
Collectors.joining("、"))));
for (ProjectOrderInfo orderInfo : list) {
orderInfo.setApproveNode(approveUserNameMap.get(orderInfo.getOrderCode()));
}
}
/**
*
*/

View File

@ -282,4 +282,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by approve_time desc
limit 1
</select>
<select id="listTodoByBusinessKeyAndProcessKey" resultType="com.ruoyi.sip.flowable.domain.Todo">
<include refid="selectTodoVo"/>
where business_key in
<foreach item="item" collection="businessKeys" open="(" separator="," close=")">
#{item}
</foreach>
and process_key in
<foreach item="item" collection="processKeys" open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>

View File

@ -200,6 +200,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
process_key in ('order_approve_online','order_approve_offline') and approve_user = #{approve} and
task_name != '售前')
</if>
<if test="approveLog!=null and approveLog!=''">and t1.order_code in (select business_key from bu_todo_completed where
process_key in ('order_approve_online','order_approve_offline') and approve_user = #{approveLog} and
task_name != '售前')
</if>
<if test="productCodeList!=null and productCodeList.size>0">
and t1.project_id in (select distinct t2.project_id from project_product_info t2 where t2.product_bom_code
in