diff --git a/common/api/customer.js b/common/api/customer.js index 48e48cc..fe5f598 100644 --- a/common/api/customer.js +++ b/common/api/customer.js @@ -124,9 +124,11 @@ export const getCustomerFollowupList = (customerId) => { * @returns {Promise} 返回项目列表 */ export const getCustomerProjects = (customerId) => { - return uni.$uv.http.get(`bst/customer/projects`, { + return uni.$uv.http.get(`bst/project/list`, { params: { - customerId: customerId + customerId: customerId, + pageNum: 1, + pageSize: 30 }, custom: { auth: true diff --git a/pages/customer/detail/index.vue b/pages/customer/detail/index.vue index b72eb45..84790a9 100644 --- a/pages/customer/detail/index.vue +++ b/pages/customer/detail/index.vue @@ -126,30 +126,42 @@ {{ getProjectStatusText(project.status) }} - + {{ project.name }} - {{ project.progress }}% + {{ getProjectProgress(project) }}% - - {{ formatParticipants(project.participants) }} + + {{ getTaskCountText(project) }} - + + + + + {{ formatProjectMembers(project.memberList) }} + + 🕐 - {{ formatDeadline(project) }} + {{ formatProjectDeadline(project) }} @@ -377,9 +389,12 @@ const loadProjectList = async () => { projectList.value = res; } else if (res && res.rows && Array.isArray(res.rows)) { projectList.value = res.rows; + } else if (res && res.data && res.data.rows && Array.isArray(res.data.rows)) { + projectList.value = res.data.rows; } } catch (error) { console.error('加载项目列表失败:', error); + uni.$uv.toast('加载项目列表失败'); } }; @@ -459,8 +474,8 @@ const formatTimeOnly = (dateTime) => { // 获取项目状态样式类 const getProjectStatusClass = (status) => { return { - 'status-developing': status === 'developing' || status === '1', - 'status-expiring': status === 'expiring' || status === '2' + 'status-developing': status === 'developing' || status === '1' || status === 'IN_PROGRESS', + 'status-expiring': status === 'expiring' || status === '2' || status === 'EXPIRING_SOON' }; }; @@ -470,33 +485,98 @@ const getProjectStatusText = (status) => { 'developing': '开发中', 'expiring': '即将到期', '1': '开发中', - '2': '即将到期' + '2': '即将到期', + 'IN_PROGRESS': '开发中', + 'EXPIRING_SOON': '即将到期' }; return statusMap[status] || '未知'; }; -// 格式化参与人 -const formatParticipants = (participants) => { - if (!participants || !Array.isArray(participants)) return '--'; - if (participants.length <= 4) { - return participants.join('、'); +// 计算项目进度 +const getProjectProgress = (project) => { + if (!project) return 0; + // 如果有任务数据,根据任务完成情况计算 + if (project.taskCount && project.taskCount > 0) { + const completed = project.taskPassCount || 0; + const progress = Math.round((completed / project.taskCount) * 100); + return Math.min(100, Math.max(0, progress)); // 确保在0-100之间 } - const firstFour = participants.slice(0, 4).join('、'); - return `${firstFour}等${participants.length}人`; + // 如果有进度字段,使用进度字段 + if (project.progress !== undefined && project.progress !== null) { + return Math.min(100, Math.max(0, project.progress)); + } + // 默认返回0 + return 0; }; -// 格式化截止日期 -const formatDeadline = (project) => { - if (!project.deadline) return '--'; - const deadline = new Date(project.deadline); +// 获取任务计数文本 +const getTaskCountText = (project) => { + if (!project) return '0 / 0'; + const completed = project.taskPassCount || 0; + const total = project.taskCount || 0; + return `${completed} / ${total}`; +}; + +// 获取要显示的成员头像(最多4个) +const getDisplayMembers = (memberList) => { + if (!memberList || !Array.isArray(memberList)) return []; + return memberList.slice(0, 4); +}; + +// 格式化项目成员列表 +const formatProjectMembers = (memberList) => { + if (!memberList || !Array.isArray(memberList) || memberList.length === 0) { + return '暂无成员'; + } + + const names = memberList.map(member => member.userName || member.name).filter(Boolean); + + if (names.length <= 4) { + return names.join('、'); + } + + const firstFour = names.slice(0, 4).join('、'); + return `${firstFour}等${names.length}人`; +}; + +// 判断项目是否逾期 +const isProjectOverdue = (project) => { + if (!project) return false; + // 优先使用 devOverdue 字段 + if (project.devOverdue !== undefined && project.devOverdue !== null) { + return project.devOverdue; + } + // 否则根据日期判断 + const deadline = project.expireTime || project.expectedCompleteDate; + if (!deadline) return false; + const deadlineDate = new Date(deadline); const now = new Date(); - const diffTime = deadline - now; + return deadlineDate < now; +}; + +// 格式化项目截止日期 +const formatProjectDeadline = (project) => { + if (!project) return '--'; + + // 优先使用 expireTime,其次使用 expectedCompleteDate + const deadline = project.expireTime || project.expectedCompleteDate; + if (!deadline) return '--'; + + const deadlineDate = new Date(deadline); + const now = new Date(); + const diffTime = deadlineDate - now; const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + // 格式化日期显示 + const year = deadlineDate.getFullYear(); + const month = String(deadlineDate.getMonth() + 1).padStart(2, '0'); + const day = String(deadlineDate.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + if (diffDays < 0) { - return `逾期${Math.abs(diffDays)}天 ${formatDateTime(project.deadline)}`; + return `逾期${Math.abs(diffDays)}天 ${dateStr}`; } else { - return `剩余${diffDays}天 ${formatDateTime(project.deadline)}`; + return `剩余${diffDays}天 ${dateStr}`; } }; @@ -867,6 +947,10 @@ onMounted(() => { } // 项目列表样式 +.project-list { + padding-bottom: 16px; +} + .project-card { background-color: #fff; border-radius: 8px; @@ -878,30 +962,32 @@ onMounted(() => { .project-header { display: flex; justify-content: space-between; - align-items: center; + align-items: flex-start; margin-bottom: 12px; } .project-status-tag { - padding: 4px 12px; + padding: 4px 10px; border-radius: 4px; font-size: 12px; + font-weight: 500; &.status-developing { - background-color: #e3f2fd; - color: #1976d2; + background-color: #1976d2; + color: #fff; } &.status-expiring { - background-color: #fff3e0; - color: #f57c00; + background-color: #ff9800; + color: #fff; } } .project-more { - font-size: 18px; + font-size: 20px; color: #999; - transform: rotate(90deg); + line-height: 1; + padding: 4px; } .project-name { @@ -910,12 +996,13 @@ onMounted(() => { color: #333; margin-bottom: 12px; display: block; + line-height: 1.4; } .project-progress { display: flex; align-items: center; - margin-bottom: 12px; + margin-bottom: 8px; } .progress-bar { @@ -938,36 +1025,70 @@ onMounted(() => { font-size: 12px; color: #1976d2; font-weight: 600; + min-width: 40px; + text-align: right; } -.project-participants { - margin-bottom: 8px; +.project-task-count { + margin-bottom: 10px; } -.participants-text { - font-size: 12px; +.task-count-text { + font-size: 13px; color: #666; } +.project-members { + display: flex; + align-items: center; + margin-bottom: 10px; + gap: 8px; +} + +.member-avatars { + display: flex; +} + +.member-avatar { + width: 24px; + height: 24px; + border-radius: 12px; + border: 2px solid #fff; + margin-left: -8px; + + &:first-child { + margin-left: 0; + } +} + +.members-text { + font-size: 13px; + color: #666; + flex: 1; +} + .project-deadline { display: flex; align-items: center; - - &.overdue { - .deadline-text { - color: #f44336; - } - } + gap: 4px; } .deadline-icon { - font-size: 14px; - margin-right: 4px; + font-size: 12px; + color: #999; } .deadline-text { font-size: 12px; - color: #666; + color: #1976d2; + + .overdue & { + color: #f44336; + } +} + +.project-deadline.overdue .deadline-text { + color: #f44336; } // 客户信息样式