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
|
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',
|
path: '/products-services',
|
||||||
name: 'ProductsServices',
|
name: 'ProductsServices',
|
||||||
|
@ -1327,7 +1261,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/system-resource/device-management/online',
|
path: '/system-resource/device-management/online',
|
||||||
name: 'DeviceOnline',
|
name: 'SystemResourceDeviceOnline',
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
component: () => import('@/components/ParentView/index.vue'),
|
||||||
redirect: '/system-resource/device-management/online/drone',
|
redirect: '/system-resource/device-management/online/drone',
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -1338,7 +1272,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/system-resource/device-management/online/drone',
|
path: '/system-resource/device-management/online/drone',
|
||||||
name: 'DeviceDrone',
|
name: 'SystemResourceDeviceDrone',
|
||||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '无人机',
|
title: '无人机',
|
||||||
|
@ -1348,7 +1282,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/system-resource/device-management/online/nest',
|
path: '/system-resource/device-management/online/nest',
|
||||||
name: 'DeviceNest',
|
name: 'SystemResourceDeviceNest',
|
||||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '机巢',
|
title: '机巢',
|
||||||
|
@ -1358,7 +1292,7 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/system-resource/device-management/online/smart-terminal',
|
path: '/system-resource/device-management/online/smart-terminal',
|
||||||
name: 'DeviceSmartTerminal',
|
name: 'SystemResourceDeviceSmartTerminal',
|
||||||
component: () => import('@/views/system-resource/device-management/index.vue'),
|
component: () => import('@/views/system-resource/device-management/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '其他智能终端',
|
title: '其他智能终端',
|
||||||
|
|
|
@ -226,6 +226,79 @@ const storeSetup = () => {
|
||||||
{
|
{
|
||||||
id: 2013,
|
id: 2013,
|
||||||
parentId: 2010,
|
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: '设备详情',
|
title: '设备详情',
|
||||||
type: 2,
|
type: 2,
|
||||||
path: '/asset-management/device-management/device-detail/:id',
|
path: '/asset-management/device-management/device-detail/:id',
|
||||||
|
@ -235,7 +308,7 @@ const storeSetup = () => {
|
||||||
isExternal: false,
|
isExternal: false,
|
||||||
isCache: false,
|
isCache: false,
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
sort: 3,
|
sort: 5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,6 +70,6 @@ declare global {
|
||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @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')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,3 +225,7 @@ export default {
|
||||||
requestRaw,
|
requestRaw,
|
||||||
download,
|
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>
|
|
@ -38,48 +38,93 @@
|
||||||
|
|
||||||
<!-- 合同状态 -->
|
<!-- 合同状态 -->
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<a-tag :color="getStatusColor(record.status)">
|
<a-tag :color="getStatusColor(record.contractStatus)">
|
||||||
{{ getStatusText(record.status) }}
|
{{ getStatusText(record.contractStatusLabel || record.contractStatus) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 合同金额 -->
|
<!-- 合同金额 -->
|
||||||
<template #contractAmount="{ record }">
|
<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>
|
||||||
|
|
||||||
<!-- 已收款金额 -->
|
<!-- 已收款金额 -->
|
||||||
<template #receivedAmount="{ record }">
|
<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>
|
||||||
|
|
||||||
<!-- 操作列 -->
|
<!-- 操作列 -->
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-link @click="viewDetail(record)">详情</a-link>
|
<a-link @click="viewDetail(record)">详情</a-link>
|
||||||
<a-link @click="editRecord(record)" v-if="record.status === 'draft'">编辑</a-link>
|
<a-link v-if="record.contractStatus === '未确认'" @click="editRecord(record)">编辑</a-link>
|
||||||
<a-link @click="approveContract(record)" v-if="record.status === 'pending'">审批</a-link>
|
<a-link v-if="record.contractStatus === '待审批'" @click="approveContract(record)">审批</a-link>
|
||||||
<a-link @click="viewPayment(record)">收款记录</a-link>
|
<a-link @click="viewPayment(record)">收款记录</a-link>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</GiTable>
|
</GiTable>
|
||||||
|
|
||||||
|
<!-- 合同详情弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="showDetailModal"
|
||||||
|
title="合同详情"
|
||||||
|
:width="800"
|
||||||
|
@cancel="closeDetailModal"
|
||||||
|
:footer="false"
|
||||||
|
>
|
||||||
|
<ContractDetail v-if="showDetailModal" :contract-id="selectedContractId" />
|
||||||
|
</a-modal>
|
||||||
</GiPageLayout>
|
</GiPageLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
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 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: '',
|
contractName: '',
|
||||||
contractCode: '',
|
contractCode: '',
|
||||||
client: '',
|
client: '',
|
||||||
status: '',
|
status: '',
|
||||||
signDate: '',
|
signDate: '',
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10
|
size: 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 查询条件配置
|
// 查询条件配置
|
||||||
|
@ -89,16 +134,16 @@ const queryFormColumns = [
|
||||||
label: '合同名称',
|
label: '合同名称',
|
||||||
type: 'input' as const,
|
type: 'input' as const,
|
||||||
props: {
|
props: {
|
||||||
placeholder: '请输入合同名称'
|
placeholder: '请输入合同名称',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'client',
|
field: 'client',
|
||||||
label: '客户',
|
label: '客户',
|
||||||
type: 'input' as const,
|
type: 'input' as const,
|
||||||
props: {
|
props: {
|
||||||
placeholder: '请输入客户名称'
|
placeholder: '请输入客户名称',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
|
@ -107,132 +152,111 @@ const queryFormColumns = [
|
||||||
props: {
|
props: {
|
||||||
placeholder: '请选择合同状态',
|
placeholder: '请选择合同状态',
|
||||||
options: [
|
options: [
|
||||||
{ label: '草稿', value: 'draft' },
|
{ label: '未确认', value: '未确认' },
|
||||||
{ label: '待审批', value: 'pending' },
|
{ label: '待审批', value: '待审批' },
|
||||||
{ label: '已签署', value: 'signed' },
|
{ label: '已签署', value: '已签署' },
|
||||||
{ label: '执行中', value: 'executing' },
|
{ label: '执行中', value: '执行中' },
|
||||||
{ label: '已完成', value: 'completed' },
|
{ label: '已完成', value: '已完成' },
|
||||||
{ label: '已终止', value: 'terminated' }
|
{ label: '已终止', value: '已终止' },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const tableColumns: TableColumnData[] = [
|
const tableColumns: TableColumnData[] = [
|
||||||
{ title: '合同编号', dataIndex: 'contractCode', width: 150 },
|
{ title: '合同编号', dataIndex: 'code', width: 150 },
|
||||||
{ title: '合同名称', dataIndex: 'contractName', width: 250, ellipsis: true, tooltip: true },
|
{ title: '项目名称', dataIndex: 'projectName', width: 250, ellipsis: true, tooltip: true },
|
||||||
{ title: '客户名称', dataIndex: 'client', width: 200, ellipsis: true, tooltip: true },
|
{ title: '客户名称', dataIndex: 'customer', width: 200, ellipsis: true, tooltip: true },
|
||||||
{ title: '合同金额', dataIndex: 'contractAmount', slotName: 'contractAmount', width: 120 },
|
{ title: '合同金额', dataIndex: 'amount', slotName: 'contractAmount', width: 120 },
|
||||||
{ title: '已收款金额', dataIndex: 'receivedAmount', slotName: 'receivedAmount', width: 120 },
|
{ title: '已收款金额', dataIndex: 'receivedAmount', slotName: 'receivedAmount', width: 120 },
|
||||||
{ title: '未收款金额', dataIndex: 'pendingAmount', width: 120 },
|
{ title: '未收款金额', dataIndex: 'pendingAmount', width: 120 },
|
||||||
{ title: '签署日期', dataIndex: 'signDate', width: 120 },
|
{ title: '签署日期', dataIndex: 'signDate', width: 120 },
|
||||||
{ title: '开始日期', dataIndex: 'startDate', width: 120 },
|
{ title: '履约期限', dataIndex: 'performanceDeadline', width: 120 },
|
||||||
{ title: '结束日期', dataIndex: 'endDate', width: 120 },
|
{ title: '付款日期', dataIndex: 'paymentDate', width: 120 },
|
||||||
{ title: '合同状态', dataIndex: 'status', slotName: 'status', width: 100 },
|
{ title: '合同状态', dataIndex: 'contractStatus', slotName: 'status', width: 100 },
|
||||||
{ title: '项目经理', dataIndex: 'projectManager', width: 100 },
|
{ title: '销售人员', dataIndex: 'salespersonName', width: 100 },
|
||||||
{ title: '销售经理', dataIndex: 'salesManager', width: 100 },
|
{ title: '销售部门', dataIndex: 'salespersonDeptName', width: 100 },
|
||||||
{ title: '完成进度', dataIndex: 'progress', width: 100 },
|
{ title: '产品服务', dataIndex: 'productService', width: 120, ellipsis: true, tooltip: true },
|
||||||
{ title: '备注', dataIndex: 'remark', width: 200, ellipsis: true, tooltip: true },
|
{ title: '备注', dataIndex: 'notes', width: 200, ellipsis: true, tooltip: true },
|
||||||
{ title: '操作', slotName: 'action', width: 200, fixed: 'right' }
|
{ title: '操作', slotName: 'action', width: 200, fixed: 'right' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const dataList = ref([
|
const dataList = ref<ContractItem[]>([])
|
||||||
{
|
|
||||||
id: 1,
|
// API调用函数
|
||||||
contractCode: 'RC2024001',
|
const fetchContractList = async () => {
|
||||||
contractName: '华能新能源风电场叶片检测服务合同',
|
try {
|
||||||
client: '华能新能源股份有限公司',
|
loading.value = true
|
||||||
contractAmount: 320,
|
const params = {
|
||||||
receivedAmount: 192,
|
page: searchForm.page,
|
||||||
pendingAmount: 128,
|
pageSize: searchForm.size,
|
||||||
signDate: '2024-02-20',
|
contractName: searchForm.contractName,
|
||||||
startDate: '2024-03-01',
|
code: searchForm.contractCode,
|
||||||
endDate: '2024-04-30',
|
customer: searchForm.client,
|
||||||
status: 'executing',
|
contractStatus: searchForm.status,
|
||||||
projectManager: '张项目经理',
|
signDate: searchForm.signDate,
|
||||||
salesManager: '李销售经理',
|
}
|
||||||
progress: '60%',
|
|
||||||
remark: '项目进展顺利,客户满意度高'
|
const response = await http.get('/contract/list', params)
|
||||||
},
|
|
||||||
{
|
if (response.code === 200) {
|
||||||
id: 2,
|
// 过滤出类型为"收入合同"的数据
|
||||||
contractCode: 'RC2024002',
|
const allContracts = response.rows || []
|
||||||
contractName: '大唐风电场防雷检测项目合同',
|
const revenueContracts = allContracts.filter((item: ContractItem) => item.type === '收入合同')
|
||||||
client: '大唐新能源股份有限公司',
|
|
||||||
contractAmount: 268,
|
// 计算未收款金额
|
||||||
receivedAmount: 134,
|
dataList.value = revenueContracts.map((item: ContractItem) => ({
|
||||||
pendingAmount: 134,
|
...item,
|
||||||
signDate: '2024-02-25',
|
pendingAmount: (item.amount || 0) - (item.receivedAmount || 0),
|
||||||
startDate: '2024-03-05',
|
}))
|
||||||
endDate: '2024-04-20',
|
|
||||||
status: 'executing',
|
pagination.total = Number.parseInt(response.total) || 0
|
||||||
projectManager: '王项目经理',
|
} else {
|
||||||
salesManager: '赵销售经理',
|
Message.error(response.msg || '获取合同列表失败')
|
||||||
progress: '45%',
|
dataList.value = []
|
||||||
remark: '按计划执行中'
|
}
|
||||||
},
|
} catch (error) {
|
||||||
{
|
console.error('获取合同列表失败:', error)
|
||||||
id: 3,
|
Message.error('获取合同列表失败')
|
||||||
contractCode: 'RC2024003',
|
dataList.value = []
|
||||||
contractName: '中广核风电场设备维护服务合同',
|
} finally {
|
||||||
client: '中广核新能源投资有限公司',
|
loading.value = false
|
||||||
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 pagination = reactive({
|
const pagination = reactive({
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 3,
|
total: 0,
|
||||||
showTotal: true,
|
showTotal: true,
|
||||||
showPageSize: true
|
showPageSize: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取状态颜色
|
// 获取状态颜色
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
const colorMap: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
'draft': 'gray',
|
未确认: 'gray',
|
||||||
'pending': 'orange',
|
待审批: 'orange',
|
||||||
'signed': 'blue',
|
已签署: 'blue',
|
||||||
'executing': 'cyan',
|
执行中: 'cyan',
|
||||||
'completed': 'green',
|
已完成: 'green',
|
||||||
'terminated': 'red'
|
已终止: 'red',
|
||||||
}
|
}
|
||||||
return colorMap[status] || 'gray'
|
return colorMap[status] || 'gray'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
const getStatusText = (status: string) => {
|
const getStatusText = (status: string) => {
|
||||||
const textMap: Record<string, string> = {
|
// 直接返回后端返回的状态文本,如果有contractStatusLabel则使用,否则使用contractStatus
|
||||||
'draft': '草稿',
|
return status || '未知状态'
|
||||||
'pending': '待审批',
|
|
||||||
'signed': '已签署',
|
|
||||||
'executing': '执行中',
|
|
||||||
'completed': '已完成',
|
|
||||||
'terminated': '已终止'
|
|
||||||
}
|
|
||||||
return textMap[status] || status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索和重置
|
// 搜索和重置
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
loading.value = true
|
await fetchContractList()
|
||||||
setTimeout(() => {
|
|
||||||
loading.value = false
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
|
@ -243,7 +267,7 @@ const reset = () => {
|
||||||
status: '',
|
status: '',
|
||||||
signDate: '',
|
signDate: '',
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10
|
size: 10,
|
||||||
})
|
})
|
||||||
pagination.current = 1
|
pagination.current = 1
|
||||||
search()
|
search()
|
||||||
|
@ -273,23 +297,33 @@ const exportContract = () => {
|
||||||
Message.info('导出合同功能开发中...')
|
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) => {
|
const closeDetailModal = () => {
|
||||||
Message.info(`编辑合同: ${record.contractName}`)
|
showDetailModal.value = false
|
||||||
|
selectedContractId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const approveContract = (record: any) => {
|
const editRecord = (record: ContractItem) => {
|
||||||
Message.info(`审批合同: ${record.contractName}`)
|
Message.info(`编辑合同: ${record.projectName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewPayment = (record: any) => {
|
const approveContract = (record: ContractItem) => {
|
||||||
Message.info(`查看收款记录: ${record.contractName}`)
|
Message.info(`审批合同: ${record.projectName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewPayment = (record: ContractItem) => {
|
||||||
|
Message.info(`查看收款记录: ${record.projectName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
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