feat(approval): 添加已审批列表功能

- 新增已审批列表接口类型定义 CompletedApprovalItem 和 CompletedListParams
- 实现已审批列表 Tab 页面,支持待审批和已审批切换- 添加已审批列表数据加载和分页逻辑
- 实现已审批详情页只读模式展示- 更新税率输入框禁用逻辑,支持只读模式- 优化审批按钮显示控制,只读模式下隐藏审批按钮
- 调整搜索功能,支持待审批和已审批分别搜索- 更新状态标签样式,区分待审批、已审批状态显示
- 添加已审批列表空状态提示和刷新功能
master
chenhao 2025-09-26 14:54:37 +08:00
parent 3f221a1216
commit 3271644a01
6 changed files with 1060 additions and 1821 deletions

View File

@ -1,5 +1,5 @@
import http from '@/utils/http'
import type { ApiResponse, Order, OrderDetailResponse, ListParams, ApprovalParams } from '@/types'
import type { ApiResponse, Order, OrderDetailResponse, ListParams, ApprovalParams, CompletedApprovalItem, CompletedListParams } from '@/types'
import type { AxiosResponse } from 'axios'
/**
@ -63,3 +63,21 @@ export const submitApproval = (params: any): Promise<AxiosResponse<ApiResponse<a
return http.post('/project/order/order/approve', formData)
}
/**
*
*/
export const getCompletedOrderList = (params: CompletedListParams): Promise<AxiosResponse<ApiResponse<{
total: number
rows: CompletedApprovalItem[]
}>>> => {
// 创建FormData对象
const formData = new FormData()
// 添加参数到FormData
formData.append('page', params.page.toString())
formData.append('pageSize', params.pageSize.toString())
if (params.businessName) formData.append('businessName', params.businessName)
return http.post('/flow/completed/list', formData)
}

View File

@ -1,9 +1,9 @@
import { defineStore } from 'pinia'
import type { Order, OrderDetailResponse, ListParams } from '@/types'
import { getOrderList, getOrderDetail } from '@/api/order'
import type { Order, OrderDetailResponse, ListParams, CompletedApprovalItem, CompletedListParams } from '@/types'
import { getOrderList, getOrderDetail, getCompletedOrderList } from '@/api/order'
interface OrderState {
// 列表相关
// 待审批列表相关
orderList: Order[]
loading: boolean
finished: boolean
@ -12,6 +12,15 @@ interface OrderState {
total: number
keyword: string
// 已审批列表相关
completedList: CompletedApprovalItem[]
completedLoading: boolean
completedFinished: boolean
completedCurrentPage: number
completedPageSize: number
completedTotal: number
completedKeyword: string
// 详情相关
currentOrder: any | null // 临时改为any来避免类型问题
detailLoading: boolean
@ -19,6 +28,7 @@ interface OrderState {
export const useOrderStore = defineStore('order', {
state: (): OrderState => ({
// 待审批列表状态
orderList: [],
loading: false,
finished: false,
@ -27,6 +37,15 @@ export const useOrderStore = defineStore('order', {
total: 0,
keyword: '',
// 已审批列表状态
completedList: [],
completedLoading: false,
completedFinished: false,
completedCurrentPage: 1,
completedPageSize: 20,
completedTotal: 0,
completedKeyword: '',
currentOrder: null,
detailLoading: false
}),
@ -41,8 +60,11 @@ export const useOrderStore = defineStore('order', {
// 获取当前用户信息
currentUser: (state) => state.currentOrder?.user,
// 检查是否还有更多数据
hasMore: (state) => state.orderList.length < state.total
// 检查待审批是否还有更多数据
hasMore: (state) => state.orderList.length < state.total,
// 检查已审批是否还有更多数据
completedHasMore: (state) => state.completedList.length < state.completedTotal
},
actions: {
@ -78,16 +100,17 @@ export const useOrderStore = defineStore('order', {
}
this.total = total
this.currentPage++
// 判断是否已加载完所有数据
if (this.orderList.length >= total) {
if (this.orderList.length >= total || rows.length === 0) {
this.finished = true
} else {
// 只有当还有更多数据时才递增页码
this.currentPage++
}
return response
} catch (error) {
console.error('加载订单列表失败:', error)
throw error
} finally {
this.loading = false
@ -107,30 +130,24 @@ export const useOrderStore = defineStore('order', {
*/
async fetchOrderDetail(id: string | number) {
this.detailLoading = true
console.log('开始获取订单详情ID:', id)
try {
const response = await getOrderDetail(id)
console.log('API响应:', response)
console.log('响应数据:', response.data)
console.log('实际数据:', response.data.data)
// 直接获取数据对象
const orderData = response.data.data
console.log('订单数据对象:', orderData)
// 确保数据存在再赋值
if (orderData) {
this.currentOrder = orderData
console.log('赋值后的存储数据:', this.currentOrder)
console.log('项目订单信息:', this.currentOrder.projectOrderInfo)
} else {
console.error('订单数据为空')
}
return response
} catch (error) {
console.error('获取订单详情失败:', error)
throw error
} finally {
this.detailLoading = false
@ -144,16 +161,95 @@ export const useOrderStore = defineStore('order', {
this.currentOrder = null
},
/**
*
*/
async loadCompletedOrderList(refresh = false) {
// 如果已经加载完所有数据且不是刷新操作,直接返回
if (this.completedFinished && !refresh) {
return Promise.resolve()
}
if (refresh) {
this.completedCurrentPage = 1
this.completedFinished = false
this.completedList = []
}
this.completedLoading = true
try {
const params: CompletedListParams = {
page: this.completedCurrentPage,
pageSize: this.completedPageSize,
businessName: this.completedKeyword || undefined
}
const response = await getCompletedOrderList(params)
const { total, rows } = response.data
if (refresh) {
this.completedList = rows
} else {
this.completedList.push(...rows)
}
this.completedTotal = total
// 判断是否已加载完所有数据
if (this.completedList.length >= total || rows.length === 0) {
this.completedFinished = true
console.log('已审批列表加载完成')
} else {
// 只有当还有更多数据时才递增页码
this.completedCurrentPage++
}
return response
} catch (error) {
console.error('加载已审批列表失败:', error)
// 发生错误时重置加载状态,但不标记为完成
this.completedLoading = false
throw error
} finally {
this.completedLoading = false
}
},
/**
*
*/
async searchCompletedOrders(keyword: string) {
this.completedKeyword = keyword
await this.loadCompletedOrderList(true)
},
/**
*
*/
resetListState() {
// 重置待审批列表状态
this.orderList = []
this.currentPage = 1
this.finished = false
this.loading = false
this.keyword = ''
this.total = 0
},
/**
*
*/
resetCompletedListState() {
this.completedList = []
this.completedCurrentPage = 1
this.completedFinished = false
this.completedLoading = false
this.completedKeyword = ''
this.completedTotal = 0
}
}
})

View File

@ -197,3 +197,45 @@ export interface ApprovalParams {
updateTime?: string
[property: string]: any
}
// 已审批列表项类型
export interface CompletedApprovalItem {
allApproveUserName: string
applyTime: string
applyUserName: string
approveOpinion: string
approveStatus: ApprovalStatus
approveTime: string
approveUser: string
approveUserName: string
businessId: number
businessKey: string
businessName: string
createBy?: string
createTime?: string
extendField1?: string
extendField2?: string
formKey?: string
id?: number
nextAllApproveUserName: string
processInstanceId: string
processKey: string
processName: string
recoveryType?: number
remark?: string
roleName: string
taskId: string
taskName: string
taxRateData?: any
todoId: string
updateBy?: string
updateTime?: string
variables?: any
}
// 已审批列表查询参数
export interface CompletedListParams {
businessName?: string
page: number
pageSize: number
}

View File

@ -17,9 +17,12 @@
<div class="project-title">
{{ currentOrderInfo.projectName }}REV.{{ currentOrderInfo.versionCode }}
</div>
<div class="project-status">
<div class="project-status" v-if="route.query.readonly !== 'true'">
待审批
</div>
<div class="project-status completed" v-else>
已审批
</div>
</div>
<!-- Tab标签页 -->
@ -191,7 +194,7 @@
<input
type="text"
v-model="product.taxRate"
:disabled="!isBusinessApproval"
:disabled="isTaxRateDisabled"
@change="updateTaxRate(product)"
class="tax-rate-input"
/>
@ -236,7 +239,7 @@
<input
type="text"
v-model="product.taxRate"
:disabled="!isBusinessApproval"
:disabled="isTaxRateDisabled"
@change="updateTaxRate(product)"
class="tax-rate-input"
/>
@ -281,7 +284,7 @@
<input
type="text"
v-model="product.taxRate"
:disabled="!isBusinessApproval"
:disabled="isTaxRateDisabled"
@change="updateTaxRate(product)"
class="tax-rate-input"
/>
@ -514,6 +517,11 @@ const isBusinessApproval = computed(() => {
return currentOrder.value?.todo?.taskName?.startsWith('商务')
})
//
const isTaxRateDisabled = computed(() => {
return !isBusinessApproval.value || route.query.readonly === 'true'
})
// Tab
const activeTab = ref('order')
@ -528,7 +536,11 @@ const hasProductInfo = computed(() => {
})
const showApprovalButtons = computed(() => {
//
//
if (route.query.readonly === 'true') {
return false
}
//
return true
})
@ -867,6 +879,12 @@ onMounted(async () => {
font-size: 12px;
font-weight: 500;
border: 1px solid #FFD591;
&.completed {
background: #F6FFED;
color: #52C41A;
border: 1px solid #B7EB8F;
}
}
.tab-content {

View File

@ -4,7 +4,7 @@
<div class="search-container">
<van-search
v-model="searchKeyword"
placeholder="搜索订单编号、客户名称"
:placeholder="currentTab === 'pending' ? '搜索订单编号、客户名称' : '搜索合同名称'"
@search="handleSearch"
@clear="handleClear"
class="custom-search"
@ -18,67 +18,129 @@
</van-search>
</div>
<!-- 下拉刷新 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 订单列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div v-for="order in orderList" :key="order.id" class="order-item" @click="goToDetail(order.id)">
<div class="order-header">
<div class="order-code">{{ order.orderCode }}</div>
<div class="status-tag pending">
待审批
</div>
</div>
<!-- Tab切换 -->
<van-tabs v-model:active="currentTab" @change="onTabChange" class="approval-tabs">
<!-- 待审批Tab -->
<van-tab name="pending" title="待审批">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div v-for="order in orderList" :key="order.id" class="order-item" @click="goToDetail(order.id)">
<div class="order-header">
<div class="order-code">{{ order.orderCode }}</div>
<div class="status-tag pending">
待审批
</div>
</div>
<div class="order-info">
<div class="info-row">
<span class="label">项目名称</span>
<span class="value">{{ order.projectName }}</span>
<div class="order-info">
<div class="info-row">
<span class="label">项目名称</span>
<span class="value">{{ order.projectName }}</span>
</div>
<div class="info-row">
<span class="label">客户名称</span>
<span class="value">{{ order.customerName }}</span>
</div>
<div class="info-row">
<span class="label">订单金额</span>
<span class="value amount">{{ formatAmount(order.shipmentAmount) }}</span>
</div>
<div class="info-row">
<span class="label">创建时间</span>
<span class="value">{{ formatDate(order.createTime, 'YYYY-MM-DD HH:mm') }}</span>
</div>
</div>
</div>
<div class="info-row">
<span class="label">客户名称</span>
<span class="value">{{ order.customerName }}</span>
</div>
<div class="info-row">
<span class="label">订单金额</span>
<span class="value amount">{{ formatAmount(order.shipmentAmount) }}</span>
</div>
<div class="info-row">
<span class="label">创建时间</span>
<span class="value">{{ formatDate(order.createTime, 'YYYY-MM-DD HH:mm') }}</span>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="!loading && orderList.length === 0" class="empty-state">
<van-empty description="暂无订单数据" />
</div>
</van-list>
</van-pull-refresh>
<!-- 空状态 -->
<div v-if="!loading && orderList.length === 0" class="empty-state">
<van-empty description="暂无待审批数据" />
</div>
</van-list>
</van-pull-refresh>
</van-tab>
<!-- 已审批Tab -->
<van-tab name="completed" title="已审批">
<van-pull-refresh v-model="completedRefreshing" @refresh="onCompletedRefresh">
<van-list
v-model:loading="completedLoading"
:finished="completedFinished"
finished-text="没有更多了"
@load="onCompletedLoad"
>
<div v-for="item in completedList" :key="item.todoId" class="order-item" @click="goToCompletedDetail(item.businessId)">
<div class="order-header">
<div class="order-code">{{ item.businessKey }}</div>
<div class="status-tag" :class="getCompletedStatusClass(item.approveStatus)">
{{ getCompletedStatusText(item.approveStatus) }}
</div>
</div>
<div class="order-info">
<div class="info-row">
<span class="label">合同名称</span>
<span class="value">{{ item.businessName }}</span>
</div>
<div class="info-row">
<span class="label">流程名称</span>
<span class="value">{{ item.processName }}</span>
</div>
<div class="info-row">
<span class="label">发起人</span>
<span class="value">{{ item.applyUserName }}</span>
</div>
<div class="info-row">
<span class="label">审批时间</span>
<span class="value">{{ item.approveTime }}</span>
</div>
<div v-if="item.approveOpinion" class="info-row">
<span class="label">审批意见</span>
<span class="value">{{ item.approveOpinion }}</span>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="!completedLoading && completedList.length === 0" class="empty-state">
<van-empty description="暂无已审批数据" />
</div>
</van-list>
</van-pull-refresh>
</van-tab>
</van-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } 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 } from '@/types'
import type { OrderStatus, ApprovalStatus } from '@/types'
const router = useRouter()
const orderStore = useOrderStore()
//
const { orderList, loading, finished } = storeToRefs(orderStore)
//
const { completedList, completedLoading, completedFinished } = storeToRefs(orderStore)
// Tab
const currentTab = ref('pending')
//
const searchKeyword = ref('')
const refreshing = ref(false)
const completedRefreshing = ref(false)
//
const getStatusClass = (status: OrderStatus) => {
@ -110,10 +172,57 @@ const onRefresh = async () => {
}
}
// Tab
const onTabChange = (name: string) => {
currentTab.value = name
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
}
}
//
const onCompletedRefresh = async () => {
try {
await orderStore.loadCompletedOrderList(true)
completedRefreshing.value = false
} catch (error) {
completedRefreshing.value = false
console.error('刷新已审批列表失败:', error)
}
}
//
const handleSearch = async () => {
try {
await orderStore.searchOrders(searchKeyword.value.trim())
const keyword = searchKeyword.value.trim()
if (currentTab.value === 'pending') {
await orderStore.searchOrders(keyword)
} else {
await orderStore.searchCompletedOrders(keyword)
}
} catch (error) {
console.error('搜索失败:', error)
}
@ -123,20 +232,53 @@ const handleSearch = async () => {
const handleClear = async () => {
searchKeyword.value = ''
try {
await orderStore.searchOrders('')
if (currentTab.value === 'pending') {
await orderStore.searchOrders('')
} else {
await orderStore.searchCompletedOrders('')
}
} catch (error) {
console.error('清空搜索失败:', error)
}
}
//
const getCompletedStatusText = (status: ApprovalStatus) => {
const statusMap = {
2: '驳回',
3: '通过'
}
return statusMap[status] || '提交'
}
//
const getCompletedStatusClass = (status: ApprovalStatus) => {
const classMap = {
2: 'rejected',
3: 'approved'
}
return classMap[status] || 'pending'
}
//
const goToDetail = (id: number) => {
router.push(`/detail/${id}`)
}
//
const goToCompletedDetail = (businessId: number) => {
router.push({
path: `/detail/${businessId}`,
query: { readonly: 'true' }
})
}
onMounted(() => {
//
orderStore.resetListState()
orderStore.resetCompletedListState()
onLoad()
})
</script>
@ -144,10 +286,33 @@ onMounted(() => {
<style lang="scss" scoped>
.order-list-page {
min-height: 100vh;
background: #ffffff;
padding: 16px;
box-sizing: border-box;
position: relative;
background-color: var(--van-background-color);
}
// Tab
.approval-tabs {
:deep(.van-tabs__wrap) {
position: sticky;
top: 0;
z-index: 10;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
:deep(.van-tabs__content) {
padding: 16px;
background: #f5f5f5;
min-height: calc(100vh - 120px);
}
:deep(.van-tab) {
font-weight: 500;
}
:deep(.van-tab--active) {
color: var(--van-primary-color);
font-weight: 600;
}
}
.order-item {

File diff suppressed because it is too large Load Diff