This commit is contained in:
2026-07-01 17:52:18 +08:00
5 changed files with 69 additions and 47 deletions

View File

@@ -3,20 +3,24 @@
<div class="current-project-bar">当前项目{{ currentProjectName }}</div>
<div class="rm-panel">
<div class="rm-panel-header">
<span>🛠 安装前准备</span>
<span>安装前准备</span>
</div>
<div class="rm-panel-body">
<el-tabs v-model="activeTab" @tab-click="onTabClick">
<el-tab-pane label="🔧 工具准备" name="tools">
<el-tab-pane name="tools">
<span slot="label"><i class="el-icon-s-tools"></i> 工具准备</span>
<install-tools ref="toolsTab" :project-id="currentProjectId" />
</el-tab-pane>
<el-tab-pane label="👷 安装人员" name="personnel">
<el-tab-pane name="personnel">
<span slot="label"><i class="el-icon-user-solid"></i> 安装人员</span>
<install-personnel ref="personnelTab" :project-id="currentProjectId" />
</el-tab-pane>
<el-tab-pane label="📏 安装精度" name="precision">
<el-tab-pane name="precision">
<span slot="label"><i class="el-icon-data-line"></i> 安装精度</span>
<install-precision ref="precisionTab" :project-id="currentProjectId" />
</el-tab-pane>
<el-tab-pane label="📊 安装进度" name="progress">
<el-tab-pane name="progress">
<span slot="label"><i class="el-icon-timer"></i> 安装进度</span>
<install-progress ref="progressTab" :project-id="currentProjectId" />
</el-tab-pane>
</el-tabs>

View File

@@ -15,7 +15,7 @@
<div v-if="precision.length === 0" style="text-align:center;color:#aaa;padding:40px;">暂无精度数据</div>
<div v-for="(grp, sys) in grouped" :key="sys" class="cat-section">
<div class="cat-header">
<span>{{ sysIcon(sys) }} {{ sys }}</span>
<span><i :class="sysIcon(sys)" style="margin-right:4px;"></i>{{ sys }}</span>
<span class="cat-count">{{ grp.length }} · 合格 {{ grp.filter(p=>p.isQualified==='1').length }}/{{ grp.length }}</span>
</div>
<el-table :data="grp" stripe border size="small" style="width:100%;">
@@ -175,7 +175,7 @@ export default {
}
},
methods: {
sysIcon(s) { return { '轧辊系统':'🔄','AGC系统':'📐','主机框架':'🏛️','液压系统':'💧','电气系统':'','辅助设备':'🔩','冷却润滑':'❄️','安全装置':'🛡️' }[s]||'📌' },
sysIcon(s) { return { '轧辊系统':'el-icon-s-operation','AGC系统':'el-icon-s-marketing','主机框架':'el-icon-s-home','液压系统':'el-icon-water-cup','电气系统':'el-icon-s-platform','辅助设备':'el-icon-s-tools','冷却润滑':'el-icon-cold-drink','安全装置':'el-icon-s-check' }[s]||'el-icon-collection' },
cleanForm() { return { systemName:'轧辊系统', itemName:'', nameEn:'', targetValue:'', unit:'mm', importance:'★★★', tool:'', methodDesc:'', standardRef:'', actualValue:'', _qualified:false, photos:'', _files:[] } },
loadData() {
if (!this.projectId) return

View File

@@ -12,13 +12,13 @@
</div>
<!-- Bar chart -->
<div style="font-weight:600;font-size:13px;margin-bottom:8px;">📊 安装进度条形图</div>
<div style="font-weight:600;font-size:13px;margin-bottom:8px;"><i class="el-icon-data-line" style="margin-right:4px;"></i>安装进度条形图</div>
<div v-if="progress.length === 0" style="text-align:center;color:#aaa;padding:20px;">暂无安装进度数据</div>
<div v-for="(p,i) in progress" :key="i" style="margin-bottom:8px;">
<div style="display:flex;justify-content:space-between;font-size:11px;margin-bottom:3px;">
<span style="font-weight:600;">
{{ p.itemName }}
<el-tag v-if="isOverdue(p)" size="mini" type="danger" style="margin-left:4px;">逾期</el-tag>
<el-tag v-if="isOverdue(p)" size="mini" type="danger" style="margin-left:4px;"><i class="el-icon-warning-outline" style="margin-right:2px;"></i>逾期</el-tag>
</span>
<span style="color:#6c757d;">{{ p.planStart||'?' }}~{{ p.planEnd||'?' }} · {{ statusLabel(p) }} · {{ barPercent(p) }}%</span>
</div>
@@ -29,14 +29,14 @@
实际: {{ p.actualStart||'-' }}~{{ p.actualEnd||'-' }}
</div>
<div v-if="isOverdue(p) && p.delayReason" style="font-size:10px;color:#e74c3c;margin-top:1px;">
延误原因{{ p.delayReason }}
<i class="el-icon-warning-outline" style="margin-right:2px;"></i>延误原因{{ p.delayReason }}
</div>
</div>
<el-divider></el-divider>
<!-- Detail table -->
<div style="font-weight:600;font-size:13px;margin-bottom:8px;">📋 安装进度明细</div>
<div style="font-weight:600;font-size:13px;margin-bottom:8px;"><i class="el-icon-tickets" style="margin-right:4px;"></i>安装进度明细</div>
<el-table :data="progress" v-loading="loading" stripe border size="small" style="width:100%;">
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="itemName" label="安装项目" min-width="130" show-overflow-tooltip />
@@ -62,10 +62,10 @@
<span v-else style="color:#bbb;">0</span>
</template>
</el-table-column>
<el-table-column label="🎬视频" width="55" align="center">
<el-table-column label="视频" width="55" align="center">
<template slot-scope="s">
<el-tag v-if="s.row.videos" size="mini" type="primary">🎬 {{ (s.row.videos||'').split(';').filter(Boolean).length }}</el-tag>
<span v-else style="color:#bbb;">🎬 0</span>
<el-tag v-if="s.row.videos" size="mini" type="primary"><i class="el-icon-video-camera" style="margin-right:2px;"></i> {{ (s.row.videos||'').split(';').filter(Boolean).length }}</el-tag>
<span v-else style="color:#bbb;"><i class="el-icon-video-camera"></i> 0</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" fixed="right">
@@ -114,7 +114,7 @@
</div>
</div>
</div>
<div class="form-group"><label>🎬 视频文件名;分隔</label><el-input v-model="form.videos" /></div>
<div class="form-group"><label><i class="el-icon-video-camera" style="margin-right:2px;"></i>视频文件名;分隔</label><el-input v-model="form.videos" /></div>
</div>
<div class="form-group"><label>备注</label><el-input v-model="form.remark" /></div>
</el-form>

View File

@@ -15,7 +15,7 @@
<div v-if="tools.length === 0" style="text-align:center;color:#aaa;padding:40px;">暂无工具数据</div>
<div v-for="(grp, cat) in grouped" :key="cat" class="cat-section">
<div class="cat-header">
<span>{{ catIcon(cat) }} {{ cat }}</span>
<span><i :class="catIcon(cat)" style="margin-right:4px;"></i>{{ cat }}</span>
<span class="cat-count">{{ grp.length }} · ¥{{ groupTotal(grp).toLocaleString() }}</span>
</div>
<el-table :data="grp" stripe border size="small" style="width:100%;">
@@ -113,7 +113,7 @@ export default {
arrivedCount() { return this.tools.filter(t => t.status==='已到位').length }
},
methods: {
catIcon(cat) { return { '起重吊装':'🏗️','测量仪器':'📐','机械安装':'🔩','液压专用':'💧','电气安装':'' }[cat]||'📌' },
catIcon(cat) { return { '起重吊装':'el-icon-ship','测量仪器':'el-icon-rank','机械安装':'el-icon-setting','液压专用':'el-icon-water-cup','电气安装':'el-icon-s-platform' }[cat]||'el-icon-collection' },
groupTotal(arr) { return arr.reduce((s,t) => s+(parseFloat(t.totalPrice)||0), 0) },
cleanForm() { return { name:'', nameEn:'', spec:'', qty:'', unit:'台', unitPrice:'', totalPrice:'', priority:'', arrivalDate:'', purpose:'', responsible:'', status:'待确认', category:'起重吊装', remark:'' } },
loadData() {

View File

@@ -73,7 +73,7 @@
<div class="progress-steps">
<template v-for="(stage, index) in stages">
<div :key="stage.key" :class="['progress-step', stageClass(stage.key)]">
<div class="circle">{{ stageIcon(stage.key) }}</div>
<div class="circle"><i :class="stage.iconClass" /></div>
<div class="step-label">{{ stage.label }}</div>
</div>
<div :key="'line-'+index" :class="['progress-line', stageLineClass(stage.key)]" v-if="index < stages.length - 1" />
@@ -85,11 +85,13 @@
<div style="font-size:12px;font-weight:600;margin-bottom:8px;"> 各阶段概览</div>
<div class="dashboard-grid">
<div v-for="stage in stages" :key="stage.key" :class="['stat-card', getStageInfo(stage.key).locked ? 'stage-locked' : '']" style="cursor:pointer;" @click="switchStage(stage.key)">
<div class="label">
{{ stage.icon }} {{ stage.label }}
<div class="label" :style="{ borderLeftColor: stage.color }">
<i :class="stage.iconClass" :style="{ color: stage.color }"></i>
{{ stage.label }}
<el-tag v-if="getStageInfo(stage.key).confirmed && getStageInfo(stage.key).confirmType === 'override'" size="mini" type="warning" style="margin-left:4px;">已放行</el-tag>
<el-tag v-else-if="getStageInfo(stage.key).confirmed" size="mini" type="success" style="margin-left:4px;">已确认</el-tag>
<el-tag v-if="getStageInfo(stage.key).locked" size="mini" type="info" style="margin-left:4px;">锁定</el-tag>
<el-tag v-if="taskCount(stage.key) > 0" size="mini" type="primary" style="margin-left:4px;">任务{{ taskCount(stage.key) }}</el-tag>
</div>
<div class="value" style="font-size:14px;">{{ stageStatusText(stage.key) }}</div>
<div class="sub">
@@ -169,23 +171,24 @@ import { selectContractByProjectId } from '@/api/oa/oaContract'
import { getStageStatus } from '@/api/rm/dashboard'
import { getProject as getRmProject } from '@/api/rm/project'
import { confirmStage, overrideStage } from '@/api/rm/stageConfirm'
import { taskCountByStage } from '@/api/oa/task'
const OVERRIDE_STAGES = ['procurement', 'manufacturing']
const STAGES = [
{ key: 'budget', label: '项目预算', icon: '💰' },
{ key: 'tech_plan', label: '技术方案', icon: '📋' },
{ key: 'layout', label: '布局图', icon: '🗺️' },
{ key: 'tech_review', label: '技术审查', icon: '🔍' },
{ key: 'drawing_design', label: '图纸设计', icon: '📐' },
{ key: 'drawing_review', label: '图纸审查', icon: '✏️' },
{ key: 'procurement', label: '采购管理', icon: '🛒' },
{ key: 'manufacturing', label: '制造进度', icon: '🏭' },
{ key: 'manuals', label: '说明书', icon: '📖' },
{ key: 'install_prep', label: '安装准备', icon: '🛠️' },
{ key: 'install_feedback', label: '问题反馈', icon: '💬' },
{ key: 'acceptance', label: '安装验收', icon: '' },
{ key: 'hot_commissioning', label: '热负荷试车', icon: '🔥' }
{ key: 'budget', label: '项目预算', iconClass: 'el-icon-coin', color: '#2176ae' },
{ key: 'tech_plan', label: '技术方案', iconClass: 'el-icon-document', color: '#1abc9c' },
{ key: 'layout', label: '布局图', iconClass: 'el-icon-map-location', color: '#3498db' },
{ key: 'tech_review', label: '技术审查', iconClass: 'el-icon-search', color: '#2980b9' },
{ key: 'drawing_design', label: '图纸设计', iconClass: 'el-icon-edit-outline', color: '#16a085' },
{ key: 'drawing_review', label: '图纸审查', iconClass: 'el-icon-view', color: '#27ae60' },
{ key: 'procurement', label: '采购管理', iconClass: 'el-icon-s-goods', color: '#e67e22' },
{ key: 'manufacturing', label: '制造进度', iconClass: 'el-icon-setting', color: '#d35400' },
{ key: 'manuals', label: '说明书', iconClass: 'el-icon-notebook-1', color: '#9b59b6' },
{ key: 'install_prep', label: '安装准备', iconClass: 'el-icon-s-tools', color: '#e74c3c' },
{ key: 'install_feedback', label: '问题反馈', iconClass: 'el-icon-chat-line-square', color: '#f39c12' },
{ key: 'acceptance', label: '安装验收', iconClass: 'el-icon-circle-check', color: '#2ecc71' },
{ key: 'hot_commissioning', label: '热负荷试车', iconClass: 'el-icon-s-promotion', color: '#e74c3c' }
]
export default {
@@ -207,7 +210,8 @@ export default {
currentProjectId: null,
projectList: [],
isCreating: false,
overrideStages: OVERRIDE_STAGES
overrideStages: OVERRIDE_STAGES,
taskCountMap: {}
}
},
computed: {
@@ -307,6 +311,13 @@ export default {
getStageStatus(this.currentProjectId).then(res => {
this.stageStatus = res.data || {}
})
taskCountByStage(this.currentProjectId).then(res => {
const map = {}
;(res.data || []).forEach(item => {
map[item.stageCode] = item.taskCount
})
this.taskCountMap = map
})
},
createProject() {
this.isCreating = true
@@ -369,10 +380,6 @@ export default {
const st = this.stageStatus[key]
return st && st.status === 'done' ? 'done' : ''
},
stageIcon(key) {
const st = this.stageStatus[key]
return st && st.status === 'done' ? '✓' : (STAGES.findIndex(s => s.key === key) + 1)
},
stageStatusText(key) {
const st = this.stageStatus[key]
const map = { done: '已完成', progress: '进行中', pending: '未开始' }
@@ -436,6 +443,9 @@ export default {
},
getStageInfo(key) {
return this.stageStatus[key] || {}
},
taskCount(key) {
return this.taskCountMap[key] || 0
}
}
}
@@ -468,7 +478,13 @@ export default {
font-size: 11px;
color: #666;
margin-bottom: 4px;
border-left: 3px solid transparent;
padding-left: 8px;
display: flex;
align-items: center;
gap: 4px;
}
.stat-card .label i { font-size: 14px; }
.stat-card .value {
font-size: 20px;
font-weight: 700;
@@ -514,21 +530,21 @@ export default {
position: relative;
}
.progress-step .circle {
width: 24px;
height: 24px;
width: 28px;
height: 28px;
border-radius: 50%;
background: #ddd;
color: #fff;
background: #e8eaed;
color: #aaa;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 600;
font-size: 14px;
flex-shrink: 0;
z-index: 1;
transition: all 0.2s;
}
.progress-step.done .circle { background: #27ae60; }
.progress-step.active .circle { background: #2176ae; }
.progress-step.done .circle { background: #27ae60; color: #fff; }
.progress-step.active .circle { background: #2176ae; color: #fff; box-shadow: 0 0 0 3px rgba(33,118,174,0.2); }
.progress-step .step-label {
font-size: 9px;
color: #666;
@@ -541,11 +557,13 @@ export default {
}
.progress-line {
height: 2px;
background: #ddd;
background: #e8eaed;
flex: 1;
min-width: 10px;
transition: background 0.3s;
}
.progress-line.done { background: #27ae60; }
.progress-step:hover .circle { transform: scale(1.1); }
.dialog-footer {
text-align: right;