From db42d47f5692ef64e5436c5a6d29dcb537b44596 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期一, 26 一月 2026 16:36:13 +0800
Subject: [PATCH] 浪潮对接单点登录:mis调整
---
src/views/oaSystem/projectManagement/projectDetail.vue | 565 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 565 insertions(+), 0 deletions(-)
diff --git a/src/views/oaSystem/projectManagement/projectDetail.vue b/src/views/oaSystem/projectManagement/projectDetail.vue
new file mode 100644
index 0000000..c3b0779
--- /dev/null
+++ b/src/views/oaSystem/projectManagement/projectDetail.vue
@@ -0,0 +1,565 @@
+// ... existing code ...
+<template>
+ <div class="app-container">
+ <!-- 椤圭洰鍩烘湰淇℃伅 -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="椤圭洰鍚嶇О">{{ projectInfo.projectName }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰璐熻矗浜�">{{ projectInfo.managerName }}</el-descriptions-item>
+ <el-descriptions-item label="寮�濮嬫棩鏈�">{{ projectInfo.startDate }}</el-descriptions-item>
+ <el-descriptions-item label="缁撴潫鏃ユ湡">{{ projectInfo.endDate }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰鐘舵��">
+ <el-tag :type="getStatusType(projectInfo.status)">{{ getStatusText(projectInfo.status) }}</el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹屾垚搴�">
+ <el-progress :percentage="projectInfo.completionRate" :stroke-width="6" />
+ </el-descriptions-item>
+ <el-descriptions-item label="椤圭洰鎻忚堪" :span="2">{{ projectInfo.description || '-' }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- 椤圭洰杩涘害姒傝 -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰杩涘害姒傝</span>
+ </div>
+ </template>
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">鎬讳綋杩涘害</div>
+ <div class="progress-number">{{ projectInfo.completionRate }}%</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">闃舵鎬绘暟</div>
+ <div class="progress-number">{{ statistics.totalPhases }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">浠诲姟鎬绘暟</div>
+ <div class="progress-number">{{ statistics.totalTasks }}</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="progress-item">
+ <div class="progress-title">宸插畬鎴愪换鍔�</div>
+ <div class="progress-number">{{ statistics.completedTasks }}</div>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 闃舵鍜屼换鍔$鐞� -->
+ <!-- <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰浠诲姟缁撴瀯</span>
+ <el-button type="primary" size="small" @click="handleAddPhase">娣诲姞闃舵</el-button>
+ </div>
+ </template>
+ <task-tree :project-id="projectId" @refresh="getProjectDetail" />
+ </el-card> -->
+
+ <!-- 閲岀▼纰戠鐞� -->
+ <el-card class="mb20">
+ <template #header>
+ <div class="card-header">
+ <span>椤圭洰闃舵閲岀▼纰�</span>
+ <el-button type="primary" size="small" @click="handleAddMilestone">娣诲姞閲岀▼纰�</el-button>
+ </div>
+ </template>
+ <milestone-list :project-id="projectId" @refresh="getProjectDetail" :key="`milestone-${refreshProjectId}`"/>
+ </el-card>
+
+ <!-- 闃舵鐩爣绠$悊 -->
+ <el-card>
+ <template #header>
+ <div class="card-header">
+ <span>闃舵浠诲姟</span>
+ <el-button type="primary" size="small" @click="handleAddPhaseGoal">娣诲姞闃舵鐩爣</el-button>
+ </div>
+ </template>
+ <phase-goal-list :project-id="projectId" @refresh="getProjectDetail" @editGoal="handleEditPhaseGoal" :key="`phaseGoal-${refreshProjectId}`"/>
+ </el-card>
+
+ <!-- 閲岀▼纰戠鐞嗗脊妗� -->
+ <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+ <el-form :model="form" ref="formRef" label-width="100px">
+ <el-form-item label="椤圭洰闃舵鍚嶇О" prop="phaseName">
+ <el-input
+ v-model="form.phaseName"
+ placeholder="璇疯緭鍏ラ」鐩樁娈靛悕绉�"
+ maxlength="50"
+ />
+ </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="form.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="form.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio label="notStarted">鏈紑濮�</el-radio>
+ <el-radio label="completed">宸插畬鎴�</el-radio>
+ <el-radio label="delayed">宸插欢杩�</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancel">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 闃舵浠诲姟绠$悊寮规 -->
+ <el-dialog :title="goalTitle" v-model="goalOpen" width="600px" append-to-body>
+ <el-form :model="goalForm" ref="goalFormRef" label-width="100px">
+ <el-form-item label="鎵�灞為樁娈�" prop="phaseId">
+ <el-select v-model="goalForm.phaseId" placeholder="璇烽�夋嫨鎵�灞為樁娈�">
+ <el-option
+ v-for="phase in phaseList"
+ :key="phase.phaseId"
+ :label="phase.phaseName"
+ :value="phase.phaseId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐩爣鍚嶇О" prop="taskName">
+ <el-input
+ v-model="goalForm.taskName"
+ placeholder="璇疯緭鍏ョ洰鏍囧悕绉�"
+ maxlength="50"
+ />
+ </el-form-item>
+ <el-form-item label="鐩爣鍊�" prop="targetValue">
+ <el-input-number
+ v-model="goalForm.targetValue"
+ :min="0"
+ :precision="2"
+ placeholder="璇疯緭鍏ョ洰鏍囧��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="褰撳墠鍊�" prop="currentValue">
+ <el-input-number
+ v-model="goalForm.currentValue"
+ :min="0"
+ :precision="2"
+ placeholder="璇疯緭鍏ュ綋鍓嶅��"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鍗曚綅" prop="unit">
+ <el-input
+ v-model="goalForm.unit"
+ placeholder="璇疯緭鍏ュ崟浣�"
+ maxlength="10"
+ />
+ </el-form-item>
+ <el-form-item label="浠诲姟瀹屾垚鏃ユ湡" prop="targetDate">
+ <el-date-picker
+ v-model="goalForm.targetDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="goalForm.startDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="goalForm.endDate"
+ type="date"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ placeholder="閫夋嫨鐩爣鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="goalForm.status" placeholder="璇烽�夋嫨鐘舵��">
+ <el-option label="鏈紑濮�" value="notStarted" />
+ <el-option label="杩涜涓�" value="inProgress" />
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="宸插欢杩�" value="delayed" />
+ </el-select>
+ </el-form-item>
+ <!-- <el-form-item label="瀹屾垚搴�" prop="completionRate">
+ <el-input-number
+ v-model="goalForm.completionRate"
+ :min="0"
+ :max="100"
+ :precision="2"
+ placeholder="璇疯緭鍏ュ畬鎴愬害"
+ style="width: 100%"
+ />
+ </el-form-item> -->
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="cancelGoal">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitGoalForm">纭畾</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import TaskTree from './components/taskTree.vue';
+import MilestoneList from './components/milestoneList.vue';
+import ProjectForm from './components/projectForm.vue';
+import PhaseGoalList from './components/phaseGoalList.vue';
+import { getProject, addProjectPhase, listProjectPhase, addProjectTask, updateProjectTask } from '@/api/oaSystem/projectManagement';
+
+const route = useRoute();
+const router = useRouter();
+const open = ref(false);
+const title = ref('');
+const projectFormRef = ref();
+const formRef = ref();
+// 椤圭洰ID
+// 鍦ㄥ叾浠杛ef瀹氫箟闄勮繎娣诲姞
+const refreshProjectId = ref(0);
+
+const projectId = ref(route.params.projectId);
+
+// 椤圭洰淇℃伅
+const projectInfo = reactive({
+ projectId: '',
+ projectName: '',
+ description: '',
+ startDate: '',
+ endDate: '',
+ managerId: '',
+ managerName: '',
+ status: 'planning',
+ completionRate: 0
+});
+
+// 缁熻淇℃伅
+const statistics = reactive({
+ totalPhases: 0,
+ totalTasks: 0,
+ completedTasks: 0
+});
+const form = reactive({
+ phaseId: '',
+ phaseName: '',
+ startDate: '',
+ endDate: '',
+ status: 'planning',
+ oaProjectId: projectId.value,
+})
+
+// 闃舵鐩爣鐩稿叧
+const goalOpen = ref(false);
+const goalTitle = ref('');
+const goalFormRef = ref();
+const phaseList = ref([]);
+const goalForm = reactive({
+ taskId: '',
+ phaseId: '',
+ taskName: '',
+ targetValue: 100,
+ currentValue: 0,
+ unit: '%',
+ targetDate: '',
+ startDate: '',
+ endDate: '',
+ status: 'notStarted',
+ completionRate: 0
+});
+
+// 鑾峰彇椤圭洰璇︽儏
+const getProjectDetail = async () => {
+ try {
+ getProject().then((res)=>{
+ console.log("椤圭洰璇︽儏",res)
+ const projectData = res.data[projectId.value];
+ // 鏇存柊椤圭洰淇℃伅
+ Object.assign(projectInfo, projectData);
+
+ // 鏇存柊缁熻淇℃伅
+ updateStatistics(projectData);
+
+ // 寮哄埗鏇存柊DOM浠ョ‘淇濆瓙缁勪欢鑳芥纭埛鏂�
+ // 杩欓噷閫氳繃瑙﹀彂refreshProjectId浜嬩欢鏉ュ己鍒跺埛鏂板瓙缁勪欢
+ refreshProjectId.value++;
+ })
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰璇︽儏澶辫触');
+ console.error('鑾峰彇椤圭洰璇︽儏澶辫触:', error);
+ }
+};
+
+// 鏇存柊缁熻淇℃伅
+const updateStatistics = (projectData) => {
+ // 杩欓噷鍋囪projectData涓寘鍚簡缁熻淇℃伅
+ // 濡傛灉娌℃湁锛岄渶瑕佸崟鐙姹傜粺璁℃暟鎹�
+ statistics.totalPhases = projectData.phases ? projectData.phases.length : 0;
+ statistics.totalTasks = projectData.tasks ? projectData.tasks.length : 0;
+ statistics.completedTasks = projectData.tasks ?
+ projectData.tasks.filter(task => task.status === 'completed').length : 0;
+};
+
+// 鑾峰彇椤圭洰闃舵鍒楄〃
+const getPhaseList = async () => {
+ try {
+ const { data } = await listProjectPhase(projectId.value);
+ phaseList.value = data.rows || data;
+ } catch (error) {
+ ElMessage.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触');
+ console.error('鑾峰彇椤圭洰闃舵鍒楄〃澶辫触:', error);
+ }
+};
+
+// 璁$畻瀹屾垚搴�
+const calculateCompletionRate = () => {
+ if (goalForm.targetValue > 0) {
+ goalForm.completionRate = Math.min(Math.round((goalForm.currentValue / goalForm.targetValue) * 100), 100);
+ } else {
+ goalForm.completionRate = 0;
+ }
+};
+
+// 娣诲姞闃舵
+const handleAddPhase = () => {
+ // resetForm();
+ ElMessage.info('娣诲姞闃舵鍔熻兘寰呭疄鐜�');
+};
+
+// 娣诲姞閲岀▼纰�
+const handleAddMilestone = () => {
+ resetForm();
+ open.value = true;
+ title.value = '鏂板椤圭洰闃舵';
+};
+
+// 娣诲姞闃舵浠诲姟
+const handleAddPhaseGoal = () => {
+ goalForm.taskId = '';
+ goalForm.phaseId = '';
+ goalForm.taskName = '';
+ goalForm.targetValue = 0;
+ goalForm.currentValue = 0;
+ goalForm.unit = '%';
+ goalForm.targetDate = '';
+ goalForm.startDate = '';
+ goalForm.endDate = '';
+ goalForm.status = 'notStarted';
+ goalForm.completionRate = 0;
+ if (goalFormRef.value) {
+ goalFormRef.value.resetFields();
+ }
+ getPhaseList();
+ goalTitle.value = '鏂板闃舵鐩爣';
+ goalOpen.value = true;
+};
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ try {
+ await formRef.value.validate();
+
+ if (form.phaseId) {
+ // await updateProject(form);
+ // ElMessage.success('淇敼椤圭洰闃舵鎴愬姛');
+ } else {
+ console.log("form",form);
+ await addProjectPhase(form);
+ ElMessage.success('鏂板椤圭洰闃舵鎴愬姛');
+ getProjectDetail();
+ }
+ open.value = false;
+ } catch (error) {
+ console.error('鎻愪氦琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 鎻愪氦闃舵浠诲姟琛ㄥ崟
+const submitGoalForm = async () => {
+ try {
+ await goalFormRef.value.validate();
+ calculateCompletionRate();
+
+ const goalData = {
+ ...goalForm,
+ oaProjectId: projectId.value
+ };
+
+ if (goalForm.taskId) {
+ await updateProjectTask(goalData);
+ ElMessage.success('淇敼闃舵鐩爣鎴愬姛');
+
+ } else {
+ await addProjectTask(goalData);
+ ElMessage.success('鏂板闃舵鐩爣鎴愬姛');
+
+ }
+ // 璋冪敤getProjectDetail鍒锋柊鎵�鏈夌浉鍏虫暟鎹�
+ getProjectDetail();
+ goalOpen.value = false;
+
+ } catch (error) {
+ console.error('鎻愪氦闃舵鐩爣琛ㄥ崟澶辫触:', error);
+ }
+};
+
+// 閲嶇疆閲岀▼纰戣〃鍗�
+const resetForm = () => {
+ form.phaseId = '';
+ form.phaseName = '';
+ form.startDate = '';
+ form.endDate = '';
+ form.status = 'planning';
+ form.oaProjectId = projectId.value;
+ if (formRef.value) {
+ formRef.value.resetFields();
+ }
+};
+
+// 鍙栨秷闃舵浠诲姟鎿嶄綔
+const cancelGoal = () => {
+ goalOpen.value = false;
+};
+
+// 鍙栨秷鎿嶄綔
+const cancel = () => {
+ open.value = false;
+};
+// 缂栬緫闃舵浠诲姟
+const handleEditPhaseGoal = async (goal) => {
+ // 澶嶅埗鐩爣鏁版嵁鍒拌〃鍗�
+ Object.assign(goalForm, goal);
+
+ // 鑾峰彇椤圭洰闃舵鍒楄〃
+ await getPhaseList();
+
+ // 鎵撳紑缂栬緫寮圭獥
+ goalTitle.value = '缂栬緫闃舵鐩爣';
+ goalOpen.value = true;
+};
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status) => {
+ const statusTypeMap = {
+ planning: 'info',
+ inProgress: 'primary',
+ completed: 'success',
+ paused: 'warning'
+ };
+ return statusTypeMap[status] || 'default';
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusTextMap = {
+ planning: '瑙勫垝涓�',
+ inProgress: '杩涜涓�',
+ completed: '宸插畬鎴�',
+ paused: '宸叉殏鍋�'
+ };
+ return statusTextMap[status] || status;
+};
+
+// 鐩戝惉璺敱鍙傛暟鍙樺寲
+watch(() => route.params.projectId, (newProjectId) => {
+ // console.log('璺敱鍙傛暟鍙樺寲:', projectId);
+ if (newProjectId) {
+ projectId.value = newProjectId;
+ getProjectDetail();
+ }
+});
+
+// 鐩戝惉褰撳墠鍊煎拰鐩爣鍊煎彉鍖栵紝閲嶆柊璁$畻瀹屾垚搴�
+watch(() => [goalForm.currentValue, goalForm.targetValue], () => {
+ calculateCompletionRate();
+});
+
+// 鍒濆鍖�
+onMounted(() => {
+ if (projectId.value) {
+ getProjectDetail();
+ }
+});
+</script>
+
+<style scoped>
+.app-container {
+ padding: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.progress-item {
+ text-align: center;
+ padding: 20px;
+ background-color: #f5f7fa;
+ border-radius: 8px;
+}
+
+.progress-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 10px;
+}
+
+.progress-number {
+ font-size: 24px;
+ font-weight: bold;
+ color: #409eff;
+}
+
+.mb20 {
+ margin-bottom: 20px;
+}
+</style>
--
Gitblit v1.9.3