feat(project): 添加日报进度分析建议功能
All checks were successful
Lint Code / Lint Code (push) Successful in 3m0s
All checks were successful
Lint Code / Lint Code (push) Successful in 3m0s
- 在项目详情页新增进度更新建议面板,展示AI分析的进度评估和具体建议 - 添加获取和应用日报建议的API接口及类型定义 - 支持批量选择和同意建议,自动更新项目状态 - 优化权限管理表格的树形选择配置,启用严格模式 - 更新.gitignore文件,排除.trae相关文件
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,3 +24,7 @@ tsconfig.tsbuildinfo
|
||||
#qoder
|
||||
|
||||
**.qoder**
|
||||
|
||||
#trae
|
||||
|
||||
**.trae**
|
||||
@@ -333,6 +333,64 @@ export const getProjectDetail = (projectId: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export type OverallProgressAssessment = {
|
||||
status?: string;
|
||||
deviationPercentage?: number;
|
||||
description?: string;
|
||||
keyIssues?: string[];
|
||||
};
|
||||
|
||||
export type DailyReportUpdateSuggestionVO = {
|
||||
suggestionId?: string;
|
||||
targetType?: string;
|
||||
targetId?: string;
|
||||
targetName?: string;
|
||||
currentStatus?: string;
|
||||
currentProgress?: number;
|
||||
suggestedStatus?: string;
|
||||
suggestedProgress?: number;
|
||||
reason?: string;
|
||||
confidence?: number;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type DailyReportAnalysisSuggestionsVO = {
|
||||
analysisId?: string;
|
||||
reportId?: string;
|
||||
projectId?: string;
|
||||
reportDate?: string;
|
||||
overallProgressAssessment?: OverallProgressAssessment;
|
||||
suggestions?: DailyReportUpdateSuggestionVO[];
|
||||
};
|
||||
|
||||
export const getDailyReportAnalysisSuggestions = (params: {
|
||||
projectId: string;
|
||||
reportId?: string;
|
||||
reportDate?: string;
|
||||
submitterUsername?: string;
|
||||
}) => {
|
||||
return http.request<Result<DailyReportAnalysisSuggestionsVO>>(
|
||||
"get",
|
||||
"/api/v1/daily-report/analysis/suggestions",
|
||||
{ params }
|
||||
);
|
||||
};
|
||||
|
||||
export type ApplyDailyReportSuggestionsRequest = {
|
||||
projectId: string;
|
||||
suggestionIds: string[];
|
||||
};
|
||||
|
||||
export const applyDailyReportAnalysisSuggestions = (
|
||||
data: ApplyDailyReportSuggestionsRequest
|
||||
) => {
|
||||
return http.request<Result<number>>(
|
||||
"post",
|
||||
"/api/v1/daily-report/analysis/suggestions/apply",
|
||||
{ data }
|
||||
);
|
||||
};
|
||||
|
||||
// ==================== 项目初始化(复用 system.ts 中的定义) ====================
|
||||
|
||||
/** 项目信息 */
|
||||
|
||||
295
src/api/日报分析建议.openapi.json
Normal file
295
src/api/日报分析建议.openapi.json
Normal file
@@ -0,0 +1,295 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "默认模块",
|
||||
"description": "",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/api/v1/daily-report/analysis/suggestions": {
|
||||
"get": {
|
||||
"summary": "获取日报进度更新建议",
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "projectId",
|
||||
"in": "query",
|
||||
"description": "项目ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reportId",
|
||||
"in": "query",
|
||||
"description": "日报ID",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reportDate",
|
||||
"in": "query",
|
||||
"description": "日报日期",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "submitterUsername",
|
||||
"in": "query",
|
||||
"description": "日报提交人用户名",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BaseResponseDailyReportAnalysisSuggestionsVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/v1/daily-report/analysis/suggestions/apply": {
|
||||
"post": {
|
||||
"summary": "应用日报进度回写建议",
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApplyDailyReportSuggestionsRequest",
|
||||
"description": "建议ID列表"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BaseResponseInteger",
|
||||
"description": "应用结果"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"OverallProgressAssessment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "进度状态:ahead-提前,on_track-正常,delayed-滞后"
|
||||
},
|
||||
"deviationPercentage": {
|
||||
"type": "number",
|
||||
"description": "进度偏差百分比 (正数表示提前,负数表示滞后)"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "评估说明"
|
||||
},
|
||||
"keyIssues": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "关键问题"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DailyReportUpdateSuggestionVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suggestionId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"targetType": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"targetId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"targetName": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"currentStatus": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"currentProgress": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"suggestedStatus": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"suggestedProgress": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"confidence": {
|
||||
"type": "number",
|
||||
"description": ""
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"DailyReportAnalysisSuggestionsVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"analysisId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"reportId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"projectId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"reportDate": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
},
|
||||
"overallProgressAssessment": {
|
||||
"$ref": "#/components/schemas/OverallProgressAssessment",
|
||||
"description": ""
|
||||
},
|
||||
"suggestions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DailyReportUpdateSuggestionVO",
|
||||
"description": "cn.yinlihupo.domain.vo.DailyReportUpdateSuggestionVO"
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"BaseResponseDailyReportAnalysisSuggestionsVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/DailyReportAnalysisSuggestionsVO",
|
||||
"description": ""
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"BaseResponseInteger": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"data": {
|
||||
"type": "integer",
|
||||
"description": ""
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApplyDailyReportSuggestionsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projectId": {
|
||||
"type": "integer",
|
||||
"description": "",
|
||||
"format": "int64"
|
||||
},
|
||||
"suggestionIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"required": ["projectId", "suggestionIds"]
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"servers": [],
|
||||
"security": []
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import { useRoute, useRouter } from "vue-router";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
getProjectDetail,
|
||||
getDailyReportAnalysisSuggestions,
|
||||
applyDailyReportAnalysisSuggestions,
|
||||
createTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
@@ -19,6 +21,8 @@ import {
|
||||
type ProjectTask,
|
||||
type ProjectResource,
|
||||
type ProjectRisk,
|
||||
type DailyReportAnalysisSuggestionsVO,
|
||||
type DailyReportUpdateSuggestionVO,
|
||||
type Resource,
|
||||
type ResourceUpdateRequest
|
||||
} from "@/api/project";
|
||||
@@ -88,6 +92,13 @@ const marginStyle = computed(() => ({
|
||||
// 项目详情数据
|
||||
const projectDetail = ref<ProjectDetail | null>(null);
|
||||
|
||||
const suggestionsLoading = ref(false);
|
||||
const suggestionsApplying = ref(false);
|
||||
const dailyReportSuggestions = ref<DailyReportAnalysisSuggestionsVO | null>(
|
||||
null
|
||||
);
|
||||
const selectedSuggestionIds = ref<string[]>([]);
|
||||
|
||||
// 成员详情模态框
|
||||
const memberDetailModal = ref(false);
|
||||
const selectedMember = ref<ProjectMember | null>(null);
|
||||
@@ -548,6 +559,140 @@ async function fetchProjectDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDailyReportSuggestions() {
|
||||
if (!projectId.value) return;
|
||||
suggestionsLoading.value = true;
|
||||
try {
|
||||
const res = await getDailyReportAnalysisSuggestions({
|
||||
projectId: projectId.value
|
||||
});
|
||||
const result = res as any;
|
||||
if (result.code === 200) {
|
||||
dailyReportSuggestions.value = result.data || null;
|
||||
selectedSuggestionIds.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取进度更新建议失败:", error);
|
||||
message("获取进度更新建议失败", { type: "error" });
|
||||
} finally {
|
||||
suggestionsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSuggestionSelectionChange(
|
||||
rows: DailyReportUpdateSuggestionVO[]
|
||||
) {
|
||||
selectedSuggestionIds.value = rows
|
||||
.map(r => String(r.suggestionId || ""))
|
||||
.filter(id => id.length > 0);
|
||||
}
|
||||
|
||||
function getOverallAssessmentType(
|
||||
status?: string
|
||||
): "success" | "warning" | "info" | "primary" | "danger" {
|
||||
switch (status) {
|
||||
case "ahead":
|
||||
return "success";
|
||||
case "on_track":
|
||||
return "primary";
|
||||
case "delayed":
|
||||
return "danger";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
}
|
||||
|
||||
function getOverallAssessmentText(status?: string) {
|
||||
switch (status) {
|
||||
case "ahead":
|
||||
return "提前";
|
||||
case "on_track":
|
||||
return "正常";
|
||||
case "delayed":
|
||||
return "滞后";
|
||||
default:
|
||||
return status || "未知";
|
||||
}
|
||||
}
|
||||
|
||||
function formatDeviation(deviationPercentage?: number) {
|
||||
if (deviationPercentage === undefined || deviationPercentage === null)
|
||||
return "--";
|
||||
const value = Number(deviationPercentage);
|
||||
if (!Number.isFinite(value)) return "--";
|
||||
const sign = value > 0 ? "+" : "";
|
||||
return `${sign}${value}%`;
|
||||
}
|
||||
|
||||
function isSuggestionApplied(status?: string) {
|
||||
const normalized = (status || "").toLowerCase();
|
||||
return (
|
||||
normalized === "applied" ||
|
||||
normalized === "accepted" ||
|
||||
normalized === "done" ||
|
||||
normalized === "completed"
|
||||
);
|
||||
}
|
||||
|
||||
function getSuggestionStatusType(
|
||||
status?: string
|
||||
): "success" | "warning" | "info" | "primary" | "danger" {
|
||||
if (isSuggestionApplied(status)) return "success";
|
||||
if (!status) return "info";
|
||||
const normalized = status.toLowerCase();
|
||||
if (normalized.includes("reject") || normalized.includes("fail"))
|
||||
return "danger";
|
||||
if (normalized.includes("pending") || normalized.includes("new"))
|
||||
return "warning";
|
||||
return "info";
|
||||
}
|
||||
|
||||
function getSuggestionStatusText(status?: string) {
|
||||
if (isSuggestionApplied(status)) return "已应用";
|
||||
if (!status) return "待处理";
|
||||
const normalized = status.toLowerCase();
|
||||
if (normalized.includes("reject")) return "已拒绝";
|
||||
if (normalized.includes("fail")) return "失败";
|
||||
if (normalized.includes("pending") || normalized.includes("new"))
|
||||
return "待处理";
|
||||
return status;
|
||||
}
|
||||
|
||||
async function handleApplySuggestions(
|
||||
ids: Array<string | number | undefined | null>
|
||||
) {
|
||||
if (!projectId.value) return;
|
||||
const suggestionIds = Array.from(
|
||||
new Set(ids.map(i => String(i || "")).filter(Boolean))
|
||||
);
|
||||
if (suggestionIds.length === 0) return;
|
||||
if (suggestionsApplying.value) return;
|
||||
suggestionsApplying.value = true;
|
||||
try {
|
||||
const res = await applyDailyReportAnalysisSuggestions({
|
||||
projectId: projectId.value,
|
||||
suggestionIds
|
||||
});
|
||||
const result = res as any;
|
||||
if (result.code === 200) {
|
||||
message("已应用进度更新建议", { type: "success" });
|
||||
await fetchProjectDetail();
|
||||
await fetchDailyReportSuggestions();
|
||||
} else {
|
||||
message(result.message || "应用建议失败", { type: "error" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("应用进度更新建议失败:", error);
|
||||
message("应用建议失败", { type: "error" });
|
||||
} finally {
|
||||
suggestionsApplying.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApplySelectedSuggestions() {
|
||||
await handleApplySuggestions(selectedSuggestionIds.value);
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
function getStatusText(status?: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
@@ -914,6 +1059,7 @@ async function handleDeleteResource(resourceId: string) {
|
||||
|
||||
onMounted(() => {
|
||||
fetchProjectDetail();
|
||||
fetchDailyReportSuggestions();
|
||||
fetchGanttData();
|
||||
});
|
||||
</script>
|
||||
@@ -1076,9 +1222,8 @@ onMounted(() => {
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 主要内容区 -->
|
||||
<el-row :gutter="16">
|
||||
<!-- 左侧:甘特图和物料清单 -->
|
||||
<!-- 左侧:甘特图 -->
|
||||
<el-col :xs="24" :lg="24">
|
||||
<!-- 甘特图 -->
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
@@ -1244,15 +1389,21 @@ onMounted(() => {
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 里程碑时间线 -->
|
||||
<div class="milestone-section">
|
||||
<div class="flex-bc mb-4">
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :xs="24" :lg="12" class="mb-4 lg:mb-0">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex-bc">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-icon :size="18" color="#f56c6c">
|
||||
<component :is="useRenderIcon('ri/flag-line')" />
|
||||
</el-icon>
|
||||
<span class="font-medium text-base">项目里程碑</span>
|
||||
<span class="font-medium">项目里程碑</span>
|
||||
<el-tag size="small" type="info"
|
||||
>{{ milestoneList.length }} 个</el-tag
|
||||
>
|
||||
@@ -1269,7 +1420,13 @@ onMounted(() => {
|
||||
新增里程碑
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="milestone-timeline">
|
||||
</template>
|
||||
|
||||
<el-empty
|
||||
v-if="milestoneList.length === 0"
|
||||
description="暂无里程碑"
|
||||
/>
|
||||
<div v-else class="milestone-timeline">
|
||||
<div
|
||||
v-for="(milestone, index) in sortedMilestones"
|
||||
:key="milestone.id"
|
||||
@@ -1331,13 +1488,14 @@ onMounted(() => {
|
||||
size="small"
|
||||
type="danger"
|
||||
effect="dark"
|
||||
>关键</el-tag
|
||||
>
|
||||
关键
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="milestone-date">
|
||||
<el-icon :size="12"
|
||||
><component :is="useRenderIcon('ri/calendar-line')"
|
||||
/></el-icon>
|
||||
<el-icon :size="12">
|
||||
<component :is="useRenderIcon('ri/calendar-line')" />
|
||||
</el-icon>
|
||||
<span>计划日期: {{ milestone.planDate }}</span>
|
||||
<span v-if="milestone.actualDate" class="actual-date">
|
||||
(实际: {{ milestone.actualDate }})
|
||||
@@ -1346,7 +1504,6 @@ onMounted(() => {
|
||||
<div v-if="milestone.description" class="milestone-desc">
|
||||
{{ milestone.description }}
|
||||
</div>
|
||||
<!-- 里程碑操作按钮 -->
|
||||
<div
|
||||
v-if="canUpdateMilestone || canDeleteMilestone"
|
||||
class="milestone-actions"
|
||||
@@ -1366,19 +1523,220 @@ onMounted(() => {
|
||||
@confirm="handleDeleteMilestone(milestone.id)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :lg="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex-bc">
|
||||
<div class="flex items-center gap-2">
|
||||
<component :is="useRenderIcon(RobotIcon)" />
|
||||
<span class="font-medium">进度更新建议</span>
|
||||
<el-tag
|
||||
v-if="dailyReportSuggestions?.suggestions?.length"
|
||||
size="small"
|
||||
type="info"
|
||||
>
|
||||
{{ dailyReportSuggestions.suggestions.length }} 条
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-popconfirm
|
||||
title="确认同意并应用所选建议吗?"
|
||||
@confirm="handleApplySelectedSuggestions"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="selectedSuggestionIds.length === 0"
|
||||
:loading="suggestionsApplying"
|
||||
>
|
||||
同意所选
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-button
|
||||
link
|
||||
:disabled="suggestionsLoading"
|
||||
@click="fetchDailyReportSuggestions"
|
||||
>
|
||||
<component :is="useRenderIcon(RefreshIcon)" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="suggestionsLoading">
|
||||
<el-empty
|
||||
v-if="!dailyReportSuggestions?.suggestions?.length"
|
||||
description="暂无进度更新建议"
|
||||
/>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="dailyReportSuggestions.overallProgressAssessment"
|
||||
class="mb-4"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="font-medium">总体评估</span>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="
|
||||
getOverallAssessmentType(
|
||||
dailyReportSuggestions.overallProgressAssessment
|
||||
.status
|
||||
)
|
||||
"
|
||||
>
|
||||
{{
|
||||
getOverallAssessmentText(
|
||||
dailyReportSuggestions.overallProgressAssessment
|
||||
.status
|
||||
)
|
||||
}}
|
||||
</el-tag>
|
||||
<span class="text-sm text-gray-500">
|
||||
偏差
|
||||
{{
|
||||
formatDeviation(
|
||||
dailyReportSuggestions.overallProgressAssessment
|
||||
.deviationPercentage
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
dailyReportSuggestions.overallProgressAssessment
|
||||
.description
|
||||
"
|
||||
class="text-sm text-gray-600"
|
||||
>
|
||||
{{
|
||||
dailyReportSuggestions.overallProgressAssessment
|
||||
.description
|
||||
}}
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
dailyReportSuggestions.overallProgressAssessment.keyIssues
|
||||
?.length
|
||||
"
|
||||
class="mt-2 flex flex-wrap gap-2"
|
||||
>
|
||||
<el-tag
|
||||
v-for="(issue, index) in dailyReportSuggestions
|
||||
.overallProgressAssessment.keyIssues"
|
||||
:key="index"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{ issue }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="dailyReportSuggestions.suggestions || []"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSuggestionSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" />
|
||||
<el-table-column label="目标" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div class="font-medium">
|
||||
{{ row.targetName || "--" }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">
|
||||
{{ row.targetType || "--" }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前" width="140">
|
||||
<template #default="{ row }">
|
||||
<div class="text-sm">{{ row.currentStatus || "--" }}</div>
|
||||
<div class="text-xs text-gray-400">
|
||||
{{ row.currentProgress ?? "--" }}%
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="建议" width="140">
|
||||
<template #default="{ row }">
|
||||
<div class="text-sm">
|
||||
{{ row.suggestedStatus || "--" }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">
|
||||
{{ row.suggestedProgress ?? "--" }}%
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="原因" min-width="260" prop="reason" />
|
||||
<el-table-column label="置信度" width="100">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
v-if="
|
||||
row.confidence !== undefined &&
|
||||
row.confidence !== null
|
||||
"
|
||||
>
|
||||
{{ Math.round(Number(row.confidence) * 100) }}%
|
||||
</span>
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="getSuggestionStatusType(row.status)"
|
||||
>
|
||||
{{ getSuggestionStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-popconfirm
|
||||
title="确认同意并应用该建议吗?"
|
||||
@confirm="handleApplySuggestions([row.suggestionId])"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="
|
||||
!row.suggestionId ||
|
||||
isSuggestionApplied(row.status)
|
||||
"
|
||||
:loading="suggestionsApplying"
|
||||
>
|
||||
同意
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 主要内容区 -->
|
||||
<el-row :gutter="16">
|
||||
<!-- 左侧:物料清单 -->
|
||||
<el-col :xs="24" :lg="24">
|
||||
<!-- 物料清单 -->
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
|
||||
@@ -137,7 +137,11 @@ const {
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:tree-props="{
|
||||
children: 'children',
|
||||
hasChildren: 'hasChildren',
|
||||
checkStrictly: true
|
||||
}"
|
||||
row-class-name="permission-table-row"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
|
||||
Reference in New Issue
Block a user