设备总包项目管理剩余页面
This commit is contained in:
203
ruoyi-ui/src/views/rm/installPrep/progress.vue
Normal file
203
ruoyi-ui/src/views/rm/installPrep/progress.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Stats -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card"><div class="label">安装总进度</div><div class="value" style="color:#1890ff;">{{ overallPct }}%</div><div class="sub">{{ doneCount }}/{{ progress.length }} 项完成</div></div>
|
||||
<div class="stat-card green"><div class="label">已完成</div><div class="value">{{ doneCount }}</div><div class="sub">项</div></div>
|
||||
<div class="stat-card orange"><div class="label">进行中</div><div class="value">{{ progCount }}</div><div class="sub">项</div></div>
|
||||
<div class="stat-card"><div class="label">未开始</div><div class="value">{{ pendingCount }}</div><div class="sub">项</div></div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button size="mini" type="primary" @click="handleAdd">+ 添加进度项</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Bar chart -->
|
||||
<div style="font-weight:600;font-size:13px;margin-bottom:8px;">📊 安装进度条形图</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>
|
||||
</span>
|
||||
<span style="color:#6c757d;">{{ p.planStart||'?' }}~{{ p.planEnd||'?' }} · {{ statusLabel(p) }} · {{ barPercent(p) }}%</span>
|
||||
</div>
|
||||
<div style="height:16px;background:#eee;border-radius:4px;overflow:hidden;">
|
||||
<div :style="'height:100%;width:'+barPercent(p)+'%;border-radius:4px;background:'+barColor(p)+';transition:width 0.5s;'"></div>
|
||||
</div>
|
||||
<div v-if="p.actualStart||p.actualEnd" style="font-size:9px;color:#6c757d;margin-top:1px;">
|
||||
实际: {{ p.actualStart||'-' }}~{{ p.actualEnd||'-' }}
|
||||
</div>
|
||||
<div v-if="isOverdue(p) && p.delayReason" style="font-size:10px;color:#e74c3c;margin-top:1px;">
|
||||
⚠️ 延误原因:{{ p.delayReason }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- Detail table -->
|
||||
<div style="font-weight:600;font-size:13px;margin-bottom:8px;">📋 安装进度明细</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 />
|
||||
<el-table-column prop="planStart" label="计划开始" width="90" />
|
||||
<el-table-column prop="planEnd" label="计划结束" width="90" />
|
||||
<el-table-column prop="actualStart" label="实际开始" width="90" />
|
||||
<el-table-column prop="actualEnd" label="实际结束" width="90" />
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.status==='done'?'success':s.row.status==='progress'?'primary':'info'" size="mini">
|
||||
{{ s.row.status==='done'?'已完成':s.row.status==='progress'?'进行中':'未开始' }}
|
||||
</el-tag>
|
||||
<el-tag v-if="isOverdue(s.row)" size="mini" type="danger" style="margin-left:2px;">逾期</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="delayReason" label="延误原因" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="📷图片" width="55" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag v-if="s.row.images" size="mini" type="success">📷 {{ (s.row.images||'').split(';').filter(Boolean).length }}</el-tag>
|
||||
<span v-else style="color:#bbb;">📷 0</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" @click="handleEdit(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body @closed="onClosed">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
||||
<div class="form-group"><label>安装项目名称</label><el-input v-model="form.itemName" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>计划开始</label><el-date-picker v-model="form.planStart" type="date" value-format="yyyy-MM-dd" style="width:100%;" /></div>
|
||||
<div class="form-group"><label>计划结束</label><el-date-picker v-model="form.planEnd" type="date" value-format="yyyy-MM-dd" style="width:100%;" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>实际开始</label><el-date-picker v-model="form.actualStart" type="date" value-format="yyyy-MM-dd" style="width:100%;" /></div>
|
||||
<div class="form-group"><label>实际结束</label><el-date-picker v-model="form.actualEnd" type="date" value-format="yyyy-MM-dd" style="width:100%;" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>状态</label><el-select v-model="form.status" style="width:100%;"><el-option label="未开始" value="pending" /><el-option label="进行中" value="progress" /><el-option label="已完成" value="done" /></el-select></div>
|
||||
<div class="form-group"><label>延误原因</label><el-input v-model="form.delayReason" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>📷 图片(文件名,;分隔)</label><el-input v-model="form.images" /></div>
|
||||
<div class="form-group"><label>🎬 视频(文件名,;分隔)</label><el-input v-model="form.videos" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>备注</label><el-input v-model="form.remark" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="dialogVisible=false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="save">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listInstallProgressAll, addInstallProgress, updateInstallProgress, delInstallProgress } from '@/api/rm/installProgress'
|
||||
|
||||
export default {
|
||||
name: 'InstallProgress',
|
||||
props: { projectId: { type: [Number, String], default: null } },
|
||||
watch: { projectId: { immediate: true, handler(v) { if (v) this.loadData() } } },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
progress: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: this.cleanForm(),
|
||||
rules: { itemName: [{ required: true, message: '请填写安装项目名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
doneCount() { return this.progress.filter(p => p.status==='done').length },
|
||||
progCount() { return this.progress.filter(p => p.status==='progress').length },
|
||||
pendingCount() { return this.progress.filter(p => p.status==='pending'||!p.status).length },
|
||||
overallPct() {
|
||||
const n = this.progress.length
|
||||
return n ? Math.round(this.doneCount/n*100) : 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanForm() { return { itemName:'', planStart:'', planEnd:'', actualStart:'', actualEnd:'', status:'pending', delayReason:'', images:'', videos:'', remark:'' } },
|
||||
loadData() {
|
||||
if (!this.projectId) return
|
||||
this.loading = true
|
||||
listInstallProgressAll({ projectId: this.projectId }).then(res => {
|
||||
this.progress = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
isOverdue(p) {
|
||||
if (p.status==='done' || !p.planEnd) return false
|
||||
return new Date(p.planEnd+'T00:00:00') < new Date()
|
||||
},
|
||||
statusLabel(p) {
|
||||
if (p.status==='done') return '已完成'
|
||||
if (p.status==='progress') return '进行中'
|
||||
return '未开始'
|
||||
},
|
||||
barPercent(p) {
|
||||
if (p.status==='done') return 100
|
||||
if (p.status==='progress') {
|
||||
if (p.planStart && p.planEnd) {
|
||||
const s = new Date(p.planStart+'T00:00:00')
|
||||
const e = new Date(p.planEnd+'T00:00:00')
|
||||
const now = new Date()
|
||||
if (now < s) return 0
|
||||
const total = e - s
|
||||
if (total <= 0) return 50
|
||||
return Math.min(100, Math.round((now - s)/total*100))
|
||||
}
|
||||
return 30
|
||||
}
|
||||
return 0
|
||||
},
|
||||
barColor(p) {
|
||||
if (p.status==='done') return '#28a745'
|
||||
if (this.isOverdue(p)) return '#e74c3c'
|
||||
if (p.status==='progress') return '#1890ff'
|
||||
return '#ddd'
|
||||
},
|
||||
handleAdd() { this.dialogTitle='添加进度项'; this.form=this.cleanForm(); this.dialogVisible=true },
|
||||
handleEdit(row) { this.dialogTitle='编辑进度项'; this.form={...row}; this.dialogVisible=true },
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除?','提示',{type:'warning'}).then(() => {
|
||||
delInstallProgress(row.progressId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.form, projectId: this.projectId }
|
||||
const act = data.progressId ? updateInstallProgress(data) : addInstallProgress(data)
|
||||
act.then(() => { this.dialogVisible=false; this.loadData() })
|
||||
})
|
||||
},
|
||||
onClosed() { this.$refs.form?.clearValidate() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 8px; margin-bottom: 12px; }
|
||||
.stat-card { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 10px; text-align: center; }
|
||||
.stat-card .label { font-size: 11px; color: #6c757d; }
|
||||
.stat-card .value { font-size: 20px; font-weight: 700; color: #1a5a9e; }
|
||||
.stat-card.green .value { color: #28a745; }
|
||||
.stat-card.orange .value { color: #e67e22; }
|
||||
.form-group { margin-bottom: 6px; }
|
||||
.form-group label { display: block; font-size: 12px; color: #555; margin-bottom: 3px; font-weight: 500; }
|
||||
.form-row { display: flex; gap: 10px; }
|
||||
.form-row .form-group { flex: 1; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user