设备总包项目管理剩余页面
This commit is contained in:
8
ruoyi-ui/src/api/rm/acceptanceChecklist.js
Normal file
8
ruoyi-ui/src/api/rm/acceptanceChecklist.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listAcceptanceChecklist(query) { return request({ url: '/rm/acceptanceChecklist/list', method: 'get', params: query }) }
|
||||
export function getAcceptanceChecklist(checkId) { return request({ url: `/rm/acceptanceChecklist/${checkId}`, method: 'get' }) }
|
||||
export function addAcceptanceChecklist(data) { return request({ url: '/rm/acceptanceChecklist', method: 'post', data }) }
|
||||
export function updateAcceptanceChecklist(data) { return request({ url: '/rm/acceptanceChecklist', method: 'put', data }) }
|
||||
export function delAcceptanceChecklist(checkIds) { return request({ url: `/rm/acceptanceChecklist/${checkIds}`, method: 'delete' }) }
|
||||
export function listAcceptanceChecklistAll(query) { return request({ url: '/rm/acceptanceChecklist/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/acceptanceItem.js
Normal file
8
ruoyi-ui/src/api/rm/acceptanceItem.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listAcceptanceItem(query) { return request({ url: '/rm/acceptanceItem/list', method: 'get', params: query }) }
|
||||
export function getAcceptanceItem(acceptItemId) { return request({ url: `/rm/acceptanceItem/${acceptItemId}`, method: 'get' }) }
|
||||
export function addAcceptanceItem(data) { return request({ url: '/rm/acceptanceItem', method: 'post', data }) }
|
||||
export function updateAcceptanceItem(data) { return request({ url: '/rm/acceptanceItem', method: 'put', data }) }
|
||||
export function delAcceptanceItem(acceptItemIds) { return request({ url: `/rm/acceptanceItem/${acceptItemIds}`, method: 'delete' }) }
|
||||
export function listAcceptanceItemAll(query) { return request({ url: '/rm/acceptanceItem/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/commissioningChecklist.js
Normal file
8
ruoyi-ui/src/api/rm/commissioningChecklist.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listCommissioningChecklist(query) { return request({ url: '/rm/commissioningChecklist/list', method: 'get', params: query }) }
|
||||
export function getCommissioningChecklist(checkId) { return request({ url: `/rm/commissioningChecklist/${checkId}`, method: 'get' }) }
|
||||
export function addCommissioningChecklist(data) { return request({ url: '/rm/commissioningChecklist', method: 'post', data }) }
|
||||
export function updateCommissioningChecklist(data) { return request({ url: '/rm/commissioningChecklist', method: 'put', data }) }
|
||||
export function delCommissioningChecklist(checkIds) { return request({ url: `/rm/commissioningChecklist/${checkIds}`, method: 'delete' }) }
|
||||
export function listCommissioningChecklistAll(query) { return request({ url: '/rm/commissioningChecklist/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/commissioningClause.js
Normal file
8
ruoyi-ui/src/api/rm/commissioningClause.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listCommissioningClause(query) { return request({ url: '/rm/commissioningClause/list', method: 'get', params: query }) }
|
||||
export function getCommissioningClause(clauseId) { return request({ url: `/rm/commissioningClause/${clauseId}`, method: 'get' }) }
|
||||
export function addCommissioningClause(data) { return request({ url: '/rm/commissioningClause', method: 'post', data }) }
|
||||
export function updateCommissioningClause(data) { return request({ url: '/rm/commissioningClause', method: 'put', data }) }
|
||||
export function delCommissioningClause(clauseIds) { return request({ url: `/rm/commissioningClause/${clauseIds}`, method: 'delete' }) }
|
||||
export function listCommissioningClauseAll(query) { return request({ url: '/rm/commissioningClause/all', method: 'get', params: query }) }
|
||||
3
ruoyi-ui/src/api/rm/dashboard.js
Normal file
3
ruoyi-ui/src/api/rm/dashboard.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getStageStatus(projectId) { return request({ url: `/rm/dashboard/stageStatus/${projectId}`, method: 'get' }) }
|
||||
8
ruoyi-ui/src/api/rm/docLib.js
Normal file
8
ruoyi-ui/src/api/rm/docLib.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listDocLib(query) { return request({ url: '/rm/docLib/list', method: 'get', params: query }) }
|
||||
export function getDocLib(docId) { return request({ url: `/rm/docLib/${docId}`, method: 'get' }) }
|
||||
export function addDocLib(data) { return request({ url: '/rm/docLib', method: 'post', data }) }
|
||||
export function updateDocLib(data) { return request({ url: '/rm/docLib', method: 'put', data }) }
|
||||
export function delDocLib(docIds) { return request({ url: `/rm/docLib/${docIds}`, method: 'delete' }) }
|
||||
export function listDocLibAll(query) { return request({ url: '/rm/docLib/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/drawingCompare.js
Normal file
8
ruoyi-ui/src/api/rm/drawingCompare.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listDrawingCompare(query) { return request({ url: '/rm/drawingCompare/list', method: 'get', params: query }) }
|
||||
export function getDrawingCompare(compareId) { return request({ url: `/rm/drawingCompare/${compareId}`, method: 'get' }) }
|
||||
export function addDrawingCompare(data) { return request({ url: '/rm/drawingCompare', method: 'post', data }) }
|
||||
export function updateDrawingCompare(data) { return request({ url: '/rm/drawingCompare', method: 'put', data }) }
|
||||
export function delDrawingCompare(compareIds) { return request({ url: `/rm/drawingCompare/${compareIds}`, method: 'delete' }) }
|
||||
export function listDrawingCompareAll(query) { return request({ url: '/rm/drawingCompare/all', method: 'get', params: query }) }
|
||||
@@ -37,3 +37,11 @@ export function delDrawingReview(reviewIds) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function listDrawingReviewAll(query) {
|
||||
return request({
|
||||
url: '/rm/drawingReview/all',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
8
ruoyi-ui/src/api/rm/installFeedback.js
Normal file
8
ruoyi-ui/src/api/rm/installFeedback.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listInstallFeedback(query) { return request({ url: '/rm/installFeedback/list', method: 'get', params: query }) }
|
||||
export function getInstallFeedback(feedbackId) { return request({ url: `/rm/installFeedback/${feedbackId}`, method: 'get' }) }
|
||||
export function addInstallFeedback(data) { return request({ url: '/rm/installFeedback', method: 'post', data }) }
|
||||
export function updateInstallFeedback(data) { return request({ url: '/rm/installFeedback', method: 'put', data }) }
|
||||
export function delInstallFeedback(feedbackIds) { return request({ url: `/rm/installFeedback/${feedbackIds}`, method: 'delete' }) }
|
||||
export function listInstallFeedbackAll(query) { return request({ url: '/rm/installFeedback/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/installPersonnel.js
Normal file
8
ruoyi-ui/src/api/rm/installPersonnel.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listInstallPersonnel(query) { return request({ url: '/rm/installPersonnel/list', method: 'get', params: query }) }
|
||||
export function getInstallPersonnel(personnelId) { return request({ url: `/rm/installPersonnel/${personnelId}`, method: 'get' }) }
|
||||
export function addInstallPersonnel(data) { return request({ url: '/rm/installPersonnel', method: 'post', data }) }
|
||||
export function updateInstallPersonnel(data) { return request({ url: '/rm/installPersonnel', method: 'put', data }) }
|
||||
export function delInstallPersonnel(personnelIds) { return request({ url: `/rm/installPersonnel/${personnelIds}`, method: 'delete' }) }
|
||||
export function listInstallPersonnelAll(query) { return request({ url: '/rm/installPersonnel/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/installPrecision.js
Normal file
8
ruoyi-ui/src/api/rm/installPrecision.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listInstallPrecision(query) { return request({ url: '/rm/installPrecision/list', method: 'get', params: query }) }
|
||||
export function getInstallPrecision(precisionId) { return request({ url: `/rm/installPrecision/${precisionId}`, method: 'get' }) }
|
||||
export function addInstallPrecision(data) { return request({ url: '/rm/installPrecision', method: 'post', data }) }
|
||||
export function updateInstallPrecision(data) { return request({ url: '/rm/installPrecision', method: 'put', data }) }
|
||||
export function delInstallPrecision(precisionIds) { return request({ url: `/rm/installPrecision/${precisionIds}`, method: 'delete' }) }
|
||||
export function listInstallPrecisionAll(query) { return request({ url: '/rm/installPrecision/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/installProgress.js
Normal file
8
ruoyi-ui/src/api/rm/installProgress.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listInstallProgress(query) { return request({ url: '/rm/installProgress/list', method: 'get', params: query }) }
|
||||
export function getInstallProgress(progressId) { return request({ url: `/rm/installProgress/${progressId}`, method: 'get' }) }
|
||||
export function addInstallProgress(data) { return request({ url: '/rm/installProgress', method: 'post', data }) }
|
||||
export function updateInstallProgress(data) { return request({ url: '/rm/installProgress', method: 'put', data }) }
|
||||
export function delInstallProgress(progressIds) { return request({ url: `/rm/installProgress/${progressIds}`, method: 'delete' }) }
|
||||
export function listInstallProgressAll(query) { return request({ url: '/rm/installProgress/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/installTool.js
Normal file
8
ruoyi-ui/src/api/rm/installTool.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listInstallTool(query) { return request({ url: '/rm/installTool/list', method: 'get', params: query }) }
|
||||
export function getInstallTool(toolId) { return request({ url: `/rm/installTool/${toolId}`, method: 'get' }) }
|
||||
export function addInstallTool(data) { return request({ url: '/rm/installTool', method: 'post', data }) }
|
||||
export function updateInstallTool(data) { return request({ url: '/rm/installTool', method: 'put', data }) }
|
||||
export function delInstallTool(toolIds) { return request({ url: `/rm/installTool/${toolIds}`, method: 'delete' }) }
|
||||
export function listInstallToolAll(query) { return request({ url: '/rm/installTool/all', method: 'get', params: query }) }
|
||||
@@ -37,3 +37,11 @@ export function delLayoutFile(layoutFileIds) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function listLayoutFileAll(query) {
|
||||
return request({
|
||||
url: '/rm/layout/all',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
8
ruoyi-ui/src/api/rm/manual.js
Normal file
8
ruoyi-ui/src/api/rm/manual.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listManual(query) { return request({ url: '/rm/manual/list', method: 'get', params: query }) }
|
||||
export function getManual(manualId) { return request({ url: `/rm/manual/${manualId}`, method: 'get' }) }
|
||||
export function addManual(data) { return request({ url: '/rm/manual', method: 'post', data }) }
|
||||
export function updateManual(data) { return request({ url: '/rm/manual', method: 'put', data }) }
|
||||
export function delManual(manualIds) { return request({ url: `/rm/manual/${manualIds}`, method: 'delete' }) }
|
||||
export function listManualAll(query) { return request({ url: '/rm/manual/all', method: 'get', params: query }) }
|
||||
47
ruoyi-ui/src/api/rm/projectMember.js
Normal file
47
ruoyi-ui/src/api/rm/projectMember.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listProjectMember(query) {
|
||||
return request({
|
||||
url: '/rm/projectMember/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getProjectMember(memberId) {
|
||||
return request({
|
||||
url: `/rm/projectMember/${memberId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addProjectMember(data) {
|
||||
return request({
|
||||
url: '/rm/projectMember',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateProjectMember(data) {
|
||||
return request({
|
||||
url: '/rm/projectMember',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function delProjectMember(memberIds) {
|
||||
return request({
|
||||
url: `/rm/projectMember/${memberIds}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function allProjectMember(query) {
|
||||
return request({
|
||||
url: '/rm/projectMember/all',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
8
ruoyi-ui/src/api/rm/shippingChecklist.js
Normal file
8
ruoyi-ui/src/api/rm/shippingChecklist.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listShippingChecklist(query) { return request({ url: '/rm/shippingChecklist/list', method: 'get', params: query }) }
|
||||
export function getShippingChecklist(checklistId) { return request({ url: `/rm/shippingChecklist/${checklistId}`, method: 'get' }) }
|
||||
export function addShippingChecklist(data) { return request({ url: '/rm/shippingChecklist', method: 'post', data }) }
|
||||
export function updateShippingChecklist(data) { return request({ url: '/rm/shippingChecklist', method: 'put', data }) }
|
||||
export function delShippingChecklist(checklistIds) { return request({ url: `/rm/shippingChecklist/${checklistIds}`, method: 'delete' }) }
|
||||
export function listShippingChecklistAll(query) { return request({ url: '/rm/shippingChecklist/all', method: 'get', params: query }) }
|
||||
8
ruoyi-ui/src/api/rm/shippingItem.js
Normal file
8
ruoyi-ui/src/api/rm/shippingItem.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listShippingItem(query) { return request({ url: '/rm/shippingItem/list', method: 'get', params: query }) }
|
||||
export function getShippingItem(itemId) { return request({ url: `/rm/shippingItem/${itemId}`, method: 'get' }) }
|
||||
export function addShippingItem(data) { return request({ url: '/rm/shippingItem', method: 'post', data }) }
|
||||
export function updateShippingItem(data) { return request({ url: '/rm/shippingItem', method: 'put', data }) }
|
||||
export function delShippingItem(itemIds) { return request({ url: `/rm/shippingItem/${itemIds}`, method: 'delete' }) }
|
||||
export function listShippingItemAll(query) { return request({ url: '/rm/shippingItem/all', method: 'get', params: query }) }
|
||||
13
ruoyi-ui/src/api/rm/siteMod.js
Normal file
13
ruoyi-ui/src/api/rm/siteMod.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listSiteMod(query) { return request({ url: '/rm/siteMod/list', method: 'get', params: query }) }
|
||||
export function getSiteMod(modId) { return request({ url: `/rm/siteMod/${modId}`, method: 'get' }) }
|
||||
export function addSiteMod(data) { return request({ url: '/rm/siteMod', method: 'post', data }) }
|
||||
export function updateSiteMod(data) { return request({ url: '/rm/siteMod', method: 'put', data }) }
|
||||
export function delSiteMod(modIds) { return request({ url: `/rm/siteMod/${modIds}`, method: 'delete' }) }
|
||||
export function listSiteModAll(query) { return request({ url: '/rm/siteMod/all', method: 'get', params: query }) }
|
||||
|
||||
export function listSiteModMedia(query) { return request({ url: '/rm/siteModMedia/list', method: 'get', params: query }) }
|
||||
export function addSiteModMedia(data) { return request({ url: '/rm/siteModMedia', method: 'post', data }) }
|
||||
export function delSiteModMedia(mediaIds) { return request({ url: `/rm/siteModMedia/${mediaIds}`, method: 'delete' }) }
|
||||
export function listSiteModMediaAll(query) { return request({ url: '/rm/siteModMedia/all', method: 'get', params: query }) }
|
||||
@@ -37,3 +37,11 @@ export function delTechPlan(planItemIds) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function listTechPlanAll(query) {
|
||||
return request({
|
||||
url: '/rm/techPlan/all',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
@@ -240,6 +240,17 @@ body {
|
||||
box-shadow: 0 1px 2px rgba(0, 21, 41, 0.04);
|
||||
}
|
||||
|
||||
.current-project-bar {
|
||||
background: #e8f4fd;
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #2176ae;
|
||||
border-bottom: 1px solid #d0e8f8;
|
||||
margin: -1px -1px 0 -1px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 圆角按钮/输入框
|
||||
.el-input__inner,
|
||||
.el-textarea__inner {
|
||||
|
||||
@@ -36,7 +36,7 @@ $base-sub-menu-background:#000c17;
|
||||
$base-sub-menu-hover:#001528;
|
||||
*/
|
||||
|
||||
$base-sidebar-width: 150px;
|
||||
$base-sidebar-width: 180px;
|
||||
|
||||
// the :export directive is the magic sauce for webpack
|
||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||
|
||||
207
ruoyi-ui/src/views/rm/acceptance/index.vue
Normal file
207
ruoyi-ui/src/views/rm/acceptance/index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>✅ 安装后验收</span>
|
||||
<el-button size="small" type="primary" @click="handleAddItem">+ 添加验收项</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
|
||||
<!-- Checklist section -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">
|
||||
📋 验收检查清单
|
||||
<el-tag v-if="checklistAllDone" size="mini" type="success" style="margin-left:6px;">✅ 全部合格</el-tag>
|
||||
<el-tag v-else size="mini" type="warning" style="margin-left:6px;">⚠️ 尚有未完成验收项</el-tag>
|
||||
<el-button size="mini" type="text" icon="el-icon-plus" style="margin-left:8px;" @click="handleAddCheck">添加检查项</el-button>
|
||||
</div>
|
||||
<div v-if="checklist.length === 0" style="color:#aaa;font-size:12px;padding:8px 0;">暂无检查项</div>
|
||||
<div v-for="(it, i) in checklist" :key="i" class="checklist-item" :class="it.isChecked === '1' ? 'checked' : ''">
|
||||
<el-checkbox v-model="it._checked" @change="toggleCheck(it)"></el-checkbox>
|
||||
<span class="checklist-text">{{ it.itemText }}</span>
|
||||
<el-button type="text" size="mini" style="margin-left:auto;color:#999;" icon="el-icon-delete" @click="handleDeleteCheck(it)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- Items table -->
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">📊 详细验收数据</div>
|
||||
<el-table :data="items" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="itemName" label="验收项目" min-width="140" />
|
||||
<el-table-column prop="requirement" label="要求值" width="120" />
|
||||
<el-table-column prop="actualValue" label="实测值" width="120" />
|
||||
<el-table-column label="结果" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="resultTag(s.row.result)" size="mini">{{ resultLabel(s.row.result) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" @click="handleDeleteItem(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Item Dialog -->
|
||||
<el-dialog :title="itemDialogTitle" :visible.sync="itemDialogVisible" width="480px" append-to-body @closed="onItemClosed">
|
||||
<el-form ref="itemForm" :model="itemForm" :rules="itemRules" label-width="0" size="small">
|
||||
<div class="form-group"><label>验收项目</label><el-input v-model="itemForm.itemName" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>要求值</label><el-input v-model="itemForm.requirement" /></div>
|
||||
<div class="form-group"><label>实测值</label><el-input v-model="itemForm.actualValue" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>验收结果</label>
|
||||
<el-select v-model="itemForm.result" style="width:100%;">
|
||||
<el-option label="待检" value="pending" />
|
||||
<el-option label="合格" value="pass" />
|
||||
<el-option label="不合格" value="fail" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-group"><label>备注</label><el-input v-model="itemForm.notes" type="textarea" :rows="2" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="itemDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveItem">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Add Checklist Item Dialog -->
|
||||
<el-dialog title="添加检查项" :visible.sync="checkDialogVisible" width="400px" append-to-body>
|
||||
<el-form ref="checkForm" :model="checkForm" label-width="0" size="small">
|
||||
<div class="form-group"><label>检查项内容</label><el-input v-model="checkForm.itemText" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="checkDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveCheck">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listAcceptanceChecklistAll, addAcceptanceChecklist, updateAcceptanceChecklist, delAcceptanceChecklist } from '@/api/rm/acceptanceChecklist'
|
||||
import { listAcceptanceItemAll, addAcceptanceItem, updateAcceptanceItem, delAcceptanceItem } from '@/api/rm/acceptanceItem'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmAcceptance',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
checklist: [],
|
||||
items: [],
|
||||
currentProjectId: null,
|
||||
itemDialogVisible: false,
|
||||
itemDialogTitle: '',
|
||||
itemForm: { itemName: '', requirement: '', actualValue: '', result: 'pending', notes: '' },
|
||||
itemRules: { itemName: [{ required: true, message: '请填写验收项目', trigger: 'blur' }] },
|
||||
checkDialogVisible: false,
|
||||
checkForm: { itemText: '' }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checklistAllDone() {
|
||||
return this.checklist.length > 0 && this.checklist.every(it => it.isChecked === '1')
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
resultTag(result) {
|
||||
return { pass: 'success', fail: 'danger', pending: 'info' }[result] || ''
|
||||
},
|
||||
resultLabel(result) {
|
||||
return { pass: '合格', fail: '不合格', pending: '待检' }[result] || result
|
||||
},
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadData()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadData() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
listAcceptanceChecklistAll({ projectId: this.currentProjectId }),
|
||||
listAcceptanceItemAll({ projectId: this.currentProjectId })
|
||||
]).then(([clRes, itRes]) => {
|
||||
this.checklist = (clRes.data || []).map(it => ({ ...it, _checked: it.isChecked === '1' }))
|
||||
this.items = itRes.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
// Checklist
|
||||
handleAddCheck() {
|
||||
this.checkForm = { itemText: '' }
|
||||
this.checkDialogVisible = true
|
||||
},
|
||||
saveCheck() {
|
||||
if (!this.checkForm.itemText) { this.$message.warning('请填写检查项内容'); return }
|
||||
addAcceptanceChecklist({ projectId: this.currentProjectId, itemText: this.checkForm.itemText, isChecked: '0', sortOrder: this.checklist.length + 1 }).then(() => {
|
||||
this.$message.success('检查项已添加')
|
||||
this.checkDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
toggleCheck(it) {
|
||||
updateAcceptanceChecklist({ checkId: it.checkId, projectId: this.currentProjectId, itemText: it.itemText, isChecked: it._checked ? '1' : '0' }).then(() => {
|
||||
it.isChecked = it._checked ? '1' : '0'
|
||||
})
|
||||
},
|
||||
handleDeleteCheck(it) {
|
||||
this.$confirm('确认删除该检查项?', '提示', { type: 'warning' }).then(() => {
|
||||
delAcceptanceChecklist(it.checkId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
// Items
|
||||
handleAddItem() {
|
||||
this.itemDialogTitle = '添加验收项'
|
||||
this.itemForm = { itemName: '', requirement: '', actualValue: '', result: 'pending', notes: '' }
|
||||
this.itemDialogVisible = true
|
||||
},
|
||||
saveItem() {
|
||||
this.$refs.itemForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.itemForm, projectId: this.currentProjectId }
|
||||
const action = data.acceptItemId ? updateAcceptanceItem(data) : addAcceptanceItem(data)
|
||||
action.then(() => {
|
||||
this.$message.success('验收项已保存')
|
||||
this.itemDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDeleteItem(row) {
|
||||
this.$confirm('确认删除该验收项?', '提示', { type: 'warning' }).then(() => {
|
||||
delAcceptanceItem(row.acceptItemId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
onItemClosed() { this.$refs.itemForm?.clearValidate() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; overflow: hidden; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 12px; }
|
||||
.checklist-item { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid #f0f0f0; font-size: 12px; }
|
||||
.checklist-item:last-child { border-bottom: none; }
|
||||
.checklist-item.checked .checklist-text { text-decoration: line-through; color: #aaa; }
|
||||
.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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<el-card shadow="never" class="module-panel">
|
||||
<div slot="header" class="module-header">
|
||||
<span>💰 项目预算管理</span>
|
||||
@@ -129,6 +130,7 @@ export default {
|
||||
name: 'RmBudget',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
budgetList: [],
|
||||
total: 0,
|
||||
@@ -173,14 +175,21 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.query.projectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
|
||||
219
ruoyi-ui/src/views/rm/commissioning/index.vue
Normal file
219
ruoyi-ui/src/views/rm/commissioning/index.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>🔥 热负荷试车</span>
|
||||
<el-button size="small" type="primary" @click="handleAddClause">+ 添加技术协议条款</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
|
||||
<!-- Notice -->
|
||||
<div style="margin-bottom:16px;padding:10px;background:#fff3cd;border-radius:6px;border:1px solid #ffe0b2;font-size:12px;">
|
||||
📋 热负荷试车按照<b>技术协议签订条款</b>逐项确定。请添加技术协议中的关键条款,并逐项确认。
|
||||
</div>
|
||||
|
||||
<!-- Checklist section -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">
|
||||
📋 热负荷试车检查清单
|
||||
<el-tag v-if="checklistAllDone" size="mini" type="success" style="margin-left:6px;">✅ 全部完成</el-tag>
|
||||
<el-tag v-else size="mini" type="warning" style="margin-left:6px;">⚠️ 尚有未完成项</el-tag>
|
||||
<el-button size="mini" type="text" style="margin-left:8px;" @click="handleAddCheck">添加检查项</el-button>
|
||||
</div>
|
||||
<div v-if="checklist.length === 0" style="color:#aaa;font-size:12px;padding:8px 0;">暂无检查项</div>
|
||||
<div v-for="(it, i) in checklist" :key="i" class="checklist-item" :class="it.isChecked === '1' ? 'checked' : ''">
|
||||
<el-checkbox v-model="it._checked" @change="toggleCheck(it)"></el-checkbox>
|
||||
<span class="checklist-text">{{ it.itemText }}</span>
|
||||
<el-button type="text" size="mini" style="margin-left:auto;color:#999;" icon="el-icon-delete" @click="handleDeleteCheck(it)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- Clauses section -->
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">📊 技术协议条款确认</div>
|
||||
<el-table :data="clauses" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="clause" label="协议条款" min-width="160" />
|
||||
<el-table-column prop="standard" label="标准要求" width="130" />
|
||||
<el-table-column prop="result" label="试车结果" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="确认状态" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.passFlag === '1' ? 'success' : 'warning'" size="mini">{{ s.row.passFlag === '1' ? '合格' : '待确认' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" @click="handleEditClause(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click="handleDeleteClause(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="clauses.length === 0" style="text-align:center;color:#aaa;font-size:12px;padding:12px 0;border:1px solid #f0f0f0;border-top:none;">
|
||||
暂无技术协议条款,请点击"添加技术协议条款"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Clause Dialog -->
|
||||
<el-dialog :title="clauseDialogTitle" :visible.sync="clauseDialogVisible" width="500px" append-to-body @closed="onClauseClosed">
|
||||
<el-form ref="clauseForm" :model="clauseForm" :rules="clauseRules" label-width="0" size="small">
|
||||
<div class="form-group"><label>技术协议条款</label><el-input v-model="clauseForm.clause" placeholder="如:轧制力控制精度±2%" /></div>
|
||||
<div class="form-group"><label>标准要求</label><el-input v-model="clauseForm.standard" placeholder="如:GB/T 标准" /></div>
|
||||
<div class="form-group"><label>试车结果</label><el-input v-model="clauseForm.result" type="textarea" :rows="2" placeholder="试车实际结果" /></div>
|
||||
<div style="margin-top:8px;">
|
||||
<el-checkbox v-model="clauseForm._pass">合格/通过</el-checkbox>
|
||||
</div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="clauseDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveClause">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Check Dialog -->
|
||||
<el-dialog title="添加检查项" :visible.sync="checkDialogVisible" width="400px" append-to-body>
|
||||
<el-form ref="checkForm" :model="checkForm" label-width="0" size="small">
|
||||
<div class="form-group"><label>检查项内容</label><el-input v-model="checkForm.itemText" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="checkDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveCheck">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCommissioningChecklistAll, addCommissioningChecklist, updateCommissioningChecklist, delCommissioningChecklist } from '@/api/rm/commissioningChecklist'
|
||||
import { listCommissioningClauseAll, addCommissioningClause, updateCommissioningClause, delCommissioningClause } from '@/api/rm/commissioningClause'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmCommissioning',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
checklist: [],
|
||||
clauses: [],
|
||||
currentProjectId: null,
|
||||
checkDialogVisible: false,
|
||||
checkForm: { itemText: '' },
|
||||
clauseDialogVisible: false,
|
||||
clauseDialogTitle: '',
|
||||
clauseForm: { clause: '', standard: '', result: '', _pass: false },
|
||||
clauseRules: { clause: [{ required: true, message: '请填写技术协议条款', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checklistAllDone() {
|
||||
return this.checklist.length > 0 && this.checklist.every(it => it.isChecked === '1')
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadData()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadData() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
listCommissioningChecklistAll({ projectId: this.currentProjectId }),
|
||||
listCommissioningClauseAll({ projectId: this.currentProjectId })
|
||||
]).then(([clRes, ccRes]) => {
|
||||
this.checklist = (clRes.data || []).map(it => ({ ...it, _checked: it.isChecked === '1' }))
|
||||
this.clauses = ccRes.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
// Checklist
|
||||
handleAddCheck() {
|
||||
this.checkForm = { itemText: '' }
|
||||
this.checkDialogVisible = true
|
||||
},
|
||||
saveCheck() {
|
||||
if (!this.checkForm.itemText) { this.$message.warning('请填写检查项内容'); return }
|
||||
addCommissioningChecklist({ projectId: this.currentProjectId, itemText: this.checkForm.itemText, isChecked: '0', sortOrder: this.checklist.length + 1 }).then(() => {
|
||||
this.checkDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
toggleCheck(it) {
|
||||
updateCommissioningChecklist({ checkId: it.checkId, projectId: this.currentProjectId, itemText: it.itemText, isChecked: it._checked ? '1' : '0' }).then(() => {
|
||||
it.isChecked = it._checked ? '1' : '0'
|
||||
})
|
||||
},
|
||||
handleDeleteCheck(it) {
|
||||
this.$confirm('确认删除该检查项?', '提示', { type: 'warning' }).then(() => {
|
||||
delCommissioningChecklist(it.checkId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
// Clauses
|
||||
handleAddClause() {
|
||||
this.clauseDialogTitle = '添加技术协议条款'
|
||||
this.clauseForm = { clause: '', standard: '', result: '', _pass: false }
|
||||
this.clauseDialogVisible = true
|
||||
},
|
||||
handleEditClause(row) {
|
||||
this.clauseDialogTitle = '编辑条款'
|
||||
this.clauseForm = {
|
||||
clauseId: row.clauseId,
|
||||
clause: row.clause,
|
||||
standard: row.standard,
|
||||
result: row.result,
|
||||
_pass: row.passFlag === '1'
|
||||
}
|
||||
this.clauseDialogVisible = true
|
||||
},
|
||||
saveClause() {
|
||||
this.$refs.clauseForm.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = {
|
||||
clauseId: this.clauseForm.clauseId,
|
||||
projectId: this.currentProjectId,
|
||||
clause: this.clauseForm.clause,
|
||||
standard: this.clauseForm.standard,
|
||||
result: this.clauseForm.result,
|
||||
passFlag: this.clauseForm._pass ? '1' : '0'
|
||||
}
|
||||
const action = data.clauseId ? updateCommissioningClause(data) : addCommissioningClause(data)
|
||||
action.then(() => {
|
||||
this.clauseDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDeleteClause(row) {
|
||||
this.$confirm('确认删除该条款?', '提示', { type: 'warning' }).then(() => {
|
||||
delCommissioningClause(row.clauseId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
onClauseClosed() { this.$refs.clauseForm?.clearValidate() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; overflow: hidden; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 12px; }
|
||||
.checklist-item { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid #f0f0f0; font-size: 12px; }
|
||||
.checklist-item:last-child { border-bottom: none; }
|
||||
.checklist-item.checked .checklist-text { text-decoration: line-through; color: #aaa; }
|
||||
.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>
|
||||
189
ruoyi-ui/src/views/rm/docLib/index.vue
Normal file
189
ruoyi-ui/src/views/rm/docLib/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸资料库</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增资料</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<div class="rm-search-bar" style="margin-bottom:8px">
|
||||
<el-input v-model="queryParams.docName" placeholder="文件名称" size="small" clearable style="width:160px;margin-right:8px" @keyup.enter="loadList" />
|
||||
<el-select v-model="queryParams.category" placeholder="分类" size="small" clearable style="width:120px;margin-right:8px">
|
||||
<el-option label="图纸" value="图纸" />
|
||||
<el-option label="技术协议" value="技术协议" />
|
||||
<el-option label="计算书" value="计算书" />
|
||||
<el-option label="说明书" value="说明书" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="loadList">查询</el-button>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="docName" label="文件名称" min-width="160" />
|
||||
<el-table-column prop="category" label="分类" width="80" align="center" />
|
||||
<el-table-column prop="version" label="版本" width="70" align="center" />
|
||||
<el-table-column prop="uploader" label="上传人" width="80" />
|
||||
<el-table-column prop="uploadDate" label="上传日期" width="100" align="center" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="handleView(s.row)">查看</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="560px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16"><el-form-item label="文件名称" prop="docName"><el-input v-model="form.docName" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="分类" prop="category">
|
||||
<el-select v-model="form.category" style="width:100%">
|
||||
<el-option label="图纸" value="图纸" />
|
||||
<el-option label="技术协议" value="技术协议" />
|
||||
<el-option label="计算书" value="计算书" />
|
||||
<el-option label="说明书" value="说明书" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6"><el-form-item label="版本" prop="version"><el-input v-model="form.version" placeholder="V1.0" /></el-form-item></el-col>
|
||||
<el-col :span="9"><el-form-item label="上传人" prop="uploader"><el-input v-model="form.uploader" /></el-form-item></el-col>
|
||||
<el-col :span="9"><el-form-item label="上传日期" prop="uploadDate"><el-date-picker v-model="form.uploadDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="2" placeholder="文件描述说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件" prop="fileUrl">
|
||||
<div style="display:flex;align-items:center">
|
||||
<el-input v-model="form.fileUrl" placeholder="请上传文件" style="flex:1;margin-right:8px" />
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false">
|
||||
<el-button size="small" type="primary">上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- View Dialog -->
|
||||
<el-dialog title="资料详情" :visible.sync="viewVisible" width="520px" append-to-body>
|
||||
<div class="detail-grid" v-if="viewItem">
|
||||
<div class="dg-label">文件名称</div><div class="dg-value">{{ viewItem.docName }}</div>
|
||||
<div class="dg-label">分类</div><div class="dg-value">{{ viewItem.category || '-' }}</div>
|
||||
<div class="dg-label">版本</div><div class="dg-value">{{ viewItem.version || '-' }}</div>
|
||||
<div class="dg-label">上传人</div><div class="dg-value">{{ viewItem.uploader || '-' }}</div>
|
||||
<div class="dg-label">上传日期</div><div class="dg-value">{{ viewItem.uploadDate || '-' }}</div>
|
||||
<div class="dg-label">描述</div><div class="dg-value">{{ viewItem.description || '无' }}</div>
|
||||
<div class="dg-label">文件</div>
|
||||
<div class="dg-value">
|
||||
<a v-if="viewItem.fileUrl" :href="viewItem.fileUrl" target="_blank" style="color:#409eff">{{ viewItem.fileName || '下载文件' }}</a>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="viewVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDocLib, getDocLib, addDocLib, updateDocLib, delDocLib } from '@/api/rm/docLib'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'RmDocLib',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false, list: [], currentProjectId: null,
|
||||
queryParams: { docName: '', category: '' },
|
||||
dialogVisible: false, dialogTitle: '', submitting: false, form: {},
|
||||
rules: { docName: [{ required: true, message: '请填写文件名称', trigger: 'blur' }] },
|
||||
viewVisible: false, viewItem: null,
|
||||
uploadUrl: process.env.VUE_APP_BASE_API + '/common/upload',
|
||||
uploadHeaders: { Authorization: 'Bearer ' + getToken() }
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadList() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
listDocLib({ ...this.queryParams, projectId: this.currentProjectId }).then(r => {
|
||||
this.list = r.rows || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.dialogTitle = '新增资料'
|
||||
this.form = { projectId: this.currentProjectId, docName: '', category: '', version: 'V1.0', uploader: '', uploadDate: '', description: '', fileUrl: '' }
|
||||
this.dialogVisible = true; this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑资料'
|
||||
getDocLib(row.docId).then(r => { this.form = r.data; this.dialogVisible = true; this.$nextTick(() => { this.$refs.formRef?.clearValidate() }) })
|
||||
},
|
||||
handleView(row) {
|
||||
this.viewItem = row; this.viewVisible = true
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.docName}"?`, '提示', { type: 'warning' }).then(() => { delDocLib(row.docId).then(() => { this.$message.success('删除成功'); this.loadList() }) })
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(v => {
|
||||
if (!v) return; this.submitting = true
|
||||
const api = this.form.docId ? updateDocLib : addDocLib
|
||||
api(this.form).then(() => { this.$message.success('保存成功'); this.dialogVisible = false; this.loadList() }).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
handleUploadSuccess(res) {
|
||||
if (res.code === 200) { this.form.fileUrl = res.url; this.$message.success('上传成功') }
|
||||
else { this.$message.error(res.msg || '上传失败') }
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const ext = file.name.split('.').pop().toLowerCase()
|
||||
const allowed = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'dwg', 'dxf', 'zip', 'rar', 'jpg', 'png']
|
||||
if (!allowed.includes(ext)) { this.$message.error('不支持的文件格式'); return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.detail-grid { display: grid; grid-template-columns: 100px 1fr; font-size: 13px; }
|
||||
.dg-label { background: #fafbfc; font-weight: 600; padding: 8px 12px; border-bottom: 1px solid #eee; }
|
||||
.dg-value { padding: 8px 12px; border-bottom: 1px solid #eee; }
|
||||
</style>
|
||||
160
ruoyi-ui/src/views/rm/drawingCompare/index.vue
Normal file
160
ruoyi-ui/src/views/rm/drawingCompare/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸优化比较</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增对比</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="drawingName" label="图纸名称" min-width="160" />
|
||||
<el-table-column prop="oldVersion" label="优化前版本" width="100" align="center" />
|
||||
<el-table-column prop="newVersion" label="优化后版本" width="100" align="center" />
|
||||
<el-table-column prop="optimizer" label="优化人" width="80" />
|
||||
<el-table-column prop="compareDate" label="优化日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="statusTag(s.row.status)" size="mini">{{ statusLabel(s.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="handleView(s.row)">查看对比</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="560px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16"><el-form-item label="图纸名称" prop="drawingName"><el-input v-model="form.drawingName" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="优化人" prop="optimizer"><el-input v-model="form.optimizer" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="优化前版本" prop="oldVersion"><el-input v-model="form.oldVersion" placeholder="V1.0" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="优化后版本" prop="newVersion"><el-input v-model="form.newVersion" placeholder="V2.0" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="优化日期" prop="compareDate"><el-date-picker v-model="form.compareDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="优化前问题描述" prop="beforeDesc">
|
||||
<el-input v-model="form.beforeDesc" type="textarea" :rows="2" placeholder="优化前存在的问题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优化后改进内容" prop="afterDesc">
|
||||
<el-input v-model="form.afterDesc" type="textarea" :rows="2" placeholder="优化后改进的内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优化效果评价" prop="diffNotes">
|
||||
<el-input v-model="form.diffNotes" type="textarea" :rows="2" placeholder="如:刚度提升15%,振动降低20%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" style="width:100%">
|
||||
<el-option label="待确认" value="pending" />
|
||||
<el-option label="已确认" value="approved" />
|
||||
<el-option label="已驳回" value="rejected" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- View Compare Dialog -->
|
||||
<el-dialog title="图纸优化对比" :visible.sync="viewVisible" width="700px" append-to-body>
|
||||
<div class="compare-grid" v-if="viewItem">
|
||||
<div class="cg-header">对比项</div>
|
||||
<div class="cg-header">优化前({{ viewItem.oldVersion }})</div>
|
||||
<div class="cg-header">优化后({{ viewItem.newVersion }})</div>
|
||||
<div class="cg-label">问题描述</div>
|
||||
<div class="cg-cell">{{ viewItem.beforeDesc || '无' }}</div>
|
||||
<div class="cg-cell">{{ viewItem.afterDesc || '无' }}</div>
|
||||
<div class="cg-label">改进效果</div>
|
||||
<div class="cg-cell cg-ok">{{ viewItem.diffNotes || '无' }}</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="viewVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDrawingCompareAll, getDrawingCompare, addDrawingCompare, updateDrawingCompare, delDrawingCompare } from '@/api/rm/drawingCompare'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmDrawingCompare',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false, list: [], currentProjectId: null,
|
||||
dialogVisible: false, dialogTitle: '', submitting: false, form: {},
|
||||
rules: { drawingName: [{ required: true, message: '请填写图纸名称', trigger: 'blur' }] },
|
||||
viewVisible: false, viewItem: null
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadList() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
listDrawingCompareAll({ projectId: this.currentProjectId }).then(r => { this.list = r.data || [] }).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.dialogTitle = '新增图纸优化对比'
|
||||
this.form = { projectId: this.currentProjectId, drawingName: '', oldVersion: 'V1.0', newVersion: 'V2.0', optimizer: '', compareDate: '', beforeDesc: '', afterDesc: '', diffNotes: '', status: 'pending' }
|
||||
this.dialogVisible = true; this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑图纸对比'
|
||||
getDrawingCompare(row.compareId).then(r => { this.form = r.data; this.dialogVisible = true; this.$nextTick(() => { this.$refs.formRef?.clearValidate() }) })
|
||||
},
|
||||
handleView(row) {
|
||||
this.viewItem = row; this.viewVisible = true
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.drawingName}" 的对比记录?`, '提示', { type: 'warning' }).then(() => { delDrawingCompare(row.compareId).then(() => { this.$message.success('删除成功'); this.loadList() }) })
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(v => {
|
||||
if (!v) return; this.submitting = true
|
||||
const api = this.form.compareId ? updateDrawingCompare : addDrawingCompare
|
||||
api(this.form).then(() => { this.$message.success('保存成功'); this.dialogVisible = false; this.loadList() }).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
statusTag(s) { return { pending: 'info', approved: 'success', rejected: 'danger' }[s] || 'info' },
|
||||
statusLabel(s) { return { pending: '待确认', approved: '已确认', rejected: '已驳回' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.compare-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; border: 1px solid #d0d7de; border-radius: 4px; overflow: hidden; font-size: 13px; }
|
||||
.cg-header { background: #f0f2f5; font-weight: 600; padding: 10px; border-bottom: 1px solid #d0d7de; }
|
||||
.cg-label { background: #fafbfc; font-weight: 600; padding: 10px; border-bottom: 1px solid #eee; }
|
||||
.cg-cell { padding: 10px; border-bottom: 1px solid #eee; }
|
||||
.cg-ok { color: #67c23a; font-weight: 600; }
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸详细设计</span>
|
||||
@@ -140,6 +141,7 @@ export default {
|
||||
name: 'RmDrawingDesign',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
@@ -155,14 +157,21 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.query.projectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸审查</span>
|
||||
@@ -98,6 +99,7 @@ export default {
|
||||
name: 'RmDrawingReview',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
@@ -113,14 +115,21 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.query.projectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
|
||||
152
ruoyi-ui/src/views/rm/installFeedback/index.vue
Normal file
152
ruoyi-ui/src/views/rm/installFeedback/index.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>💬 安装时出现的问题反馈</span>
|
||||
<el-button size="small" type="primary" @click="handleAdd">+ 反馈问题</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="title" label="问题标题" min-width="160" />
|
||||
<el-table-column prop="location" label="发生位置" width="120" />
|
||||
<el-table-column prop="proposer" label="反馈人" width="80" />
|
||||
<el-table-column prop="feedbackDate" label="反馈日期" width="100" align="center" />
|
||||
<el-table-column label="处理状态" width="90" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="statusTag(s.row.status)" size="mini">{{ statusLabel(s.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="560px" 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.title" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>发生位置</label><el-input v-model="form.location" placeholder="如:主轧机底座安装" /></div>
|
||||
<div class="form-group"><label>反馈人</label><el-input v-model="form.proposer" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>问题描述</label><el-input v-model="form.issueDesc" type="textarea" :rows="2" /></div>
|
||||
<div class="form-group"><label>解决方案</label><el-input v-model="form.solution" type="textarea" :rows="2" /></div>
|
||||
<div class="form-group"><label>防止再发措施</label><el-input v-model="form.preventAction" type="textarea" :rows="2" placeholder="防止同类问题再次发生的措施" /></div>
|
||||
<div class="form-group"><label>处理状态</label>
|
||||
<el-select v-model="form.status" style="width:100%;">
|
||||
<el-option label="待处理" value="pending" />
|
||||
<el-option label="处理中" value="processing" />
|
||||
<el-option label="已解决" value="resolved" />
|
||||
</el-select>
|
||||
</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 { listInstallFeedbackAll, addInstallFeedback, updateInstallFeedback, delInstallFeedback } from '@/api/rm/installFeedback'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmInstallFeedback',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
currentProjectId: null,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: { title: '', location: '', proposer: '', issueDesc: '', solution: '', preventAction: '', status: 'pending' },
|
||||
rules: { title: [{ required: true, message: '请填写问题标题', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
statusTag(status) {
|
||||
return { pending: 'warning', processing: 'primary', resolved: 'success' }[status] || ''
|
||||
},
|
||||
statusLabel(status) {
|
||||
return { pending: '待处理', processing: '处理中', resolved: '已解决' }[status] || status
|
||||
},
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadData()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadData() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
listInstallFeedbackAll({ projectId: this.currentProjectId }).then(res => {
|
||||
this.list = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() {
|
||||
this.dialogTitle = '反馈问题'
|
||||
this.form = { title: '', location: '', proposer: '', issueDesc: '', solution: '', preventAction: '', status: 'pending' }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '处理问题反馈'
|
||||
this.form = {
|
||||
feedbackId: row.feedbackId,
|
||||
title: row.title,
|
||||
location: row.location,
|
||||
proposer: row.proposer,
|
||||
issueDesc: row.issueDesc,
|
||||
solution: row.solution,
|
||||
preventAction: row.preventAction,
|
||||
status: row.status
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.form, projectId: this.currentProjectId }
|
||||
const action = data.feedbackId ? updateInstallFeedback(data) : addInstallFeedback(data)
|
||||
action.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除?', '提示', { type: 'warning' }).then(() => {
|
||||
delInstallFeedback(row.feedbackId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
onClosed() { this.$refs.form?.clearValidate() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; overflow: hidden; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 12px; }
|
||||
.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>
|
||||
70
ruoyi-ui/src/views/rm/installPrep/index.vue
Normal file
70
ruoyi-ui/src/views/rm/installPrep/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>🛠️ 安装前准备</span>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-tabs v-model="activeTab" @tab-click="onTabClick">
|
||||
<el-tab-pane label="🔧 工具准备" name="tools">
|
||||
<install-tools ref="toolsTab" :project-id="currentProjectId" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="👷 安装人员" name="personnel">
|
||||
<install-personnel ref="personnelTab" :project-id="currentProjectId" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="📏 安装精度" name="precision">
|
||||
<install-precision ref="precisionTab" :project-id="currentProjectId" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="📊 安装进度" name="progress">
|
||||
<install-progress ref="progressTab" :project-id="currentProjectId" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProject } from '@/api/rm/project'
|
||||
import InstallTools from './tools.vue'
|
||||
import InstallPersonnel from './personnel.vue'
|
||||
import InstallPrecision from './precision.vue'
|
||||
import InstallProgress from './progress.vue'
|
||||
|
||||
export default {
|
||||
name: 'RmInstallPrep',
|
||||
components: { InstallTools, InstallPersonnel, InstallPrecision, InstallProgress },
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
activeTab: 'tools',
|
||||
currentProjectId: null
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId }
|
||||
})
|
||||
}
|
||||
},
|
||||
onTabClick() {
|
||||
// Child components load data reactively via projectId watcher
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; overflow: hidden; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 12px; }
|
||||
</style>
|
||||
141
ruoyi-ui/src/views/rm/installPrep/personnel.vue
Normal file
141
ruoyi-ui/src/views/rm/installPrep/personnel.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Stats -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card"><div class="label">人员总数</div><div class="value">{{ personnel.length }}</div><div class="sub">人</div></div>
|
||||
<div class="stat-card green"><div class="label">人工费总预算</div><div class="value">¥{{ totalWages }}</div><div class="sub">含全部人员</div></div>
|
||||
<div class="stat-card orange"><div class="label">总出勤天数</div><div class="value">{{ totalDays }}</div><div class="sub">人·天</div></div>
|
||||
<div class="stat-card"><div class="label">日均工资</div><div class="value">¥{{ avgWage }}</div><div class="sub">人均</div></div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button size="mini" type="primary" @click="handleAdd">+ 添加人员</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<el-table :data="personnel" v-loading="loading" stripe border size="small" style="width:100%;">
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="name" label="姓名" width="80" />
|
||||
<el-table-column prop="position" label="岗位" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="positionEn" label="Position" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="planIn" label="计划入场" width="90" />
|
||||
<el-table-column prop="planOut" label="计划退场" width="90" />
|
||||
<el-table-column prop="days" label="天数" width="55" />
|
||||
<el-table-column prop="dailyRate" label="日工资" width="80" align="right" />
|
||||
<el-table-column prop="totalWages" label="总工资" width="90" align="right" />
|
||||
<el-table-column prop="duty" label="主要职责" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="qualification" label="资质要求" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="phone" label="电话" width="100" />
|
||||
<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>
|
||||
<div v-if="personnel.length===0" style="text-align:center;color:#aaa;padding:20px;">暂无人员数据</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body @closed="onClosed">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:1;"><label>姓名</label><el-input v-model="form.name" /></div>
|
||||
<div class="form-group" style="flex:1;"><label>岗位(中文)</label><el-input v-model="form.position" /></div>
|
||||
<div class="form-group" style="flex:1;"><label>Position (EN)</label><el-input v-model="form.positionEn" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>计划入场</label><el-input v-model="form.planIn" placeholder="如:D+0(开工)" /></div>
|
||||
<div class="form-group"><label>计划退场</label><el-input v-model="form.planOut" placeholder="如:D+82" /></div>
|
||||
<div class="form-group"><label>在岗天数</label><el-input v-model="form.days" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>日工资(元)</label><el-input v-model="form.dailyRate" @input="autoCalcWages" /></div>
|
||||
<div class="form-group"><label>总工资(元)</label><el-input v-model="form.totalWages" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>主要职责</label><el-input v-model="form.duty" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>资质要求</label><el-input v-model="form.qualification" /></div>
|
||||
<div class="form-group"><label>联系电话</label><el-input v-model="form.phone" /></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 { listInstallPersonnelAll, addInstallPersonnel, updateInstallPersonnel, delInstallPersonnel } from '@/api/rm/installPersonnel'
|
||||
|
||||
export default {
|
||||
name: 'InstallPersonnel',
|
||||
props: { projectId: { type: [Number, String], default: null } },
|
||||
watch: { projectId: { immediate: true, handler(v) { if (v) this.loadData() } } },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
personnel: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: this.cleanForm(),
|
||||
rules: { name: [{ required: true, message: '请填写姓名', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalWages() { return this.personnel.reduce((s,p) => s+(parseFloat(p.totalWages)||0), 0).toLocaleString() },
|
||||
totalDays() { return this.personnel.reduce((s,p) => s+(parseInt(p.days)||0), 0) },
|
||||
avgWage() {
|
||||
const n = this.personnel.length
|
||||
if (!n) return '0'
|
||||
const total = this.personnel.reduce((s,p) => s+(parseFloat(p.totalWages)||0), 0)
|
||||
return Math.round(total/n).toLocaleString()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanForm() { return { name:'', position:'', positionEn:'', planIn:'', planOut:'', days:'', dailyRate:'', totalWages:'', duty:'', qualification:'', phone:'', remark:'' } },
|
||||
loadData() {
|
||||
if (!this.projectId) return
|
||||
this.loading = true
|
||||
listInstallPersonnelAll({ projectId: this.projectId }).then(res => {
|
||||
this.personnel = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
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(() => {
|
||||
delInstallPersonnel(row.personnelId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
autoCalcWages() {
|
||||
const days = parseInt(this.form.days)||0
|
||||
const rate = parseFloat(this.form.dailyRate)||0
|
||||
if (days && rate) this.form.totalWages = (days * rate).toString()
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.form, projectId: this.projectId }
|
||||
const act = data.personnelId ? updateInstallPersonnel(data) : addInstallPersonnel(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>
|
||||
194
ruoyi-ui/src/views/rm/installPrep/precision.vue
Normal file
194
ruoyi-ui/src/views/rm/installPrep/precision.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Stats -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card"><div class="label">精度项总数</div><div class="value">{{ precision.length }}</div><div class="sub">{{ groupKeys.length }} 个子系统</div></div>
|
||||
<div class="stat-card green"><div class="label">合格率</div><div class="value">{{ passRate }}%</div><div class="sub">{{ passCount }}/{{ precision.length }} 项合格</div></div>
|
||||
<div class="stat-card orange"><div class="label">关键项(★★★)</div><div class="value">{{ criticalCount }}</div><div class="sub">必须全部合格</div></div>
|
||||
<div class="stat-card"><div class="label">待检项</div><div class="value">{{ precision.length - passCount }}</div><div class="sub">项</div></div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button size="mini" type="primary" @click="handleAdd">+ 添加精度项</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Grouped sections -->
|
||||
<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 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%;">
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="itemName" label="精度项目" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="nameEn" label="English" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="targetValue" label="目标值" width="90" />
|
||||
<el-table-column prop="unit" label="单位" width="50" />
|
||||
<el-table-column label="重要性" width="70" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag v-if="s.row.importance==='★★★'" size="mini" type="danger">关键</el-tag>
|
||||
<el-tag v-else-if="s.row.importance==='★★'" size="mini" type="warning">重要</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tool" label="检测工具" width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="methodDesc" label="检测方法" width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="standardRef" label="依据标准" width="90" />
|
||||
<el-table-column prop="actualValue" label="实测值" width="80" />
|
||||
<el-table-column label="状态" width="60" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.isQualified==='1'?'success':'info'" size="mini">{{ s.row.isQualified==='1'?'合格':'待检' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="照片" width="60" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag v-if="s.row.photos" size="mini" type="success" style="cursor:pointer;" @click="viewPhotos(s.row)">📷 {{ (s.row.photos||'').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>
|
||||
</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body @closed="onClosed">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:0 0 140px;"><label>子系统</label><el-select v-model="form.systemName" style="width:100%;"><el-option v-for="s in systems" :key="s" :label="s" :value="s" /></el-select></div>
|
||||
<div class="form-group" style="flex:1;"><label>精度项目(中文)</label><el-input v-model="form.itemName" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>English Name</label><el-input v-model="form.nameEn" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>目标值</label><el-input v-model="form.targetValue" /></div>
|
||||
<div class="form-group" style="flex:0 0 70px;"><label>单位</label><el-input v-model="form.unit" /></div>
|
||||
<div class="form-group" style="flex:0 0 120px;"><label>重要性</label><el-select v-model="form.importance" style="width:100%;"><el-option label="★★★ 关键" value="★★★" /><el-option label="★★ 重要" value="★★" /><el-option label="★ 一般" value="★" /></el-select></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>检测工具</label><el-input v-model="form.tool" /></div>
|
||||
<div class="form-group"><label>检测方法</label><el-input v-model="form.methodDesc" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>依据标准</label><el-input v-model="form.standardRef" /></div>
|
||||
<div class="form-group"><label>实测值</label><el-input v-model="form.actualValue" /></div>
|
||||
</div>
|
||||
<div style="margin-top:8px;">
|
||||
<el-checkbox v-model="form._qualified">合格</el-checkbox>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:8px;">
|
||||
<label>📷 检测照片(文件名,用 ; 分隔多个)</label>
|
||||
<el-input v-model="form.photos" />
|
||||
</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>
|
||||
|
||||
<!-- Photos dialog -->
|
||||
<el-dialog title="检测照片" :visible.sync="photoVisible" width="400px" append-to-body>
|
||||
<div v-if="photoList.length===0" style="text-align:center;color:#aaa;">暂无照片</div>
|
||||
<div v-for="(f,i) in photoList" :key="i" class="photo-item">📷 {{ f }}</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listInstallPrecisionAll, addInstallPrecision, updateInstallPrecision, delInstallPrecision } from '@/api/rm/installPrecision'
|
||||
|
||||
export default {
|
||||
name: 'InstallPrecision',
|
||||
props: { projectId: { type: [Number, String], default: null } },
|
||||
watch: { projectId: { immediate: true, handler(v) { if (v) this.loadData() } } },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
precision: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: this.cleanForm(),
|
||||
rules: { itemName: [{ required: true, message: '请填写精度项目', trigger: 'blur' }] },
|
||||
systems: ['轧辊系统','AGC系统','主机框架','液压系统','电气系统','辅助设备','冷却润滑','安全装置'],
|
||||
photoVisible: false,
|
||||
photoList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
grouped() {
|
||||
const map = {}
|
||||
this.precision.forEach(p => { const s = p.systemName||'其他'; if (!map[s]) map[s]=[]; map[s].push(p) })
|
||||
return map
|
||||
},
|
||||
groupKeys() { return Object.keys(this.grouped) },
|
||||
passCount() { return this.precision.filter(p => p.isQualified==='1').length },
|
||||
criticalCount() { return this.precision.filter(p => p.importance==='★★★').length },
|
||||
passRate() {
|
||||
const n = this.precision.length
|
||||
return n ? Math.round(this.passCount/n*100) : 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sysIcon(s) { return { '轧辊系统':'🔄','AGC系统':'📐','主机框架':'🏛️','液压系统':'💧','电气系统':'⚡','辅助设备':'🔩','冷却润滑':'❄️','安全装置':'🛡️' }[s]||'📌' },
|
||||
cleanForm() { return { systemName:'轧辊系统', itemName:'', nameEn:'', targetValue:'', unit:'mm', importance:'★★★', tool:'', methodDesc:'', standardRef:'', actualValue:'', _qualified:false, photos:'' } },
|
||||
loadData() {
|
||||
if (!this.projectId) return
|
||||
this.loading = true
|
||||
listInstallPrecisionAll({ projectId: this.projectId }).then(res => {
|
||||
this.precision = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() { this.dialogTitle='添加精度项'; this.form=this.cleanForm(); this.dialogVisible=true },
|
||||
handleEdit(row) {
|
||||
this.dialogTitle='编辑精度项'
|
||||
this.form = { ...row, _qualified: row.isQualified==='1' }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除?','提示',{type:'warning'}).then(() => {
|
||||
delInstallPrecision(row.precisionId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
viewPhotos(row) {
|
||||
const list = (row.photos||'').split(';').filter(Boolean)
|
||||
if (list.length === 0) { this.$message.info('暂无检测照片'); return }
|
||||
this.photoList = list
|
||||
this.photoVisible = true
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = {
|
||||
...this.form,
|
||||
projectId: this.projectId,
|
||||
isQualified: this.form._qualified ? '1' : '0'
|
||||
}
|
||||
delete data._qualified
|
||||
const act = data.precisionId ? updateInstallPrecision(data) : addInstallPrecision(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; }
|
||||
.cat-section { margin-bottom: 12px; border: 1px solid #e9ecef; border-radius: 6px; overflow: hidden; }
|
||||
.cat-header { padding: 6px 10px; background: #f8f9fa; font-size: 13px; font-weight: 600; display: flex; justify-content: space-between; border-bottom: 1px solid #e9ecef; }
|
||||
.cat-count { font-size: 11px; color: #6c757d; font-weight: 400; }
|
||||
.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; }
|
||||
.photo-item { padding: 4px 0; font-size: 12px; }
|
||||
</style>
|
||||
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>
|
||||
162
ruoyi-ui/src/views/rm/installPrep/tools.vue
Normal file
162
ruoyi-ui/src/views/rm/installPrep/tools.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Stats -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card"><div class="label">工具总项数</div><div class="value">{{ tools.length }}</div><div class="sub">{{ groupKeys.length }} 个分类</div></div>
|
||||
<div class="stat-card green"><div class="label">工具总金额</div><div class="value">¥{{ totalAmount }}</div><div class="sub">预估采购总价</div></div>
|
||||
<div class="stat-card orange"><div class="label">关键工具(★★)</div><div class="value">{{ criticalCount }}</div><div class="sub">必须到位</div></div>
|
||||
<div class="stat-card"><div class="label">已到位</div><div class="value">{{ arrivedCount }}/{{ tools.length }}</div><div class="sub">项</div></div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button size="mini" type="primary" @click="handleAdd">+ 添加工具</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Grouped sections -->
|
||||
<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 class="cat-count">{{ grp.length }}项 · ¥{{ groupTotal(grp).toLocaleString() }}</span>
|
||||
</div>
|
||||
<el-table :data="grp" stripe border size="small" style="width:100%;">
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="name" label="工具名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="spec" label="规格型号" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="qty" label="数量" width="50" />
|
||||
<el-table-column prop="unit" label="单位" width="50" />
|
||||
<el-table-column prop="unitPrice" label="单价" width="80" align="right" />
|
||||
<el-table-column prop="totalPrice" label="总价" width="90" align="right" />
|
||||
<el-table-column label="重要" width="65" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag v-if="s.row.priority==='★★'" size="mini" type="danger">关键</el-tag>
|
||||
<el-tag v-else-if="s.row.priority==='★'" size="mini" type="warning">重要</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="purpose" label="用途" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="responsible" label="责任人" width="70" />
|
||||
<el-table-column label="状态" width="70" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.status==='已到位'?'success':s.row.status==='已确认'?'':'info'" size="mini">{{ s.row.status||'待确认' }}</el-tag>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body @closed="onClosed">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:1;"><label>工具名称</label><el-input v-model="form.name" /></div>
|
||||
<div class="form-group" style="flex:0 0 140px;"><label>分类</label><el-select v-model="form.category" style="width:100%;"><el-option v-for="c in categories" :key="c" :label="c" :value="c" /></el-select></div>
|
||||
</div>
|
||||
<div class="form-group"><label>英文名称</label><el-input v-model="form.nameEn" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:1;"><label>规格型号</label><el-input v-model="form.spec" /></div>
|
||||
<div class="form-group" style="flex:0 0 70px;"><label>数量</label><el-input v-model="form.qty" /></div>
|
||||
<div class="form-group" style="flex:0 0 70px;"><label>单位</label><el-input v-model="form.unit" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>单价(元)</label><el-input v-model="form.unitPrice" @input="autoCalcTotal" /></div>
|
||||
<div class="form-group"><label>总价(元)</label><el-input v-model="form.totalPrice" /></div>
|
||||
<div class="form-group"><label>重要程度</label><el-select v-model="form.priority" style="width:100%;" clearable><el-option label="普通" value="" /><el-option label="★ 重要" value="★" /><el-option label="★★ 关键" value="★★" /></el-select></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>到位日期</label><el-date-picker v-model="form.arrivalDate" type="date" value-format="yyyy-MM-dd" style="width:100%;" /></div>
|
||||
<div class="form-group"><label>责任人</label><el-input v-model="form.responsible" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>主要用途</label><el-input v-model="form.purpose" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>状态</label><el-select v-model="form.status" style="width:100%;"><el-option label="待确认" value="待确认" /><el-option label="已确认" value="已确认" /><el-option label="已到位" value="已到位" /><el-option label="已取消" value="已取消" /></el-select></div>
|
||||
<div class="form-group"><label>备注</label><el-input v-model="form.remark" /></div>
|
||||
</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 { listInstallToolAll, addInstallTool, updateInstallTool, delInstallTool } from '@/api/rm/installTool'
|
||||
|
||||
export default {
|
||||
name: 'InstallTools',
|
||||
props: { projectId: { type: [Number, String], default: null } },
|
||||
watch: { projectId: { immediate: true, handler(v) { if (v) this.loadData() } } },
|
||||
data() {
|
||||
return {
|
||||
tools: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: this.cleanForm(),
|
||||
rules: { name: [{ required: true, message: '请填写工具名称', trigger: 'blur' }] },
|
||||
categories: ['起重吊装','测量仪器','机械安装','液压专用','电气安装','其他']
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
grouped() {
|
||||
const map = {}
|
||||
this.tools.forEach(t => { const c = t.category||'其他'; if (!map[c]) map[c]=[]; map[c].push(t) })
|
||||
return map
|
||||
},
|
||||
groupKeys() { return Object.keys(this.grouped) },
|
||||
totalAmount() { return this.tools.reduce((s,t) => s+(parseFloat(t.totalPrice)||0), 0).toLocaleString() },
|
||||
criticalCount() { return this.tools.filter(t => t.priority==='★★').length },
|
||||
arrivedCount() { return this.tools.filter(t => t.status==='已到位').length }
|
||||
},
|
||||
methods: {
|
||||
catIcon(cat) { return { '起重吊装':'🏗️','测量仪器':'📐','机械安装':'🔩','液压专用':'💧','电气安装':'⚡' }[cat]||'📌' },
|
||||
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() {
|
||||
if (!this.projectId) return
|
||||
listInstallToolAll({ projectId: this.projectId }).then(res => { this.tools = res.data || [] })
|
||||
},
|
||||
handleAdd() { this.dialogTitle='添加工具'; this.form=this.cleanForm(); this.dialogVisible=true },
|
||||
handleEdit(row) { this.dialogTitle='编辑工具'; this.form={...row, arrivalDate: row.arrivalDate||''}; this.dialogVisible=true },
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除?','提示',{type:'warning'}).then(() => {
|
||||
delInstallTool(row.toolId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
autoCalcTotal() {
|
||||
const qty = parseFloat(this.form.qty)||0
|
||||
const price = parseFloat(this.form.unitPrice)||0
|
||||
if (qty && price) this.form.totalPrice = (qty * price).toString()
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.form, projectId: this.projectId }
|
||||
const act = data.toolId ? updateInstallTool(data) : addInstallTool(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; }
|
||||
.cat-section { margin-bottom: 12px; border: 1px solid #e9ecef; border-radius: 6px; overflow: hidden; }
|
||||
.cat-header { padding: 6px 10px; background: #f8f9fa; font-size: 13px; font-weight: 600; display: flex; justify-content: space-between; border-bottom: 1px solid #e9ecef; }
|
||||
.cat-count { font-size: 11px; color: #6c757d; font-weight: 400; }
|
||||
.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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>布局图确定</span>
|
||||
@@ -115,6 +116,7 @@ export default {
|
||||
name: 'RmLayoutFile',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
@@ -130,14 +132,21 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.query.projectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
|
||||
148
ruoyi-ui/src/views/rm/manual/index.vue
Normal file
148
ruoyi-ui/src/views/rm/manual/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>📖 设备说明书和图纸</span>
|
||||
<el-button size="small" type="primary" @click="handleAdd">+ 上传说明书/图纸</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="manualName" label="文件名称" min-width="160" />
|
||||
<el-table-column prop="docType" label="类型" width="100" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag size="mini" :type="tagType(s.row.docType)">{{ s.row.docType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本" width="70" align="center" />
|
||||
<el-table-column prop="uploadDate" label="上传日期" width="100" align="center" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.manualName" placeholder="如:1380mm轧机操作说明书" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>类型</label>
|
||||
<el-select v-model="form.docType" style="width:100%;">
|
||||
<el-option label="说明书" value="说明书" />
|
||||
<el-option label="图纸" value="图纸" />
|
||||
<el-option label="维护手册" value="维护手册" />
|
||||
<el-option label="备件清单" value="备件清单" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-group"><label>版本</label><el-input v-model="form.version" placeholder="V1.0" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>上传日期</label><el-input v-model="form.uploadDate" placeholder="yyyy-MM-dd" /></div>
|
||||
<div class="form-group"><label>文件链接/路径</label><el-input v-model="form.fileUrl" placeholder="文件URL或路径" /></div>
|
||||
<div class="form-group"><label>描述</label><el-input v-model="form.description" type="textarea" :rows="2" /></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 { listManualAll, addManual, updateManual, delManual } from '@/api/rm/manual'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmManual',
|
||||
data() {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
currentProjectId: null,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
form: { manualName: '', docType: '说明书', version: 'V1.0', uploadDate: today, fileUrl: '', description: '' },
|
||||
rules: { manualName: [{ required: true, message: '请填写文件名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
tagType(type) {
|
||||
return { '说明书': 'primary', '图纸': 'success', '维护手册': 'warning', '备件清单': 'info' }[type] || ''
|
||||
},
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadData()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadData() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
listManualAll({ projectId: this.currentProjectId }).then(res => {
|
||||
this.list = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() {
|
||||
this.dialogTitle = '上传说明书/图纸'
|
||||
this.form = { manualName: '', docType: '说明书', version: 'V1.0', uploadDate: new Date().toISOString().slice(0, 10), fileUrl: '', description: '' }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑'
|
||||
this.form = {
|
||||
manualId: row.manualId,
|
||||
manualName: row.manualName,
|
||||
docType: row.docType,
|
||||
version: row.version,
|
||||
uploadDate: row.uploadDate,
|
||||
fileUrl: row.fileUrl,
|
||||
description: row.description
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
save() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
const data = { ...this.form, projectId: this.currentProjectId }
|
||||
const action = data.manualId ? updateManual(data) : addManual(data)
|
||||
action.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除?', '提示', { type: 'warning' }).then(() => {
|
||||
delManual(row.manualId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
onClosed() { this.$refs.form?.clearValidate() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; overflow: hidden; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 12px; }
|
||||
.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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>设备制造进度</span>
|
||||
@@ -118,6 +119,7 @@ export default {
|
||||
name: 'RmManufacturing',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
deviceList: [],
|
||||
stageMap: {},
|
||||
@@ -132,13 +134,19 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadDevices()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadDevices()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadDevices()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadDevices() {
|
||||
if (!this.currentProjectId) return
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>采购管理</span>
|
||||
@@ -238,6 +239,7 @@ export default {
|
||||
name: 'RmProcurement',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
activeTab: 'quotes',
|
||||
currentProjectId: null,
|
||||
// Quotes
|
||||
@@ -286,13 +288,19 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadAll()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadAll()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadAll()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadAll() {
|
||||
this.loadQuotes(); this.loadContracts(); this.loadProgress()
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
<template>
|
||||
<div class="dashboard-page">
|
||||
<!-- Project Selector Bar -->
|
||||
<div class="project-selector-bar">
|
||||
<span class="selector-label">当前项目:</span>
|
||||
<el-select
|
||||
v-model="currentProjectId"
|
||||
placeholder="请选择项目"
|
||||
size="small"
|
||||
style="width: 320px;"
|
||||
filterable
|
||||
@change="onProjectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in projectList"
|
||||
:key="p.projectId"
|
||||
:label="p.projectName + ' (' + p.projectNo + ')'"
|
||||
:value="p.projectId"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" style="margin-left: 8px;" @click="createProject">新建项目</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card">
|
||||
@@ -7,7 +28,7 @@
|
||||
<div class="value" style="font-size:14px;color:#1a1a2e;">{{ projectInfo.projectName || '未设置' }}</div>
|
||||
<div class="sub">
|
||||
编号: {{ projectInfo.projectNo || '-' }}
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="openProjectInfoModal" style="color:#2176ae;" />
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="editProject" style="color:#2176ae;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card green">
|
||||
@@ -70,7 +91,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Project Info Edit Modal -->
|
||||
<el-dialog title="项目信息设置" :visible.sync="dialogVisible" width="600px" append-to-body>
|
||||
<el-dialog :title="isCreating ? '新建项目' : '项目信息设置'" :visible.sync="dialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
@@ -91,8 +112,25 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目经理" prop="manager">
|
||||
<el-input v-model="form.manager" placeholder="请输入项目经理" />
|
||||
<el-form-item label="项目经理" prop="managerId">
|
||||
<el-select
|
||||
v-model="form.managerId"
|
||||
placeholder="请搜索并选择项目经理"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="searchUsers"
|
||||
:loading="userSearchLoading"
|
||||
style="width:100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.userId"
|
||||
:label="u.nickName + ' (' + u.userName + ')'"
|
||||
:value="u.userId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -121,7 +159,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProject, updateProject, listProject, addProject } from '@/api/rm/project'
|
||||
import { getProject, updateProject, listProject, addProject, allProject } from '@/api/rm/project'
|
||||
import { getStageStatus } from '@/api/rm/dashboard'
|
||||
import { selectUser } from '@/api/system/user'
|
||||
|
||||
const STAGES = [
|
||||
{ key: 'budget', label: '项目预算', icon: '💰' },
|
||||
@@ -159,7 +199,11 @@ export default {
|
||||
clientName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }]
|
||||
},
|
||||
// Default project will be fetched from backend
|
||||
currentProjectId: null
|
||||
currentProjectId: null,
|
||||
projectList: [],
|
||||
userOptions: [],
|
||||
userSearchLoading: false,
|
||||
isCreating: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -184,47 +228,95 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadProject()
|
||||
this.loadProjectList()
|
||||
},
|
||||
methods: {
|
||||
loadProject() {
|
||||
// Try to get the first project as default
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
return getProject(rows[0].projectId)
|
||||
loadProjectList() {
|
||||
allProject({}).then(res => {
|
||||
this.projectList = res.data || []
|
||||
if (this.projectList.length > 0) {
|
||||
// Try sessionStorage first, fall back to first project
|
||||
const savedId = sessionStorage.getItem('rm_current_project_id')
|
||||
if (savedId && this.projectList.find(p => p.projectId === savedId)) {
|
||||
this.currentProjectId = savedId
|
||||
} else {
|
||||
this.currentProjectId = this.projectList[0].projectId
|
||||
}
|
||||
sessionStorage.setItem('rm_current_project_id', this.currentProjectId)
|
||||
this.loadProjectInfo()
|
||||
this.loadStageStatus()
|
||||
} else {
|
||||
// No project exists yet, show empty form
|
||||
this.projectInfo = {}
|
||||
return null
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
this.projectInfo = res.data
|
||||
this.currentProjectId = null
|
||||
}
|
||||
})
|
||||
},
|
||||
openProjectInfoModal() {
|
||||
// If no project exists, check if we need to create one first
|
||||
if (!this.currentProjectId) {
|
||||
this.$confirm('暂无项目数据,是否先创建一个默认项目?', '提示', { type: 'info' }).then(() => {
|
||||
addProject({
|
||||
projectName: '1380mm六辊可逆轧机设备总包项目',
|
||||
projectNo: 'DRF-2026-001',
|
||||
clientName: '昆山德睿福成套设备有限公司',
|
||||
startDate: '2026-06-01',
|
||||
endDate: '2027-02-28',
|
||||
manager: '工程师',
|
||||
remark: ''
|
||||
}).then(res => {
|
||||
this.$message.success('默认项目已创建')
|
||||
this.loadProject()
|
||||
})
|
||||
})
|
||||
loadProjectInfo() {
|
||||
if (!this.currentProjectId) return
|
||||
getProject(this.currentProjectId).then(res => {
|
||||
if (res.data) {
|
||||
this.projectInfo = res.data
|
||||
sessionStorage.setItem('rm_current_project_name', res.data.projectName || '')
|
||||
}
|
||||
})
|
||||
},
|
||||
onProjectChange(projectId) {
|
||||
if (projectId) {
|
||||
sessionStorage.setItem('rm_current_project_id', projectId)
|
||||
sessionStorage.removeItem('rm_current_project_name')
|
||||
this.loadProjectInfo()
|
||||
this.loadStageStatus()
|
||||
}
|
||||
},
|
||||
loadStageStatus() {
|
||||
if (!this.currentProjectId) return
|
||||
getStageStatus(this.currentProjectId).then(res => {
|
||||
this.stageStatus = res.data || {}
|
||||
})
|
||||
},
|
||||
searchUsers(query) {
|
||||
if (query === '') {
|
||||
this.userOptions = []
|
||||
return
|
||||
}
|
||||
this.form = { ...this.projectInfo }
|
||||
this.userSearchLoading = true
|
||||
selectUser({ nickName: query, pageNum: 1, pageSize: 20 }).then(res => {
|
||||
this.userOptions = res.rows || []
|
||||
}).finally(() => {
|
||||
this.userSearchLoading = false
|
||||
})
|
||||
},
|
||||
createProject() {
|
||||
this.isCreating = true
|
||||
this.form = {
|
||||
projectName: '',
|
||||
projectNo: '',
|
||||
clientName: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
managerId: null,
|
||||
remark: ''
|
||||
}
|
||||
this.userOptions = []
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
editProject() {
|
||||
this.isCreating = false
|
||||
this.form = {
|
||||
...this.projectInfo,
|
||||
managerId: this.projectInfo.managerId || null
|
||||
}
|
||||
// Pre-populate user select with current manager
|
||||
if (this.projectInfo.managerId && this.projectInfo.managerName) {
|
||||
this.userOptions = [{
|
||||
userId: this.projectInfo.managerId,
|
||||
nickName: this.projectInfo.managerName,
|
||||
userName: ''
|
||||
}]
|
||||
} else {
|
||||
this.userOptions = []
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
@@ -232,14 +324,24 @@ export default {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
this.form.projectId = this.currentProjectId
|
||||
updateProject(this.form).then(() => {
|
||||
this.$message.success('项目信息已更新')
|
||||
this.dialogVisible = false
|
||||
Object.assign(this.projectInfo, this.form)
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
if (this.isCreating) {
|
||||
addProject(this.form).then(() => {
|
||||
this.$message.success('项目已创建')
|
||||
this.dialogVisible = false
|
||||
this.loadProjectList()
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
} else {
|
||||
this.form.projectId = this.currentProjectId
|
||||
updateProject(this.form).then(() => {
|
||||
this.$message.success('项目信息已更新')
|
||||
this.dialogVisible = false
|
||||
this.loadProjectList()
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
stageClass(key) {
|
||||
@@ -258,8 +360,28 @@ export default {
|
||||
return map[this.stageStatus[key]] || '未开始'
|
||||
},
|
||||
switchStage(key) {
|
||||
this.$message.info(`"${STAGES.find(s => s.key === key).label}" 功能开发中`)
|
||||
}
|
||||
const routeMap = {
|
||||
budget: '/zongbao/budget',
|
||||
tech_plan: '/zongbao/tech-group/techPlan',
|
||||
layout: '/zongbao/tech-group/layout',
|
||||
tech_review: '/zongbao/tech-group/techReview',
|
||||
drawing_design: '/zongbao/tech-group/drawingDesign',
|
||||
drawing_review: '/zongbao/tech-group/drawingReview',
|
||||
procurement: '/zongbao/procurement-group/procurement',
|
||||
manufacturing: '/zongbao/procurement-group/manufacturing',
|
||||
drawing_compare: '/zongbao/drawing-group/drawingCompare',
|
||||
doc_lib: '/zongbao/drawing-group/docLib',
|
||||
site_mod: '/zongbao/drawing-group/siteMod',
|
||||
shipping: '/zongbao/shipping-group/shipping',
|
||||
manuals: '/zongbao/shipping-group/manuals',
|
||||
install_prep: '/zongbao/shipping-group/installPrep',
|
||||
install_feedback: '/zongbao/shipping-group/installFeedback',
|
||||
acceptance: '/zongbao/shipping-group/acceptance',
|
||||
hot_commissioning: '/zongbao/shipping-group/hotCommissioning'
|
||||
}
|
||||
const path = routeMap[key]
|
||||
if (path) this.$router.push({ path, query: { projectId: this.currentProjectId, projectName: this.projectInfo.projectName || '' } })
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -371,4 +493,21 @@ export default {
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
.project-selector-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 8px 14px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #d0d7de;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
.project-selector-bar .selector-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
margin-right: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
277
ruoyi-ui/src/views/rm/shipping/index.vue
Normal file
277
ruoyi-ui/src/views/rm/shipping/index.vue
Normal file
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>📦 发货前设备清单</span>
|
||||
<el-button size="small" type="primary" @click="handleAddItem">+ 添加设备项</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
|
||||
<!-- Checklist section -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">
|
||||
📋 发货前检查清单
|
||||
<el-tag v-if="checklistAllDone" size="mini" type="success" style="margin-left:6px;">✅ 全部完成</el-tag>
|
||||
<el-tag v-else size="mini" type="warning" style="margin-left:6px;">⚠️ 尚有未完成项</el-tag>
|
||||
<el-button size="mini" type="text" icon="el-icon-plus" style="margin-left:8px;" @click="handleAddCheck">添加检查项</el-button>
|
||||
</div>
|
||||
<div v-if="checklist.length === 0" style="color:#aaa;font-size:12px;padding:8px 0;">暂无检查项</div>
|
||||
<div v-for="(it, i) in checklist" :key="i" class="checklist-item" :class="it.isChecked === '1' ? 'checked' : ''">
|
||||
<el-checkbox v-model="it._checked" @change="toggleCheck(it, i)"></el-checkbox>
|
||||
<span class="checklist-text">{{ it.itemText }}</span>
|
||||
<el-button type="text" size="mini" style="margin-left:auto;color:#999;" icon="el-icon-delete" @click="handleDeleteCheck(it, i)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- Equipment items table -->
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:13px;">📦 设备清单明细</div>
|
||||
<el-table :data="items" v-loading="loading" stripe border highlight-current-row size="small" style="width:100%;">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="deviceName" label="设备名称" min-width="130" />
|
||||
<el-table-column prop="spec" label="规格" width="120" />
|
||||
<el-table-column prop="qty" label="数量" width="60" align="center" />
|
||||
<el-table-column label="打包状态" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.packed === '1' ? 'success' : 'info'" size="mini">{{ s.row.packed === '1' ? '已打包' : '未打包' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="照片" width="70" align="center">
|
||||
<template slot-scope="s">
|
||||
<span v-if="photoCount(s.row) > 0" class="tag-media tag-media-img" @click="handleViewPhotos(s.row)">📷 {{ photoCount(s.row) }}</span>
|
||||
<span v-else class="tag-media tag-media-none">📷 0</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="note" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditItem(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteItem(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Item Dialog -->
|
||||
<el-dialog :title="itemDialogTitle" :visible.sync="itemDialogVisible" width="480px" append-to-body @closed="onItemDialogClosed">
|
||||
<el-form ref="itemFormRef" :model="itemForm" :rules="itemRules" label-width="0" size="small">
|
||||
<div class="form-group"><label>设备名称 *</label><el-input v-model="itemForm.deviceName" /></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>规格型号</label><el-input v-model="itemForm.spec" /></div>
|
||||
<div class="form-group"><label>数量</label><el-input v-model="itemForm.qty" type="number" /></div>
|
||||
</div>
|
||||
<div style="margin-top:8px;">
|
||||
<el-checkbox v-model="itemForm._packed">已打包</el-checkbox>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:10px;">
|
||||
<label>📷 设备照片(文件名,用 ; 分隔多个文件)</label>
|
||||
<el-input v-model="itemForm._photosStr" placeholder="主机整体.jpg;铭牌特写.jpg;打包外观.jpg" />
|
||||
<div v-if="itemForm._photosStr" style="font-size:10px;color:#999;margin-top:3px;">
|
||||
已上传 {{ itemForm._photosArr.length }} 张照片
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top:8px;"><label>备注</label><el-input v-model="itemForm.note" type="textarea" :rows="2" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="itemDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveItem">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Add Checklist Item Dialog -->
|
||||
<el-dialog title="添加检查项" :visible.sync="checkDialogVisible" width="400px" append-to-body>
|
||||
<el-form ref="checkFormRef" :model="checkForm" label-width="0" size="small">
|
||||
<div class="form-group"><label>检查项内容 *</label><el-input v-model="checkForm.itemText" placeholder="如:设备外观无损伤" /></div>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="checkDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveCheck">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- View Photos Dialog -->
|
||||
<el-dialog :title="photoDialogTitle" :visible.sync="photoDialogVisible" width="400px" append-to-body>
|
||||
<div style="font-size:12px;color:#999;margin-bottom:10px;">共 {{ viewingPhotos.length }} 张照片</div>
|
||||
<div v-if="viewingPhotos.length === 0" style="color:#aaa;text-align:center;padding:20px;">暂无照片</div>
|
||||
<div v-for="(p, i) in viewingPhotos" :key="i" class="evidence-item ok" style="margin-bottom:4px;">
|
||||
<span class="ev-icon">📷</span>{{ p }}
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="photoDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listShippingChecklistAll, addShippingChecklist, updateShippingChecklist, delShippingChecklist } from '@/api/rm/shippingChecklist'
|
||||
import { listShippingItemAll, addShippingItem, updateShippingItem, delShippingItem } from '@/api/rm/shippingItem'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'Shipping',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
checklist: [],
|
||||
items: [],
|
||||
currentProjectId: null,
|
||||
// item dialog
|
||||
itemDialogVisible: false,
|
||||
itemDialogTitle: '添加设备项',
|
||||
itemForm: { deviceName: '', spec: '', qty: 0, _packed: false, _photosStr: '', note: '' },
|
||||
itemRules: { deviceName: [{ required: true, message: '请填写设备名称', trigger: 'blur' }] },
|
||||
// check dialog
|
||||
checkDialogVisible: false,
|
||||
checkForm: { itemText: '' },
|
||||
// photo dialog
|
||||
photoDialogVisible: false,
|
||||
photoDialogTitle: '',
|
||||
viewingPhotos: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checklistAllDone() {
|
||||
return this.checklist.length > 0 && this.checklist.every(it => it.isChecked === '1')
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadData()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadData() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
listShippingChecklistAll({ projectId: this.currentProjectId }),
|
||||
listShippingItemAll({ projectId: this.currentProjectId })
|
||||
]).then(([clRes, itRes]) => {
|
||||
this.checklist = (clRes.data || []).map(it => ({ ...it, _checked: it.isChecked === '1' }))
|
||||
this.items = itRes.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
photoCount(row) {
|
||||
if (!row.photos) return 0
|
||||
try { return JSON.parse(row.photos).length } catch { return row.photos.split(';').filter(Boolean).length }
|
||||
},
|
||||
// ---- Checklist ----
|
||||
handleAddCheck() {
|
||||
this.checkForm = { itemText: '' }
|
||||
this.checkDialogVisible = true
|
||||
},
|
||||
saveCheck() {
|
||||
if (!this.checkForm.itemText) { this.$message.warning('请填写检查项内容'); return }
|
||||
addShippingChecklist({ projectId: this.currentProjectId, itemText: this.checkForm.itemText, isChecked: '0', sortOrder: this.checklist.length + 1 }).then(() => {
|
||||
this.$message.success('检查项已添加')
|
||||
this.checkDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
toggleCheck(it) {
|
||||
const val = it._checked ? '1' : '0'
|
||||
updateShippingChecklist({ checklistId: it.checklistId, projectId: this.currentProjectId, itemText: it.itemText, isChecked: val }).then(() => {
|
||||
it.isChecked = val
|
||||
})
|
||||
},
|
||||
handleDeleteCheck(it) {
|
||||
this.$confirm('确认删除该检查项?', '提示', { type: 'warning' }).then(() => {
|
||||
delShippingChecklist(it.checklistId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
// ---- Items ----
|
||||
handleAddItem() {
|
||||
this.itemDialogTitle = '添加设备项'
|
||||
this.itemForm = { deviceName: '', spec: '', qty: 0, _packed: false, _photosStr: '', note: '' }
|
||||
this.itemDialogVisible = true
|
||||
},
|
||||
handleEditItem(row) {
|
||||
this.itemDialogTitle = '编辑设备项'
|
||||
let photosStr = ''
|
||||
if (row.photos) {
|
||||
try { const arr = JSON.parse(row.photos); photosStr = arr.join(';') } catch { photosStr = row.photos }
|
||||
}
|
||||
this.itemForm = {
|
||||
itemId: row.itemId,
|
||||
deviceName: row.deviceName,
|
||||
spec: row.spec,
|
||||
qty: row.qty,
|
||||
_packed: row.packed === '1',
|
||||
_photosStr: photosStr,
|
||||
note: row.note
|
||||
}
|
||||
this.itemDialogVisible = true
|
||||
},
|
||||
saveItem() {
|
||||
this.$refs.itemFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
const photosArr = this.itemForm._photosStr ? this.itemForm._photosStr.split(';').map(f => f.trim()).filter(Boolean) : []
|
||||
const data = {
|
||||
itemId: this.itemForm.itemId,
|
||||
projectId: this.currentProjectId,
|
||||
deviceName: this.itemForm.deviceName,
|
||||
spec: this.itemForm.spec,
|
||||
qty: this.itemForm.qty,
|
||||
packed: this.itemForm._packed ? '1' : '0',
|
||||
photos: photosArr.length > 0 ? JSON.stringify(photosArr) : null,
|
||||
note: this.itemForm.note
|
||||
}
|
||||
const action = data.itemId ? updateShippingItem(data) : addShippingItem(data)
|
||||
action.then(() => {
|
||||
this.$message.success(data.itemId ? '设备项已更新' : '设备项已添加')
|
||||
this.itemDialogVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDeleteItem(row) {
|
||||
this.$confirm(`确认删除设备「${row.deviceName}」?`, '提示', { type: 'warning' }).then(() => {
|
||||
delShippingItem(row.itemId).then(() => { this.loadData() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
onItemDialogClosed() { this.$refs.itemFormRef?.clearValidate() },
|
||||
handleViewPhotos(row) {
|
||||
let arr = []
|
||||
if (row.photos) {
|
||||
try { arr = JSON.parse(row.photos) } catch { arr = row.photos.split(';').filter(Boolean) }
|
||||
}
|
||||
if (arr.length === 0) { this.$message.info('该设备暂无照片'); return }
|
||||
this.viewingPhotos = arr
|
||||
this.photoDialogTitle = `📷 ${row.deviceName} - 设备照片`
|
||||
this.photoDialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.checklist-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.checklist-item:last-child { border-bottom: none; }
|
||||
.checklist-item.checked .checklist-text { text-decoration: line-through; color: #aaa; }
|
||||
.tag-media { font-size: 12px; cursor: pointer; padding: 2px 8px; border-radius: 4px; }
|
||||
.tag-media-img { background: #e8f5e9; color: #2e7d32; }
|
||||
.tag-media-none { background: #f5f5f5; color: #999; cursor: default; }
|
||||
.tag-media:hover { opacity: 0.8; }
|
||||
.evidence-item { display: flex; align-items: center; gap: 6px; padding: 6px 10px; background: #f9f9f9; border-radius: 4px; font-size: 12px; }
|
||||
.ev-icon { font-size: 14px; }
|
||||
</style>
|
||||
417
ruoyi-ui/src/views/rm/siteMod/index.vue
Normal file
417
ruoyi-ui/src/views/rm/siteMod/index.vue
Normal file
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>现场修改管理(防止问题重复发生)</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">+ 记录现场修改</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<div class="rm-warning">
|
||||
<b>重要:</b>现场修改后的设备必须上传修改后的图纸至资料库,防止下次再次出现同样问题。所有现场修改记录将自动同步至图纸资料库。
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="deviceName" label="设备名称" min-width="130" />
|
||||
<el-table-column prop="location" label="修改位置" width="120" />
|
||||
<el-table-column prop="modReason" label="问题描述" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="modPerson" label="修改人" width="80" />
|
||||
<el-table-column prop="modDate" label="修改日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.status === 'done' ? 'success' : 'warning'" size="mini">{{ s.row.status === 'done' ? '已整改' : '待整改' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图片" width="64" align="center">
|
||||
<template slot-scope="s">
|
||||
<span class="tag-media" :class="s.row._imageCount ? 'tag-media-img' : 'tag-media-none'" @click="s.row._imageCount && handleMedia(s.row)">
|
||||
📷 {{ s.row._imageCount || 0 }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="视频" width="64" align="center">
|
||||
<template slot-scope="s">
|
||||
<span class="tag-media" :class="s.row._videoCount ? 'tag-media-vid' : 'tag-media-none'" @click="s.row._videoCount && handleMedia(s.row)">
|
||||
🎬 {{ s.row._videoCount || 0 }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="handleView(s.row)">查看</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog — matching HTML prototype -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="620px" append-to-body @closed="onDialogClosed">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="0" size="small">
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>设备名称</label><el-input v-model="form.deviceName" /></div>
|
||||
<div class="form-group"><label>修改位置/部位</label><el-input v-model="form.location" placeholder="如:主传动轴轴承座" /></div>
|
||||
</div>
|
||||
<div class="form-group"><label>现场问题描述</label><el-input v-model="form.modReason" type="textarea" :rows="2" /></div>
|
||||
<div class="form-group"><label>修改方案/解决措施</label><el-input v-model="form.solution" type="textarea" :rows="2" /></div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group sm-media-section">
|
||||
<label>📷 现场照片 <span style="font-weight:400;color:#888;">(可多选)</span></label>
|
||||
<div class="sm-upload-zone" @click="$refs.imgInput.click()">
|
||||
<div class="uz-icon">📤</div>
|
||||
<div class="uz-text">点击选择现场照片</div>
|
||||
<div class="uz-hint">支持 JPG/PNG/WebP,单张 ≤ 5MB</div>
|
||||
</div>
|
||||
<input ref="imgInput" type="file" accept="image/*" multiple style="display:none" @change="onImageSelect">
|
||||
<div v-if="existingImages.length || pendingImages.length" class="sm-media-grid">
|
||||
<div class="sm-media-thumb" v-for="(img, i) in existingImages" :key="'e'+i">
|
||||
<el-image :src="img.fileUrl" fit="cover" class="sm-thumb-img" :preview-src-list="existingPreviewList" />
|
||||
<div class="sm-thumb-name">{{ img.fileName }}</div>
|
||||
<button class="sm-thumb-remove" @click="removeExistingMedia(i, 'image')" title="移除">×</button>
|
||||
</div>
|
||||
<div class="sm-media-thumb" v-for="(img, i) in pendingImages" :key="'p'+i">
|
||||
<img :src="img.url" class="sm-thumb-img" />
|
||||
<div class="sm-thumb-name">{{ img.name }}</div>
|
||||
<button class="sm-thumb-remove" @click="removePendingMedia(i, 'image')" title="移除">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group sm-media-section">
|
||||
<label>🎬 现场视频 <span style="font-weight:400;color:#888;">(可选)</span></label>
|
||||
<div class="sm-upload-zone" @click="$refs.vidInput.click()">
|
||||
<div class="uz-icon">🎥</div>
|
||||
<div class="uz-text">点击选择现场视频</div>
|
||||
<div class="uz-hint">支持 MP4/WebM,单段 ≤ 100MB</div>
|
||||
</div>
|
||||
<input ref="vidInput" type="file" accept="video/*" style="display:none" @change="onVideoSelect">
|
||||
<div v-if="existingVideos.length || pendingVideos.length" class="sm-video-list">
|
||||
<div class="sm-video-item" v-for="(v, i) in existingVideos" :key="'e'+i">
|
||||
<span class="vi-icon">🎬</span>
|
||||
<div class="vi-info"><div class="vi-name">{{ v.fileName }}</div></div>
|
||||
<span class="vi-remove" @click="removeExistingMedia(i, 'video')" title="移除">×</span>
|
||||
</div>
|
||||
<div class="sm-video-item" v-for="(v, i) in pendingVideos" :key="'p'+i">
|
||||
<span class="vi-icon">🎬</span>
|
||||
<div class="vi-info"><div class="vi-name">{{ v.name }}</div></div>
|
||||
<span class="vi-remove" @click="removePendingMedia(i, 'video')" title="移除">×</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>修改人</label><el-input v-model="form.modPerson" /></div>
|
||||
<div class="form-group"><label>修改日期</label><el-date-picker v-model="form.modDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></div>
|
||||
<div class="form-group"><label>状态</label>
|
||||
<el-select v-model="form.status" style="width:100%">
|
||||
<el-option label="待整改" value="pending" />
|
||||
<el-option label="已整改" value="done" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>防止再发措施(经验反馈)</label><el-input v-model="form.preventAction" type="textarea" :rows="2" placeholder="记录防止同样问题再次发生的措施" /></div>
|
||||
<div style="margin-top:8px;">
|
||||
<el-checkbox v-model="form.drawingUpdated" true-label="1" false-label="0">已上传修改后的图纸至资料库</el-checkbox>
|
||||
</div>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- View Dialog -->
|
||||
<el-dialog title="修改详情" :visible.sync="viewVisible" width="560px" append-to-body>
|
||||
<div class="detail-grid" v-if="viewItem">
|
||||
<div class="dg-label">设备名称</div><div class="dg-value">{{ viewItem.deviceName }}</div>
|
||||
<div class="dg-label">修改位置</div><div class="dg-value">{{ viewItem.location || '-' }}</div>
|
||||
<div class="dg-label">问题描述</div><div class="dg-value">{{ viewItem.modReason || '无' }}</div>
|
||||
<div class="dg-label">修改方案</div><div class="dg-value">{{ viewItem.solution || '无' }}</div>
|
||||
<div class="dg-label">修改人</div><div class="dg-value">{{ viewItem.modPerson || '-' }}</div>
|
||||
<div class="dg-label">修改日期</div><div class="dg-value">{{ viewItem.modDate || '-' }}</div>
|
||||
<div class="dg-label">状态</div>
|
||||
<div class="dg-value">
|
||||
<el-tag :type="viewItem.status === 'done' ? 'success' : 'warning'" size="mini">{{ viewItem.status === 'done' ? '已整改' : '待整改' }}</el-tag>
|
||||
</div>
|
||||
<div class="dg-label">防止再发措施</div><div class="dg-value">{{ viewItem.preventAction || '无' }}</div>
|
||||
<div class="dg-label">图纸更新</div><div class="dg-value">{{ viewItem.drawingUpdated === '1' ? '已上传' : '未上传' }}</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="viewVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Media View Dialog (read-only popup for image/video badge click) -->
|
||||
<el-dialog title="多媒体查看" :visible.sync="mediaVisible" width="580px" append-to-body @closed="mediaList = []">
|
||||
<div v-if="mediaImages.length" style="margin-bottom:16px">
|
||||
<strong>图片 ({{ mediaImages.length }})</strong>
|
||||
<div class="sm-media-grid" style="margin-top:8px">
|
||||
<div class="sm-media-thumb" v-for="m in mediaImages" :key="m.mediaId">
|
||||
<el-image :src="m.fileUrl" fit="cover" class="sm-thumb-img" :preview-src-list="mediaPreviewList" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaVideos.length">
|
||||
<strong>视频 ({{ mediaVideos.length }})</strong>
|
||||
<div class="sm-video-list" style="margin-top:8px">
|
||||
<div class="sm-video-item" v-for="m in mediaVideos" :key="m.mediaId">
|
||||
<span class="vi-icon">🎬</span>
|
||||
<div class="vi-info"><a :href="m.fileUrl" target="_blank" class="vi-name" style="color:#409eff">{{ m.fileName }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!mediaImages.length && !mediaVideos.length" style="text-align:center;color:#999;padding:20px">暂无多媒体文件</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="mediaVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { listSiteModAll, getSiteMod, addSiteMod, updateSiteMod, delSiteMod } from '@/api/rm/siteMod'
|
||||
import { listSiteModMediaAll, addSiteModMedia, delSiteModMedia } from '@/api/rm/siteMod'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'RmSiteMod',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false, list: [], currentProjectId: null,
|
||||
dialogVisible: false, dialogTitle: '', submitting: false,
|
||||
form: {
|
||||
deviceName: '', location: '', modReason: '', solution: '',
|
||||
modPerson: '', modDate: '', status: 'pending',
|
||||
preventAction: '', drawingUpdated: '0'
|
||||
},
|
||||
rules: { deviceName: [{ required: true, message: '请填写设备名称', trigger: 'blur' }] },
|
||||
viewVisible: false, viewItem: null,
|
||||
mediaVisible: false, mediaLoading: false, mediaList: [],
|
||||
// Media management for dialog
|
||||
existingMedia: [], // from DB
|
||||
pendingImages: [], // { file, name, url }
|
||||
pendingVideos: [], // { file, name, url }
|
||||
deletedMediaIds: [] // media IDs to remove on save
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
existingImages() { return this.existingMedia.filter(m => m.mediaType === 'image') },
|
||||
existingVideos() { return this.existingMedia.filter(m => m.mediaType === 'video') },
|
||||
existingPreviewList() { return this.existingImages.map(m => m.fileUrl) },
|
||||
mediaImages() { return this.mediaList.filter(m => m.mediaType === 'image') },
|
||||
mediaVideos() { return this.mediaList.filter(m => m.mediaType === 'video') },
|
||||
mediaPreviewList() { return this.mediaImages.map(m => m.fileUrl) }
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) { this.currentProjectId = rows[0].projectId; this.loadList() }
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
listSiteModAll({ projectId: this.currentProjectId }),
|
||||
listSiteModMediaAll({})
|
||||
]).then(([modRes, mediaRes]) => {
|
||||
const mods = modRes.data || []
|
||||
const counts = {}
|
||||
;(mediaRes.data || []).forEach(m => {
|
||||
if (!counts[m.modId]) counts[m.modId] = { images: 0, videos: 0 }
|
||||
if (m.mediaType === 'image') counts[m.modId].images++
|
||||
else if (m.mediaType === 'video') counts[m.modId].videos++
|
||||
})
|
||||
mods.forEach(item => {
|
||||
const c = counts[item.modId] || { images: 0, videos: 0 }
|
||||
item._imageCount = c.images
|
||||
item._videoCount = c.videos
|
||||
})
|
||||
this.list = mods
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleAdd() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.dialogTitle = '记录现场修改'
|
||||
this.form = { projectId: this.currentProjectId, deviceName: '', location: '', modReason: '', solution: '', modPerson: '', modDate: '', status: 'pending', preventAction: '', drawingUpdated: '0' }
|
||||
this.existingMedia = []
|
||||
this.pendingImages = []
|
||||
this.pendingVideos = []
|
||||
this.deletedMediaIds = []
|
||||
this.dialogVisible = true; this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑现场修改'
|
||||
this.pendingImages = []
|
||||
this.pendingVideos = []
|
||||
this.deletedMediaIds = []
|
||||
getSiteMod(row.modId).then(r => {
|
||||
this.form = r.data
|
||||
this.dialogVisible = true
|
||||
this.loadExistingMedia(row.modId)
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
loadExistingMedia(modId) {
|
||||
listSiteModMediaAll({ modId }).then(r => {
|
||||
this.existingMedia = r.data || []
|
||||
})
|
||||
},
|
||||
handleView(row) {
|
||||
this.viewItem = row; this.viewVisible = true
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.deviceName}" 的修改记录?`, '提示', { type: 'warning' }).then(() => { delSiteMod(row.modId).then(() => { this.$message.success('删除成功'); this.loadList() }) })
|
||||
},
|
||||
// File selection
|
||||
onImageSelect(e) {
|
||||
const files = Array.from(e.target.files || [])
|
||||
files.forEach(file => {
|
||||
if (file.size > 5 * 1024 * 1024) { this.$message.warning(`图片 "${file.name}" 超过5MB限制`); return }
|
||||
this.pendingImages.push({ file, name: file.name, url: URL.createObjectURL(file) })
|
||||
})
|
||||
e.target.value = ''
|
||||
},
|
||||
onVideoSelect(e) {
|
||||
const files = Array.from(e.target.files || [])
|
||||
files.forEach(file => {
|
||||
if (file.size > 100 * 1024 * 1024) { this.$message.warning(`视频 "${file.name}" 超过100MB限制`); return }
|
||||
this.pendingVideos.push({ file, name: file.name, url: URL.createObjectURL(file) })
|
||||
})
|
||||
e.target.value = ''
|
||||
},
|
||||
removePendingMedia(index, type) {
|
||||
if (type === 'image') {
|
||||
URL.revokeObjectURL(this.pendingImages[index].url)
|
||||
this.pendingImages.splice(index, 1)
|
||||
} else {
|
||||
URL.revokeObjectURL(this.pendingVideos[index].url)
|
||||
this.pendingVideos.splice(index, 1)
|
||||
}
|
||||
},
|
||||
removeExistingMedia(index, type) {
|
||||
const items = type === 'image' ? this.existingImages : this.existingVideos
|
||||
const item = items[index]
|
||||
if (item.mediaId) this.deletedMediaIds.push(item.mediaId)
|
||||
const idx = this.existingMedia.indexOf(item)
|
||||
if (idx >= 0) this.existingMedia.splice(idx, 1)
|
||||
},
|
||||
onDialogClosed() {
|
||||
// Cleanup pending object URLs
|
||||
this.pendingImages.forEach(img => URL.revokeObjectURL(img.url))
|
||||
this.pendingVideos.forEach(vid => URL.revokeObjectURL(vid.url))
|
||||
},
|
||||
// Submit
|
||||
async uploadPending(modId) {
|
||||
const uploadUrl = process.env.VUE_APP_BASE_API + '/common/upload'
|
||||
const headers = { Authorization: 'Bearer ' + getToken() }
|
||||
const allPending = [
|
||||
...this.pendingImages.map(p => ({ ...p, mediaType: 'image' })),
|
||||
...this.pendingVideos.map(p => ({ ...p, mediaType: 'video' }))
|
||||
]
|
||||
for (const item of allPending) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', item.file)
|
||||
try {
|
||||
const res = await axios.post(uploadUrl, formData, { headers })
|
||||
if (res.data?.code === 200) {
|
||||
await addSiteModMedia({ modId, mediaType: item.mediaType, fileName: item.name, fileUrl: res.data.url })
|
||||
}
|
||||
} catch (e) { console.error('Upload failed:', e) }
|
||||
}
|
||||
},
|
||||
async handleSubmit() {
|
||||
this.$refs.formRef.validate(async v => {
|
||||
if (!v) return
|
||||
this.submitting = true
|
||||
try {
|
||||
const api = this.form.modId ? updateSiteMod : addSiteMod
|
||||
const res = await api(this.form)
|
||||
const modId = this.form.modId || res.data || this.form.projectId
|
||||
// Upload pending media
|
||||
if (this.pendingImages.length || this.pendingVideos.length) {
|
||||
await this.uploadPending(this.form.modId || modId)
|
||||
}
|
||||
// Delete removed media
|
||||
for (const id of this.deletedMediaIds) {
|
||||
await delSiteModMedia(id).catch(() => {})
|
||||
}
|
||||
this.$message.success('保存成功')
|
||||
this.dialogVisible = false
|
||||
this.loadList()
|
||||
} catch (e) { console.error(e) }
|
||||
finally { this.submitting = false }
|
||||
})
|
||||
},
|
||||
handleMedia(row) {
|
||||
this.mediaVisible = true
|
||||
this.mediaLoading = true
|
||||
listSiteModMediaAll({ modId: row.modId }).then(r => {
|
||||
this.mediaList = r.data || []
|
||||
}).finally(() => { this.mediaLoading = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.rm-warning { font-size: 12px; color: #856404; margin-bottom: 12px; padding: 10px; background: #fff3cd; border-radius: 6px; border: 1px solid #ffe0b2; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.detail-grid { display: grid; grid-template-columns: 100px 1fr; font-size: 13px; }
|
||||
.dg-label { background: #fafbfc; font-weight: 600; padding: 8px 12px; border-bottom: 1px solid #eee; }
|
||||
.dg-value { padding: 8px 12px; border-bottom: 1px solid #eee; }
|
||||
|
||||
/* Form layout matching HTML */
|
||||
.form-row { display: flex; gap: 12px; margin-bottom: 12px; }
|
||||
.form-row .form-group { flex: 1; }
|
||||
.form-group { margin-bottom: 12px; }
|
||||
.form-group label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 4px; color: #333; }
|
||||
|
||||
/* Media upload zone */
|
||||
.sm-media-section { flex: 1; }
|
||||
.sm-upload-zone { border: 2px dashed #d0d7de; border-radius: 8px; padding: 16px; text-align: center; cursor: pointer; transition: border-color .2s; margin-bottom: 8px; }
|
||||
.sm-upload-zone:hover { border-color: #409eff; }
|
||||
.uz-icon { font-size: 24px; line-height: 1; }
|
||||
.uz-text { font-size: 13px; color: #666; margin-top: 4px; }
|
||||
.uz-hint { font-size: 11px; color: #999; margin-top: 2px; }
|
||||
|
||||
/* Media grid */
|
||||
.sm-media-grid { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.sm-media-thumb { width: 90px; border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; position: relative; }
|
||||
.sm-thumb-img { width: 90px; height: 68px; display: block; object-fit: cover; }
|
||||
.sm-thumb-name { font-size: 10px; color: #666; padding: 2px 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.sm-thumb-remove { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; border-radius: 50%; border: none; background: rgba(0,0,0,.5); color: #fff; font-size: 12px; line-height: 18px; text-align: center; cursor: pointer; padding: 0; }
|
||||
.sm-thumb-remove:hover { background: rgba(245,108,108,.8); }
|
||||
|
||||
/* Video list */
|
||||
.sm-video-list { display: flex; flex-direction: column; gap: 4px; }
|
||||
.sm-video-item { display: flex; align-items: center; padding: 6px 8px; border: 1px solid #eee; border-radius: 4px; }
|
||||
.vi-icon { margin-right: 6px; }
|
||||
.vi-info { flex: 1; min-width: 0; }
|
||||
.vi-name { font-size: 12px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.vi-remove { cursor: pointer; color: #999; font-size: 16px; line-height: 1; padding: 0 4px; }
|
||||
.vi-remove:hover { color: #f56c6c; }
|
||||
|
||||
/* Media count tags in table */
|
||||
.tag-media { font-size: 12px; cursor: pointer; padding: 2px 6px; border-radius: 3px; display: inline-block; }
|
||||
.tag-media-img { background: #e6f7e6; color: #52c41a; }
|
||||
.tag-media-vid { background: #e6f0ff; color: #409eff; }
|
||||
.tag-media-none { background: #f5f5f5; color: #bbb; cursor: default; }
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>技术方案确定</span>
|
||||
@@ -109,6 +110,7 @@ export default {
|
||||
name: 'RmTechPlan',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
@@ -126,14 +128,21 @@ export default {
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.query.projectId = pid
|
||||
this.loadList()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.query.projectId = rows[0].projectId
|
||||
this.loadList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
this.loading = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="current-project-bar">当前项目:{{ currentProjectName }}</div>
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>技术审查</span>
|
||||
@@ -186,6 +187,7 @@ export default {
|
||||
name: 'RmTechReview',
|
||||
data() {
|
||||
return {
|
||||
currentProjectName: this.$route.query.projectName || sessionStorage.getItem('rm_current_project_name') || '',
|
||||
activeTab: 'mechanical',
|
||||
loading: false,
|
||||
reviewList: [],
|
||||
@@ -218,14 +220,21 @@ export default {
|
||||
methods: {
|
||||
tabLabel(key) { return TAB_LABELS[key] || key },
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadReviews()
|
||||
this.loadColors()
|
||||
}
|
||||
})
|
||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||
if (pid) {
|
||||
this.currentProjectId = pid
|
||||
this.loadReviews()
|
||||
this.loadColors()
|
||||
} else {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadReviews()
|
||||
this.loadColors()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadReviews() {
|
||||
if (!this.currentProjectId) return
|
||||
|
||||
Reference in New Issue
Block a user