Merge branch 'devlopment' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web into devlopment
This commit is contained in:
commit
aa3e6c732c
|
@ -0,0 +1,49 @@
|
|||
import http from '@/utils/http'
|
||||
import type { EquipmentApprovalReq, EquipmentApprovalResp, EquipmentApprovalListReq } from './type'
|
||||
|
||||
/**
|
||||
* 设备审批管理API
|
||||
*/
|
||||
export const equipmentApprovalApi = {
|
||||
/**
|
||||
* 分页查询待审批的设备采购申请
|
||||
*/
|
||||
getPendingApprovals: (params: EquipmentApprovalListReq) => {
|
||||
return http.get<ApiRes<PageRes<EquipmentApprovalResp>>>('/equipment/approval/pending', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询已审批的设备采购申请
|
||||
*/
|
||||
getApprovedApprovals: (params: EquipmentApprovalListReq) => {
|
||||
return http.get<ApiRes<PageRes<EquipmentApprovalResp>>>('/equipment/approval/approved', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 审批通过
|
||||
*/
|
||||
approve: (approvalId: string, data: EquipmentApprovalReq) => {
|
||||
return http.post<ApiRes<null>>(`/equipment/approval/${approvalId}/approve`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 审批拒绝
|
||||
*/
|
||||
reject: (approvalId: string, data: EquipmentApprovalReq) => {
|
||||
return http.post<ApiRes<null>>(`/equipment/approval/${approvalId}/reject`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取审批详情
|
||||
*/
|
||||
getApprovalDetail: (approvalId: string) => {
|
||||
return http.get<ApiRes<EquipmentApprovalResp>>(`/equipment/approval/${approvalId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取审批统计信息
|
||||
*/
|
||||
getApprovalStats: () => {
|
||||
return http.get<ApiRes<unknown>>('/equipment/approval/stats')
|
||||
}
|
||||
}
|
|
@ -275,3 +275,117 @@ export interface EquipmentReq {
|
|||
/** 动态记录 */
|
||||
dynamicRecord?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备审批状态枚举
|
||||
*/
|
||||
export enum ApprovalStatus {
|
||||
PENDING = 'PENDING',
|
||||
APPROVED = 'APPROVED',
|
||||
REJECTED = 'REJECTED'
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备审批业务类型枚举
|
||||
*/
|
||||
export enum BusinessType {
|
||||
PROCUREMENT = 'PROCUREMENT',
|
||||
BORROW = 'BORROW',
|
||||
RETURN = 'RETURN'
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备审批列表查询请求
|
||||
*/
|
||||
export interface EquipmentApprovalListReq {
|
||||
/** 设备名称 */
|
||||
equipmentName?: string
|
||||
/** 申请人 */
|
||||
applicantName?: string
|
||||
/** 审批状态 */
|
||||
approvalStatus?: ApprovalStatus
|
||||
/** 业务类型 */
|
||||
businessType?: BusinessType
|
||||
/** 申请时间开始 */
|
||||
applyTimeStart?: string
|
||||
/** 申请时间结束 */
|
||||
applyTimeEnd?: string
|
||||
/** 审批时间开始 */
|
||||
approvalTimeStart?: string
|
||||
/** 审批时间结束 */
|
||||
approvalTimeEnd?: string
|
||||
/** 当前页码 */
|
||||
page?: number
|
||||
/** 页码(后端可能期望的字段名) */
|
||||
pageNum?: number
|
||||
/** 每页大小 */
|
||||
pageSize?: number
|
||||
/** 排序字段 */
|
||||
orderBy?: string
|
||||
/** 排序方向 */
|
||||
orderDirection?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备审批请求
|
||||
*/
|
||||
export interface EquipmentApprovalReq {
|
||||
/** 审批意见 */
|
||||
approvalComment?: string
|
||||
/** 审批结果 */
|
||||
approvalResult: 'APPROVED' | 'REJECTED'
|
||||
/** 审批人 */
|
||||
approverName?: string
|
||||
/** 审批人ID */
|
||||
approverId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备审批响应
|
||||
*/
|
||||
export interface EquipmentApprovalResp {
|
||||
/** 审批ID */
|
||||
approvalId: string
|
||||
/** 设备ID */
|
||||
equipmentId: string
|
||||
/** 设备名称 */
|
||||
equipmentName: string
|
||||
/** 设备类型 */
|
||||
equipmentType: string
|
||||
/** 设备型号 */
|
||||
equipmentModel: string
|
||||
/** 品牌 */
|
||||
brand?: string
|
||||
/** 供应商名称 */
|
||||
supplierName?: string
|
||||
/** 采购价格 */
|
||||
purchasePrice?: number
|
||||
/** 总价 */
|
||||
totalPrice?: number
|
||||
/** 数量 */
|
||||
quantity?: number
|
||||
/** 申请人 */
|
||||
applicantName: string
|
||||
/** 申请人ID */
|
||||
applicantId: string
|
||||
/** 申请时间 */
|
||||
applyTime: string
|
||||
/** 申请原因 */
|
||||
applyReason?: string
|
||||
/** 业务类型 */
|
||||
businessType: BusinessType
|
||||
/** 审批状态 */
|
||||
approvalStatus: ApprovalStatus
|
||||
/** 审批人 */
|
||||
approverName?: string
|
||||
/** 审批人ID */
|
||||
approverId?: string
|
||||
/** 审批时间 */
|
||||
approvalTime?: string
|
||||
/** 审批意见 */
|
||||
approvalComment?: string
|
||||
/** 创建时间 */
|
||||
createTime: string
|
||||
/** 更新时间 */
|
||||
updateTime: string
|
||||
}
|
||||
|
|
|
@ -324,73 +324,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/asset-management',
|
||||
name: 'AssetManagement',
|
||||
component: Layout,
|
||||
redirect: '/asset-management/device/inventory',
|
||||
meta: { title: '资产管理', icon: 'property-safety', hidden: false, sort: 3 },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty1',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '设备管理', icon: 'copyright', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty11',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '库存管理', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty12',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '设备采购', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property1',
|
||||
name: 'IntellectualProperty13',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '在线管理', hidden: false },
|
||||
children: [
|
||||
{
|
||||
path: '/asset-management/intellectual-property11',
|
||||
name: 'IntellectualProperty14',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '无人机', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property12',
|
||||
name: 'IntellectualProperty15',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '机巢', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property13',
|
||||
name: 'IntellectualProperty16',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '其他智能终端', hidden: false },
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property14',
|
||||
name: 'IntellectualProperty17',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '车辆管理', hidden: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/asset-management/intellectual-property',
|
||||
name: 'IntellectualProperty',
|
||||
component: () => import('@/views/system-resource/information-system/software-management/index.vue'),
|
||||
meta: { title: '其他资产', icon: 'copyright', hidden: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: '/products-services',
|
||||
name: 'ProductsServices',
|
||||
|
@ -1327,7 +1261,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online',
|
||||
name: 'DeviceOnline',
|
||||
name: 'SystemResourceDeviceOnline',
|
||||
component: () => import('@/components/ParentView/index.vue'),
|
||||
redirect: '/system-resource/device-management/online/drone',
|
||||
meta: {
|
||||
|
@ -1338,7 +1272,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
children: [
|
||||
{
|
||||
path: '/system-resource/device-management/online/drone',
|
||||
name: 'DeviceDrone',
|
||||
name: 'SystemResourceDeviceDrone',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '无人机',
|
||||
|
@ -1348,7 +1282,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online/nest',
|
||||
name: 'DeviceNest',
|
||||
name: 'SystemResourceDeviceNest',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '机巢',
|
||||
|
@ -1358,7 +1292,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: '/system-resource/device-management/online/smart-terminal',
|
||||
name: 'DeviceSmartTerminal',
|
||||
name: 'SystemResourceDeviceSmartTerminal',
|
||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||
meta: {
|
||||
title: '其他智能终端',
|
||||
|
|
|
@ -226,6 +226,79 @@ const storeSetup = () => {
|
|||
{
|
||||
id: 2013,
|
||||
parentId: 2010,
|
||||
title: '审批台',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/approval',
|
||||
name: 'DeviceApproval',
|
||||
component: 'system-resource/device-management/approval/index',
|
||||
icon: 'check-circle',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 3,
|
||||
},
|
||||
{
|
||||
id: 2014,
|
||||
parentId: 2010,
|
||||
title: '在线管理',
|
||||
type: 1,
|
||||
path: '/asset-management/device-management/online',
|
||||
name: 'DeviceOnline',
|
||||
component: 'Layout',
|
||||
redirect: '/asset-management/device-management/online/drone',
|
||||
icon: 'cloud',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 4,
|
||||
children: [
|
||||
{
|
||||
id: 20141,
|
||||
parentId: 2014,
|
||||
title: '无人机',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/online/drone',
|
||||
name: 'DeviceDrone',
|
||||
component: 'system-resource/device-management/index',
|
||||
icon: 'drone',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 1,
|
||||
},
|
||||
{
|
||||
id: 20142,
|
||||
parentId: 2014,
|
||||
title: '机巢',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/online/nest',
|
||||
name: 'DeviceNest',
|
||||
component: 'system-resource/device-management/index',
|
||||
icon: 'nest',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 2,
|
||||
},
|
||||
{
|
||||
id: 20143,
|
||||
parentId: 2014,
|
||||
title: '其他智能终端',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/online/smart-terminal',
|
||||
name: 'DeviceSmartTerminal',
|
||||
component: 'system-resource/device-management/index',
|
||||
icon: 'terminal',
|
||||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: false,
|
||||
sort: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2015,
|
||||
parentId: 2010,
|
||||
title: '设备详情',
|
||||
type: 2,
|
||||
path: '/asset-management/device-management/device-detail/:id',
|
||||
|
@ -235,7 +308,7 @@ const storeSetup = () => {
|
|||
isExternal: false,
|
||||
isCache: false,
|
||||
isHidden: true,
|
||||
sort: 3,
|
||||
sort: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -70,6 +70,6 @@ declare global {
|
|||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
|
|
@ -70,10 +70,10 @@ http.interceptors.response.use(
|
|||
if (data && data.rows !== undefined && data.data === undefined) {
|
||||
data.data = data.rows
|
||||
}
|
||||
|
||||
|
||||
// 兼容不同的API响应结构
|
||||
const { success, code, msg } = data
|
||||
|
||||
|
||||
// 检查响应类型是否是blob
|
||||
if (response.request.responseType === 'blob') {
|
||||
const contentType = data.type
|
||||
|
@ -96,7 +96,7 @@ http.interceptors.response.use(
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 判断请求是否成功:明确的success字段为true,或者code为200都视为成功
|
||||
const isSuccess = success !== undefined ? success : (code === 200 || code === '200')
|
||||
if (isSuccess) {
|
||||
|
@ -139,17 +139,17 @@ const request = async <T = unknown>(config: AxiosRequestConfig): Promise<ApiRes<
|
|||
.then((res: AxiosResponse) => {
|
||||
// 处理返回数据结构,兼容rows和data字段
|
||||
const responseData = res.data
|
||||
|
||||
|
||||
// 如果返回的数据中有rows字段但没有data字段,将rows赋值给data
|
||||
if (responseData.rows !== undefined && responseData.data === undefined) {
|
||||
responseData.data = responseData.rows
|
||||
}
|
||||
|
||||
|
||||
// 如果返回的code是200但没有设置success字段,将success设置为true
|
||||
if ((responseData.code === 200 || responseData.code === '200') && responseData.success === undefined) {
|
||||
responseData.success = true
|
||||
}
|
||||
|
||||
|
||||
return responseData
|
||||
})
|
||||
.catch((err: { msg: string }) => Promise.reject(err))
|
||||
|
@ -168,7 +168,7 @@ const requestRaw = async <T = unknown>(config: AxiosRequestConfig): Promise<Axio
|
|||
baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 30 * 1000,
|
||||
})
|
||||
|
||||
|
||||
// 只添加请求拦截器来设置token,不添加响应拦截器
|
||||
rawAxios.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
|
@ -183,7 +183,7 @@ const requestRaw = async <T = unknown>(config: AxiosRequestConfig): Promise<Axio
|
|||
},
|
||||
(error) => Promise.reject(error),
|
||||
)
|
||||
|
||||
|
||||
return rawAxios.request<T>(config)
|
||||
}
|
||||
|
||||
|
@ -225,3 +225,7 @@ export default {
|
|||
requestRaw,
|
||||
download,
|
||||
}
|
||||
|
||||
export const updateContract = (contractId, contractData) => {
|
||||
return http.put(`/contract/${contractId}`, contractData)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<a-spin :loading="loading">
|
||||
<div v-if="contractDetail">
|
||||
<a-descriptions
|
||||
:column="1"
|
||||
size="medium"
|
||||
:label-style="{ width: '120px' }"
|
||||
>
|
||||
<a-descriptions-item label="合同编号">
|
||||
{{ contractDetail.code }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="项目名称">
|
||||
{{ contractDetail.projectName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户名称">
|
||||
{{ contractDetail.customer }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="合同金额">
|
||||
<span class="font-medium text-green-600">¥{{ (contractDetail.amount || 0).toLocaleString() }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="已收款金额">
|
||||
<span class="font-medium text-blue-600">¥{{ (contractDetail.receivedAmount || 0).toLocaleString() }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="未收款金额">
|
||||
<span class="font-medium text-orange-600">¥{{ (contractDetail.pendingAmount || 0).toLocaleString() }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="签署日期">
|
||||
{{ contractDetail.signDate }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="履约期限">
|
||||
{{ contractDetail.performanceDeadline }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="付款日期">
|
||||
{{ contractDetail.paymentDate }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="合同状态">
|
||||
<a-tag :color="getStatusColor(contractDetail.contractStatus)">
|
||||
{{ getStatusText(contractDetail.contractStatusLabel || contractDetail.contractStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="销售人员">
|
||||
{{ contractDetail.salespersonName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="销售部门">
|
||||
{{ contractDetail.salespersonDeptName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品服务">
|
||||
{{ contractDetail.productService }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注">
|
||||
{{ contractDetail.notes }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<div v-else-if="!loading" class="empty-container">
|
||||
<a-empty description="暂无信息" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import http from '@/utils/http'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface ContractDetail {
|
||||
contractId: string
|
||||
customer: string
|
||||
code: string
|
||||
projectId: string
|
||||
type: string
|
||||
productService: string
|
||||
paymentDate: string | null
|
||||
performanceDeadline: string | null
|
||||
paymentAddress: string
|
||||
amount: number
|
||||
accountNumber: string
|
||||
notes: string
|
||||
contractStatus: string
|
||||
contractText: string | null
|
||||
projectName: string
|
||||
salespersonName: string | null
|
||||
salespersonDeptName: string
|
||||
settlementAmount: number | null
|
||||
receivedAmount: number | null
|
||||
contractStatusLabel: string | null
|
||||
createBy: string | null
|
||||
updateBy: string | null
|
||||
createTime: string
|
||||
updateTime: string
|
||||
page: number
|
||||
pageSize: number
|
||||
signDate: string
|
||||
duration: string
|
||||
pendingAmount?: number
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
contractId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const contractDetail = ref<ContractDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
未确认: 'gray',
|
||||
待审批: 'orange',
|
||||
已签署: 'blue',
|
||||
执行中: 'cyan',
|
||||
已完成: 'green',
|
||||
已终止: 'red'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
return status || '未知状态'
|
||||
}
|
||||
|
||||
const fetchContractDetail = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await http.get(`/contract/${props.contractId}`)
|
||||
if (response.code === 200) {
|
||||
contractDetail.value = response.data
|
||||
// 计算未收款金额
|
||||
if (contractDetail.value) {
|
||||
contractDetail.value.pendingAmount = (contractDetail.value.amount || 0) - (contractDetail.value.receivedAmount || 0)
|
||||
}
|
||||
} else {
|
||||
Message.error(response.msg || '获取合同详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取合同详情失败:', error)
|
||||
Message.error('获取合同详情失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchContractDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty-container {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
</style>
|
|
@ -1,28 +1,28 @@
|
|||
<template>
|
||||
<GiPageLayout>
|
||||
<GiTable
|
||||
row-key="id"
|
||||
title="收入合同管理"
|
||||
:data="dataList"
|
||||
:columns="tableColumns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1600 }"
|
||||
:pagination="pagination"
|
||||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
@refresh="search"
|
||||
row-key="id"
|
||||
title="收入合同管理"
|
||||
:data="dataList"
|
||||
:columns="tableColumns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1600 }"
|
||||
:pagination="pagination"
|
||||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #top>
|
||||
<GiForm
|
||||
v-model="searchForm"
|
||||
search
|
||||
:columns="queryFormColumns"
|
||||
size="medium"
|
||||
@search="search"
|
||||
@reset="reset"
|
||||
<GiForm
|
||||
v-model="searchForm"
|
||||
search
|
||||
:columns="queryFormColumns"
|
||||
size="medium"
|
||||
@search="search"
|
||||
@reset="reset"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
<template #toolbar-left>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="openAddModal">
|
||||
|
@ -35,51 +35,96 @@
|
|||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 合同状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
<a-tag :color="getStatusColor(record.contractStatus)">
|
||||
{{ getStatusText(record.contractStatusLabel || record.contractStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 合同金额 -->
|
||||
<template #contractAmount="{ record }">
|
||||
<span class="font-medium text-green-600">¥{{ record.contractAmount.toLocaleString() }}万</span>
|
||||
<span class="font-medium text-green-600">¥{{ (record.amount || 0).toLocaleString() }}</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 已收款金额 -->
|
||||
<template #receivedAmount="{ record }">
|
||||
<span class="font-medium text-blue-600">¥{{ record.receivedAmount.toLocaleString() }}万</span>
|
||||
<span class="font-medium text-blue-600">¥{{ (record.receivedAmount || 0).toLocaleString() }}</span>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click="viewDetail(record)">详情</a-link>
|
||||
<a-link @click="editRecord(record)" v-if="record.status === 'draft'">编辑</a-link>
|
||||
<a-link @click="approveContract(record)" v-if="record.status === 'pending'">审批</a-link>
|
||||
<a-link v-if="record.contractStatus === '未确认'" @click="editRecord(record)">编辑</a-link>
|
||||
<a-link v-if="record.contractStatus === '待审批'" @click="approveContract(record)">审批</a-link>
|
||||
<a-link @click="viewPayment(record)">收款记录</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<!-- 合同详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="showDetailModal"
|
||||
title="合同详情"
|
||||
:width="800"
|
||||
@cancel="closeDetailModal"
|
||||
:footer="false"
|
||||
>
|
||||
<ContractDetail v-if="showDetailModal" :contract-id="selectedContractId" />
|
||||
</a-modal>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import http from '@/utils/http'
|
||||
import ContractDetail from './ContractDetail.vue'
|
||||
|
||||
// 接口数据类型定义
|
||||
interface ContractItem {
|
||||
contractId: string
|
||||
customer: string
|
||||
code: string
|
||||
projectId: string
|
||||
type: string
|
||||
productService: string
|
||||
paymentDate: string | null
|
||||
performanceDeadline: string | null
|
||||
paymentAddress: string
|
||||
amount: number
|
||||
accountNumber: string
|
||||
notes: string
|
||||
contractStatus: string
|
||||
contractText: string | null
|
||||
projectName: string
|
||||
salespersonName: string | null
|
||||
salespersonDeptName: string
|
||||
settlementAmount: number | null
|
||||
receivedAmount: number | null
|
||||
contractStatusLabel: string | null
|
||||
createBy: string | null
|
||||
updateBy: string | null
|
||||
createTime: string
|
||||
updateTime: string
|
||||
page: number
|
||||
pageSize: number
|
||||
signDate: string
|
||||
duration: string
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
let searchForm = reactive({
|
||||
const searchForm = reactive({
|
||||
contractName: '',
|
||||
contractCode: '',
|
||||
client: '',
|
||||
status: '',
|
||||
signDate: '',
|
||||
page: 1,
|
||||
size: 10
|
||||
size: 10,
|
||||
})
|
||||
|
||||
// 查询条件配置
|
||||
|
@ -89,16 +134,16 @@ const queryFormColumns = [
|
|||
label: '合同名称',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入合同名称'
|
||||
}
|
||||
placeholder: '请输入合同名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'client',
|
||||
label: '客户',
|
||||
type: 'input' as const,
|
||||
props: {
|
||||
placeholder: '请输入客户名称'
|
||||
}
|
||||
placeholder: '请输入客户名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
|
@ -107,132 +152,111 @@ const queryFormColumns = [
|
|||
props: {
|
||||
placeholder: '请选择合同状态',
|
||||
options: [
|
||||
{ label: '草稿', value: 'draft' },
|
||||
{ label: '待审批', value: 'pending' },
|
||||
{ label: '已签署', value: 'signed' },
|
||||
{ label: '执行中', value: 'executing' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已终止', value: 'terminated' }
|
||||
]
|
||||
}
|
||||
}
|
||||
{ label: '未确认', value: '未确认' },
|
||||
{ label: '待审批', value: '待审批' },
|
||||
{ label: '已签署', value: '已签署' },
|
||||
{ label: '执行中', value: '执行中' },
|
||||
{ label: '已完成', value: '已完成' },
|
||||
{ label: '已终止', value: '已终止' },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns: TableColumnData[] = [
|
||||
{ title: '合同编号', dataIndex: 'contractCode', width: 150 },
|
||||
{ title: '合同名称', dataIndex: 'contractName', width: 250, ellipsis: true, tooltip: true },
|
||||
{ title: '客户名称', dataIndex: 'client', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '合同金额', dataIndex: 'contractAmount', slotName: 'contractAmount', width: 120 },
|
||||
{ title: '合同编号', dataIndex: 'code', width: 150 },
|
||||
{ title: '项目名称', dataIndex: 'projectName', width: 250, ellipsis: true, tooltip: true },
|
||||
{ title: '客户名称', dataIndex: 'customer', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '合同金额', dataIndex: 'amount', slotName: 'contractAmount', width: 120 },
|
||||
{ title: '已收款金额', dataIndex: 'receivedAmount', slotName: 'receivedAmount', width: 120 },
|
||||
{ title: '未收款金额', dataIndex: 'pendingAmount', width: 120 },
|
||||
{ title: '签署日期', dataIndex: 'signDate', width: 120 },
|
||||
{ title: '开始日期', dataIndex: 'startDate', width: 120 },
|
||||
{ title: '结束日期', dataIndex: 'endDate', width: 120 },
|
||||
{ title: '合同状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
||||
{ title: '项目经理', dataIndex: 'projectManager', width: 100 },
|
||||
{ title: '销售经理', dataIndex: 'salesManager', width: 100 },
|
||||
{ title: '完成进度', dataIndex: 'progress', width: 100 },
|
||||
{ title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '操作', slotName: 'action', width: 200, fixed: 'right' }
|
||||
{ title: '履约期限', dataIndex: 'performanceDeadline', width: 120 },
|
||||
{ title: '付款日期', dataIndex: 'paymentDate', width: 120 },
|
||||
{ title: '合同状态', dataIndex: 'contractStatus', slotName: 'status', width: 100 },
|
||||
{ title: '销售人员', dataIndex: 'salespersonName', width: 100 },
|
||||
{ title: '销售部门', dataIndex: 'salespersonDeptName', width: 100 },
|
||||
{ title: '产品服务', dataIndex: 'productService', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: '备注', dataIndex: 'notes', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '操作', slotName: 'action', width: 200, fixed: 'right' },
|
||||
]
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false)
|
||||
const dataList = ref([
|
||||
{
|
||||
id: 1,
|
||||
contractCode: 'RC2024001',
|
||||
contractName: '华能新能源风电场叶片检测服务合同',
|
||||
client: '华能新能源股份有限公司',
|
||||
contractAmount: 320,
|
||||
receivedAmount: 192,
|
||||
pendingAmount: 128,
|
||||
signDate: '2024-02-20',
|
||||
startDate: '2024-03-01',
|
||||
endDate: '2024-04-30',
|
||||
status: 'executing',
|
||||
projectManager: '张项目经理',
|
||||
salesManager: '李销售经理',
|
||||
progress: '60%',
|
||||
remark: '项目进展顺利,客户满意度高'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
contractCode: 'RC2024002',
|
||||
contractName: '大唐风电场防雷检测项目合同',
|
||||
client: '大唐新能源股份有限公司',
|
||||
contractAmount: 268,
|
||||
receivedAmount: 134,
|
||||
pendingAmount: 134,
|
||||
signDate: '2024-02-25',
|
||||
startDate: '2024-03-05',
|
||||
endDate: '2024-04-20',
|
||||
status: 'executing',
|
||||
projectManager: '王项目经理',
|
||||
salesManager: '赵销售经理',
|
||||
progress: '45%',
|
||||
remark: '按计划执行中'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
contractCode: 'RC2024003',
|
||||
contractName: '中广核风电场设备维护服务合同',
|
||||
client: '中广核新能源投资有限公司',
|
||||
contractAmount: 450,
|
||||
receivedAmount: 450,
|
||||
pendingAmount: 0,
|
||||
signDate: '2024-01-15',
|
||||
startDate: '2024-01-20',
|
||||
endDate: '2024-01-31',
|
||||
status: 'completed',
|
||||
projectManager: '刘项目经理',
|
||||
salesManager: '孙销售经理',
|
||||
progress: '100%',
|
||||
remark: '项目已完成,客户验收通过'
|
||||
const dataList = ref<ContractItem[]>([])
|
||||
|
||||
// API调用函数
|
||||
const fetchContractList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: searchForm.page,
|
||||
pageSize: searchForm.size,
|
||||
contractName: searchForm.contractName,
|
||||
code: searchForm.contractCode,
|
||||
customer: searchForm.client,
|
||||
contractStatus: searchForm.status,
|
||||
signDate: searchForm.signDate,
|
||||
}
|
||||
|
||||
const response = await http.get('/contract/list', params)
|
||||
|
||||
if (response.code === 200) {
|
||||
// 过滤出类型为"收入合同"的数据
|
||||
const allContracts = response.rows || []
|
||||
const revenueContracts = allContracts.filter((item: ContractItem) => item.type === '收入合同')
|
||||
|
||||
// 计算未收款金额
|
||||
dataList.value = revenueContracts.map((item: ContractItem) => ({
|
||||
...item,
|
||||
pendingAmount: (item.amount || 0) - (item.receivedAmount || 0),
|
||||
}))
|
||||
|
||||
pagination.total = Number.parseInt(response.total) || 0
|
||||
} else {
|
||||
Message.error(response.msg || '获取合同列表失败')
|
||||
dataList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取合同列表失败:', error)
|
||||
Message.error('获取合同列表失败')
|
||||
dataList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 3,
|
||||
total: 0,
|
||||
showTotal: true,
|
||||
showPageSize: true
|
||||
showPageSize: true,
|
||||
})
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
'draft': 'gray',
|
||||
'pending': 'orange',
|
||||
'signed': 'blue',
|
||||
'executing': 'cyan',
|
||||
'completed': 'green',
|
||||
'terminated': 'red'
|
||||
未确认: 'gray',
|
||||
待审批: 'orange',
|
||||
已签署: 'blue',
|
||||
执行中: 'cyan',
|
||||
已完成: 'green',
|
||||
已终止: 'red',
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
'draft': '草稿',
|
||||
'pending': '待审批',
|
||||
'signed': '已签署',
|
||||
'executing': '执行中',
|
||||
'completed': '已完成',
|
||||
'terminated': '已终止'
|
||||
}
|
||||
return textMap[status] || status
|
||||
// 直接返回后端返回的状态文本,如果有contractStatusLabel则使用,否则使用contractStatus
|
||||
return status || '未知状态'
|
||||
}
|
||||
|
||||
// 搜索和重置
|
||||
const search = async () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
await fetchContractList()
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
|
@ -243,7 +267,7 @@ const reset = () => {
|
|||
status: '',
|
||||
signDate: '',
|
||||
page: 1,
|
||||
size: 10
|
||||
size: 10,
|
||||
})
|
||||
pagination.current = 1
|
||||
search()
|
||||
|
@ -273,23 +297,33 @@ const exportContract = () => {
|
|||
Message.info('导出合同功能开发中...')
|
||||
}
|
||||
|
||||
const viewDetail = (record: any) => {
|
||||
Message.info(`查看合同详情: ${record.contractName}`)
|
||||
// 显示合同详情弹窗
|
||||
const showDetailModal = ref(false)
|
||||
const selectedContractId = ref<string | null>(null)
|
||||
|
||||
const viewDetail = (record: ContractItem) => {
|
||||
selectedContractId.value = record.contractId
|
||||
showDetailModal.value = true
|
||||
}
|
||||
|
||||
const editRecord = (record: any) => {
|
||||
Message.info(`编辑合同: ${record.contractName}`)
|
||||
const closeDetailModal = () => {
|
||||
showDetailModal.value = false
|
||||
selectedContractId.value = null
|
||||
}
|
||||
|
||||
const approveContract = (record: any) => {
|
||||
Message.info(`审批合同: ${record.contractName}`)
|
||||
const editRecord = (record: ContractItem) => {
|
||||
Message.info(`编辑合同: ${record.projectName}`)
|
||||
}
|
||||
|
||||
const viewPayment = (record: any) => {
|
||||
Message.info(`查看收款记录: ${record.contractName}`)
|
||||
const approveContract = (record: ContractItem) => {
|
||||
Message.info(`审批合同: ${record.projectName}`)
|
||||
}
|
||||
|
||||
const viewPayment = (record: ContractItem) => {
|
||||
Message.info(`查看收款记录: ${record.projectName}`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
search()
|
||||
fetchContractList()
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,339 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="modalTitle"
|
||||
width="600px"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
:confirm-loading="loading"
|
||||
>
|
||||
<div class="approval-action" v-if="approvalData">
|
||||
<!-- 审批信息预览 -->
|
||||
<div class="preview-section">
|
||||
<h4 class="preview-title">
|
||||
<IconInfoCircle style="margin-right: 8px; color: var(--color-primary);" />
|
||||
审批信息预览
|
||||
</h4>
|
||||
<a-descriptions :column="1" bordered size="small">
|
||||
<a-descriptions-item label="设备名称">
|
||||
{{ approvalData.equipmentName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">
|
||||
{{ approvalData.equipmentType }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备型号">
|
||||
{{ approvalData.equipmentModel }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请人">
|
||||
{{ approvalData.applicantName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请时间">
|
||||
{{ formatDateTime(approvalData.applyTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="总价">
|
||||
<span v-if="approvalData.totalPrice" class="price-text">
|
||||
¥{{ formatPrice(approvalData.totalPrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 审批表单 -->
|
||||
<div class="form-section">
|
||||
<h4 class="form-title">
|
||||
<IconEdit style="margin-right: 8px; color: var(--color-warning);" />
|
||||
审批意见
|
||||
</h4>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item field="approvalComment" label="审批意见" class="form-item">
|
||||
<a-textarea
|
||||
v-model="formData.approvalComment"
|
||||
:placeholder="actionType === 'approve' ? '请输入审批通过的意见(可选)' : '请输入审批拒绝的原因(必填)'"
|
||||
:rows="4"
|
||||
class="form-textarea"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 确认提示 -->
|
||||
<div class="confirm-section">
|
||||
<a-alert
|
||||
:type="actionType === 'approve' ? 'success' : 'error'"
|
||||
:title="actionType === 'approve' ? '审批通过确认' : '审批拒绝确认'"
|
||||
:description="actionType === 'approve' ? '确认通过此设备采购申请吗?通过后该申请将进入采购流程。' : '确认拒绝此设备采购申请吗?拒绝后申请人将收到通知。'"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { IconInfoCircle, IconEdit } from '@arco-design/web-vue/es/icon'
|
||||
import message from '@arco-design/web-vue/es/message'
|
||||
import type { EquipmentApprovalResp, EquipmentApprovalReq } from '@/apis/equipment/type'
|
||||
import { equipmentApprovalApi } from '@/apis/equipment/approval'
|
||||
|
||||
defineOptions({ name: 'ApprovalActionModal' })
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
approvalData: EquipmentApprovalResp | null
|
||||
actionType: 'approve' | 'reject'
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
success: []
|
||||
}>()
|
||||
|
||||
// 计算属性
|
||||
const visible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return props.actionType === 'approve' ? '审批通过' : '审批拒绝'
|
||||
})
|
||||
|
||||
// 表单相关
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const formData = reactive<EquipmentApprovalReq>({
|
||||
approvalComment: '',
|
||||
approvalResult: props.actionType === 'approve' ? 'APPROVED' : 'REJECTED',
|
||||
approverName: '当前用户', // 这里应该从用户认证系统获取
|
||||
approverId: 'current-user-id' // 这里应该从用户认证系统获取
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = computed(() => ({
|
||||
approvalComment: props.actionType === 'reject' ? [
|
||||
{ required: true, message: '请输入审批拒绝的原因' }
|
||||
] : []
|
||||
}))
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price: number) => {
|
||||
return price.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string) => {
|
||||
if (!dateTime) return '-'
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// 提交审批
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
|
||||
if (!props.approvalData) {
|
||||
message.error('审批数据不存在')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const approvalId = props.approvalData.approvalId
|
||||
const submitData: EquipmentApprovalReq = {
|
||||
approvalComment: formData.approvalComment,
|
||||
approvalResult: props.actionType === 'approve' ? 'APPROVED' : 'REJECTED',
|
||||
approverName: formData.approverName,
|
||||
approverId: formData.approverId
|
||||
}
|
||||
|
||||
console.log('提交审批数据:', submitData)
|
||||
|
||||
if (props.actionType === 'approve') {
|
||||
await equipmentApprovalApi.approve(approvalId, submitData)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
await equipmentApprovalApi.reject(approvalId, submitData)
|
||||
message.success('审批拒绝成功')
|
||||
}
|
||||
|
||||
emit('success')
|
||||
visible.value = false
|
||||
resetForm()
|
||||
} catch (error: any) {
|
||||
console.error('审批操作失败:', error)
|
||||
message.error(error?.message || '审批操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(formData, {
|
||||
approvalComment: '',
|
||||
approvalResult: props.actionType === 'approve' ? 'APPROVED' : 'REJECTED',
|
||||
approverName: '当前用户',
|
||||
approverId: 'current-user-id'
|
||||
})
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 监听弹窗显示状态,重置表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.approval-action {
|
||||
.preview-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.preview-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.arco-descriptions {
|
||||
.arco-descriptions-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
background-color: var(--color-fill-1);
|
||||
}
|
||||
|
||||
.arco-descriptions-item-value {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.form-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
.arco-form-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary-light-3);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.arco-textarea-focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
|
||||
}
|
||||
|
||||
.arco-textarea-inner {
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-section {
|
||||
.arco-alert {
|
||||
border-radius: 6px;
|
||||
|
||||
.arco-alert-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arco-alert-description {
|
||||
color: var(--color-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: var(--color-text-4);
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.approval-action {
|
||||
.preview-section,
|
||||
.form-section {
|
||||
.arco-descriptions {
|
||||
.arco-descriptions-item {
|
||||
display: block;
|
||||
|
||||
.arco-descriptions-item-label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.arco-descriptions-item-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,362 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="审批详情"
|
||||
width="800px"
|
||||
:footer="false"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="approval-detail" v-if="approvalData">
|
||||
<!-- 基本信息 -->
|
||||
<div class="detail-section">
|
||||
<h3 class="section-title">
|
||||
<IconInfoCircle style="margin-right: 8px; color: var(--color-primary);" />
|
||||
基本信息
|
||||
</h3>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="设备名称">
|
||||
{{ approvalData.equipmentName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备类型">
|
||||
{{ approvalData.equipmentType }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="业务类型">
|
||||
<a-tag :color="getBusinessTypeColor(approvalData.businessType)">
|
||||
{{ getBusinessTypeText(approvalData.businessType) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="设备型号">
|
||||
{{ approvalData.equipmentModel }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="品牌">
|
||||
{{ approvalData.brand || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="供应商">
|
||||
{{ approvalData.supplierName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="数量">
|
||||
{{ approvalData.quantity || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 价格信息 -->
|
||||
<div class="detail-section">
|
||||
<h3 class="section-title">
|
||||
<IconFile style="margin-right: 8px; color: var(--color-success);" />
|
||||
价格信息
|
||||
</h3>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="采购价格">
|
||||
<span v-if="approvalData.purchasePrice" class="price-text">
|
||||
¥{{ formatPrice(approvalData.purchasePrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="总价">
|
||||
<span v-if="approvalData.totalPrice" class="price-text">
|
||||
¥{{ formatPrice(approvalData.totalPrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 申请信息 -->
|
||||
<div class="detail-section">
|
||||
<h3 class="section-title">
|
||||
<IconUser style="margin-right: 8px; color: var(--color-warning);" />
|
||||
申请信息
|
||||
</h3>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="申请人">
|
||||
{{ approvalData.applicantName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请时间">
|
||||
{{ formatDateTime(approvalData.applyTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请原因" :span="2">
|
||||
{{ approvalData.applyReason || '暂无申请原因' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 审批信息 -->
|
||||
<div class="detail-section" v-if="approvalData.approvalStatus !== ApprovalStatus.PENDING">
|
||||
<h3 class="section-title">
|
||||
<IconCheckCircle style="margin-right: 8px; color: var(--color-success);" />
|
||||
审批信息
|
||||
</h3>
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="审批状态">
|
||||
<a-tag :color="getApprovalStatusColor(approvalData.approvalStatus)">
|
||||
{{ getApprovalStatusText(approvalData.approvalStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审批人">
|
||||
{{ approvalData.approverName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审批时间">
|
||||
{{ formatDateTime(approvalData.approvalTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="审批意见" :span="2">
|
||||
{{ approvalData.approvalComment || '暂无审批意见' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 审批状态 -->
|
||||
<div class="detail-section" v-else>
|
||||
<h3 class="section-title">
|
||||
<IconClockCircle style="margin-right: 8px; color: var(--color-warning);" />
|
||||
审批状态
|
||||
</h3>
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="当前状态">
|
||||
<a-tag :color="getApprovalStatusColor(approvalData.approvalStatus)">
|
||||
{{ getApprovalStatusText(approvalData.approvalStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="detail-footer">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="approvalData.approvalStatus === ApprovalStatus.PENDING"
|
||||
type="primary"
|
||||
@click="handleApprove"
|
||||
>
|
||||
<template #icon><IconCheckCircle /></template>
|
||||
审批通过
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="approvalData.approvalStatus === ApprovalStatus.PENDING"
|
||||
status="danger"
|
||||
@click="handleReject"
|
||||
>
|
||||
<template #icon><IconCloseCircle /></template>
|
||||
审批拒绝
|
||||
</a-button>
|
||||
<a-button @click="handleCancel">
|
||||
关闭
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
IconInfoCircle,
|
||||
IconFile,
|
||||
IconUser,
|
||||
IconCheckCircle,
|
||||
IconCloseCircle,
|
||||
IconClockCircle
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import type { EquipmentApprovalResp } from '@/apis/equipment/type'
|
||||
import { ApprovalStatus, BusinessType } from '@/apis/equipment/type'
|
||||
|
||||
defineOptions({ name: 'ApprovalDetailModal' })
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
approvalData: EquipmentApprovalResp | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
success: []
|
||||
approve: [data: EquipmentApprovalResp]
|
||||
reject: [data: EquipmentApprovalResp]
|
||||
}>()
|
||||
|
||||
// 计算属性
|
||||
const visible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price: number) => {
|
||||
return price.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string) => {
|
||||
if (!dateTime) return '-'
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// 获取业务类型颜色
|
||||
const getBusinessTypeColor = (type: BusinessType) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
[BusinessType.PROCUREMENT]: 'blue',
|
||||
[BusinessType.BORROW]: 'green',
|
||||
[BusinessType.RETURN]: 'orange',
|
||||
}
|
||||
return colorMap[type] || 'gray'
|
||||
}
|
||||
|
||||
// 获取业务类型文本
|
||||
const getBusinessTypeText = (type: BusinessType) => {
|
||||
const textMap: Record<string, string> = {
|
||||
[BusinessType.PROCUREMENT]: '采购',
|
||||
[BusinessType.BORROW]: '借用',
|
||||
[BusinessType.RETURN]: '归还',
|
||||
}
|
||||
return textMap[type] || '未知'
|
||||
}
|
||||
|
||||
// 获取审批状态颜色
|
||||
const getApprovalStatusColor = (status: ApprovalStatus) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
[ApprovalStatus.PENDING]: 'orange',
|
||||
[ApprovalStatus.APPROVED]: 'green',
|
||||
[ApprovalStatus.REJECTED]: 'red',
|
||||
}
|
||||
return colorMap[status] || 'blue'
|
||||
}
|
||||
|
||||
// 获取审批状态文本
|
||||
const getApprovalStatusText = (status: ApprovalStatus) => {
|
||||
const textMap: Record<string, string> = {
|
||||
[ApprovalStatus.PENDING]: '待审批',
|
||||
[ApprovalStatus.APPROVED]: '已通过',
|
||||
[ApprovalStatus.REJECTED]: '已拒绝',
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 审批通过
|
||||
const handleApprove = () => {
|
||||
if (props.approvalData) {
|
||||
emit('approve', props.approvalData)
|
||||
}
|
||||
}
|
||||
|
||||
// 审批拒绝
|
||||
const handleReject = () => {
|
||||
if (props.approvalData) {
|
||||
emit('reject', props.approvalData)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.approval-detail {
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.arco-descriptions {
|
||||
.arco-descriptions-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
background-color: var(--color-fill-1);
|
||||
}
|
||||
|
||||
.arco-descriptions-item-value {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: var(--color-text-4);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.detail-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
.arco-btn {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.approval-detail {
|
||||
.detail-section {
|
||||
.arco-descriptions {
|
||||
.arco-descriptions-item {
|
||||
display: block;
|
||||
|
||||
.arco-descriptions-item-label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.arco-descriptions-item-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-footer {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.arco-space {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.arco-btn {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,303 @@
|
|||
<template>
|
||||
<div class="approval-search-container">
|
||||
<a-form layout="inline" :model="searchForm" class="search-form">
|
||||
<a-form-item label="设备名称">
|
||||
<a-input
|
||||
v-model="searchForm.equipmentName"
|
||||
placeholder="请输入设备名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请人">
|
||||
<a-input
|
||||
v-model="searchForm.applicantName"
|
||||
placeholder="请输入申请人"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="业务类型">
|
||||
<a-select
|
||||
v-model="searchForm.businessType"
|
||||
placeholder="请选择业务类型"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@change="debouncedSearch"
|
||||
>
|
||||
<a-option value="">全部</a-option>
|
||||
<a-option :value="BusinessType.PROCUREMENT">采购</a-option>
|
||||
<a-option :value="BusinessType.BORROW">借用</a-option>
|
||||
<a-option :value="BusinessType.RETURN">归还</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="审批状态">
|
||||
<a-select
|
||||
v-model="searchForm.approvalStatus"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@change="debouncedSearch"
|
||||
>
|
||||
<a-option value="">全部</a-option>
|
||||
<a-option :value="ApprovalStatus.PENDING">待审批</a-option>
|
||||
<a-option :value="ApprovalStatus.APPROVED">已通过</a-option>
|
||||
<a-option :value="ApprovalStatus.REJECTED">已拒绝</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请时间">
|
||||
<a-range-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
@change="handleDateChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search" :loading="loading">
|
||||
<template #icon><icon-search /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="reset" :loading="loading">
|
||||
<template #icon><icon-refresh /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||
import type { EquipmentApprovalListReq } from '@/apis/equipment/type'
|
||||
import { ApprovalStatus, BusinessType } from '@/apis/equipment/type'
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (func: Function, delay: number) => {
|
||||
let timeoutId: NodeJS.Timeout
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => func.apply(null, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
defineOptions({ name: 'ApprovalSearch' })
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
search: [params: EquipmentApprovalListReq]
|
||||
reset: []
|
||||
}>()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive<EquipmentApprovalListReq>({
|
||||
equipmentName: '',
|
||||
applicantName: '',
|
||||
businessType: undefined,
|
||||
approvalStatus: undefined,
|
||||
applyTimeStart: '',
|
||||
applyTimeEnd: ''
|
||||
})
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
|
||||
// 防抖搜索函数
|
||||
const debouncedSearch = debounce(() => {
|
||||
search()
|
||||
}, 300)
|
||||
|
||||
// 搜索
|
||||
const search = () => {
|
||||
console.log('🔍 ApprovalSearch - 搜索按钮被点击')
|
||||
console.log('🔍 ApprovalSearch - 搜索表单数据:', searchForm)
|
||||
console.log('🔍 ApprovalSearch - 搜索表单数据类型:', typeof searchForm)
|
||||
console.log('🔍 ApprovalSearch - 搜索表单的键值对:')
|
||||
Object.entries(searchForm).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value} (${typeof value})`)
|
||||
})
|
||||
|
||||
// 构建搜索参数
|
||||
const searchParams: EquipmentApprovalListReq = {
|
||||
equipmentName: searchForm.equipmentName || undefined,
|
||||
applicantName: searchForm.applicantName || undefined,
|
||||
businessType: searchForm.businessType,
|
||||
approvalStatus: searchForm.approvalStatus,
|
||||
applyTimeStart: searchForm.applyTimeStart,
|
||||
applyTimeEnd: searchForm.applyTimeEnd
|
||||
}
|
||||
|
||||
console.log('🔍 ApprovalSearch - 发送的搜索参数:', searchParams)
|
||||
console.log('🔍 ApprovalSearch - 发送参数的类型:', typeof searchParams)
|
||||
console.log('🔍 ApprovalSearch - 发送参数的键值对:')
|
||||
Object.entries(searchParams).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value} (${typeof value})`)
|
||||
})
|
||||
|
||||
emit('search', searchParams)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
console.log('🔄 ApprovalSearch - 重置操作')
|
||||
|
||||
// 重置表单
|
||||
Object.assign(searchForm, {
|
||||
equipmentName: '',
|
||||
applicantName: '',
|
||||
businessType: undefined,
|
||||
approvalStatus: undefined,
|
||||
applyTimeStart: '',
|
||||
applyTimeEnd: ''
|
||||
})
|
||||
|
||||
// 重置日期范围
|
||||
dateRange.value = null
|
||||
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
// 处理日期变化
|
||||
const handleDateChange = (dates: [string, string] | null) => {
|
||||
if (dates && dates.length === 2) {
|
||||
searchForm.applyTimeStart = dates[0]
|
||||
searchForm.applyTimeEnd = dates[1]
|
||||
} else {
|
||||
searchForm.applyTimeStart = ''
|
||||
searchForm.applyTimeEnd = ''
|
||||
}
|
||||
debouncedSearch()
|
||||
}
|
||||
|
||||
// 监听loading状态
|
||||
watch(() => props.loading, (newVal) => {
|
||||
console.log('🔄 ApprovalSearch - loading状态变化:', newVal)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.approval-search-container {
|
||||
// 确保搜索区域有良好的背景对比度
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e8e8e8;
|
||||
|
||||
.search-form {
|
||||
.arco-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 16px;
|
||||
|
||||
.arco-form-item-label {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-input,
|
||||
.arco-select,
|
||||
.arco-picker {
|
||||
border-radius: 4px;
|
||||
|
||||
// 确保输入框文字颜色清晰可见
|
||||
.arco-input-inner {
|
||||
color: #333 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 确保占位符文字颜色清晰
|
||||
.arco-input-inner::placeholder {
|
||||
color: #666 !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// 确保选择框文字颜色清晰
|
||||
.arco-select-view-input {
|
||||
color: #333 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 确保选择框占位符颜色清晰
|
||||
.arco-select-view-placeholder {
|
||||
color: #666 !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// 确保日期选择器文字颜色清晰
|
||||
.arco-picker-input {
|
||||
color: #333 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 确保日期选择器占位符颜色清晰
|
||||
.arco-picker-input::placeholder {
|
||||
color: #666 !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// 确保所有输入框在聚焦时有更好的对比度
|
||||
&:focus-within {
|
||||
.arco-input-inner,
|
||||
.arco-select-view-input,
|
||||
.arco-picker-input {
|
||||
color: #000 !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保输入框在悬停时有更好的对比度
|
||||
&:hover {
|
||||
.arco-input-inner,
|
||||
.arco-select-view-input,
|
||||
.arco-picker-input {
|
||||
color: #000 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arco-btn {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.approval-search-container {
|
||||
.search-form {
|
||||
.arco-form-item {
|
||||
margin-bottom: 12px;
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
|
||||
.arco-input,
|
||||
.arco-select,
|
||||
.arco-picker {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,829 @@
|
|||
<template>
|
||||
<div class="equipment-approval-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="page-title">
|
||||
<IconCheckCircle style="font-size: 24px; margin-right: 12px; color: var(--color-primary);" />
|
||||
<h1>设备审批控制台</h1>
|
||||
</div>
|
||||
<div class="page-description">
|
||||
管理设备采购申请的审批流程,包括待审批、已审批等状态管理
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<a-space>
|
||||
<ApprovalSearch
|
||||
:loading="loading"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
<a-button type="primary" size="large" @click="refreshData">
|
||||
<template #icon>
|
||||
<IconRefresh />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-container">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<IconClockCircle />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ getPendingCount() }}</div>
|
||||
<div class="stat-label">待审批</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<IconCheckCircle />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ getApprovedCount() }}</div>
|
||||
<div class="stat-label">已通过</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||
<IconCloseCircle />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">{{ getRejectedCount() }}</div>
|
||||
<div class="stat-label">已拒绝</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="stat-card" :bordered="false">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
||||
<IconApps />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">¥{{ getTotalAmount() }}</div>
|
||||
<div class="stat-label">审批总额</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title">
|
||||
<a-tabs v-model:active-key="activeTab" @change="handleTabChange">
|
||||
<a-tab-pane key="pending" title="待审批">
|
||||
<template #title>
|
||||
<span>
|
||||
<IconClockCircle style="margin-right: 4px;" />
|
||||
待审批 ({{ getPendingCount() }})
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="approved" title="已审批">
|
||||
<template #title>
|
||||
<span>
|
||||
<IconCheckCircle style="margin-right: 4px;" />
|
||||
已审批 ({{ getApprovedCount() + getRejectedCount() }})
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="approvalId"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<!-- 业务类型 -->
|
||||
<template #businessType="{ record }">
|
||||
<a-tag :color="getBusinessTypeColor(record.businessType)">
|
||||
{{ getBusinessTypeText(record.businessType) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 审批状态 -->
|
||||
<template #approvalStatus="{ record }">
|
||||
<a-tag :color="getApprovalStatusColor(record.approvalStatus)">
|
||||
{{ getApprovalStatusText(record.approvalStatus) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 采购价格 -->
|
||||
<template #purchasePrice="{ record }">
|
||||
<span v-if="record.purchasePrice" class="price-text">
|
||||
¥{{ formatPrice(record.purchasePrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 总价 -->
|
||||
<template #totalPrice="{ record }">
|
||||
<span v-if="record.totalPrice" class="price-text">
|
||||
¥{{ formatPrice(record.totalPrice) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 申请时间 -->
|
||||
<template #applyTime="{ record }">
|
||||
<span v-if="record.applyTime" class="time-text">
|
||||
{{ formatDateTime(record.applyTime) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 审批时间 -->
|
||||
<template #approvalTime="{ record }">
|
||||
<span v-if="record.approvalTime" class="time-text">
|
||||
{{ formatDateTime(record.approvalTime) }}
|
||||
</span>
|
||||
<span v-else class="no-data">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleView(record)">
|
||||
查看详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.approvalStatus === ApprovalStatus.PENDING"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleApprove(record)"
|
||||
>
|
||||
审批通过
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.approvalStatus === ApprovalStatus.PENDING"
|
||||
type="text"
|
||||
size="small"
|
||||
status="danger"
|
||||
@click="handleReject(record)"
|
||||
>
|
||||
审批拒绝
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 审批详情弹窗 -->
|
||||
<ApprovalDetailModal
|
||||
v-model:visible="detailModalVisible"
|
||||
:approval-data="currentApproval"
|
||||
@success="handleModalSuccess"
|
||||
@approve="handleApproveFromDetail"
|
||||
@reject="handleRejectFromDetail"
|
||||
/>
|
||||
|
||||
<!-- 审批操作弹窗 -->
|
||||
<ApprovalActionModal
|
||||
v-model:visible="actionModalVisible"
|
||||
:approval-data="currentApproval"
|
||||
:action-type="actionType"
|
||||
@success="handleModalSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconApps,
|
||||
IconCheckCircle,
|
||||
IconClockCircle,
|
||||
IconCloseCircle,
|
||||
IconRefresh,
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
import message from '@arco-design/web-vue/es/message'
|
||||
import ApprovalDetailModal from './components/ApprovalDetailModal.vue'
|
||||
import ApprovalActionModal from './components/ApprovalActionModal.vue'
|
||||
import ApprovalSearch from './components/ApprovalSearch.vue'
|
||||
import { equipmentApprovalApi } from '@/apis/equipment/approval'
|
||||
import type { EquipmentApprovalListReq, EquipmentApprovalResp } from '@/apis/equipment/type'
|
||||
import { ApprovalStatus, BusinessType } from '@/apis/equipment/type'
|
||||
|
||||
defineOptions({ name: 'EquipmentApproval' })
|
||||
|
||||
// 当前搜索参数
|
||||
const currentSearchParams = ref<EquipmentApprovalListReq>({})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<EquipmentApprovalResp[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive<any>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showPageSize: true,
|
||||
showJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`,
|
||||
})
|
||||
|
||||
// 弹窗控制
|
||||
const detailModalVisible = ref(false)
|
||||
const actionModalVisible = ref(false)
|
||||
const currentApproval = ref<EquipmentApprovalResp | null>(null)
|
||||
const actionType = ref<'approve' | 'reject'>('approve')
|
||||
|
||||
// 当前标签页
|
||||
const activeTab = ref('pending')
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'equipmentName',
|
||||
key: 'equipmentName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'equipmentType',
|
||||
key: 'equipmentType',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '业务类型',
|
||||
dataIndex: 'businessType',
|
||||
key: 'businessType',
|
||||
slotName: 'businessType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '设备型号',
|
||||
dataIndex: 'equipmentModel',
|
||||
key: 'equipmentModel',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '品牌',
|
||||
dataIndex: 'brand',
|
||||
key: 'brand',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '供应商',
|
||||
dataIndex: 'supplierName',
|
||||
key: 'supplierName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '采购价格',
|
||||
dataIndex: 'purchasePrice',
|
||||
key: 'purchasePrice',
|
||||
slotName: 'purchasePrice',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '总价',
|
||||
dataIndex: 'totalPrice',
|
||||
key: 'totalPrice',
|
||||
slotName: 'totalPrice',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
dataIndex: 'quantity',
|
||||
key: 'quantity',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
dataIndex: 'applicantName',
|
||||
key: 'applicantName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applyTime',
|
||||
key: 'applyTime',
|
||||
slotName: 'applyTime',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '审批状态',
|
||||
dataIndex: 'approvalStatus',
|
||||
key: 'approvalStatus',
|
||||
slotName: 'approvalStatus',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '审批人',
|
||||
dataIndex: 'approverName',
|
||||
key: 'approverName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '审批时间',
|
||||
dataIndex: 'approvalTime',
|
||||
key: 'approvalTime',
|
||||
slotName: 'approvalTime',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
slotName: 'action',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
|
||||
// 获取审批状态颜色
|
||||
const getApprovalStatusColor = (status: ApprovalStatus) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
[ApprovalStatus.PENDING]: 'orange',
|
||||
[ApprovalStatus.APPROVED]: 'green',
|
||||
[ApprovalStatus.REJECTED]: 'red',
|
||||
}
|
||||
return colorMap[status] || 'blue'
|
||||
}
|
||||
|
||||
// 获取业务类型颜色
|
||||
const getBusinessTypeColor = (type: BusinessType) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
[BusinessType.PROCUREMENT]: 'blue',
|
||||
[BusinessType.BORROW]: 'green',
|
||||
[BusinessType.RETURN]: 'orange',
|
||||
}
|
||||
return colorMap[type] || 'gray'
|
||||
}
|
||||
|
||||
// 获取业务类型文本
|
||||
const getBusinessTypeText = (type: BusinessType) => {
|
||||
const textMap: Record<string, string> = {
|
||||
[BusinessType.PROCUREMENT]: '采购',
|
||||
[BusinessType.BORROW]: '借用',
|
||||
[BusinessType.RETURN]: '归还',
|
||||
}
|
||||
return textMap[type] || '未知'
|
||||
}
|
||||
|
||||
// 获取审批状态文本
|
||||
const getApprovalStatusText = (status: ApprovalStatus) => {
|
||||
const textMap: Record<string, string> = {
|
||||
[ApprovalStatus.PENDING]: '待审批',
|
||||
[ApprovalStatus.APPROVED]: '已通过',
|
||||
[ApprovalStatus.REJECTED]: '已拒绝',
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price: number) => {
|
||||
return price.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string) => {
|
||||
if (!dateTime) return '-'
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// 转换后端数据
|
||||
const transformBackendData = (data: any[]): EquipmentApprovalResp[] => {
|
||||
return data.map((item: any) => ({
|
||||
approvalId: item.approvalId || item.id,
|
||||
equipmentId: item.equipmentId,
|
||||
equipmentName: item.equipmentName,
|
||||
equipmentType: item.equipmentType,
|
||||
equipmentModel: item.equipmentModel,
|
||||
brand: item.brand,
|
||||
supplierName: item.supplierName,
|
||||
purchasePrice: item.purchasePrice,
|
||||
totalPrice: item.totalPrice,
|
||||
quantity: item.quantity,
|
||||
applicantName: item.applicantName,
|
||||
applicantId: item.applicantId,
|
||||
applyTime: item.applyTime,
|
||||
applyReason: item.applyReason,
|
||||
businessType: item.businessType || BusinessType.PROCUREMENT,
|
||||
approvalStatus: item.approvalStatus,
|
||||
approverName: item.approverName,
|
||||
approverId: item.approverId,
|
||||
approvalTime: item.approvalTime,
|
||||
approvalComment: item.approvalComment,
|
||||
createTime: item.createTime,
|
||||
updateTime: item.updateTime,
|
||||
}))
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const loadData = async (searchParams?: EquipmentApprovalListReq) => {
|
||||
console.log('📊 loadData - 开始加载数据')
|
||||
console.log('📊 loadData - 接收到的搜索参数:', searchParams)
|
||||
console.log('📊 loadData - 当前标签页:', activeTab.value)
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params: EquipmentApprovalListReq = {
|
||||
pageSize: pagination.pageSize || 10,
|
||||
page: pagination.current || 1,
|
||||
pageNum: pagination.current || 1, // 添加 pageNum 参数,以防后端期望这个名称
|
||||
...(searchParams || {}),
|
||||
}
|
||||
|
||||
console.log('📊 loadData - 构建的完整请求参数:', params)
|
||||
|
||||
let res: any
|
||||
if (activeTab.value === 'pending') {
|
||||
console.log('📊 loadData - 调用待审批API')
|
||||
res = await equipmentApprovalApi.getPendingApprovals(params)
|
||||
} else {
|
||||
console.log('📊 loadData - 调用已审批API')
|
||||
res = await equipmentApprovalApi.getApprovedApprovals(params)
|
||||
}
|
||||
|
||||
console.log('API响应:', res)
|
||||
|
||||
// 处理不同的响应格式
|
||||
let dataList: any[] = []
|
||||
let total = 0
|
||||
|
||||
if (res && res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
dataList = res.data
|
||||
total = res.data.length
|
||||
} else if (res.data && Array.isArray((res.data as any).records)) {
|
||||
dataList = (res.data as any).records
|
||||
total = (res.data as any).total || 0
|
||||
} else if (res.data && Array.isArray((res.data as any).list)) {
|
||||
dataList = (res.data as any).list
|
||||
total = (res.data as any).total || 0
|
||||
} else if (res.data && Array.isArray((res.data as any).rows)) {
|
||||
dataList = (res.data as any).rows
|
||||
total = (res.data as any).total || 0
|
||||
} else if (res.data && Array.isArray((res.data as any).data)) {
|
||||
dataList = (res.data as any).data
|
||||
total = (res.data as any).total || 0
|
||||
}
|
||||
} else if (Array.isArray(res)) {
|
||||
dataList = res
|
||||
total = res.length
|
||||
}
|
||||
|
||||
console.log('处理后的数据列表:', dataList)
|
||||
console.log('总数:', total)
|
||||
|
||||
if (dataList.length > 0) {
|
||||
const transformedData = transformBackendData(dataList)
|
||||
console.log('转换后的数据:', transformedData)
|
||||
tableData.value = transformedData
|
||||
} else {
|
||||
console.log('没有数据,设置空数组')
|
||||
tableData.value = []
|
||||
}
|
||||
|
||||
pagination.total = total
|
||||
console.log('设置分页总数:', pagination.total)
|
||||
} catch (error: any) {
|
||||
console.error('加载数据失败:', error)
|
||||
message.error(error?.message || '加载数据失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
console.log('📊 loadData - 加载完成')
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = (searchParams: EquipmentApprovalListReq) => {
|
||||
console.log('🔍 主组件 - 接收到的搜索参数:', searchParams)
|
||||
pagination.current = 1
|
||||
currentSearchParams.value = { ...searchParams }
|
||||
loadData(searchParams)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
console.log('🔄 主组件 - 重置操作')
|
||||
pagination.current = 1
|
||||
currentSearchParams.value = {}
|
||||
loadData({}) // 传递空对象而不是 undefined
|
||||
}
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current || 1
|
||||
pagination.pageSize = pag.pageSize || 10
|
||||
loadData(currentSearchParams.value || {})
|
||||
}
|
||||
|
||||
// 标签页变化
|
||||
const handleTabChange = (key: string) => {
|
||||
activeTab.value = key
|
||||
pagination.current = 1
|
||||
loadData(currentSearchParams.value || {})
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (record: EquipmentApprovalResp) => {
|
||||
currentApproval.value = { ...record }
|
||||
detailModalVisible.value = true
|
||||
}
|
||||
|
||||
// 审批通过
|
||||
const handleApprove = (record: EquipmentApprovalResp) => {
|
||||
currentApproval.value = { ...record }
|
||||
actionType.value = 'approve'
|
||||
actionModalVisible.value = true
|
||||
}
|
||||
|
||||
// 审批拒绝
|
||||
const handleReject = (record: EquipmentApprovalResp) => {
|
||||
currentApproval.value = { ...record }
|
||||
actionType.value = 'reject'
|
||||
actionModalVisible.value = true
|
||||
}
|
||||
|
||||
// 从详情弹窗审批通过
|
||||
const handleApproveFromDetail = (record: EquipmentApprovalResp) => {
|
||||
currentApproval.value = { ...record }
|
||||
actionType.value = 'approve'
|
||||
actionModalVisible.value = true
|
||||
}
|
||||
|
||||
// 从详情弹窗审批拒绝
|
||||
const handleRejectFromDetail = (record: EquipmentApprovalResp) => {
|
||||
currentApproval.value = { ...record }
|
||||
actionType.value = 'reject'
|
||||
actionModalVisible.value = true
|
||||
}
|
||||
|
||||
// 弹窗成功回调
|
||||
const handleModalSuccess = () => {
|
||||
detailModalVisible.value = false
|
||||
actionModalVisible.value = false
|
||||
loadData(currentSearchParams.value || {})
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = () => {
|
||||
loadData(currentSearchParams.value || {})
|
||||
}
|
||||
|
||||
// 统计函数
|
||||
const getPendingCount = () => {
|
||||
return tableData.value.filter(item =>
|
||||
item.approvalStatus === ApprovalStatus.PENDING
|
||||
).length
|
||||
}
|
||||
|
||||
const getApprovedCount = () => {
|
||||
return tableData.value.filter(item =>
|
||||
item.approvalStatus === ApprovalStatus.APPROVED
|
||||
).length
|
||||
}
|
||||
|
||||
const getRejectedCount = () => {
|
||||
return tableData.value.filter(item =>
|
||||
item.approvalStatus === ApprovalStatus.REJECTED
|
||||
).length
|
||||
}
|
||||
|
||||
const getTotalAmount = () => {
|
||||
const total = tableData.value.reduce((sum, item) => {
|
||||
return sum + (item.totalPrice || 0)
|
||||
}, 0)
|
||||
return formatPrice(total)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData({}) // 传递空对象而不是 undefined
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.equipment-approval-container {
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-left {
|
||||
.page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.arco-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.stat-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
|
||||
.arco-icon {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.card-title {
|
||||
.arco-tabs {
|
||||
.arco-tabs-nav {
|
||||
background: transparent;
|
||||
|
||||
.arco-tabs-tab {
|
||||
border-radius: 8px;
|
||||
margin-right: 8px;
|
||||
|
||||
&.arco-tabs-tab-active {
|
||||
background: var(--color-primary-light-1);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arco-table {
|
||||
.arco-table-th {
|
||||
background-color: var(--color-fill-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arco-table-tr:hover {
|
||||
background-color: var(--color-fill-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: var(--color-text-4);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// 确保搜索区域有良好的对比度
|
||||
.approval-search-container {
|
||||
background: #ffffff;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.arco-form-item-label {
|
||||
color: #333 !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.equipment-approval-container {
|
||||
.page-header {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.header-right {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
|
||||
.arco-space {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
.arco-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue