feat(project): 项目管理功能增强

- 实现行业类型多选功能,支持多个行业筛选
- 添加技术方案终审下载按钮,仅在联合试用为1时显示
- 集成文件上传组件,支持项目相关附件管理
- 新增授权、终端、服务器等技术信息字段管理
- 实现技术方案单个导出功能,添加权限控制
- 优化项目详情展示,增加虚拟机配置信息
- 添加会审结论和项目风险技术问题字段
- 完善项目表单验证和数据绑定逻辑
- 修复联合试用状态变更时的业务逻辑验证
dev_1.0.0
chenhao 2026-01-07 20:07:36 +08:00
parent 97bcc1ac0a
commit 59b045d457
13 changed files with 607 additions and 68 deletions

View File

@ -4,8 +4,10 @@ import request from '@/utils/request'
export function listProject(query) { export function listProject(query) {
return request({ return request({
url: '/sip/project/vue/list', url: '/sip/project/vue/list',
method: 'get', method: 'post',
params: query data: query,
headers: { 'Content-Type': 'multipart/form-data' },
}) })
} }
@ -70,4 +72,11 @@ export function editJoinTrial(data) {
needLoading:true needLoading:true
}) })
} }
export function exportSingle(id) {
return request({
url: `/sip/project/vue/joinTrial/export/${id}`,
method: 'get',
needLoading:true
})
}

View File

@ -185,15 +185,53 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="关键技术问题" prop="keyProblem">
<el-input v-model="form.keyProblem" type="textarea" readonly />
</el-form-item>
<el-form-item label="项目简述" prop="projectDesc"> <el-form-item label="项目简述" prop="projectDesc">
<el-input v-model="form.projectDesc" type="textarea" readonly /> <el-input v-model="form.projectDesc" type="textarea" readonly />
</el-form-item> </el-form-item>
<el-form-item label="服务器配置" prop="serverConfiguration"> <el-form-item label="授权" prop="softwareInfo">
<el-input v-model="form.softwareInfo" readonly type="textarea" />
</el-form-item>
<el-form-item label="终端" prop="hardwareInfo">
<el-input v-model="form.hardwareInfo" readonly type="textarea" />
</el-form-item>
<el-form-item label="其他终端、外设" prop="terminalPeripheral">
<el-input v-model="form.terminalPeripheral" type="textarea" readonly />
</el-form-item>
<el-form-item label="服务器" prop="serverConfiguration">
<el-input v-model="form.serverConfiguration" type="textarea" readonly /> <el-input v-model="form.serverConfiguration" type="textarea" readonly />
</el-form-item> </el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="管理端版本" prop="managementVersion">
<el-input v-model="form.managementVersion" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="桌面虚拟机OS版本" prop="desktopVmOsVersion">
<el-input v-model="form.desktopVmOsVersion" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟机规格、数量" prop="vmSpecQuantity">
<el-input v-model="form.vmSpecQuantity" readonly></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="项目风险及技术问题" prop="keyProblem">
<el-input v-model="form.keyProblem" type="textarea" readonly />
</el-form-item>
<el-form-item label="会审结论" prop="jointTrialResult">
<el-input v-model="form.jointTrialResult" type="textarea" readonly />
</el-form-item>
<el-form-item label="附件">
<file-upload
:value="fileList"
:limit="5"
:file-size="10"
:file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf', 'zip', 'rar']"
:disabled="true"
/>
</el-form-item>
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="配置信息" name="productConfig"> <el-tab-pane label="配置信息" name="productConfig">
@ -330,10 +368,11 @@
<script> <script>
import { getProject } from "@/api/project/info"; import { getProject } from "@/api/project/info";
import ProductConfig from "./ProductConfig.vue"; import ProductConfig from "./ProductConfig.vue";
import FileUpload from "@/components/FileUpload";
export default { export default {
name: "ProjectDetailDrawer", name: "ProjectDetailDrawer",
components: { ProductConfig }, components: { ProductConfig, FileUpload },
dicts: ['bg_type', 'bg_yys', 'bg_hysy', 'project_stage', 'operate_institution'], dicts: ['bg_type', 'bg_yys', 'bg_hysy', 'project_stage', 'operate_institution'],
props: { props: {
visible: { visible: {
@ -347,13 +386,15 @@ export default {
}, },
data() { data() {
return { return {
fileList: [],
form: { form: {
productConfig: {}, productConfig: {},
projectWorkProgressList: [], projectWorkProgressList: [],
projectOperateLogList: [], projectOperateLogList: [],
projectPocInfo: { projectPocInfo: {
projectPocInfoDetailList: [] projectPocInfoDetailList: []
} },
projectFileList: []
}, },
activeTab: 'projectInfo', activeTab: 'projectInfo',
localVisible: this.visible, localVisible: this.visible,
@ -388,8 +429,10 @@ export default {
projectData.projectOperateLogList = projectData.projectOperateLogList || []; projectData.projectOperateLogList = projectData.projectOperateLogList || [];
projectData.projectPocInfo = projectData.projectPocInfo || { projectPocInfoDetailList: [] }; projectData.projectPocInfo = projectData.projectPocInfo || { projectPocInfoDetailList: [] };
projectData.projectPocInfo.projectPocInfoDetailList = projectData.projectPocInfo.projectPocInfoDetailList || []; projectData.projectPocInfo.projectPocInfoDetailList = projectData.projectPocInfo.projectPocInfoDetailList || [];
projectData.projectFileList = projectData.projectFileList || [];
this.form = projectData; this.form = projectData;
this.fileList = this.form.projectFileList;
this.setupIndustryOptions(); this.setupIndustryOptions();
}); });
}, },

View File

@ -201,15 +201,87 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="关键技术问题" prop="keyProblem"> <el-form-item label="项目简述" prop="projectDesc">
<el-input v-model="form.projectDesc" type="textarea" placeholder="请输入项目简述" maxlength="500" :disabled="isFormDisabled" />
</el-form-item>
<el-form-item label="授权" prop="softwareInfo">
<el-input v-model="form.softwareInfo" readonly type="textarea" placeholder="其他终端、外设" maxlength="500" :disabled="isFormDisabled" />
</el-form-item>
<el-form-item label="终端" prop="hardwareInfo">
<el-input v-model="form.hardwareInfo" readonly type="textarea" placeholder="其他终端、外设" maxlength="500" :disabled="isFormDisabled" />
</el-form-item>
<el-form-item label="其他终端、外设" prop="terminalPeripheral">
<el-input v-model="form.terminalPeripheral" type="textarea" placeholder="其他终端、外设" maxlength="500" :disabled="isFormDisabled" />
</el-form-item>
<el-form-item label="服务器" prop="serverConfiguration">
<el-input v-model="form.serverConfiguration" type="textarea" placeholder="请输入服务器配置" maxlength="500" :disabled="isFormDisabled" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="管理端版本" prop="managementVersion">
<el-input v-model="form.managementVersion"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="桌面虚拟机OS版本" prop="desktopVmOsVersion">
<el-input v-model="form.desktopVmOsVersion"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟机规格、数量" prop="vmSpecQuantity">
<el-input v-model="form.vmSpecQuantity"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="项目风险及技术问题" prop="keyProblem">
<template #label>
项目风险及技术问题<el-tooltip class="item" effect="dark" placement="right-start">
<template #content>
<el-row>
1.方案风险如涉及对接外置存储安全网关部署城域网等复杂网络两节点超融合部署Spacecenter多异地部署备份对接功能开发需求评估等
</el-row>
<el-row>
2.技术风险如涉及功能不满足项强行应标POC测试不满足用例老版本部署混管升级跨网段唤醒等技术要求
</el-row>
<el-row>
3.服务风险如涉及异地部署多区域部署非省代/原厂交付大规模点位部署等
</el-row>
<el-row>
4.其他风险上述未列出的其他特殊风险
</el-row>
</template>
<i class="el-icon-question"></i>
</el-tooltip>
</template>
<el-input v-model="form.keyProblem" type="textarea" placeholder="请输入关键技术问题" maxlength="500" :disabled="isFormDisabled" /> <el-input v-model="form.keyProblem" type="textarea" placeholder="请输入关键技术问题" maxlength="500" :disabled="isFormDisabled" />
</el-form-item> </el-form-item>
<el-form-item label="项目简述" prop="projectDesc"> <el-form-item prop="jointTrialResult">
<el-input v-model="form.projectDesc" type="textarea" placeholder="请输入项目简述" maxlength="500" :disabled="isFormDisabled" /> <template #label>
</el-form-item> 会审结论<el-tooltip class="item" effect="dark" placement="right-start">
<el-form-item label="服务器配置" prop="serverConfiguration"> <template #content>
<el-input v-model="form.serverConfiguration" type="textarea" placeholder="请输入服务器配置" maxlength="500" :disabled="isFormDisabled" /> <el-row>
</el-form-item> 1.如果有POC测试项目必须上传POC测试报告
</el-row>
<el-row>
2.会审给出的结论售前需与新华三销售或者客户确认认可上传对话截图或者邮件截图
</el-row>
</template>
<i class="el-icon-question"></i>
</el-tooltip>
</template>
<el-input v-model="form.jointTrialResult" type="textarea" placeholder="请输入关键技术问题" maxlength="500" :disabled="isFormDisabled || !canUpdateJoinTrial" />
</el-form-item>
<el-form-item label="附件上传">
<file-upload
:value="fileList"
@file-list-changed="handleFileListChanged"
:limit="5"
:file-size="10"
:file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf', 'zip', 'rar']"
:disabled="isFormDisabled"
/>
</el-form-item>
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="配置信息" name="productConfig"> <el-tab-pane label="配置信息" name="productConfig">
@ -371,6 +443,8 @@ import SelectCustomer from "../../system/customer/selectCustomer.vue";
import SelectPartner from "../../system/partner/selectPartner.vue"; import SelectPartner from "../../system/partner/selectPartner.vue";
import SelectUser from "@/views/system/user/selectUser"; import SelectUser from "@/views/system/user/selectUser";
import ProductConfig from "./ProductConfig.vue"; import ProductConfig from "./ProductConfig.vue";
import FileUpload from "@/components/FileUpload";
import { getToken } from "@/utils/auth";
import {isNaN} from "@riophae/vue-treeselect/src/utils"; import {isNaN} from "@riophae/vue-treeselect/src/utils";
export default { export default {
@ -381,9 +455,14 @@ export default {
SelectPartner, SelectPartner,
SelectUser, SelectUser,
ProductConfig, ProductConfig,
FileUpload,
}, },
dicts: ['bg_type', 'bg_yys', 'bg_hysy', 'project_stage', 'operate_institution'], dicts: ['bg_type', 'bg_yys', 'bg_hysy', 'project_stage', 'operate_institution'],
props: { props: {
canUpdateJoinTrial:{
type: Boolean,
default: false,
},
visible: { visible: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -413,6 +492,7 @@ export default {
selectCustomerVisible: false, selectCustomerVisible: false,
selectPartnerVisible: false, selectPartnerVisible: false,
selectUserVisible: false, selectUserVisible: false,
fileList: [],
form: { form: {
competitorList: [], competitorList: [],
otherCompetitor: '', otherCompetitor: '',
@ -425,7 +505,8 @@ export default {
projectOperateLogList: [], projectOperateLogList: [],
projectPocInfo: { projectPocInfo: {
projectPocInfoDetailList: [] projectPocInfoDetailList: []
} },
projectFileList: []
}, },
rules: { rules: {
projectName: [ projectName: [
@ -504,6 +585,40 @@ export default {
this.handleAdd(); this.handleAdd();
} }
} }
},
'form.productConfig': {
handler(newVal) {
if (!newVal) return;
// 1. Authorization (software)
const softwareList = newVal.softwareProjectProductInfoList || [];
if (softwareList.length > 0) {
this.form.softwareInfo = softwareList
.filter(item => item.productBomCode || item.model || (item.quantity !== undefined && item.quantity !== null))
.map(item => {
const code = item.productBomCode || '';
const model = item.model || '';
const qty = (item.quantity !== undefined && item.quantity !== null && item.quantity !== '') ? item.quantity : '';
return `${code},${model},${qty}`;
})
.join(';');
}
// 2. Terminal (hardware)
const hardwareList = newVal.hardwareProjectProductInfoList || [];
if (hardwareList.length > 0) {
this.form.hardwareInfo = hardwareList
.filter(item => item.productBomCode || item.model || (item.quantity !== undefined && item.quantity !== null))
.map(item => {
const code = item.productBomCode || '';
const model = item.model || '';
const qty = (item.quantity !== undefined && item.quantity !== null && item.quantity !== '') ? item.quantity : '';
return `${code},${model},${qty}`;
})
.join(';');
}
},
deep: true
} }
}, },
methods: { methods: {
@ -579,6 +694,7 @@ export default {
projectPocInfo: { projectPocInfo: {
projectPocInfoDetailList: [] projectPocInfoDetailList: []
}, },
projectFileList: [],
createAt: null, createAt: null,
updatedAt: null updatedAt: null
}; };
@ -586,6 +702,7 @@ export default {
this.isFormDisabled = false; this.isFormDisabled = false;
this.cityOptions = []; this.cityOptions = [];
this.industryOptions = []; this.industryOptions = [];
this.fileList = [];
this.resetForm("form"); this.resetForm("form");
}, },
handleAdd() { handleAdd() {
@ -635,9 +752,17 @@ export default {
this.$set(this.form, 'projectPocInfo', this.form.projectPocInfo || { projectPocInfoDetailList: [] }); this.$set(this.form, 'projectPocInfo', this.form.projectPocInfo || { projectPocInfoDetailList: [] });
this.$set(this.form.projectPocInfo, 'projectPocInfoDetailList', this.form.projectPocInfo.projectPocInfoDetailList || []); this.$set(this.form.projectPocInfo, 'projectPocInfoDetailList', this.form.projectPocInfo.projectPocInfoDetailList || []);
this.$set(this.form, 'projectFileList', this.form.projectFileList || []);
this.fileList = this.form.projectFileList;
this.title = "修改项目管理"; this.title = "修改项目管理";
}); });
}, },
handleFileListChanged(fileList) {
this.fileList = fileList;
this.form.projectFileList = fileList;
this.form.fileId = fileList.map(f => f.id).filter(id => !!id).join(',')
},
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {

View File

@ -36,7 +36,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="行业" prop="industryType"> <el-form-item label="行业" prop="industryType">
<el-select v-model="queryParams.industryType" placeholder="请选择行业" clearable> <el-select multiple v-model="queryParams.industryTypeList" placeholder="请选择行业" clearable>
<el-option <el-option
v-for="item in searchIndustryOptions" v-for="item in searchIndustryOptions"
:key="item.value" :key="item.value"
@ -237,6 +237,13 @@
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
v-hasPermi="['sip:project:remove']" v-hasPermi="['sip:project:remove']"
>删除</el-button> >删除</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleExportSingle(scope.row)"
v-show="scope.row.jointTrial==='1'"
>技术方案终审下载</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -256,6 +263,7 @@
<project-form <project-form
:visible.sync="projectFormVisible" :visible.sync="projectFormVisible"
:project-id="selectedProjectId" :project-id="selectedProjectId"
:canUpdateJoinTrial="canUpdateJoinTrial"
@success="handleFormSuccess" @success="handleFormSuccess"
/> />
@ -265,7 +273,7 @@
</template> </template>
<script> <script>
import {listProject, delProject, exportProject, addCollect, updateProject, editJoinTrial} from "@/api/project/info"; import {listProject, delProject, exportProject, addCollect, exportSingle, editJoinTrial} from "@/api/project/info";
import ProjectDetailDrawer from "./ProjectDetailDrawer.vue"; import ProjectDetailDrawer from "./ProjectDetailDrawer.vue";
import ProjectForm from "./ProjectForm.vue"; import ProjectForm from "./ProjectForm.vue";
import OrderDetail from "../order/OrderDetail.vue"; import OrderDetail from "../order/OrderDetail.vue";
@ -317,6 +325,7 @@ export default {
customerName: null, customerName: null,
bgProperty: null, bgProperty: null,
industryType: null, industryType: null,
industryTypeList: null,
agentName: null, agentName: null,
jointTrial: null, jointTrial: null,
collect: null, collect: null,
@ -336,13 +345,10 @@ export default {
created() { created() {
this.getList(); this.getList();
const permissions = store.getters && store.getters.permissions const permissions = store.getters && store.getters.permissions
console.log(permissions)
const all_permission = "*:*:*" const all_permission = "*:*:*"
debugger
this.canUpdateJoinTrial = permissions.some(permission => { this.canUpdateJoinTrial = permissions.some(permission => {
return all_permission === permission || "sip:project:jointTrial"===permission return all_permission === permission || "sip:project:jointTrial"===permission
}) })
console.log(this.canUpdateJoinTrial)
}, },
watch: { watch: {
dateRange(val) { dateRange(val) {
@ -415,6 +421,7 @@ export default {
this.queryParams.lastWorkUpdateTimeStart = null; this.queryParams.lastWorkUpdateTimeStart = null;
this.queryParams.lastWorkUpdateTimeEnd = null; this.queryParams.lastWorkUpdateTimeEnd = null;
this.queryParams.industryType = null; // Clear industry type this.queryParams.industryType = null; // Clear industry type
this.queryParams.industryTypeList = null; // Clear industry type
this.handleQuery(); this.handleQuery();
}, },
// //
@ -448,6 +455,10 @@ export default {
this.$modal.msgSuccess("删除成功"); this.$modal.msgSuccess("删除成功");
}).catch(() => {}); }).catch(() => {});
}, },
handleExportSingle(row){
window.location.href=process.env.VUE_APP_BASE_API +`/sip/project/vue/joinTrial/export/${row.id}`
// exportSingle(row.id)
},
/** 导出按钮操作 */ /** 导出按钮操作 */
handleExport() { handleExport() {
this.$modal.confirm('是否确认导出所有项目管理数据项?').then(() => { this.$modal.confirm('是否确认导出所有项目管理数据项?').then(() => {

View File

@ -2,19 +2,30 @@ package com.ruoyi.sip.controller.vue;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.sip.domain.OmsFileLog;
import com.ruoyi.sip.domain.ProjectInfo; import com.ruoyi.sip.domain.ProjectInfo;
import com.ruoyi.sip.domain.ProjectOrderInfo; import com.ruoyi.sip.domain.ProjectOrderInfo;
import com.ruoyi.sip.service.IOmsFileLogService;
import com.ruoyi.sip.service.IProjectInfoService; import com.ruoyi.sip.service.IProjectInfoService;
import com.ruoyi.sip.service.IProjectOrderInfoService; import com.ruoyi.sip.service.IProjectOrderInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -27,6 +38,7 @@ import java.util.Map;
* @date 2025-05-29 * @date 2025-05-29
*/ */
@RestController @RestController
@Slf4j
@RequestMapping("/sip/project/vue") @RequestMapping("/sip/project/vue")
public class VueProjectInfoController extends BaseController { public class VueProjectInfoController extends BaseController {
@ -39,7 +51,7 @@ public class VueProjectInfoController extends BaseController {
* *
*/ */
@RequiresPermissions("sip:project:list") @RequiresPermissions("sip:project:list")
@GetMapping("/list") @PostMapping("/list")
public TableDataInfo list(ProjectInfo projectInfo) { public TableDataInfo list(ProjectInfo projectInfo) {
startPage(); startPage();
projectInfo.setCurrentUserId(getUserId()); projectInfo.setCurrentUserId(getUserId());
@ -98,6 +110,49 @@ public class VueProjectInfoController extends BaseController {
public AjaxResult editJoinTrial(@RequestBody ProjectInfo projectInfo) { public AjaxResult editJoinTrial(@RequestBody ProjectInfo projectInfo) {
return toAjax(projectInfoService.editJoinTrial(projectInfo)); return toAjax(projectInfoService.editJoinTrial(projectInfo));
} }
@RequiresPermissions("sip:project:export")
@PostMapping("/export/joinTrial")
public AjaxResult exportJoinTrial(ProjectInfo projectInfo) {
return AjaxResult.success(projectInfoService.exportJoinTrial(projectInfo));
}
@GetMapping("/joinTrial/export/{id}")
public void exportSingleJoinTrial(@PathVariable("id") Long id, @RequestParam(required = false) String fileName,
HttpServletRequest request, HttpServletResponse response) {
try {
if (id != null) {
// 获取订单信息
ProjectInfo projectInfo = projectInfoService.selectProjectInfoById(id);
if (projectInfo == null) {
throw new ServiceException("订单信息不存在");
}
if (!"1".equals(projectInfo.getJointTrial())){
throw new ServiceException("项目未会审,无法导出");
}
// 新逻辑:生成填充了订单信息的合同模板
byte[] fileBytes = projectInfoService.exportSingleJoinTrial(projectInfo);
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
String downloadFileName = StringUtils.isEmpty(fileName) ? "技术会审_" + projectInfo.getProjectName() + ".docx" : fileName;
FileUtils.setAttachmentResponseHeader(response, downloadFileName);
// 写入响应流
response.getOutputStream().write(fileBytes);
response.getOutputStream().flush();
} else {
// 原逻辑:直接下载模板文件
String localPath = RuoYiConfig.getExcelTemplate();
String downloadPath = localPath + File.separator + (StringUtils.isEmpty(fileName) ? "orderDownloadTemplate.docx" : fileName);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, StringUtils.isEmpty(fileName) ? "orderDownloadTemplate.docx" : fileName);
FileUtils.writeBytes(downloadPath, response.getOutputStream());
}
} catch (Exception e) {
log.error("下载文件失败", e);
throw new ServiceException("下载文件失败");
}
}
/** /**
* *

View File

@ -6,6 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
@ -21,6 +22,7 @@ public class OmsFileLog extends BaseEntity {
* ID * ID
*/ */
private Integer id; private Integer id;
private List<Integer> idList;
/** /**
* *

View File

@ -44,6 +44,7 @@ public class ProjectInfo extends BaseEntity
/** 行业 */ /** 行业 */
@Excel(name = "行业") @Excel(name = "行业")
private String industryType; private String industryType;
private List<String> industryTypeList;
/** 代表处 */ /** 代表处 */
private String agentCode; private String agentCode;
@ -199,6 +200,38 @@ public class ProjectInfo extends BaseEntity
private List<ProjectWorkProgress> projectWorkProgressList; private List<ProjectWorkProgress> projectWorkProgressList;
private ProjectPocInfo projectPocInfo; private ProjectPocInfo projectPocInfo;
/** 授权信息 */
@Excel(name = "授权信息")
private String softwareInfo;
/** 终端类型 */
@Excel(name = "终端类型")
private String hardwareInfo;
/** 其它终端外设 */
@Excel(name = "其它终端外设")
private String terminalPeripheral;
/** 管理端版本 */
@Excel(name = "管理端版本")
private String managementVersion;
/** 桌面虚拟机OS版本 */
@Excel(name = "桌面虚拟机OS版本")
private String desktopVmOsVersion;
/** 虚拟机规格及数量 */
@Excel(name = "虚拟机规格及数量")
private String vmSpecQuantity;
/** 会审结论 */
@Excel(name = "会审结论")
private String jointTrialResult;
private List<OmsFileLog> projectFileList;
private String fileId;
private Boolean availableForOrder; private Boolean availableForOrder;
} }

View File

@ -46,4 +46,6 @@ public interface OmsFileLogMapper {
*/ */
int batchRemove(Integer[] ids); int batchRemove(Integer[] ids);
int insertBatch(List<OmsFileLog> fileLogList);
} }

View File

@ -70,4 +70,8 @@ public interface IProjectInfoService
ProjectInfo selectProjectInfoByOrderCode(String orderCode); ProjectInfo selectProjectInfoByOrderCode(String orderCode);
int editJoinTrial(ProjectInfo projectInfo); int editJoinTrial(ProjectInfo projectInfo);
byte[] exportSingleJoinTrial(ProjectInfo projectInfo);
String exportJoinTrial(ProjectInfo projectInfo);
} }

View File

@ -1,12 +1,17 @@
package com.ruoyi.sip.service.impl; package com.ruoyi.sip.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.sip.domain.OmsFileLog; import com.ruoyi.sip.domain.OmsFileLog;
import com.ruoyi.sip.mapper.OmsFileLogMapper; import com.ruoyi.sip.mapper.OmsFileLogMapper;
import com.ruoyi.sip.service.IOmsFileLogService; import com.ruoyi.sip.service.IOmsFileLogService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* @Author ch * @Author ch

View File

@ -1,7 +1,9 @@
package com.ruoyi.sip.service.impl; package com.ruoyi.sip.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.Head;
@ -10,6 +12,7 @@ import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy; import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
@ -27,13 +30,20 @@ import com.ruoyi.sip.service.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xwpf.usermodel.*;
import org.hibernate.validator.internal.constraintvalidators.bv.time.future.FutureValidatorForOffsetTime; import org.hibernate.validator.internal.constraintvalidators.bv.time.future.FutureValidatorForOffsetTime;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Period; import java.time.Period;
import java.time.ZoneId; import java.time.ZoneId;
@ -67,6 +77,9 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
@Autowired @Autowired
private ICustomerInfoService customerInfoService; private ICustomerInfoService customerInfoService;
@Autowired
private IOmsFileLogService omsFileLogService;
@Autowired @Autowired
private IProjectUserCollectInfoService projectUserCollectInfoService; private IProjectUserCollectInfoService projectUserCollectInfoService;
@Autowired @Autowired
@ -75,7 +88,6 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
private static final Integer PROJECT_CODE_LENGTH = 6; private static final Integer PROJECT_CODE_LENGTH = 6;
public static final String INDUSTRY_TYPE_YYS_DICT_TYPE = "bg_yys"; public static final String INDUSTRY_TYPE_YYS_DICT_TYPE = "bg_yys";
public static final String INDUSTRY_TYPE_DICT_TYPE = "bg_hysy"; public static final String INDUSTRY_TYPE_DICT_TYPE = "bg_hysy";
public static final String BG_TYPE_DICT_TYPE = "bg_type"; public static final String BG_TYPE_DICT_TYPE = "bg_type";
@ -108,7 +120,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
projectInfo.setProjectWorkProgressList(projectWorkProgresses); projectInfo.setProjectWorkProgressList(projectWorkProgresses);
//查询操作日志信息 //查询操作日志信息
List<ProjectOperateLog> projectOperateLogs = operateLogService.selectProjectOperateLogListByProjectId(projectInfo.getId()); List<ProjectOperateLog> projectOperateLogs = operateLogService.selectProjectOperateLogListByProjectId(projectInfo.getId());
projectOperateLogs=projectOperateLogs.stream().filter(item->{ projectOperateLogs = projectOperateLogs.stream().filter(item -> {
if (ShiroUtils.getSysUser().isAdmin()) { if (ShiroUtils.getSysUser().isAdmin()) {
return ProjectOperateLog.LogTypeEnum.DETAIL.getValue().equals(item.getLogType()); return ProjectOperateLog.LogTypeEnum.DETAIL.getValue().equals(item.getLogType());
} }
@ -119,6 +131,12 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
if (CollUtil.isNotEmpty(projectPocInfos)) { if (CollUtil.isNotEmpty(projectPocInfos)) {
projectInfo.setProjectPocInfo(projectPocInfos.get(0)); projectInfo.setProjectPocInfo(projectPocInfos.get(0));
} }
if (StringUtils.isNotEmpty(projectInfo.getFileId())) {
OmsFileLog queryFileLog = new OmsFileLog();
queryFileLog.setIdList(Arrays.stream(projectInfo.getFileId().split(",")).map(item -> Integer.parseInt(item)).collect(Collectors.toList()));
List<OmsFileLog> omsFileLogs = omsFileLogService.queryAll(queryFileLog);
projectInfo.setProjectFileList(omsFileLogs);
}
return projectInfo; return projectInfo;
} }
@ -132,7 +150,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
@DataScope(deptAlias = "t3", userAlias = "t3") @DataScope(deptAlias = "t3", userAlias = "t3")
public List<ProjectInfo> selectProjectInfoList(ProjectInfo projectInfo) { public List<ProjectInfo> selectProjectInfoList(ProjectInfo projectInfo) {
List<ProjectInfo> projectInfos = projectInfoMapper.selectProjectInfoList(projectInfo); List<ProjectInfo> projectInfos = projectInfoMapper.selectProjectInfoList(projectInfo);
if (CollUtil.isEmpty(projectInfos)){ if (CollUtil.isEmpty(projectInfos)) {
return projectInfos; return projectInfos;
} }
//处理客户信息地址 //处理客户信息地址
@ -166,7 +184,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
} else { } else {
info.setHighlight(false); info.setHighlight(false);
} }
info.setCollect(projectCollectMap.getOrDefault(info.getId(),"0")); info.setCollect(projectCollectMap.getOrDefault(info.getId(), "0"));
} }
return projectInfos; return projectInfos;
} }
@ -268,7 +286,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
saveOtherInfo(projectInfo); saveOtherInfo(projectInfo);
// 记录操作日志 // 记录操作日志
recordOperationLogs(projectInfo, oldProjectInfo); recordOperationLogs(projectInfo, oldProjectInfo);
if (CollUtil.isNotEmpty(projectOrderInfos)){ if (CollUtil.isNotEmpty(projectOrderInfos)) {
ProjectOrderInfo projectOrderInfo = projectOrderInfos.get(0); ProjectOrderInfo projectOrderInfo = projectOrderInfos.get(0);
ProjectOrderInfo update = new ProjectOrderInfo(); ProjectOrderInfo update = new ProjectOrderInfo();
update.setId(projectOrderInfo.getId()); update.setId(projectOrderInfo.getId());
@ -367,7 +385,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
operateLogService.insertProjectOperateLog(operateLog); operateLogService.insertProjectOperateLog(operateLog);
//记录type //记录type
} }
if (logSimpleContent.length() > 0){ if (logSimpleContent.length() > 0) {
ProjectOperateLog operateLog = new ProjectOperateLog(); ProjectOperateLog operateLog = new ProjectOperateLog();
operateLog.setProjectId(projectInfo.getId()); operateLog.setProjectId(projectInfo.getId());
operateLog.setOperateLog(logSimpleContent.toString()); operateLog.setOperateLog(logSimpleContent.toString());
@ -388,21 +406,21 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
return ++index; return ++index;
} }
if (oldInfo != null) { if (oldInfo != null) {
index=compareField(logContent, index, "POC服务器配置", oldInfo.getServerConfig(), info.getServerConfig()); index = compareField(logContent, index, "POC服务器配置", oldInfo.getServerConfig(), info.getServerConfig());
index=compareField(logContent, index, "POC云桌面版本", oldInfo.getVdiVersion(), info.getVdiVersion()); index = compareField(logContent, index, "POC云桌面版本", oldInfo.getVdiVersion(), info.getVdiVersion());
index=compareField(logContent, index, "POC配置终端", oldInfo.getTerminalConfig(), info.getTerminalConfig()); index = compareField(logContent, index, "POC配置终端", oldInfo.getTerminalConfig(), info.getTerminalConfig());
index=compareField(logContent, index, "POC操作系统", oldInfo.getOperateSystem(), info.getOperateSystem()); index = compareField(logContent, index, "POC操作系统", oldInfo.getOperateSystem(), info.getOperateSystem());
index=compareField(logContent, index, "POC H3C接口人", oldInfo.getH3cPerson(), info.getH3cPerson()); index = compareField(logContent, index, "POC H3C接口人", oldInfo.getH3cPerson(), info.getH3cPerson());
index=compareField(logContent, index, "POC H3C TEL", oldInfo.getH3cPhone(), info.getH3cPhone()); index = compareField(logContent, index, "POC H3C TEL", oldInfo.getH3cPhone(), info.getH3cPhone());
index=compareField(logContent, index, "POC汇智接口人", oldInfo.getHzInterfacePerson(), info.getHzInterfacePerson()); index = compareField(logContent, index, "POC汇智接口人", oldInfo.getHzInterfacePerson(), info.getHzInterfacePerson());
index=compareField(logContent, index, "POC汇智 TEL", oldInfo.getHzInterfacePhone(), info.getHzInterfacePhone()); index = compareField(logContent, index, "POC汇智 TEL", oldInfo.getHzInterfacePhone(), info.getHzInterfacePhone());
index=compareField(logContent, index, "POC研发接口人", oldInfo.getProcessPerson(), info.getProcessPerson()); index = compareField(logContent, index, "POC研发接口人", oldInfo.getProcessPerson(), info.getProcessPerson());
index=compareField(logContent, index, "POC研发 TEL", oldInfo.getProcessPhone(), info.getProcessPhone()); index = compareField(logContent, index, "POC研发 TEL", oldInfo.getProcessPhone(), info.getProcessPhone());
index=compareField(logContent, index, "POC现场接口人", oldInfo.getHandlePerson(), info.getHandlePerson()); index = compareField(logContent, index, "POC现场接口人", oldInfo.getHandlePerson(), info.getHandlePerson());
index=compareField(logContent, index, "POC现场 TEL", oldInfo.getHandlePhone(), info.getHandlePhone()); index = compareField(logContent, index, "POC现场 TEL", oldInfo.getHandlePhone(), info.getHandlePhone());
index=compareField(logContent, index, "POC启动时间", formatterDate(oldInfo.getStartDate()), formatterDate(info.getStartDate())); index = compareField(logContent, index, "POC启动时间", formatterDate(oldInfo.getStartDate()), formatterDate(info.getStartDate()));
index=compareField(logContent, index, "POC预计完成时间", formatterDate(oldInfo.getPlanFinishTime()), formatterDate(info.getPlanFinishTime())); index = compareField(logContent, index, "POC预计完成时间", formatterDate(oldInfo.getPlanFinishTime()), formatterDate(info.getPlanFinishTime()));
index=compareField(logContent, index, "POC实际完成时间", formatterDate(oldInfo.getRealFinishTime()), formatterDate(info.getRealFinishTime())); index = compareField(logContent, index, "POC实际完成时间", formatterDate(oldInfo.getRealFinishTime()), formatterDate(info.getRealFinishTime()));
} }
return index; return index;
} }
@ -413,6 +431,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
} }
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date); return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date);
} }
/** /**
* *
* *
@ -435,7 +454,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
index++; index++;
} }
//删除 //删除
List<String> deleteCode =CollUtil.isEmpty(oldList) ? Collections.emptyList() : oldList.stream().filter(item -> !newMap.containsKey(item.getId())).map(ProjectProductInfo::getProductBomCode).collect(Collectors.toList()); List<String> deleteCode = CollUtil.isEmpty(oldList) ? Collections.emptyList() : oldList.stream().filter(item -> !newMap.containsKey(item.getId())).map(ProjectProductInfo::getProductBomCode).collect(Collectors.toList());
if (CollUtil.isNotEmpty(deleteCode)) { if (CollUtil.isNotEmpty(deleteCode)) {
logContent.append(index).append(".产品删除").append(type).append(":").append(String.join(",", deleteCode)).append("\n"); logContent.append(index).append(".产品删除").append(type).append(":").append(String.join(",", deleteCode)).append("\n");
index++; index++;
@ -482,7 +501,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
index, index,
fieldName, fieldName,
oldValue, oldValue,
newValue)); newValue));
index++; index++;
} }
return index; return index;
@ -520,7 +539,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
enrichProjectData(projectInfos); enrichProjectData(projectInfos);
// 计算各类产品的最大数量 // 计算各类产品的最大数量
List<Integer> maxCounts= calculateMaxProductCounts(projectInfos); List<Integer> maxCounts = calculateMaxProductCounts(projectInfos);
int maxSoftware = maxCounts.get(0); int maxSoftware = maxCounts.get(0);
int maxHardware = maxCounts.get(1); int maxHardware = maxCounts.get(1);
int maxMaintenance = maxCounts.get(2); int maxMaintenance = maxCounts.get(2);
@ -545,7 +564,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
StringBuilder authSql = new StringBuilder(); StringBuilder authSql = new StringBuilder();
authSql.append(" and t1.id in (select project_id from project_order_info where "); authSql.append(" and t1.id in (select project_id from project_order_info where ");
authSql.append(StringUtils.format(" order_status in ('{}','{}')" authSql.append(StringUtils.format(" order_status in ('{}','{}')"
,ProjectOrderInfo.OrderStatus.APPROVE_COMPLETE.getCode(),ProjectOrderInfo.OrderStatus.WAIT_APPROVE.getCode())); , ProjectOrderInfo.OrderStatus.APPROVE_COMPLETE.getCode(), ProjectOrderInfo.OrderStatus.WAIT_APPROVE.getCode()));
if ("总代".equals(sysUser.getDept().getDeptName())) { if ("总代".equals(sysUser.getDept().getDeptName())) {
//总代 //总代
authSql.append(" and ( "); authSql.append(" and ( ");
@ -558,7 +577,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
} }
authSql.append(" ) "); authSql.append(" ) ");
dto.setAuthSql(authSql.toString()); dto.setAuthSql(authSql.toString());
}else{ } else {
dto.setAuthSql(null); dto.setAuthSql(null);
} }
@ -604,13 +623,183 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
@Override @Override
public int editJoinTrial(ProjectInfo projectInfo) { public int editJoinTrial(ProjectInfo projectInfo) {
if ("0".equals(projectInfo.getJointTrial())){ if ("0".equals(projectInfo.getJointTrial())) {
List<ProjectOrderInfo> projectOrderInfos = orderInfoService.selectProjectOrderInfoByProjectId(Collections.singletonList(projectInfo.getId())); List<ProjectOrderInfo> projectOrderInfos = orderInfoService.selectProjectOrderInfoByProjectId(Collections.singletonList(projectInfo.getId()));
if (CollUtil.isNotEmpty(projectOrderInfos)){ if (CollUtil.isNotEmpty(projectOrderInfos)) {
throw new ServiceException("已生成项目,无法取消会审"); throw new ServiceException("已生成项目,无法取消会审");
} }
} }
return projectInfoMapper.updateProjectInfo(projectInfo); return projectInfoMapper.updateProjectInfo(projectInfo);
}
@Override
public byte[] exportSingleJoinTrial(ProjectInfo projectInfo) {
try {
// 加载模板文件 - 模板文件位于 ruoyi-admin/src/main/resources/wordTemplate/
String localPath = RuoYiConfig.getExcelTemplate() + File.separator + "jointTrialTemplate.docx";
InputStream templateStream = Files.newInputStream(Paths.get(localPath));
if (templateStream == null) {
throw new ServiceException("模板文件不存在,请确认 wordTemplate/orderDownloadTemplate.docx 文件是否在资源目录中");
}
XWPFDocument document = new XWPFDocument(templateStream);
// 替换文档中的占位符
replaceTextInDocument(document, projectInfo);
// 将文档写入字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.write(baos);
document.close();
templateStream.close();
return baos.toByteArray();
} catch (Exception e) {
log.error("导出合同模板失败", e);
throw new ServiceException("导出合同模板失败: " + e.getMessage());
}
}
private void replaceTextInDocument(XWPFDocument document, ProjectInfo projectInfo) {
// 替换段落中的文本
for (XWPFParagraph paragraph : document.getParagraphs()) {
replaceTextInParagraph(paragraph, projectInfo);
}
}
private void replaceTextInParagraph(XWPFParagraph paragraph, ProjectInfo projectInfo) {
String text = paragraph.getText();
if (text != null && text.contains("${")) {
// 创建替换映射
Map<String, String> replacements = createReplacementMap(projectInfo);
// 替换文本
for (Map.Entry<String, String> entry : replacements.entrySet()) {
String placeholder = "${" + entry.getKey() + "}";
if (text.contains(placeholder)) {
text = text.replace(placeholder, entry.getValue() != null ? entry.getValue() : "");
}
}
// 清除原有运行并设置新文本
for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) {
paragraph.removeRun(i);
}
XWPFRun run = paragraph.createRun();
run.setText(text);
}
}
private Map<String, String> createReplacementMap(ProjectInfo projectInfo) {
Map<String, String> replacements = new HashMap<>();
// 基本订单信息
replacements.put("projectCode", projectInfo.getProjectCode());
replacements.put("projectName", projectInfo.getProjectName());
// 金额信息
replacements.put("customerName", projectInfo.getCustomerName());
replacements.put("customerUserName", StrUtil.isEmpty(projectInfo.getCustomerUserName())?" ":projectInfo.getCustomerUserName());
replacements.put("customerPhone", projectInfo.getCustomerPhone());
replacements.put("countryProduct", formatterStr(projectInfo.getCountryProduct()));
replacements.put("poc", formatterStr(projectInfo.getPoc()));
replacements.put("hzSupportUserName", projectInfo.getHzSupportUserName());
replacements.put("projectDesc", projectInfo.getProjectDesc());
// 责任人信息
replacements.put("softwareInfo", projectInfo.getSoftwareInfo());
replacements.put("hardwareInfo", projectInfo.getHardwareInfo());
// 进货商信息
replacements.put("terminalPeripheral", projectInfo.getTerminalPeripheral());
replacements.put("serverConfiguration", projectInfo.getServerConfiguration());
replacements.put("managementVersion", projectInfo.getManagementVersion());
replacements.put("desktopVmOsVersion", projectInfo.getDesktopVmOsVersion());
replacements.put("vmSpecQuantity", projectInfo.getVmSpecQuantity());
replacements.put("keyProblem", projectInfo.getKeyProblem());
replacements.put("jointTrialResult", projectInfo.getJointTrialResult());
return replacements;
}
@Override
public String exportJoinTrial(ProjectInfo projectInfo) {
try {
projectInfo.setJointTrial("1");
// 获取项目信息列表
List<ProjectInfo> projectInfos = fetchProjectInfos(projectInfo);
// 构建 Excel 表头和数据
List<List<String>> header = buildExcelJointTrialHeader();
List<List<String>> data = buildExcelJointTrialData(projectInfos);
// 导出 Excel 文件
return writeExcelToFile(header, data);
} catch (Exception e) {
log.error("导出项目信息失败", e);
throw new ServiceException("导出项目信息失败,请稍后重试");
}
}
private List<List<String>> buildExcelJointTrialData(List<ProjectInfo> projectInfos) {
List<List<String>> dataList = new ArrayList<>();
for (ProjectInfo info : projectInfos) {
List<String> row = new ArrayList<>();
// 添加基础字段
row.add(info.getProjectCode());
row.add(info.getProjectName());
row.add(info.getCustomerName());
row.add(info.getCustomerUserName());
row.add(info.getCustomerPhone());
row.add(info.getHzSupportUserName());
row.add(formatterStr(info.getCountryProduct()));
row.add(formatterStr(info.getPoc()));
row.add(info.getProjectDesc());
row.add(info.getSoftwareInfo());
row.add(info.getHardwareInfo());
row.add(info.getTerminalPeripheral());
row.add(info.getServerConfiguration());
row.add(info.getManagementVersion());
row.add(info.getDesktopVmOsVersion());
row.add(info.getVmSpecQuantity());
row.add(info.getKeyProblem());
row.add(info.getJointTrialResult());
dataList.add(row);
}
return dataList;
}
private List<List<String>> buildExcelJointTrialHeader() {
List<List<String>> headerList = new ArrayList<>();
headerList.add(Collections.singletonList("项目编号"));
headerList.add(Collections.singletonList("项目名称"));
headerList.add(Collections.singletonList("最终客户"));
headerList.add(Collections.singletonList("客户联系人"));
headerList.add(Collections.singletonList("客户 TEL"));
headerList.add(Collections.singletonList("汇智负责人"));
headerList.add(Collections.singletonList("是否国产"));
headerList.add(Collections.singletonList("poc"));
headerList.add(Collections.singletonList("项目描述"));
headerList.add(Collections.singletonList("授权"));
headerList.add(Collections.singletonList("终端"));
headerList.add(Collections.singletonList("其它终端、外设"));
headerList.add(Collections.singletonList("服务器"));
headerList.add(Collections.singletonList("管理端版本"));
headerList.add(Collections.singletonList("桌面虚拟机OS版本"));
headerList.add(Collections.singletonList("虚拟机规格、数量"));
headerList.add(Collections.singletonList("技术风险与问题"));
headerList.add(Collections.singletonList("会审结论"));
return headerList;
} }
private List<ProjectInfo> fetchProjectInfos(ProjectInfo projectInfo) { private List<ProjectInfo> fetchProjectInfos(ProjectInfo projectInfo) {
@ -626,7 +815,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
Map<Long, String> projectCollectMap = projectUserCollectInfos.stream().collect( Map<Long, String> projectCollectMap = projectUserCollectInfos.stream().collect(
Collectors.toMap(ProjectUserCollectInfo::getProjectId, ProjectUserCollectInfo::getCollect, (v1, v2) -> v1)); Collectors.toMap(ProjectUserCollectInfo::getProjectId, ProjectUserCollectInfo::getCollect, (v1, v2) -> v1));
projectInfos.forEach(item -> { projectInfos.forEach(item -> {
item.setCollect(projectCollectMap.getOrDefault(item.getId(),"0")); item.setCollect(projectCollectMap.getOrDefault(item.getId(), "0"));
}); });
return projectInfos; return projectInfos;
} }
@ -710,7 +899,6 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
public static class CustomColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy { public static class CustomColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
@Override @Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) { protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
int columnIndex = cell.getColumnIndex(); int columnIndex = cell.getColumnIndex();
@ -720,7 +908,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
if (columnIndex >= 0 && columnIndex <= 24) { if (columnIndex >= 0 && columnIndex <= 24) {
// 自适应列宽逻辑 // 自适应列宽逻辑
sheet.setColumnWidth(columnIndex, 256 * 30); // 设置固定宽度为 20 个字符 sheet.setColumnWidth(columnIndex, 256 * 30); // 设置固定宽度为 20 个字符
} }
// 产品和服务列范围(从第 25 列开始) // 产品和服务列范围(从第 25 列开始)
else { else {
// 固定列宽逻辑 // 固定列宽逻辑
@ -729,7 +917,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
} }
} }
public List<List<String>> buildExcelHeader( int maxSoftware, int maxHardware, int maxMaintenance, int maxWorkIndex) { public List<List<String>> buildExcelHeader(int maxSoftware, int maxHardware, int maxMaintenance, int maxWorkIndex) {
List<List<String>> headerList = new ArrayList<>(); List<List<String>> headerList = new ArrayList<>();
headerList.add(Collections.singletonList("项目编号")); headerList.add(Collections.singletonList("项目编号"));
headerList.add(Collections.singletonList("项目名称")); headerList.add(Collections.singletonList("项目名称"));
@ -788,7 +976,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
headerList.add(Collections.singletonList("产品总价")); headerList.add(Collections.singletonList("产品总价"));
for (int i = 1; i <=maxWorkIndex; i++) { for (int i = 1; i <= maxWorkIndex; i++) {
headerList.add(Collections.singletonList("变更内容" + i)); headerList.add(Collections.singletonList("变更内容" + i));
headerList.add(Collections.singletonList("变更人" + i)); headerList.add(Collections.singletonList("变更人" + i));
headerList.add(Collections.singletonList("变更时间" + i)); headerList.add(Collections.singletonList("变更时间" + i));
@ -812,13 +1000,13 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
row.add(info.getCustomerName()); row.add(info.getCustomerName());
row.add(info.getCustomerUserName()); row.add(info.getCustomerUserName());
row.add(info.getCustomerPhone()); row.add(info.getCustomerPhone());
row.add(DictUtils.getDictLabel("operate_institution",info.getOperateInstitution())); row.add(DictUtils.getDictLabel("operate_institution", info.getOperateInstitution()));
row.add(info.getH3cPerson()); row.add(info.getH3cPerson());
row.add(info.getH3cPhone()); row.add(info.getH3cPhone());
row.add(info.getPartnerName()); row.add(info.getPartnerName());
row.add(info.getPartnerUserName()); row.add(info.getPartnerUserName());
row.add(info.getContactWay()); row.add(info.getContactWay());
row.add(info.getEstimatedAmount()!=null?info.getEstimatedAmount().toString():""); row.add(info.getEstimatedAmount() != null ? info.getEstimatedAmount().toString() : "");
row.add(DateUtil.format(info.getEstimatedOrderTime(), "yyyy-MM-dd")); row.add(DateUtil.format(info.getEstimatedOrderTime(), "yyyy-MM-dd"));
row.add(formatterStr(info.getPoc())); row.add(formatterStr(info.getPoc()));
row.add(info.getCompetitor()); row.add(info.getCompetitor());
@ -827,14 +1015,14 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
row.add(info.getProjectDesc()); row.add(info.getProjectDesc());
row.add(info.getServerConfiguration()); row.add(info.getServerConfiguration());
row.add(DateUtil.format(info.getUpdateTime(), "yyyy-MM-dd")); row.add(DateUtil.format(info.getUpdateTime(), "yyyy-MM-dd"));
row.add("1".equals(info.getCollect())?"是":"否"); row.add("1".equals(info.getCollect()) ? "是" : "否");
row.add("1".equals(info.getJointTrial())?"是":"否"); row.add("1".equals(info.getJointTrial()) ? "是" : "否");
BigDecimal totalPrice = BigDecimal.ZERO; BigDecimal totalPrice = BigDecimal.ZERO;
// 添加软件产品列 // 添加软件产品列
for (int i = 0; i < maxSoftware; i++) { for (int i = 0; i < maxSoftware; i++) {
if (CollUtil.isNotEmpty(info.getSoftwareProjectProductInfoList()) && i < info.getSoftwareProjectProductInfoList().size()) { if (CollUtil.isNotEmpty(info.getSoftwareProjectProductInfoList()) && i < info.getSoftwareProjectProductInfoList().size()) {
totalPrice= addProductRow(info.getSoftwareProjectProductInfoList().get(i), row, totalPrice); totalPrice = addProductRow(info.getSoftwareProjectProductInfoList().get(i), row, totalPrice);
} else { } else {
addProductRow(null, row, totalPrice); addProductRow(null, row, totalPrice);
} }
@ -843,7 +1031,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
// 添加终端产品列 // 添加终端产品列
for (int i = 0; i < maxHardware; i++) { for (int i = 0; i < maxHardware; i++) {
if (CollUtil.isNotEmpty(info.getHardwareProjectProductInfoList()) && i < info.getHardwareProjectProductInfoList().size()) { if (CollUtil.isNotEmpty(info.getHardwareProjectProductInfoList()) && i < info.getHardwareProjectProductInfoList().size()) {
totalPrice=addProductRow(info.getHardwareProjectProductInfoList().get(i), row, totalPrice); totalPrice = addProductRow(info.getHardwareProjectProductInfoList().get(i), row, totalPrice);
} else { } else {
addProductRow(null, row, totalPrice); addProductRow(null, row, totalPrice);
} }
@ -852,7 +1040,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
// 添加服务产品列 // 添加服务产品列
for (int i = 0; i < maxMaintenance; i++) { for (int i = 0; i < maxMaintenance; i++) {
if (CollUtil.isNotEmpty(info.getMaintenanceProjectProductInfoList()) && i < info.getMaintenanceProjectProductInfoList().size()) { if (CollUtil.isNotEmpty(info.getMaintenanceProjectProductInfoList()) && i < info.getMaintenanceProjectProductInfoList().size()) {
totalPrice=addProductRow(info.getMaintenanceProjectProductInfoList().get(i), row, totalPrice); totalPrice = addProductRow(info.getMaintenanceProjectProductInfoList().get(i), row, totalPrice);
} else { } else {
addProductRow(null, row, totalPrice); addProductRow(null, row, totalPrice);
} }
@ -880,12 +1068,13 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
return dataList; return dataList;
} }
private String formatterStr(String value){ private String formatterStr(String value) {
if (StringUtils.isEmpty(value)){ if (StringUtils.isEmpty(value)) {
return ""; return "";
} }
return "0".equals(value)?"否":"是"; return "1".equals(value) ? "是":"否";
} }
private static BigDecimal addProductRow(ProjectProductInfo productInfo, List<String> row, BigDecimal totalPrice) { private static BigDecimal addProductRow(ProjectProductInfo productInfo, List<String> row, BigDecimal totalPrice) {
if (productInfo == null) { if (productInfo == null) {
row.add(""); row.add("");

View File

@ -30,6 +30,13 @@
<if test="id != null"> <if test="id != null">
and id = #{id} and id = #{id}
</if> </if>
<if test="idList != null">
and id in (
<foreach item="item" index="index" collection="idList" separator=",">
#{item}
</foreach>
)
</if>
<if test="newFilename != null and newFilename != ''"> <if test="newFilename != null and newFilename != ''">
and new_filename = #{newFilename} and new_filename = #{newFilename}
</if> </if>
@ -141,6 +148,18 @@
</if> </if>
</trim> </trim>
</insert> </insert>
<insert id="insertBatch">
INSERT INTO oms_file_log
(new_filename,file_name,file_path,file_size,file_type,create_by,
create_time, update_by, update_time, remark,project_id)
values
<foreach item="item" index="index" collection="list" separator="," open="(" close=")">
#{item.newFilename}, #{item.fileName}, #{item.filePath}, #{item.fileSize}, #{item.fileType},
#{item.createBy},
#{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark},#{item.projectId}
</foreach>
</insert>
<!--通过主键修改数据--> <!--通过主键修改数据-->
<update id="update"> <update id="update">

View File

@ -33,12 +33,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="bgProperty" column="bg_property" /> <result property="bgProperty" column="bg_property" />
<result property="jointTrial" column="joint_trial" /> <result property="jointTrial" column="joint_trial" />
<result property="softwareInfo" column="software_info" />
<result property="hardwareInfo" column="hardware_info" />
<result property="terminalPeripheral" column="terminal_peripheral" />
<result property="managementVersion" column="management_version" />
<result property="desktopVmOsVersion" column="desktop_vm_os_version" />
<result property="vmSpecQuantity" column="vm_spec_quantity" />
<result property="jointTrialResult" column="joint_trial_result" />
</resultMap> </resultMap>
<sql id="selectProjectInfoVo"> <sql id="selectProjectInfoVo">
select id, project_code, project_name,bg_property, customer_code, customer_name, industry_type, agent_code, project_stage, project_grasp_degree, hz_support_user, operate_institution select id, project_code, project_name,bg_property, customer_code, customer_name, industry_type, agent_code, project_stage, project_grasp_degree, hz_support_user, operate_institution
, partner_code, partner_name, contact_way, estimated_amount, currency_type, estimated_order_time, estimated_deliver_time, competitor, country_product, server_configuration , partner_code, partner_name, contact_way, estimated_amount, currency_type, estimated_order_time, estimated_deliver_time, competitor, country_product, server_configuration,file_id
, key_problem, project_desc, create_by, create_time, update_by, update_time,customer_user_name,customer_phone,partner_email,partner_user_name,h3c_person,h3c_phone,poc,joint_trial from project_info t1 , key_problem, project_desc, create_by, create_time, update_by, update_time,customer_user_name,customer_phone,partner_email,partner_user_name,h3c_person,h3c_phone,poc,joint_trial,software_info,hardware_info,terminal_peripheral,management_version,desktop_vm_os_version,vm_spec_quantity,joint_trial_result from project_info t1
</sql> </sql>
<sql id="selectRelationProjectInfoVo"> <sql id="selectRelationProjectInfoVo">
select t1.id, select t1.id,
@ -70,6 +77,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
t1.update_by, t1.update_by,
t1.update_time, t1.update_time,
t1.joint_trial, t1.joint_trial,
t1.software_info,
t1.hardware_info,
t1.terminal_peripheral,
t1.management_version,
t1.desktop_vm_os_version,
t1.vm_spec_quantity,
t1.joint_trial_result,
t1.file_id,
t1.customer_user_name,t1.customer_phone,t1.partner_user_name,t1.h3c_person,t1.poc,t1.h3c_phone, t1.customer_user_name,t1.customer_phone,t1.partner_user_name,t1.h3c_person,t1.poc,t1.h3c_phone,
t2.agent_name,t2.contact_email,t2.contact_phone,t2.contact_person, t2.agent_name,t2.contact_email,t2.contact_phone,t2.contact_person,
t3.user_name as hz_support_user_name, t3.user_name as hz_support_user_name,
@ -97,6 +112,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="agentName != null and agentName != ''"> and t2.agent_name like concat('%', #{agentName}, '%')</if> <if test="agentName != null and agentName != ''"> and t2.agent_name like concat('%', #{agentName}, '%')</if>
<if test="customerName != null and customerName != ''"> and t1.customer_name like concat('%', #{customerName}, '%')</if> <if test="customerName != null and customerName != ''"> and t1.customer_name like concat('%', #{customerName}, '%')</if>
<if test="industryType != null and industryType != ''"> and t1.industry_type = #{industryType}</if> <if test="industryType != null and industryType != ''"> and t1.industry_type = #{industryType}</if>
<if test="industryTypeList != null and industryTypeList.size>0"> and t1.industry_type in
<foreach collection="industryTypeList" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
<if test="bgProperty != null and bgProperty != ''"> and t1.bg_property = #{bgProperty}</if> <if test="bgProperty != null and bgProperty != ''"> and t1.bg_property = #{bgProperty}</if>
<if test="jointTrial != null and jointTrial != ''"> and t1.joint_trial = #{jointTrial}</if> <if test="jointTrial != null and jointTrial != ''"> and t1.joint_trial = #{jointTrial}</if>
<if test="collect != null and collect != ''"> <if test="collect != null and collect != ''">
@ -258,6 +278,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by,</if> <if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="jointTrial != null and jointTrial != ''"> joint_trial ,</if> <if test="jointTrial != null and jointTrial != ''"> joint_trial ,</if>
<if test="softwareInfo != null">software_info,</if>
<if test="hardwareInfo != null">hardware_info,</if>
<if test="terminalPeripheral != null">terminal_peripheral,</if>
<if test="managementVersion != null">management_version,</if>
<if test="desktopVmOsVersion != null">desktop_vm_os_version,</if>
<if test="vmSpecQuantity != null">vm_spec_quantity,</if>
<if test="jointTrialResult != null">joint_trial_result,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="projectCode != null">#{projectCode},</if> <if test="projectCode != null">#{projectCode},</if>
@ -295,6 +322,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">#{updateBy},</if> <if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
<if test="jointTrial != null and jointTrial != ''"> #{jointTrial},</if> <if test="jointTrial != null and jointTrial != ''"> #{jointTrial},</if>
<if test="softwareInfo != null">#{softwareInfo},</if>
<if test="hardwareInfo != null">#{hardwareInfo},</if>
<if test="terminalPeripheral != null">#{terminalPeripheral},</if>
<if test="managementVersion != null">#{managementVersion},</if>
<if test="desktopVmOsVersion != null">#{desktopVmOsVersion},</if>
<if test="vmSpecQuantity != null">#{vmSpecQuantity},</if>
<if test="jointTrialResult != null">#{jointTrialResult},</if>
</trim> </trim>
</insert> </insert>
<update id="updateCustomerCodeByCode"> <update id="updateCustomerCodeByCode">
@ -320,6 +354,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="hzSupportUser != null">hz_support_user = #{hzSupportUser},</if> <if test="hzSupportUser != null">hz_support_user = #{hzSupportUser},</if>
<if test="operateInstitution != null">operate_institution = #{operateInstitution},</if> <if test="operateInstitution != null">operate_institution = #{operateInstitution},</if>
partner_code = #{partnerCode}, partner_code = #{partnerCode},
file_id = #{fileId},
<if test="partnerName != null">partner_name = #{partnerName},</if> <if test="partnerName != null">partner_name = #{partnerName},</if>
<if test="partnerUserName != null">partner_user_name = #{partnerUserName},</if> <if test="partnerUserName != null">partner_user_name = #{partnerUserName},</if>
<if test="partnerEmail != null">partner_email=#{partnerEmail},</if> <if test="partnerEmail != null">partner_email=#{partnerEmail},</if>
@ -338,6 +373,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by = #{updateBy},</if> <if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if> <if test="updateTime != null">update_time = #{updateTime},</if>
<if test="jointTrial != null and jointTrial != ''"> joint_trial = #{jointTrial},</if> <if test="jointTrial != null and jointTrial != ''"> joint_trial = #{jointTrial},</if>
<if test="softwareInfo != null">software_info = #{softwareInfo},</if>
<if test="hardwareInfo != null">hardware_info = #{hardwareInfo},</if>
<if test="terminalPeripheral != null">terminal_peripheral = #{terminalPeripheral},</if>
<if test="managementVersion != null">management_version = #{managementVersion},</if>
<if test="desktopVmOsVersion != null">desktop_vm_os_version = #{desktopVmOsVersion},</if>
<if test="vmSpecQuantity != null">vm_spec_quantity = #{vmSpecQuantity},</if>
<if test="jointTrialResult != null">joint_trial_result = #{jointTrialResult},</if>
</trim> </trim>
where id = #{id} where id = #{id}
</update> </update>