更新一键审批

master
chenhao 2025-12-10 18:14:28 +08:00
parent 3d3af1867b
commit 8c3cf68639
5 changed files with 305 additions and 72 deletions

View File

@ -1,18 +1,18 @@
import http from '@/utils/http'
import type { ApiResponse, Order, OrderDetailResponse, ListParams, ApprovalParams, CompletedApprovalItem, CompletedListParams } from '@/types'
import type { ApiResponse, Order, OrderDetailResponse, ListParams, CompletedApprovalItem, CompletedListParams, BatchApprovalParams } from '@/types'
import type { AxiosResponse } from 'axios'
/**
*
*
*/
export const getOrderList = (params: ListParams): Promise<AxiosResponse<ApiResponse<{
total: number
rows: Order[]
}>>> => {
// 创建FormData对象
// 创建一个新的FormData对象
const formData = new FormData()
// 添加参数到FormData
// 动态地将params中的所有键值对添加到formData中
if (params.approve) formData.append('approve', params.approve)
formData.append('page', params.page.toString())
formData.append('pageSize', params.pageSize.toString())
@ -29,23 +29,23 @@ export const getOrderDetail = (id: string | number): Promise<AxiosResponse<ApiRe
}
/**
*
*
*/
export const submitApproval = (params: any): Promise<AxiosResponse<ApiResponse<any>>> => {
// 创建FormData对象
// 创建一个新的FormData对象
const formData = new FormData()
// 将所有参数添加到FormData中
// 动态地将params中的所有键值对添加到formData中
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) {
// 特殊处理variables参数它应该是一个对象
// 如果字段是variables它是一个对象需要特殊处理
if (key === 'variables' && typeof params[key] === 'object' && params[key] !== null) {
// 将variables对象的每个属性单独添加到FormData中
// 将variables对象中的每个键值对添加到formData
Object.keys(params[key]).forEach(variableKey => {
if (params[key][variableKey] !== undefined && params[key][variableKey] !== null) {
formData.append(`variables[${variableKey}]`, params[key][variableKey].toString())
formData.append(`variables[${variableKey}]`, params[key][variableKey].toString());
}
})
});
} else if (key === 'taxRateData' && Array.isArray(params[key])) {
// 特殊处理taxRateData数组
params[key].forEach((item: any, index: number) => {
@ -71,14 +71,21 @@ export const getCompletedOrderList = (params: CompletedListParams): Promise<Axio
total: number
rows: CompletedApprovalItem[]
}>>> => {
// 创建FormData对象
// 创建一个新的FormData对象
const formData = new FormData()
// 添加参数到FormData
// 动态地将params中的所有键值对添加到formData中
formData.append('page', params.page.toString())
formData.append('pageSize', params.pageSize.toString())
if (params.businessName) formData.append('businessName', params.businessName)
if (params.processKeyList) formData.append('processKeyList', params.processKeyList)
if (params.processKeyList) formData.append('processKeyList', params.processKeyList.join(','))
return http.post('/flow/completed/list', formData)
}
/**
*
*/
export const batchApproval = (params: BatchApprovalParams): Promise<AxiosResponse<ApiResponse<any>>> => {
return http.post('/project/order/order/approve/batch', params)
}

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import type { Order, OrderDetailResponse, ListParams, CompletedApprovalItem, CompletedListParams } from '@/types'
import { getOrderList, getOrderDetail, getCompletedOrderList } from '@/api/order'
import type { Order, OrderDetailResponse, ListParams, CompletedApprovalItem, CompletedListParams, BatchApprovalParams } from '@/types'
import { getOrderList, getOrderDetail, getCompletedOrderList, batchApproval } from '@/api/order'
interface OrderState {
// 待审批列表相关
@ -220,6 +220,18 @@ export const useOrderStore = defineStore('order', {
}
},
/**
*
*/
async batchApproval(params: BatchApprovalParams) {
try {
const response = await batchApproval(params)
return response
} catch (error) {
throw error
}
},
/**
*
*/

View File

@ -242,6 +242,14 @@ export interface CompletedListParams {
processKeyList: Array<string>
}
// 批量审批参数
export interface BatchApprovalParams {
variables: {
approveBtn: ApproveBtn
comment: string
}
}
// ============= 采购相关类型定义 =============
// 采购订单信息类型

View File

@ -77,7 +77,9 @@ class HttpClient {
message = data?.msg || `请求失败 (${status})`
}
} else if (error.request) {
message = '网络连接失败'
message = '会话已过期,请重新登录'
localStorage.removeItem('isAuthenticated')
window.location.href = '/login'
}
showFailToast(message)

View File

@ -114,16 +114,98 @@
</van-pull-refresh>
</van-tab>
</van-tabs>
<!-- 一键审批操作悬浮框 -->
<div v-if="currentTab === 'pending' && orderList.length > 0" class="batch-actions-footer">
<div class="footer-content">
<van-button
type="primary"
size="large"
@click="showBatchApprovalDialog"
:loading="batchSubmitting"
class="batch-approve-btn"
>
一键审批
</van-button>
</div>
</div>
<!-- 批量审批意见弹窗 -->
<van-popup
v-model:show="batchApprovalDialogVisible"
position="bottom"
round
:style="{ height: '50%' }"
>
<div class="approval-dialog">
<div class="dialog-header">
<span>审批意见</span>
<van-icon name="cross" @click="batchApprovalDialogVisible = false"/>
</div>
<div class="dialog-body">
<!-- 默认意见标签 -->
<div class="opinion-tags">
<div class="tags-title">常用意见</div>
<div class="tags-container">
<van-tag
v-for="tag in getBatchOpinionTags()"
:key="tag"
:type="selectedBatchTag === tag ? 'primary' : 'default'"
size="medium"
@click="selectBatchTag(tag)"
class="opinion-tag"
>
{{ tag }}
</van-tag>
</div>
</div>
<div class="opinion-input">
<van-field
v-model="batchApprovalOpinion"
type="textarea"
placeholder="请输入审批意见"
rows="4"
autosize
maxlength="500"
show-word-limit
/>
</div>
</div>
<div class="dialog-footer">
<div class="footer-buttons">
<van-button
type="default"
block
@click="submitBatchApproval(0)"
:loading="batchSubmitting"
class="action-btn reject-btn"
>
驳回
</van-button>
<van-button
type="primary"
block
@click="submitBatchApproval(1)"
:loading="batchSubmitting"
class="action-btn approve-btn"
>
通过
</van-button>
</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useOrderStore } from '@/store/order'
import { formatOrderStatus, formatAmount, formatDate } from '@/utils'
import type { OrderStatus, ApprovalStatus } from '@/types'
import { formatAmount, formatDate } from '@/utils'
import type { ApprovalStatus, ApproveBtn, BatchApprovalParams } from '@/types'
import { showToast, showSuccessToast } from 'vant'
const router = useRouter()
const orderStore = useOrderStore()
@ -142,15 +224,11 @@ const searchKeyword = ref('')
const refreshing = ref(false)
const completedRefreshing = ref(false)
//
const getStatusClass = (status: OrderStatus) => {
const classMap = {
'0': 'pending',
'1': 'approved',
'2': 'rejected'
}
return classMap[status] || 'pending'
}
//
const batchApprovalDialogVisible = ref(false)
const batchApprovalOpinion = ref('')
const batchSubmitting = ref(false)
const selectedBatchTag = ref('')
//
const onLoad = async () => {
@ -178,27 +256,16 @@ const onTabChange = (name: string) => {
searchKeyword.value = ''
if (name === 'completed' && completedList.value.length === 0) {
//
onCompletedLoad()
}
}
//
const onCompletedLoad = async () => {
console.log('=== van-list 触发 onCompletedLoad ===')
console.log('当前状态:', {
loading: completedLoading.value,
finished: completedFinished.value,
listLength: completedList.value.length,
currentPage: orderStore.completedCurrentPage
})
try {
await orderStore.loadCompletedOrderList()
console.log('onCompletedLoad 执行完成')
} catch (error) {
console.error('加载已审批列表失败:', error)
// loading
orderStore.completedLoading = false
}
}
@ -244,19 +311,13 @@ const handleClear = async () => {
//
const getCompletedStatusText = (status: ApprovalStatus) => {
const statusMap = {
2: '驳回',
3: '通过'
}
const statusMap = { 2: '驳回', 3: '通过' }
return statusMap[status] || '提交'
}
//
const getCompletedStatusClass = (status: ApprovalStatus) => {
const classMap = {
2: 'rejected',
3: 'approved'
}
const classMap = { 2: 'rejected', 3: 'approved' }
return classMap[status] || 'pending'
}
@ -265,16 +326,68 @@ const goToDetail = (id: number) => {
router.push({ path: `/detail/${id}`, query: { from: 'order' } })
}
//
//
const goToCompletedDetail = (businessId: number) => {
router.push({
path: `/detail/${businessId}`,
query: { readonly: 'true', from: 'order' }
})
router.push({ path: `/detail/${businessId}`, query: { readonly: 'true', from: 'order' } })
}
//
const showBatchApprovalDialog = () => {
batchApprovalOpinion.value = ''
selectedBatchTag.value = ''
batchApprovalDialogVisible.value = true
}
//
const getBatchOpinionTags = () => {
return [
'所有信息已阅,审核通过',
'经审查有问题,驳回'
]
}
//
const selectBatchTag = (tag: string) => {
if (selectedBatchTag.value === tag) {
selectedBatchTag.value = ''
batchApprovalOpinion.value = ''
} else {
selectedBatchTag.value = tag
batchApprovalOpinion.value = tag
}
}
//
const submitBatchApproval = async (approveStatus: ApproveBtn) => {
const opinion = batchApprovalOpinion.value.trim()
if (approveStatus === 0 && !opinion) {
showToast('请输入驳回原因')
return
}
batchSubmitting.value = true
try {
const params: BatchApprovalParams = {
variables: {
approveBtn: approveStatus+'',
comment: opinion || (approveStatus === 1 ? '同意' : '')
}
}
await orderStore.batchApproval(params)
showSuccessToast(approveStatus === 1 ? '一键审批通过成功' : '一键驳回成功')
batchApprovalDialogVisible.value = false
await onRefresh()
} catch (error) {
console.error('一键审批失败:', error)
showToast('一键审批失败,请稍后重试')
} finally {
batchSubmitting.value = false
}
}
onMounted(() => {
//
orderStore.resetListState()
orderStore.resetCompletedListState()
onLoad()
@ -285,6 +398,7 @@ onMounted(() => {
.order-list-page {
min-height: 100vh;
background-color: var(--van-background-color);
padding-bottom: 80px; /* 为底部批量操作栏留出空间 */
}
// Tab
@ -471,4 +585,94 @@ onMounted(() => {
color: #666;
margin-right: 8px;
}
.batch-actions-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 10px 16px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
.footer-content {
display: flex;
align-items: center;
justify-content: flex-end;
}
.batch-approve-btn {
width: 100%;
}
}
.approval-dialog {
height: 100%;
display: flex;
flex-direction: column;
.dialog-header {
padding: 16px;
border-bottom: 1px solid #ebedf0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: 500;
}
.dialog-body {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.dialog-footer {
padding: 16px;
border-top: 1px solid #ebedf0;
.footer-buttons {
display: flex;
gap: 10px;
}
}
}
.opinion-tags {
margin-bottom: 16px;
.tags-title {
font-size: 14px;
color: #646566;
margin-bottom: 12px;
font-weight: 500;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
.opinion-tag {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
}
}
.opinion-input {
.van-field {
border: 1px solid #ebedf0;
border-radius: 8px;
background: #f7f8fa;
}
}
</style>