This commit is contained in:
马诗敏 2025-08-12 17:34:37 +08:00
commit 7c455f59eb
18 changed files with 1246 additions and 269 deletions

View File

@ -1,4 +1,4 @@
// @/apis/bussiness/index.ts - 商务数据库信息模块API
// @/apis/bussiness/index.ts - 智能商务API
import http from '@/utils/http'
import type {
FolderInfo,
@ -59,7 +59,9 @@ export function getFilesApi(params?: FileListParams) {
page: params?.page || 1,
pageSize: params?.pageSize || 10,
folderId: params?.folderId || '0',
fileName: params?.fileName
fileName: params?.fileName,
sortField: params?.sortField,
sortOrder: params?.sortOrder
}
})
}

View File

@ -31,6 +31,8 @@ export interface FileListParams {
pageSize?: number
folderId?: string
fileName?: string
sortField?: string
sortOrder?: string
}
/** 文件夹列表响应 */

View File

@ -194,6 +194,12 @@ export interface EquipmentResp {
inventoryBasis?: string
/** 动态记录 */
dynamicRecord?: string
/** 采购状态 */
procurementStatus?: string
/** 审批状态 */
approvalStatus?: string
}
/**
@ -294,6 +300,17 @@ export enum BusinessType {
RETURN = 'RETURN'
}
/**
*
*/
export enum ProcurementStatus {
NOT_STARTED = 'NOT_STARTED',
PENDING_APPROVAL = 'PENDING_APPROVAL',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
COMPLETED = 'COMPLETED'
}
/**
*
*/

View File

@ -0,0 +1,297 @@
<template>
<div
class="approval-message-item"
:class="{
'unread': !notification.read,
'urgent': notification.priority === 'URGENT',
'high': notification.priority === 'HIGH',
'action-required': notification.actionRequired
}"
@click="handleClick"
>
<!-- 消息图标 -->
<div class="message-icon">
<component :is="getIconByType(notification.type)" />
</div>
<!-- 消息内容 -->
<div class="message-content">
<div class="message-header">
<div class="message-title">
{{ notification.title }}
<a-tag
v-if="notification.actionRequired"
size="small"
color="red"
>
需操作
</a-tag>
<a-tag
v-if="notification.priority === 'URGENT'"
size="small"
color="red"
>
紧急
</a-tag>
<a-tag
v-if="notification.priority === 'HIGH'"
size="small"
color="orange"
>
重要
</a-tag>
</div>
<div class="message-time">
{{ formatTime(notification.createTime) }}
</div>
</div>
<div class="message-body">
{{ notification.content }}
</div>
<div class="message-footer">
<div class="message-meta">
<span class="category">{{ notification.category }}</span>
<span v-if="notification.source" class="source">{{ notification.source }}</span>
</div>
<div class="message-actions">
<a-button
type="text"
size="small"
@click.stop="handleView"
>
查看详情
</a-button>
<a-button
type="text"
size="small"
@click.stop="handleApprove"
v-if="canApprove"
>
审批
</a-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import {
IconCheckCircle,
IconClockCircle,
IconApps,
IconSettings,
IconExclamationCircle
} from '@arco-design/web-vue/es/icon'
interface Props {
notification: {
id: string
type: string
title: string
content: string
priority?: string
category: string
source?: string
createTime: string
read: boolean
actionRequired: boolean
targetUrl?: string
metadata?: any
reminderTime?: string
reminderType?: string
lastReminderTime?: string
}
}
const props = defineProps<Props>()
const router = useRouter()
//
const canApprove = computed(() => {
return props.notification.type.includes('APPROVAL') ||
props.notification.type === 'PENDING' ||
props.notification.type === 'PROCUREMENT'
})
//
const getIconByType = (type: string) => {
const iconMap: Record<string, any> = {
'APPROVAL': IconCheckCircle,
'PENDING': IconClockCircle,
'PROCUREMENT': IconApps,
'EQUIPMENT_BORROW': IconApps,
'EQUIPMENT_RETURN': IconApps,
'EQUIPMENT_MAINTENANCE': IconSettings,
'EQUIPMENT_ALERT': IconExclamationCircle,
'PROCUREMENT_APPROVAL': IconApps,
'BORROW_APPROVAL': IconApps,
'RETURN_APPROVAL': IconApps
}
return iconMap[type] || IconCheckCircle
}
//
const formatTime = (time: string) => {
if (!time) return '-'
const date = new Date(time)
const now = new Date()
const diff = now.getTime() - date.getTime()
if (diff < 60000) return '刚刚'
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`
if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`
if (diff < 2592000000) return `${Math.floor(diff / 86400000)}天前`
return date.toLocaleDateString()
}
//
const handleClick = () => {
if (props.notification.targetUrl) {
router.push(props.notification.targetUrl)
}
}
//
const handleView = () => {
if (props.notification.targetUrl) {
router.push(props.notification.targetUrl)
}
}
//
const handleApprove = () => {
if (props.notification.targetUrl) {
router.push(props.notification.targetUrl)
}
}
</script>
<style scoped lang="scss">
.approval-message-item {
display: flex;
align-items: flex-start;
padding: 20px;
border: 1px solid var(--color-border-2);
border-radius: 8px;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.2s;
background: var(--color-bg-2);
&:hover {
background-color: var(--color-fill-2);
border-color: var(--color-primary-light-3);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&.unread {
background-color: var(--color-primary-light-1);
border-color: var(--color-primary-light-3);
&:hover {
background-color: var(--color-primary-light-2);
}
}
&.urgent {
border-left: 4px solid var(--color-danger);
background-color: var(--color-danger-light-1);
}
&.high {
border-left: 4px solid var(--color-warning);
background-color: var(--color-warning-light-1);
}
&.action-required {
border-left: 4px solid var(--color-primary);
background-color: var(--color-primary-light-1);
}
.message-icon {
margin-right: 16px;
margin-top: 2px;
color: var(--color-text-2);
font-size: 20px;
}
.message-content {
flex: 1;
min-width: 0;
.message-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
.message-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
color: var(--color-text-1);
font-size: 16px;
flex: 1;
margin-right: 16px;
}
.message-time {
font-size: 12px;
color: var(--color-text-3);
white-space: nowrap;
}
}
.message-body {
font-size: 14px;
color: var(--color-text-2);
margin-bottom: 16px;
line-height: 1.5;
}
.message-footer {
display: flex;
justify-content: space-between;
align-items: center;
.message-meta {
display: flex;
gap: 12px;
font-size: 12px;
color: var(--color-text-3);
.category {
background: var(--color-fill-3);
padding: 2px 8px;
border-radius: 4px;
font-weight: 500;
}
.source {
color: var(--color-text-4);
}
}
.message-actions {
display: flex;
gap: 8px;
opacity: 0;
transition: opacity 0.2s;
}
}
&:hover .message-actions {
opacity: 1;
}
}
}
</style>

View File

@ -1,15 +1,13 @@
<template>
<div class="notification-center">
<!-- 消息中心图标和徽章 -->
<div class="notification-trigger" @click="toggleDropdown">
<a-badge :count="unreadCount" :dot="hasUrgentNotifications">
<a-button type="text" class="notification-btn" title="消息中心">
<template #icon>
<IconNotification />
</template>
<span class="notification-text">消息中心</span>
</a-button>
</a-badge>
<!-- 聊天信息图标 -->
<div class="notification-trigger">
<a-button type="text" class="notification-btn" title="聊天信息">
<template #icon>
<IconNotification />
</template>
<span class="notification-text">聊天信息</span>
</a-button>
</div>
<!-- 消息中心弹窗 -->
@ -229,16 +227,37 @@ const reminderForm = ref({
//
const notifications = computed(() => notificationService.getAllNotifications())
const unreadCount = computed(() => notificationService.unreadCount.value)
const unreadCount = computed(() => {
const count = notificationService.unreadCount.value
// NaN
if (typeof count === 'number' && !isNaN(count) && isFinite(count)) {
return count
}
return 0
})
const totalCount = computed(() => notifications.value.length)
const pendingCount = computed(() => notificationService.pendingCount.value)
const equipmentCount = computed(() =>
notificationService.equipmentBorrowCount.value +
notificationService.equipmentReturnCount.value +
notificationService.equipmentMaintenanceCount.value +
notificationService.equipmentAlertCount.value
)
const urgentCount = computed(() => notificationService.urgentCount.value)
const pendingCount = computed(() => {
const count = notificationService.pendingCount.value
if (typeof count === 'number' && !isNaN(count) && isFinite(count)) {
return count
}
return 0
})
const equipmentCount = computed(() => {
const borrowCount = notificationService.equipmentBorrowCount.value || 0
const returnCount = notificationService.equipmentReturnCount.value || 0
const maintenanceCount = notificationService.equipmentMaintenanceCount.value || 0
const alertCount = notificationService.equipmentAlertCount.value || 0
return borrowCount + returnCount + maintenanceCount + alertCount
})
const urgentCount = computed(() => {
const count = notificationService.urgentCount.value
if (typeof count === 'number' && !isNaN(count) && isFinite(count)) {
return count
}
return 0
})
const hasUrgentNotifications = computed(() => urgentCount.value > 0)
//
@ -644,6 +663,11 @@ onUnmounted(() => {
clearInterval(reminderCheckInterval)
}
})
//
defineExpose({
toggleDropdown
})
</script>
<style scoped lang="scss">

View File

@ -34,7 +34,9 @@
<a-dropdown trigger="hover">
<a-row align="center" :wrap="false" class="user">
<!-- 管理员头像 -->
<Avatar :src="userStore.avatar" :name="userStore.nickname" :size="32" />
<a-badge :count="unreadMessageCount > 0 ? unreadMessageCount : undefined" :dot="false">
<Avatar :src="userStore.avatar" :name="userStore.nickname" :size="32" />
</a-badge>
<span class="username">{{ userStore.nickname }}</span>
<icon-down />
</a-row>
@ -42,8 +44,14 @@
<a-doption @click="router.push('/user/profile')">
<span>个人中心</span>
</a-doption>
<a-doption @click="router.push('/user/message')">
<a-doption @click="showNotificationCenter">
<span>消息中心</span>
<a-badge
v-if="unreadMessageCount > 0"
:count="unreadMessageCount"
:dot="false"
class="dropdown-notification-badge"
/>
</a-doption>
<a-divider :margin="0" />
<a-doption @click="logout">
@ -78,7 +86,14 @@ const { breakpoint } = useBreakpoint()
const notificationCenterRef = ref()
// 使
const unreadMessageCount = computed(() => notificationService.unreadCount.value)
const unreadMessageCount = computed(() => {
const count = notificationService.unreadCount.value
// NaN
if (typeof count === 'number' && !isNaN(count) && isFinite(count)) {
return count
}
return 0
})
//
const playNotificationSound = () => {
@ -224,6 +239,27 @@ const router = useRouter()
const userStore = useUserStore()
const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>()
//
const showNotificationCenter = () => {
console.log('showNotificationCenter 被调用')
console.log('notificationCenterRef.value:', notificationCenterRef.value)
if (notificationCenterRef.value) {
console.log('调用 toggleDropdown 方法')
try {
notificationCenterRef.value.toggleDropdown()
} catch (error) {
console.error('调用 toggleDropdown 失败:', error)
//
if (notificationCenterRef.value.modalVisible !== undefined) {
notificationCenterRef.value.modalVisible = true
}
}
} else {
console.error('notificationCenterRef.value 为空')
}
}
// 退
const logout = () => {
Modal.warning({
@ -262,22 +298,6 @@ onMounted(() => {
transform: rotate(180deg);
}
.user {
cursor: pointer;
color: var(--color-text-1);
.username {
margin-left: 10px;
white-space: nowrap;
}
.arco-icon-down {
transition: all 0.3s;
margin-left: 2px;
}
}
//
.notification-badge {
.arco-badge-dot {
background-color: #f53f3f;
@ -328,4 +348,49 @@ onMounted(() => {
transform: translate3d(0, -2px, 0);
}
}
//
.dropdown-notification-badge {
margin-left: 8px;
:deep(.arco-badge-count) {
background-color: #f53f3f;
font-weight: bold;
font-size: 12px;
min-width: 18px;
height: 18px;
line-height: 18px;
border-radius: 9px;
animation: bounce 0.6s ease-in-out;
}
}
//
.user {
cursor: pointer;
color: var(--color-text-1);
.username {
margin-left: 10px;
white-space: nowrap;
}
.arco-icon-down {
transition: all 0.3s;
margin-left: 2px;
}
//
:deep(.arco-badge-count) {
background-color: #f53f3f;
font-weight: bold;
font-size: 11px;
min-width: 16px;
height: 16px;
line-height: 16px;
border-radius: 8px;
animation: bounce 0.6s ease-in-out;
box-shadow: 0 2px 4px rgba(245, 63, 63, 0.3);
}
}
</style>

View File

@ -1104,7 +1104,7 @@ export const systemRoutes: RouteRecordRaw[] = [
},
// ],
// },
// 商务数据库信息模块
// 智能商务模块
{
path: '/bussiness-knowledge',
name: 'bussinesskonwledge',
@ -1117,7 +1117,7 @@ export const systemRoutes: RouteRecordRaw[] = [
name: 'bussiness-knowledge',
component: () => import('@/views/bussiness-data/bussiness.vue'),
meta: {
title: '商务数据库信息',
title: '智能商务',
icon: 'info-circle',
hidden: false,
},

View File

@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ApprovalAssistant: typeof import('./../components/ApprovalAssistant/index.vue')['default']
ApprovalMessageItem: typeof import('./../components/NotificationCenter/ApprovalMessageItem.vue')['default']
Avatar: typeof import('./../components/Avatar/index.vue')['default']
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
CellCopy: typeof import('./../components/CellCopy/index.vue')['default']

View File

@ -4,10 +4,9 @@
<a-layout-sider
width="260"
:collapsed-width="80"
theme="light"
theme="dark"
class="folder-sidebar"
:collapsed="sidebarCollapsed"
collapsible
@collapse="handleSidebarCollapse"
@expand="handleSidebarExpand"
>
@ -102,29 +101,7 @@
</div>
</div>
<!-- 侧边栏底部分页控件 -->
<div class="sidebar-footer" v-if="!sidebarCollapsed && folderList.length > 0">
<div class="pagination-info">
<a-typography-text type="secondary" size="small">
{{ totalFolders }} 个文件夹
</a-typography-text>
</div>
<!-- 隐藏分页控件因为现在获取所有文件夹 -->
<!-- <div class="pagination-controls">
<a-pagination
:current="currentPage"
:page-size="pageSize"
:total="totalFolders"
:show-size-changer="true"
:page-size-options="['10', '20', '50', '100']"
@change="handlePageChange"
@showSizeChange="handlePageSizeChange"
size="small"
show-total
/>
</div> -->
</div>
</a-layout-sider>
<a-layout>
@ -242,10 +219,42 @@
<div class="file-grid-container" v-if="currentFolderId && !loading">
<!-- 表头行 -->
<a-row class="table-header-row">
<a-col :span="10" class="table-column name-column">文件名</a-col>
<a-col :span="4" class="table-column type-column">类型</a-col>
<a-col :span="3" class="table-column size-column">大小</a-col>
<a-col :span="5" class="table-column time-column">修改时间</a-col>
<a-col :span="10" class="table-column name-column">
<div class="sortable-header" @click="handleSortChange('fileName')">
<span>文件名</span>
<div class="sort-indicator">
<div class="sort-arrow up" :class="{ active: sortField === 'file_name' && sortOrder === 'asc' }"></div>
<div class="sort-arrow down" :class="{ active: sortField === 'file_name' && sortOrder === 'desc' }"></div>
</div>
</div>
</a-col>
<a-col :span="4" class="table-column type-column">
<div class="sortable-header" @click="handleSortChange('fileType')">
<span>类型</span>
<div class="sort-indicator">
<div class="sort-arrow up" :class="{ active: sortField === 'file_type' && sortOrder === 'asc' }"></div>
<div class="sort-arrow down" :class="{ active: sortField === 'file_type' && sortOrder === 'desc' }"></div>
</div>
</div>
</a-col>
<a-col :span="3" class="table-column size-column">
<div class="sortable-header" @click="handleSortChange('fileSize')">
<span>大小</span>
<div class="sort-indicator">
<div class="sort-arrow up" :class="{ active: sortField === 'file_size' && sortOrder === 'asc' }"></div>
<div class="sort-arrow down" :class="{ active: sortField === 'file_size' && sortOrder === 'desc' }"></div>
</div>
</div>
</a-col>
<a-col :span="5" class="table-column time-column">
<div class="sortable-header" @click="handleSortChange('uploadTime')">
<span>修改时间</span>
<div class="sort-indicator">
<div class="sort-arrow up" :class="{ active: sortField === 'upload_time' && sortOrder === 'asc' }"></div>
<div class="sort-arrow down" :class="{ active: sortField === 'upload_time' && sortOrder === 'desc' }"></div>
</div>
</div>
</a-col>
<a-col :span="2" class="table-column action-column">操作</a-col>
</a-row>
@ -273,7 +282,7 @@
<!-- 大小列 -->
<a-col :span="3" class="table-column size-column">
<div class="cell-content">{{ formatFileSize(file.fileSize || file.size) }}</div>
<div class="cell-content">{{ formatFileListSize(file.fileSize || file.size) }}</div>
</a-col>
<!-- 时间列 -->
@ -613,6 +622,18 @@ const fileCurrentPage = ref(1);
const filePageSize = ref(10);
const totalFiles = ref(0);
//
const sortField = ref('');
const sortOrder = ref('');
// ->
const sortFieldMap = {
'fileName': 'file_name',
'fileType': 'file_type',
'fileSize': 'file_size',
'uploadTime': 'upload_time'
};
//
const folderForm = reactive({
id: '',
@ -638,7 +659,7 @@ const fileListTemp = ref([]);
const folderFormRef = ref(null);
const uploadFormRef = ref(null);
const uploadRef = ref(null);
const folderColor = '#165DFF';
const folderColor = 'var(--color-primary)';
const refreshing = ref(false);
const folderSubmitting = ref(false);
const uploading = ref(false);
@ -941,6 +962,9 @@ const handleFileSearch = () => {
console.log('文件搜索关键词:', fileSearchKeyword.value);
//
fileCurrentPage.value = 1;
//
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
@ -960,6 +984,9 @@ const handleFileSearchInput = (value) => {
searchTimeout.value = setTimeout(() => {
console.log('=== 防抖文件搜索执行 ===');
fileCurrentPage.value = 1;
//
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
@ -976,6 +1003,9 @@ const handleFileSearchClear = () => {
console.log('清除文件搜索定时器');
}
fileCurrentPage.value = 1;
//
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
@ -985,12 +1015,20 @@ const handleFileSearchClear = () => {
const loadFiles = async (folderId) => {
try {
loading.value = true;
const res = await getFilesApi({
const apiParams = {
folderId: folderId,
page: fileCurrentPage.value,
pageSize: filePageSize.value,
fileName: fileSearchKeyword.value || undefined
});
};
//
if (sortField.value && sortOrder.value) {
apiParams.sortField = sortField.value;
apiParams.sortOrder = sortOrder.value;
}
const res = await getFilesApi(apiParams);
//
if (res.code === 200 && res.data) {
@ -1012,6 +1050,26 @@ const loadFiles = async (folderId) => {
}
};
//
const handleSortChange = (field) => {
const backendField = sortFieldMap[field];
if (!backendField) return;
//
if (sortField.value === backendField) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
} else {
//
sortField.value = backendField;
sortOrder.value = 'desc';
}
//
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
//
// const handleFolderClick = (folderId) => {
@ -1030,8 +1088,10 @@ const handleFolderSelect = (selectedKeys, info) => {
const folderId = selectedKeys[0];
if (currentFolderId.value !== folderId) {
fileCurrentPage.value = 1;
//
//
fileSearchKeyword.value = '';
sortField.value = '';
sortOrder.value = '';
}
currentFolderId.value = folderId;
loadFiles(folderId);
@ -1136,6 +1196,9 @@ const handleBreadcrumbClick = (index) => {
if (index === 0) {
// ""
currentFolderId.value = '0';
//
sortField.value = '';
sortOrder.value = '';
loadFiles('0');
} else {
// ID
@ -1146,6 +1209,9 @@ const handleBreadcrumbClick = (index) => {
const targetFolder = folderList.value.find(folder => folder.name === targetFolderName);
if (targetFolder) {
currentFolderId.value = targetFolder.id;
//
sortField.value = '';
sortOrder.value = '';
loadFiles(targetFolder.id);
}
}
@ -1307,6 +1373,9 @@ const refreshData = async () => {
//
searchKeyword.value = '';
currentPage.value = 1;
//
sortField.value = '';
sortOrder.value = '';
await initData();
if (currentFolderId.value) {
@ -1532,7 +1601,7 @@ const fileColor = (extension) => {
bmp: '#722ed1',
webp: '#13c2c2'
};
return colorMap[extension.toLowerCase()] || '#8c8c8c';
return colorMap[extension.toLowerCase()] || 'var(--color-text-3)';
};
@ -1789,8 +1858,8 @@ const showTextPreview = async (blob, fileName) => {
maxWidth: '100%',
maxHeight: '70vh',
overflow: 'auto',
backgroundColor: '#f8f9fa',
border: '1px solid #e9ecef',
backgroundColor: 'var(--color-fill-1)',
border: '1px solid var(--color-border)',
borderRadius: '8px',
padding: '20px',
fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace",
@ -1798,7 +1867,7 @@ const showTextPreview = async (blob, fileName) => {
lineHeight: '1.6',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: '#333',
color: 'var(--color-text-1)',
textAlign: 'left'
}
}, text)
@ -2244,6 +2313,21 @@ const formatFileSize = (fileSize) => {
return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
};
// KB
const formatFileListSize = (fileSize) => {
const size = Number(fileSize);
if (isNaN(size) || size < 0) return '未知';
// KB
if (size < 1024) {
return `${size} KB`;
} else if (size < 1024 * 1024) {
return `${(size / 1024).toFixed(1)} MB`;
} else {
return `${(size / (1024 * 1024)).toFixed(1)} GB`;
}
};
const fileTypeText = (type) => {
@ -2366,7 +2450,7 @@ onMounted(() => {
<style scoped>
.knowledge-container {
height: 100vh;
background-color: var(--color-bg-2);
background-color: var(--color-bg-1);
}
@ -2378,13 +2462,15 @@ onMounted(() => {
transition: all 0.3s ease;
overflow: hidden;
position: relative;
background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
background: var(--color-bg-1);
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 20px 16px;
border-bottom: 1px solid var(--color-border);
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
background: var(--color-bg-1);
position: relative;
&::after {
@ -2400,12 +2486,18 @@ onMounted(() => {
.folder-content {
padding: 16px 0;
height: calc(100vh - 320px); /* 为底部分页控件留出更多空间,因为文件夹项现在更高 */
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
background: rgba(255, 255, 255, 0.6);
background: var(--color-bg-1);
display: flex;
flex-direction: column;
min-height: 0;
max-height: calc(100vh - 200px);
}
.folder-content::-webkit-scrollbar {
width: 8px;
}
@ -2445,7 +2537,7 @@ onMounted(() => {
border: 1px solid transparent;
&:hover {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
background: linear-gradient(135deg, var(--color-fill-2) 0%, var(--color-fill-3) 100%);
border-color: var(--color-primary-light-2);
transform: translateX(2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
@ -2514,7 +2606,7 @@ onMounted(() => {
justify-content: space-between;
align-items: center;
padding: 0 24px;
background: var(--color-bg-2);
background: var(--color-bg-1);
border-bottom: 1px solid var(--color-border);
height: 64px;
}
@ -2531,10 +2623,12 @@ onMounted(() => {
display: flex;
flex-direction: column;
padding: 24px;
overflow: auto;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
background: var(--color-bg-2);
background: var(--color-bg-1);
min-height: 0;
max-height: calc(100vh - 120px);
}
.file-card {
@ -2545,6 +2639,7 @@ onMounted(() => {
flex-direction: column;
position: relative;
height: 100%;
overflow: hidden;
}
/* 表格容器 */
@ -2557,7 +2652,9 @@ onMounted(() => {
overflow-y: auto;
background-color: var(--color-bg-1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
padding-bottom: 80px; /* 为分页留出空间 */
margin-bottom: 0;
min-height: 300px;
max-height: calc(100vh - 300px);
}
/* 表头行样式 */
@ -2637,8 +2734,8 @@ onMounted(() => {
}
.folder-icon {
color: #165DFF;
background-color: #E8F3FF;
color: var(--color-primary);
background-color: var(--color-primary-light-1);
}
.file-name {
@ -2652,7 +2749,7 @@ onMounted(() => {
}
.table-data-row:hover .file-name {
color: #165DFF;
color: var(--color-primary);
}
.type-column, .size-column, .time-column {
@ -2697,7 +2794,7 @@ onMounted(() => {
white-space: nowrap;
.table-data-row:hover & {
color: #165DFF;
color: var(--color-primary);
}
}
@ -2830,6 +2927,35 @@ onMounted(() => {
}
}
/* 浏览器缩放调整 */
@media (max-height: 800px) {
.folder-content {
max-height: calc(100vh - 180px);
}
.file-content {
max-height: calc(100vh - 100px);
}
.file-grid-container {
max-height: calc(100vh - 280px);
}
}
@media (max-height: 600px) {
.folder-content {
max-height: calc(100vh - 160px);
}
.file-content {
max-height: calc(100vh - 80px);
}
.file-grid-container {
max-height: calc(100vh - 260px);
}
}
/* 空状态样式 */
.initial-state, .empty-state {
display: flex;
@ -2838,7 +2964,7 @@ onMounted(() => {
justify-content: center;
padding: 64px 0;
color: var(--color-text-3);
background-color: #fafafa;
background-color: var(--color-fill-1);
border-radius: 8px;
text-align: center;
}
@ -2852,7 +2978,7 @@ onMounted(() => {
:deep(.empty-state .arco-btn) {
margin-top: 16px;
padding: 8px 16px;
background-color: #165DFF;
background-color: var(--color-primary);
color: white;
border-radius: 4px;
border: none;
@ -2862,7 +2988,7 @@ onMounted(() => {
}
:deep(.empty-state .arco-btn:hover) {
background-color: #0E42D2;
background-color: var(--color-primary-dark-1);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@ -2984,50 +3110,7 @@ onMounted(() => {
border-top: 1px solid var(--color-border);
}
/* 侧边栏底部分页样式 */
.sidebar-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%);
border-top: 1px solid var(--color-border);
padding: 20px 16px;
z-index: 10;
backdrop-filter: blur(10px);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
.pagination-info {
margin-bottom: 16px;
text-align: center;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.8);
border-radius: 8px;
border: 1px solid var(--color-border);
}
.pagination-controls {
display: flex;
justify-content: center;
:deep(.arco-pagination) {
.arco-pagination-item {
border-radius: 6px;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
}
}
}
/* 确保文件夹内容区域不被底部分页遮挡 */
.folder-content {
/* 高度已调整无需额外padding */
}
/* 动画效果 */
:deep(.arco-icon-refresh.spin) {
@ -3158,8 +3241,8 @@ onMounted(() => {
border: 1px solid #e2e8f0;
&:hover {
background: #e2e8f0;
color: #165DFF;
background: var(--color-fill-2);
color: var(--color-primary);
}
}
@ -3168,8 +3251,8 @@ onMounted(() => {
transition: all 0.2s ease;
&:hover {
border-color: #165DFF;
color: #165DFF;
border-color: var(--color-primary);
color: var(--color-primary);
}
&:active {
@ -3278,7 +3361,7 @@ onMounted(() => {
border-radius: 8px;
border: 1px solid var(--color-border);
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
background: rgba(var(--color-bg-1-rgb), 0.9);
&:hover {
border-color: var(--color-primary-light-2);
@ -3296,7 +3379,7 @@ onMounted(() => {
.search-result-tip {
padding: 12px 16px;
margin: 12px 16px;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
background: linear-gradient(135deg, var(--color-primary-light-1) 0%, var(--color-primary-light-2) 100%);
border-radius: 8px;
border-left: 4px solid var(--color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
@ -3310,7 +3393,7 @@ onMounted(() => {
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
background: linear-gradient(45deg, transparent 30%, rgba(var(--color-bg-1-rgb), 0.1) 50%, transparent 70%);
animation: shimmer 2s infinite;
}
}
@ -3348,7 +3431,7 @@ onMounted(() => {
margin-top: 16px;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-bg-2);
background: var(--color-bg-1);
max-height: 300px;
overflow-y: auto;
}
@ -3464,19 +3547,74 @@ onMounted(() => {
}
}
/* 可排序表头样式 */
.sortable-header {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.2s ease;
user-select: none;
}
.sortable-header:hover {
background: var(--color-fill-2);
color: var(--color-primary);
}
.sort-indicator {
display: flex;
flex-direction: column;
gap: 1px;
margin-left: 4px;
}
.sort-arrow {
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
transition: all 0.2s ease;
}
.sort-arrow.up {
border-bottom: 3px solid var(--color-text-4);
}
.sort-arrow.down {
border-top: 3px solid var(--color-text-4);
}
.sort-arrow.active {
border-bottom-color: var(--color-primary);
border-top-color: var(--color-primary);
}
.sortable-header:hover .sort-arrow.up {
border-bottom-color: var(--color-primary);
}
.sortable-header:hover .sort-arrow.down {
border-top-color: var(--color-primary);
}
/* 文件分页样式 */
.file-pagination {
position: absolute;
position: sticky;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
margin-top: 0;
margin-top: 16px;
padding: 16px 0;
display: flex;
justify-content: center;
border-top: 1px solid var(--color-border);
background: var(--color-bg-1);
flex-shrink: 0;
z-index: 10;
.arco-pagination {
.arco-pagination-total {
@ -3516,7 +3654,7 @@ onMounted(() => {
/* 树形文件夹结构 */
.folder-tree-container {
padding: 8px;
background: var(--color-bg-2);
background: var(--color-bg-1);
border-radius: 6px;
margin: 8px;
overflow: hidden;

View File

@ -50,6 +50,11 @@
<a-descriptions-item label="备注">
{{ contractDetail.notes }}
</a-descriptions-item>
<a-descriptions-item label="合同内容">
<a-typography-paragraph :ellipsis="{ rows: 5, expandable: true, collapseText: '收起', suffix: '' }">
{{ contractDetail.contractText || '—' }}
</a-typography-paragraph>
</a-descriptions-item>
</a-descriptions>
</div>
<div v-else-if="!loading" class="empty-container">

View File

@ -7,16 +7,8 @@
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="projectId" label="项目">
<a-select v-model="contractData.projectId"
:options="projectOptions"
:loading="projectLoading"
:virtual-list-props="virtualListProps"
placeholder="请选择项目"
allow-search allow-clear
@focus="handleProjectFocus"
@dropdown-visible-change="handleProjectDropdown"
@search="handleProjectSearch" />
<a-form-item field="projectName" label="项目">
<a-input v-model="contractData.projectName" placeholder="请输入项目名称" allow-clear />
</a-form-item>
</a-col>
</a-row>
@ -43,11 +35,11 @@
<a-col :span="12">
<a-form-item field="contractStatus" label="合同状态">
<a-select v-model="contractData.contractStatus">
<a-option value="未确认">未确认</a-option>
<a-option value="待审批">待审批</a-option>
<a-option value="已签署">已签署</a-option>
<a-option value="未执行">未执行</a-option>
<a-option value="执行中">执行中</a-option>
<a-option value="已完成">已完成</a-option>
<a-option value="验收中">验收中</a-option>
<a-option value="结算中">结算中</a-option>
<a-option value="已结算">已结算</a-option>
<a-option value="已终止">已终止</a-option>
</a-select>
</a-form-item>
@ -99,6 +91,11 @@
</a-col>
</a-row>
<a-form-item field="productService" label="产品或服务">
<a-input v-model="contractData.productService" />
</a-form-item>
<a-form-item field="paymentAddress" label="付款地址">
<a-input v-model="contractData.paymentAddress" />
</a-form-item>

View File

@ -48,18 +48,23 @@
<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 || 0).toLocaleString() }}</span>
<!-- 结算金额支出合同 -->
<template #settlementAmount="{ record }">
<span class="font-medium text-blue-600">{{ (record.settlementAmount || record.receivedAmount || 0).toLocaleString() }}</span>
</template>
<template #action="{ record }">
<a-space>
<a-link @click="viewDetail(record)">查看</a-link>
<a-link @click="editRecord(record)">编辑</a-link>
<a-link :disabled="record.contractStatus === '已结算'" @click="!(record.contractStatus === '已结算') && openSettlement(record)">结算</a-link>
<a-link status="danger" @click="deleteContract(record)">删除</a-link>
</a-space>
</template>
<!-- 日期展示仅年月日放在 action 模板之外作为独立列插槽 -->
<template #signDate="{ record }">{{ (record.signDate || '').slice(0,10) }}</template>
<template #performanceDeadline="{ record }">{{ (record.performanceDeadline || '').slice(0,10) }}</template>
<template #paymentDate="{ record }">{{ (record.paymentDate || '').slice(0,10) }}</template>
</GiTable>
<!-- 合同详情弹窗 -->
@ -102,6 +107,15 @@
@update:contract-data="handleNewContractDataUpdate"
/>
</a-modal>
<!-- 合同结算弹窗 -->
<a-modal v-model:visible="showSettlementModal" title="合同结算" :width="520" @before-ok="submitSettlement">
<a-form :model="settlementForm" layout="vertical">
<a-form-item field="amount" label="结算金额"><a-input-number v-model="settlementForm.amount" style="width:100%" /></a-form-item>
<a-form-item field="paymentDate" label="付款日期"><a-date-picker v-model="settlementForm.paymentDate" show-time value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/></a-form-item>
<a-form-item field="paymentPeriod" label="账期"><a-date-picker v-model="settlementForm.paymentPeriod" show-time value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/></a-form-item>
<a-form-item field="notes" label="备注"><a-textarea v-model="settlementForm.notes" /></a-form-item>
</a-form>
</a-modal>
</GiPageLayout>
</template>
@ -173,11 +187,11 @@ const queryFormColumns = [
props: {
placeholder: '请选择合同状态',
options: [
{ label: '未确认', value: '未确认' },
{ label: '待审批', value: '待审批' },
{ label: '已签署', value: '已签署' },
{ label: '未执行', value: '未执行' },
{ label: '执行中', value: '执行中' },
{ label: '已完成', value: '已完成' },
{ label: '验收中', value: '验收中' },
{ label: '结算中', value: '结算中' },
{ label: '已结算', value: '已结算' },
{ label: '已终止', value: '已终止' },
],
},
@ -187,9 +201,8 @@ const queryFormColumns = [
label: '签署时间',
type: 'range-picker' as const,
props: {
placeholder: ['开始时间', '结束时间'],
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: ['开始日期', '结束日期'],
format: 'YYYY-MM-DD',
},
},
]
@ -200,11 +213,11 @@ const tableColumns: TableColumnData[] = [
{ 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: 'performanceDeadline', width: 120 },
{ title: '付款日期', dataIndex: 'paymentDate', width: 120 },
{ title: '已结算金额', dataIndex: 'settlementAmount', slotName: 'settlementAmount', width: 120 },
{ title: '未结算金额', dataIndex: 'pendingAmount', width: 120 },
{ title: '签署日期', dataIndex: 'signDate', slotName: 'signDate', width: 120 },
{ title: '履约期限', dataIndex: 'performanceDeadline', slotName: 'performanceDeadline', width: 120 },
{ title: '付款日期', dataIndex: 'paymentDate', slotName: 'paymentDate', width: 120 },
{ title: '合同状态', dataIndex: 'contractStatus', slotName: 'status', width: 100 },
{ title: '销售人员', dataIndex: 'salespersonName', width: 100 },
{ title: '销售部门', dataIndex: 'salespersonDeptName', width: 100 },
@ -252,10 +265,10 @@ const fetchContractList = async () => {
}
}
//
//
dataList.value = filtered.map((item: ContractItem) => ({
...item,
pendingAmount: (item.amount || 0) - (item.receivedAmount || 0),
pendingAmount: (item.amount || 0) - ((item.settlementAmount || item.receivedAmount || 0)),
}))
//
@ -284,11 +297,11 @@ const pagination = reactive({
//
const getStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
未确认: 'gray',
待审批: 'orange',
已签署: 'blue',
未执行: 'gray',
执行中: 'cyan',
已完成: 'green',
验收中: 'arcoblue',
结算中: 'orange',
已结算: 'green',
已终止: 'red',
}
return colorMap[status] || 'gray'
@ -381,7 +394,7 @@ const openAddModal = () => {
amount: 0,
accountNumber: '',
notes: '',
contractStatus: '未确认',
contractStatus: '未执行',
contractText: '',
projectName: '',
salespersonName: null,
@ -426,7 +439,8 @@ const handleAddSubmit = async () => {
paymentDate: newContractData.value.paymentDate || null,
performanceDeadline: newContractData.value.performanceDeadline || null,
productService: newContractData.value.productService || '',
projectId: newContractData.value.projectId || '',
// projectId
projectName: newContractData.value.projectName || '',
salespersonId: (newContractData.value as any).salespersonId || '',
signDate: newContractData.value.signDate || null,
type: newContractData.value.type || '支出合同',
@ -476,8 +490,8 @@ const editRecord = (record: ContractItem) => {
...record,
amount: record.amount || 0,
projectId: record.projectId || '',
type: record.type || '收入合同',
contractStatus: record.contractStatus || '未确认',
type: record.type || '支出合同',
contractStatus: record.contractStatus || '未执行',
}
selectedContractData.value = completeRecord
@ -525,7 +539,7 @@ const handleEditSubmit = async () => {
type: editedContractData.value.type || '',
};
console.log('Edited Contract Data:', requestData); // 便
// console.log('Edited Contract Data:', requestData); // 便
// /contract PUT
const response = await http.put('/contract', requestData);
@ -547,6 +561,91 @@ const handleEditSubmit = async () => {
}
}
//
const showSettlementModal = ref(false)
const settlementForm = reactive({
//
contractId: '',
amount: 0,
paymentDate: '',
paymentPeriod: '',
notes: '',
//
accountNumber: '',
code: '',
customer: '',
departmentId: '',
duration: '',
productService: '',
projectId: '',
salespersonId: '',
settlementId: '',
settlementStatus: '',
})
const settlementRecord = ref<ContractItem | null>(null)
const openSettlement = (record: ContractItem) => {
if (record.contractStatus === '已结算') return
settlementRecord.value = record
Object.assign(settlementForm, {
//
contractId: record.contractId,
amount: record.amount || 0,
paymentDate: record.paymentDate || '',
paymentPeriod: '',
notes: '',
// 便
accountNumber: (record as any).accountNumber || '',
code: (record as any).code || '',
customer: (record as any).customer || '',
departmentId: (record as any).departmentId || '',
duration: (record as any).duration || '',
productService: (record as any).productService || '',
projectId: (record as any).projectId || '',
salespersonId: (record as any).salespersonId || '',
settlementId: (record as any).settlementId || '',
settlementStatus: (record as any).settlementStatus || '',
})
showSettlementModal.value = true
}
const submitSettlement = async () => {
try {
const payload:any = {
accountNumber: settlementForm.accountNumber || '',
amount: settlementForm.amount,
code: settlementForm.code || '',
contractId: settlementForm.contractId,
customer: settlementForm.customer || '',
departmentId: settlementForm.departmentId || '',
duration: settlementForm.duration || '',
notes: settlementForm.notes || '',
paymentDate: settlementForm.paymentDate || null,
paymentPeriod: settlementForm.paymentPeriod || '',
productService: settlementForm.productService || '',
projectId: settlementForm.projectId || '',
salespersonId: settlementForm.salespersonId || '',
settlementId: settlementForm.settlementId || '',
settlementStatus: settlementForm.settlementStatus || '',
}
const res = await http.post('/contract-settlement', payload)
if (res.code === 200 || (res as any).status === 200) {
Message.success('合同结算成功')
// 0 0 => /
const origin = settlementRecord.value
const settledAmount = (origin?.settlementAmount ?? origin?.receivedAmount ?? 0) + (settlementForm.amount || 0)
const pendingAfter = Math.max((origin?.amount || 0) - settledAmount, 0)
const targetStatus = pendingAfter === 0 ? '已结算' : '结算中'
await http.put('/contract', { contractId: settlementForm.contractId, contractStatus: targetStatus, type: origin?.type || '支出合同' })
showSettlementModal.value = false
search()
return true
}
Message.error(res.msg || '合同结算失败')
return false
} catch (e:any) {
Message.error(e?.message || '合同结算失败')
return false
}
}
//
const deleteContract = (record: ContractItem) => {

View File

@ -50,6 +50,11 @@
<a-descriptions-item label="备注">
{{ contractDetail.notes }}
</a-descriptions-item>
<a-descriptions-item label="合同内容">
<a-typography-paragraph :ellipsis="{ rows: 5, expandable: true, collapseText: '收起', suffix: '' }">
{{ contractDetail.contractText || '—' }}
</a-typography-paragraph>
</a-descriptions-item>
</a-descriptions>
</div>
<div v-else-if="!loading" class="empty-container">

View File

@ -7,16 +7,8 @@
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="projectId" label="项目">
<a-select v-model="contractData.projectId"
:options="projectOptions"
:loading="projectLoading"
:virtual-list-props="virtualListProps"
placeholder="请选择项目"
allow-search allow-clear
@focus="handleProjectFocus"
@dropdown-visible-change="handleProjectDropdown"
@search="handleProjectSearch" />
<a-form-item field="projectName" label="项目">
<a-input v-model="contractData.projectName" placeholder="请输入项目名称" allow-clear />
</a-form-item>
</a-col>
</a-row>
@ -43,11 +35,11 @@
<a-col :span="12">
<a-form-item field="contractStatus" label="合同状态">
<a-select v-model="contractData.contractStatus">
<a-option value="未确认">未确认</a-option>
<a-option value="待审批">待审批</a-option>
<a-option value="已签署">已签署</a-option>
<a-option value="未执行">未执行</a-option>
<a-option value="执行中">执行中</a-option>
<a-option value="已完成">已完成</a-option>
<a-option value="验收中">验收中</a-option>
<a-option value="结算中">结算中</a-option>
<a-option value="已结算">已结算</a-option>
<a-option value="已终止">已终止</a-option>
</a-select>
</a-form-item>
@ -99,6 +91,11 @@
</a-col>
</a-row>
<a-form-item field="productService" label="产品或服务">
<a-input v-model="contractData.productService" />
</a-form-item>
<a-form-item field="paymentAddress" label="付款地址">
<a-input v-model="contractData.paymentAddress" />
</a-form-item>

View File

@ -48,18 +48,24 @@
<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 || 0).toLocaleString() }}</span>
<span class="font-medium text-blue-600">{{ (record.receivedAmount || record.settlementAmount || 0).toLocaleString() }}</span>
</template>
<template #action="{ record }">
<a-space>
<a-link @click="viewDetail(record)">查看</a-link>
<a-link @click="editRecord(record)">编辑</a-link>
<a-link :disabled="record.contractStatus === '已结算'" @click="!(record.contractStatus === '已结算') && openSettlement(record)">结算</a-link>
<a-link status="danger" @click="deleteContract(record)">删除</a-link>
</a-space>
</template>
<!-- 日期展示仅年月日放在 action 模板之外作为独立列插槽 -->
<template #signDate="{ record }">{{ (record.signDate || '').slice(0,10) }}</template>
<template #performanceDeadline="{ record }">{{ (record.performanceDeadline || '').slice(0,10) }}</template>
<template #paymentDate="{ record }">{{ (record.paymentDate || '').slice(0,10) }}</template>
</GiTable>
<!-- 合同详情弹窗 -->
@ -87,8 +93,6 @@
@update:contract-data="handleContractDataUpdate"
/>
</a-modal>
</GiPageLayout>
<!-- 新建合同弹窗 -->
<a-modal
v-model:visible="showAddModal"
@ -102,7 +106,18 @@
:contract-data="newContractData"
@update:contract-data="handleNewContractDataUpdate"
/>
</a-modal>
<!-- 合同结算弹窗 -->
<a-modal v-model:visible="showSettlementModal" title="合同结算" :width="520" @before-ok="submitSettlement">
<a-form :model="settlementForm" layout="vertical">
<a-form-item field="amount" label="结算金额"><a-input-number v-model="settlementForm.amount" style="width:100%" /></a-form-item>
<a-form-item field="paymentDate" label="付款日期"><a-date-picker v-model="settlementForm.paymentDate" show-time value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/></a-form-item>
<a-form-item field="paymentPeriod" label="账期"><a-date-picker v-model="settlementForm.paymentPeriod" show-time value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/></a-form-item>
<a-form-item field="notes" label="备注"><a-textarea v-model="settlementForm.notes" /></a-form-item>
</a-form>
</a-modal>
</GiPageLayout>
</template>
<script setup lang="ts">
@ -148,49 +163,33 @@ interface ContractItem {
//
const searchForm = reactive({
contractName: '',
contractCode: '',
client: '',
status: '',
signDate: '',
signDateRange: [] as [string, string] | [],
page: 1,
size: 10,
})
//
const queryFormColumns = [
{
field: 'contractName',
label: '合同名称',
type: 'input' as const,
props: {
placeholder: '请输入合同名称',
},
},
{
field: 'client',
label: '客户',
type: 'input' as const,
props: {
placeholder: '请输入客户名称',
},
},
{
field: 'status',
label: '合同状态',
type: 'select' as const,
props: {
{ field: 'client', label: '客户', type: 'input' as const, props: { placeholder: '请输入客户名称' } },
{ field: 'status', label: '合同状态', type: 'select' as const, props: {
placeholder: '请选择合同状态',
options: [
{ label: '未确认', value: '未确认' },
{ label: '待审批', value: '待审批' },
{ label: '已签署', value: '已签署' },
{ label: '未执行', value: '未执行' },
{ label: '执行中', value: '执行中' },
{ label: '已完成', value: '已完成' },
{ label: '验收中', value: '验收中' },
{ label: '结算中', value: '结算中' },
{ label: '已结算', value: '已结算' },
{ label: '已终止', value: '已终止' },
],
},
},
{ field: 'signDateRange', label: '签署时间', type: 'range-picker' as const, props: {
placeholder: ['开始日期', '结束日期'], format: 'YYYY-MM-DD',
}
},
]
//
@ -201,16 +200,105 @@ const tableColumns: TableColumnData[] = [
{ 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: 'performanceDeadline', width: 120 },
{ title: '付款日期', dataIndex: 'paymentDate', width: 120 },
{ title: '合同状态', dataIndex: 'contractStatus', slotName: 'status', width: 100 },
{ title: '签署日期', dataIndex: 'signDate', slotName: 'signDate', width: 120 },
{ title: '履约期限', dataIndex: 'performanceDeadline', slotName: 'performanceDeadline', width: 120 },
{ title: '付款日期', dataIndex: 'paymentDate', slotName: 'paymentDate', width: 120 },
{ title: '合同状态', dataIndex: 'contractStatus', slotName: 'status', width: 120 },
{ 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' },
{ title: '备注', dataIndex: 'notes', width: 220, ellipsis: true, tooltip: true },
{ title: '操作', slotName: 'action', width: 240, fixed: 'right' },
]
//
const showSettlementModal = ref(false)
const settlementForm = reactive({
//
contractId: '',
amount: 0,
paymentDate: '',
paymentPeriod: '',
notes: '',
//
accountNumber: '',
code: '',
customer: '',
departmentId: '',
duration: '',
productService: '',
projectId: '',
salespersonId: '',
settlementId: '',
settlementStatus: '',
})
const settlementRecord = ref<ContractItem | null>(null)
const openSettlement = (record: ContractItem) => {
if (record.contractStatus === '已结算') return
settlementRecord.value = record
Object.assign(settlementForm, {
//
contractId: record.contractId,
amount: record.amount || 0,
paymentDate: record.paymentDate || '',
paymentPeriod: '',
notes: '',
// 便
accountNumber: (record as any).accountNumber || '',
code: (record as any).code || '',
customer: (record as any).customer || '',
departmentId: (record as any).departmentId || '',
duration: (record as any).duration || '',
productService: (record as any).productService || '',
projectId: (record as any).projectId || '',
salespersonId: (record as any).salespersonId || '',
settlementId: (record as any).settlementId || '',
settlementStatus: (record as any).settlementStatus || '',
})
showSettlementModal.value = true
}
const submitSettlement = async () => {
try {
const payload:any = {
accountNumber: settlementForm.accountNumber || '',
amount: settlementForm.amount,
code: settlementForm.code || '',
contractId: settlementForm.contractId,
customer: settlementForm.customer || '',
departmentId: settlementForm.departmentId || '',
duration: settlementForm.duration || '',
notes: settlementForm.notes || '',
paymentDate: settlementForm.paymentDate || null,
paymentPeriod: settlementForm.paymentPeriod || '',
productService: settlementForm.productService || '',
projectId: settlementForm.projectId || '',
salespersonId: settlementForm.salespersonId || '',
settlementId: settlementForm.settlementId || '',
settlementStatus: settlementForm.settlementStatus || '',
}
const res = await http.post('/contract-settlement', payload)
if (res.code === 200 || (res as any).status === 200) {
Message.success('合同结算成功')
// 0 0 =>
const origin = settlementRecord.value
const settledAmount = (origin?.settlementAmount ?? origin?.receivedAmount ?? 0) + (settlementForm.amount || 0)
const pendingAfter = Math.max((origin?.amount || 0) - settledAmount, 0)
const targetStatus = pendingAfter === 0 ? '已结算' : '结算中'
await http.put('/contract', { contractId: settlementForm.contractId, contractStatus: targetStatus, type: origin?.type || '收入合同' })
showSettlementModal.value = false
search()
return true
}
Message.error(res.msg || '合同结算失败')
return false
} catch (e:any) {
Message.error(e?.message || '合同结算失败')
return false
}
}
//
const loading = ref(false)
const dataList = ref<ContractItem[]>([])
@ -222,27 +310,39 @@ const fetchContractList = async () => {
const params = {
page: searchForm.page,
pageSize: searchForm.size,
contractName: searchForm.contractName,
code: searchForm.contractCode,
customer: searchForm.client,
contractStatus: searchForm.status,
signDate: searchForm.signDate,
signDateStart: Array.isArray(searchForm.signDateRange) && searchForm.signDateRange.length === 2 ? searchForm.signDateRange[0] : undefined,
signDateEnd: Array.isArray(searchForm.signDateRange) && searchForm.signDateRange.length === 2 ? searchForm.signDateRange[1] : undefined,
}
const response = await http.get('/contract/list', params)
if (response.code === 200) {
// ""
const allContracts = response.rows || []
const revenueContracts = allContracts.filter((item: ContractItem) => item.type === '收入合同')
let revenueContracts = allContracts.filter((item: ContractItem) => item.type === '收入合同')
//
const range = Array.isArray(searchForm.signDateRange) && searchForm.signDateRange.length === 2 ? searchForm.signDateRange : null
if (range) {
const [start, end] = range
const s = new Date(start as any).getTime(), e = new Date(end as any).getTime()
if (!Number.isNaN(s) && !Number.isNaN(e)) {
revenueContracts = revenueContracts.filter((item: ContractItem) => {
if (!item.signDate) return false
const t = new Date(item.signDate as any).getTime()
return !Number.isNaN(t) && t >= s && t <= e
})
}
}
//
dataList.value = revenueContracts.map((item: ContractItem) => ({
...item,
pendingAmount: (item.amount || 0) - (item.receivedAmount || 0),
pendingAmount: (item.amount || 0) - ((item.receivedAmount || item.settlementAmount || 0)),
}))
pagination.total = Number.parseInt(response.total) || 0
pagination.total = dataList.value.length
} else {
Message.error(response.msg || '获取合同列表失败')
dataList.value = []
@ -267,11 +367,11 @@ const pagination = reactive({
//
const getStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
未确认: 'gray',
待审批: 'orange',
已签署: 'blue',
未执行: 'gray',
执行中: 'cyan',
已完成: 'green',
验收中: 'arcoblue',
结算中: 'orange',
已结算: 'green',
已终止: 'red',
}
return colorMap[status] || 'gray'
@ -290,11 +390,10 @@ const search = async () => {
const reset = () => {
Object.assign(searchForm, {
contractName: '',
contractCode: '',
client: '',
status: '',
signDate: '',
signDateRange: [],
page: 1,
size: 10,
})
@ -323,7 +422,7 @@ const newContractData = ref<ContractItem>({
contractId: '', customer: '', code: '', projectId: '', type: '收入合同',
productService: '', paymentDate: null, performanceDeadline: null,
paymentAddress: '', amount: 0, accountNumber: '', notes: '',
contractStatus: '未确认', contractText: '', projectName: '',
contractStatus: '未执行', contractText: '', projectName: '',
salespersonName: null, salespersonDeptName: '', settlementAmount: null,
receivedAmount: null, contractStatusLabel: null, createBy: null, updateBy: null,
createTime: '', updateTime: '', page: 1, pageSize: 10, signDate: '', duration: '',
@ -334,7 +433,7 @@ const openAddModal = () => {
contractId: '', customer: '', code: '', projectId: '', type: '收入合同',
productService: '', paymentDate: null, performanceDeadline: null,
paymentAddress: '', amount: 0, accountNumber: '', notes: '',
contractStatus: '未确认', contractText: '', projectName: '',
contractStatus: '未执行', contractText: '', projectName: '',
salespersonName: null, salespersonDeptName: '', settlementAmount: null,
receivedAmount: null, contractStatusLabel: null, createBy: null, updateBy: null,
createTime: '', updateTime: '', page: 1, pageSize: 10, signDate: '', duration: '',
@ -352,7 +451,7 @@ const handleAddSubmit = async () => {
accountNumber: newContractData.value.accountNumber || '',
amount: newContractData.value.amount || 0,
code: newContractData.value.code || '',
contractStatus: newContractData.value.contractStatus || '',
contractStatus: newContractData.value.contractStatus || '未执行',
contractText: newContractData.value.contractText || '',
customer: newContractData.value.customer || '',
departmentId: (newContractData.value as any).departmentId || '',
@ -362,7 +461,8 @@ const handleAddSubmit = async () => {
paymentDate: newContractData.value.paymentDate || null,
performanceDeadline: newContractData.value.performanceDeadline || null,
productService: newContractData.value.productService || '',
projectId: newContractData.value.projectId || '',
// projectId
projectName: newContractData.value.projectName || '',
salespersonId: (newContractData.value as any).salespersonId || '',
signDate: newContractData.value.signDate || null,
type: newContractData.value.type || '收入合同',
@ -395,7 +495,7 @@ const editRecord = (record: ContractItem) => {
amount: record.amount || 0,
projectId: record.projectId || '',
type: record.type || '收入合同',
contractStatus: record.contractStatus || '未确认',
contractStatus: record.contractStatus || '未执行',
}
selectedContractData.value = completeRecord
@ -422,7 +522,7 @@ const handleEditSubmit = async () => {
amount: editedContractData.value.amount || 0,
code: editedContractData.value.code || '',
contractId: editedContractData.value.contractId,
contractStatus: editedContractData.value.contractStatus || '',
contractStatus: editedContractData.value.contractStatus || '未执行',
contractText: editedContractData.value.contractText || '',
customer: editedContractData.value.customer || '',
departmentId: editedContractData.value.departmentId || '',

View File

@ -1,11 +1,163 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import type { TableColumnData } from '@arco-design/web-vue'
//
type OrderStatus = '待支付' | '进行中' | '已完成' | '已取消'
interface OrderItem { id: string; orderNo: string; customer: string; amount: number; status: OrderStatus; createTime: string }
const StatusOptions: { label: string; value: OrderStatus }[] = [
{ label: '待支付', value: '待支付' },
{ label: '进行中', value: '进行中' },
{ label: '已完成', value: '已完成' },
{ label: '已取消', value: '已取消' },
]
//
const allOrders = ref<OrderItem[]>([])
const stats = computed(() => ({
total: allOrders.value.length,
pending: allOrders.value.filter(o=>o.status==='待支付').length,
progress: allOrders.value.filter(o=>o.status==='进行中').length,
done: allOrders.value.filter(o=>o.status==='已完成').length,
cancel: allOrders.value.filter(o=>o.status==='已取消').length,
}))
//
const statCards = computed(() => [
{ key: 'total', title: '订单总数', value: stats.value.total, color: '#2f54eb', iconChar: 'T' },
{ key: 'pending', title: '待支付', value: stats.value.pending, color: '#faad14', iconChar: 'P' },
{ key: 'progress', title: '进行中', value: stats.value.progress, color: '#1677ff', iconChar: 'R' },
{ key: 'done', title: '已完成', value: stats.value.done, color: '#52c41a', iconChar: 'D' },
{ key: 'cancel', title: '已取消', value: stats.value.cancel, color: '#8c8c8c', iconChar: 'C' },
])
//
const searchForm = reactive({ orderNo: '', status: '' as ''|OrderStatus, timeRange: [] as [string,string]|[], page: 1, size: 10 })
const filtered = computed(() => {
let list = allOrders.value.slice()
if (searchForm.orderNo) list = list.filter(o=>o.orderNo.includes(searchForm.orderNo.trim()))
if (searchForm.status) list = list.filter(o=>o.status===searchForm.status)
if (Array.isArray(searchForm.timeRange) && searchForm.timeRange.length===2) {
const [s,e] = searchForm.timeRange; const S=+new Date(s as any), E=+new Date(e as any)
list = list.filter(o=>{ const t=+new Date(o.createTime as any); return t>=S && t<=E })
}
return list
})
const paged = computed(() => {
const start = (searchForm.page-1)*searchForm.size
return filtered.value.slice(start, start+searchForm.size)
})
const pagination = reactive({ current: 1, pageSize: 10, total: 0, showTotal: true, showPageSize: true })
//
const columns: TableColumnData[] = [
{ title: '订单编号', dataIndex: 'orderNo', width: 180 },
{ title: '客户', dataIndex: 'customer', width: 160 },
{ title: '金额', dataIndex: 'amount', width: 120, render: ({record}:any)=>`${(record.amount||0).toLocaleString()}` },
{ title: '状态', dataIndex: 'status', width: 100, slotName: 'status' },
{ title: '下单时间', dataIndex: 'createTime', width: 180 },
{ title: '操作', slotName: 'action', width: 140, fixed: 'right' },
]
//
const loading = ref(false)
const search = () => { pagination.total = filtered.value.length }
const reset = () => { Object.assign(searchForm, { orderNo: '', status: '', timeRange: [], page: 1, size: 10 }); pagination.current=1; pagination.pageSize=10; search() }
const onPageChange = (p:number)=>{ searchForm.page=p; pagination.current=p }
const onPageSizeChange=(s:number)=>{ searchForm.size=s; searchForm.page=1; pagination.pageSize=s; pagination.current=1; search() }
const getStatusColor = (s:OrderStatus)=>({ '待支付':'orange','进行中':'blue','已完成':'green','已取消':'gray' }[s])
//
const showAdd = ref(false)
const newOrder = reactive<{orderNo:string;customer:string;amount:number;status:OrderStatus;createTime:string}>(
{ orderNo:'', customer:'', amount:0, status:'待支付', createTime:'' }
)
const openAdd = ()=>{ Object.assign(newOrder,{ orderNo:'',customer:'',amount:0,status:'待支付',createTime:new Date().toISOString().slice(0,19).replace('T',' ') }); showAdd.value=true }
const submitAdd = ()=>{
const id = Math.random().toString(36).slice(2)
allOrders.value.unshift({ id, ...newOrder })
showAdd.value=false; search()
}
//
const genMock = (n=60)=>{
const pick=<T,>(arr:T[])=>arr[Math.floor(Math.random()*arr.length)]
const now=Date.now()
return Array.from({length:n}).map((_,i)=>{
const offset = Math.floor(Math.random()*60)*86400000
const dt = new Date(now-offset).toISOString().slice(0,19).replace('T',' ')
const st = pick<OrderStatus>(['待支付','进行中','已完成','已取消'])
return { id:`O${i+1}`, orderNo:`NO${100000+i}`, customer:`客户${(i%20)+1}`, amount: Math.floor(Math.random()*50000)+1000, status: st, createTime: dt }
})
}
onMounted(()=>{ allOrders.value = genMock(88); search() })
</script>
<template>
<GiPageLayout>
<!-- 顶部统计美化 + 居中 + 独立容器 -->
<a-row :gutter="12" class="mb-3 stats-row">
<a-col :span="24">
<div class="stats-wrap">
<a-row :gutter="12" justify="center">
<a-col v-for="card in statCards" :key="card.key" :xs="12" :sm="12" :md="6" :lg="4">
<a-card hoverable class="stat-card">
<div class="stat-inner">
<div class="stat-icon" :style="{ background: card.color }">
<span>{{ card.iconChar }}</span>
</div>
<div class="stat-value">{{ card.value }}</div>
<div class="stat-title">{{ card.title }}</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
</a-col>
</a-row>
<!-- 查询表单 + 新增按钮 -->
<GiForm :columns="[
{ field:'orderNo', label:'订单编号', type:'input', props:{placeholder:'支持模糊查询'} },
{ field:'status', label:'订单状态', type:'select', props:{ placeholder:'全部', options: StatusOptions } },
{ field:'timeRange', label:'订单时间', type:'range-picker', props:{ showTime:true, format:'YYYY-MM-DD HH:mm:ss' } },
]" v-model="searchForm" search size="medium" @search="search" @reset="reset">
<template #extra>
<a-button type="primary" @click="openAdd"><icon-plus/> 新增订单</a-button>
</template>
</GiForm>
<!-- 订单列表 -->
<GiTable :data="paged" :columns="columns" :loading="loading" :pagination="pagination"
@page-change="onPageChange" @page-size-change="onPageSizeChange" @refresh="search">
<template #status="{ record }"><a-tag :color="getStatusColor(record.status)">{{ record.status }}</a-tag></template>
<template #action="{ record }"><a-space>
<a-link @click="()=>{}">查看</a-link>
<a-link @click="()=>{}">编辑</a-link>
</a-space></template>
</GiTable>
<!-- 新增订单弹窗本地 -->
<a-modal v-model:visible="showAdd" title="新增订单" :width="520" @before-ok="submitAdd">
<a-form :model="newOrder" layout="vertical">
<a-form-item field="orderNo" label="订单编号"><a-input v-model="newOrder.orderNo" placeholder="NO123456"/></a-form-item>
<a-form-item field="customer" label="客户"><a-input v-model="newOrder.customer"/></a-form-item>
<a-form-item field="amount" label="金额"><a-input-number v-model="newOrder.amount" style="width:100%"/></a-form-item>
<a-form-item field="status" label="状态"><a-select v-model="newOrder.status" :options="StatusOptions"/></a-form-item>
<a-form-item field="createTime" label="下单时间"><a-date-picker v-model="newOrder.createTime" show-time style="width:100%"/></a-form-item>
</a-form>
</a-modal>
</GiPageLayout>
</template>
<style scoped lang="scss">
<style scoped>
.mb-3{ margin-bottom:12px; }
.stats-row{ }
.stats-wrap{ max-width: 1200px; margin: 0 auto; }
.stat-card{ border: 1px solid var(--color-border-2,#f0f0f0); background: linear-gradient(180deg,#ffffff 0%, #fafbff 100%); }
.stat-inner{ display:flex; flex-direction:column; align-items:center; justify-content:center; padding:18px 10px; text-align:center; }
.stat-icon{ width:44px; height:44px; border-radius:12px; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:700; margin-bottom:8px; box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
.stat-value{ font-size:28px; font-weight:700; color:#1d2129; line-height:1.2; }
.stat-title{ margin-top:6px; color:#86909c; }
</style>

View File

@ -237,6 +237,7 @@ const rules = {
//
watch(() => props.equipmentData, (newData) => {
if (newData) {
console.log('采购申请弹窗 - 接收到设备数据:', newData)
//
Object.assign(formData, {
equipmentId: newData.equipmentId || '', // ID
@ -254,6 +255,7 @@ watch(() => props.equipmentData, (newData) => {
technicalRequirements: '',
businessJustification: ''
})
console.log('采购申请弹窗 - 填充后的表单数据:', formData)
}
}, { immediate: true })
@ -291,10 +293,18 @@ const handleSubmit = async () => {
//
console.log('提交的表单数据:', submitData)
console.log('设备ID:', submitData.equipmentId)
console.log('设备名称:', submitData.equipmentName)
console.log('applyReason 值:', submitData.applyReason)
console.log('applyReason 类型:', typeof submitData.applyReason)
console.log('applyReason 长度:', submitData.applyReason?.length)
// ID
if (!submitData.equipmentId) {
Message.error('设备ID不能为空请检查设备数据')
return
}
await equipmentApprovalApi.submitProcurementApplication(submitData)
//

View File

@ -128,6 +128,17 @@
</a-tag>
</template>
<!-- 采购状态 -->
<template #procurementStatus="{ record }">
<a-tag
v-if="record.procurementStatus && record.procurementStatus !== 'NOT_STARTED'"
:color="getProcurementStatusColor(record.procurementStatus)"
>
{{ getProcurementStatusText(record.procurementStatus) }}
</a-tag>
<a-tag v-else color="gray">未开始</a-tag>
</template>
<!-- 位置状态 -->
<template #locationStatus="{ record }">
<a-tag :color="getLocationStatusColor(record.locationStatus)">
@ -175,7 +186,7 @@
<a-button type="text" size="small" @click="handleEdit(record)">
编辑
</a-button>
<!-- 申请采购按钮 -->
<!-- 申请采购按钮 - 只在特定状态下显示 -->
<a-button
v-if="canApplyProcurement(record)"
type="primary"
@ -184,12 +195,20 @@
>
申请采购
</a-button>
<!-- 显示采购状态 - 优先显示采购状态 -->
<a-tag
v-if="record.procurementStatus && record.procurementStatus !== 'NOT_STARTED'"
:color="getProcurementStatusColor(record.procurementStatus)"
>
{{ getProcurementStatusText(record.procurementStatus) }}
</a-tag>
<!-- 显示审批状态 -->
<a-tag v-if="record.approvalStatus" :color="getApprovalStatusColor(record.approvalStatus)">
{{ getApprovalStatusText(record.approvalStatus) }}
</a-tag>
<!-- 删除按钮 -->
<a-popconfirm
content="确定要删除这条采购记录吗?"
content="确定要删除这条记录吗?"
@ok="handleDelete(record)"
>
<a-button type="text" size="small" status="danger">
@ -345,6 +364,14 @@ const columns = [
slotName: 'equipmentStatus',
width: 120,
},
{
title: '采购状态',
dataIndex: 'procurementStatus',
key: 'procurementStatus',
slotName: 'procurementStatus',
width: 120,
fixed: false,
},
{
title: '位置状态',
dataIndex: 'locationStatus',
@ -403,6 +430,30 @@ const getEquipmentStatusText = (status: string) => {
return textMap[status] || '未知'
}
//
const getProcurementStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
NOT_STARTED: 'gray',
PENDING_APPROVAL: 'blue',
APPROVED: 'green',
REJECTED: 'red',
COMPLETED: 'purple',
}
return colorMap[status] || 'blue'
}
//
const getProcurementStatusText = (status: string) => {
const textMap: Record<string, string> = {
NOT_STARTED: '未开始',
PENDING_APPROVAL: '待审批',
APPROVED: '已通过',
REJECTED: '已拒绝',
COMPLETED: '已完成',
}
return textMap[status] || '未知'
}
//
const getLocationStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
@ -530,6 +581,7 @@ const transformBackendData = (data: any[]): EquipmentResp[] => {
totalPrice: item.totalPrice,
inventoryBasis: item.inventoryBasis,
dynamicRecord: item.dynamicRecord,
procurementStatus: item.procurementStatus,
}))
}
@ -572,6 +624,10 @@ const loadData = async (searchParams?: EquipmentListReq) => {
if (dataList.length > 0) {
const transformedData = transformBackendData(dataList)
console.log('转换后的数据:', transformedData)
//
transformedData.forEach((item, index) => {
console.log(`设备 ${index + 1} - 名称: ${item.equipmentName}, 采购状态: ${item.procurementStatus}`)
})
tableData.value = transformedData
} else {
tableData.value = []
@ -662,9 +718,15 @@ const handleModalSuccess = () => {
}
//
const handleApplicationSuccess = () => {
const handleApplicationSuccess = async () => {
applicationModalVisible.value = false
loadData(currentSearchParams.value)
console.log('采购申请成功,准备刷新数据...')
//
setTimeout(async () => {
console.log('开始刷新数据...')
await loadData(currentSearchParams.value)
message.success('采购申请已提交,请等待审批')
}, 1000)
}
//
@ -714,8 +776,12 @@ const getTotalAmount = () => {
//
const canApplyProcurement = (record: EquipmentResp) => {
//
return !record.approvalStatus || record.approvalStatus === 'PENDING_APPLICATION'
//
//
const allowedStatuses = ['NOT_STARTED', 'REJECTED', 'COMPLETED', null, undefined]
const canApply = allowedStatuses.includes(record.procurementStatus)
console.log(`设备 ${record.equipmentName} 采购状态: ${record.procurementStatus}, 可申请: ${canApply}`)
return canApply
}
//