feat(quotation): 新增报价单详情页面并完善相关功能

- 新增报价单详情页面实现查看功能
- 添加选择报价单组件支持项目关联报价单
- 更新权限配置将base模块改为sip模块
- 在项目表中新增合作商系统用户ID字段
- 完善报价单控制器的权限注解
- 优化报价单导出Excel格式和样式
- 修复查询条件中的表别名问题
- 添加省代服务产品类型支持
dev_1.0.1^2
chenhao 2026-02-03 15:15:12 +08:00
parent 50ee54d6ef
commit a7f7a29d74
10 changed files with 447 additions and 63 deletions

View File

@ -0,0 +1,143 @@
<template>
<el-drawer
title="报价单详情"
:visible.sync="visible"
direction="rtl"
size="80%"
:before-close="handleClose"
append-to-body
>
<div class="detail-container" v-loading="loading">
<!-- Basic Info -->
<el-divider content-position="left">基本信息</el-divider>
<el-descriptions :column="2" border size="medium">
<el-descriptions-item label="报价单号">{{ form.quotationCode }}</el-descriptions-item>
<el-descriptions-item label="报价单名称">{{ form.quotationName }}</el-descriptions-item>
<el-descriptions-item label="币种">
<dict-tag :options="dict.type.currency_type" :value="form.amountType"/>
</el-descriptions-item>
<el-descriptions-item label="代表处">{{ agentName }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ form.customerName }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ form.quotationStatus }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(form.createTime) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ form.remark }}</el-descriptions-item>
</el-descriptions>
<div v-if="form.quotationCode || catalogueTotalPrice > 0 || discountedTotalPrice > 0" style="margin-top: 20px;">
<el-row type="flex" justify="space-between" style="margin-bottom: 20px; font-size: 14px;">
<el-col :span="24" style="text-align: right;">
<span v-if="catalogueTotalPrice > 0" style="margin-right: 20px;">
<span style="font-weight: bold;">目录总价</span>{{ formatAmount(catalogueTotalPrice) }}
</span>
<span v-if="discountedTotalPrice > 0">
<span style="font-weight: bold;">折后总价</span>{{ formatAmount(discountedTotalPrice) }}
</span>
</el-col>
</el-row>
</div>
<!-- Config Info -->
<product-config :value="form" readonly />
</div>
</el-drawer>
</template>
<script>
import { getQuotation } from "@/api/base/quotation";
import ProductConfig from "@/views/project/info/ProductConfig";
export default {
name: "QuotationDetail",
components: { ProductConfig },
dicts: ['currency_type'],
props: {
agentOptions: {
type: Array,
default: () => []
}
},
data() {
return {
visible: false,
loading: false,
form: {
softwareProjectProductInfoList: [],
hardwareProjectProductInfoList: [],
maintenanceProjectProductInfoList: []
}
};
},
computed: {
agentName() {
if (!this.form.agentCode || !this.agentOptions) return this.form.agentCode;
const agent = this.agentOptions.find(item => item.agentCode === this.form.agentCode);
return agent ? agent.agentName : this.form.agentCode;
},
catalogueTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.catalogueAllPrice) || 0;
});
}
});
return total;
},
discountedTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.allPrice) || 0;
});
}
});
return total;
}
},
methods: {
open(id) {
this.visible = true;
this.getDetail(id);
},
getDetail(id) {
this.loading = true;
getQuotation(id).then(response => {
this.form = response.data;
// Ensure lists are arrays
this.form.softwareProjectProductInfoList = this.form.softwareProjectProductInfoList || [];
this.form.hardwareProjectProductInfoList = this.form.hardwareProjectProductInfoList || [];
this.form.maintenanceProjectProductInfoList = this.form.maintenanceProjectProductInfoList || [];
this.loading = false;
});
},
handleClose(done) {
this.visible = false;
if (done) done();
},
formatAmount(value) {
if (value === null || value === undefined) return '';
return Number(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
}
};
</script>
<style scoped>
.detail-container {
padding: 20px;
}
</style>

View File

@ -50,7 +50,7 @@
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['base:quotation:add']"
v-hasPermi="['sip:quotation:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
@ -61,7 +61,7 @@
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['base:quotation:edit']"
v-hasPermi="['sip:quotation:update']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
@ -72,7 +72,7 @@
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['base:quotation:remove']"
v-hasPermi="['sip:quotation:delete']"
>删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@ -103,35 +103,34 @@
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
v-hasPermi="['base:quotation:query']"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['base:quotation:edit']"
v-hasPermi="['sip:quotation:update']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document-copy"
@click="handleCopy(scope.row)"
v-hasPermi="['base:quotation:add']"
v-hasPermi="['sip:quotation:add']"
>复制创建</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['base:quotation:remove']"
v-hasPermi="['sip:quotation:delete']"
>删除</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-download"
@click="handleExport(scope.row)"
v-hasPermi="['base:quotation:export']"
v-hasPermi="['sip:quotation:export']"
>导出</el-button>
</template>
</el-table-column>

View File

@ -0,0 +1,126 @@
<template>
<el-dialog title="选择报价单" :visible.sync="visible" :close-on-click-modal="false" width="900px" append-to-body @close="handleClose">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" @submit.native.prevent>
<el-form-item label="报价单号" prop="quotationCode">
<el-input
v-model="queryParams.quotationCode"
placeholder="请输入报价单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="报价单名称" prop="quotationName">
<el-input
v-model="queryParams.quotationName"
placeholder="请输入报价单名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="quotationList" @row-click="handleRowClick" highlight-current-row>
<el-table-column label="报价单号" align="center" prop="quotationCode" />
<el-table-column label="报价单名称" align="center" prop="quotationName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="报价金额" align="center" prop="discountAmount" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { listQuotation } from "@/api/base/quotation";
export default {
name: "SelectQuotation",
props: {
visible: {
type: Boolean,
default: false,
},
createBy: {
type: String,
default: "-1",
},
},
data() {
return {
//
loading: true,
//
total: 0,
//
quotationList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
createBy: this.createBy,
quotationCode: null,
quotationName: null,
orderByColumn: 'createTime',
isAsc: 'desc'
},
};
},
watch: {
visible(val) {
if (val) {
this.queryParams.createBy = this.createBy||"-1";
this.getList();
}
},
},
methods: {
/** 查询报价单列表 */
getList() {
this.loading = true;
listQuotation(this.queryParams).then(response => {
this.quotationList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 行点击事件 */
handleRowClick(row) {
this.$emit("quotation-selected", row);
this.handleClose();
},
/** 关闭按钮 */
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>

View File

@ -433,7 +433,7 @@
</el-dialog>
<select-agent :visible.sync="selectAgentVisible" @agent-selected="handleAgentSelected" />
<select-quotation :visible.sync="selectQuotationVisible" @quotation-selected="handleQuotationSelected" />
<select-quotation :visible.sync="selectQuotationVisible" :create-by="form.partnerSystemUserId" @quotation-selected="handleQuotationSelected" />
<select-customer :visible.sync="selectCustomerVisible" @customer-selected="handleCustomerSelected" />
<select-partner :visible.sync="selectPartnerVisible" @partner-selected="handlePartnerSelected" />
<select-user :visible.sync="selectUserVisible" @user-selected="handleUserSelected" />
@ -683,6 +683,7 @@ export default {
partnerName: null,
partnerCode: null,
partnerUserName: null,
partnerSystemUserId: null,
contactWay: null,
estimatedAmount: null,
estimatedOrderTime: null,
@ -834,6 +835,7 @@ export default {
if (value === 'h3c') {
this.form.partnerName = '新华三';
this.form.partnerCode = null;
this.form.partnerSystemUserId = null;
}
if (this.$refs.form) {
this.$nextTick(() => {
@ -923,6 +925,8 @@ export default {
this.form.partnerCode = partner.partnerCode;
this.form.partnerUserName = partner.contactPerson;
this.form.contactWay = partner.contactPhone;
this.$set(this.form,'partnerSystemUserId', partner.systemUserId);
this.selectPartnerVisible = false;
},
openSelectPeople() {

View File

@ -5,6 +5,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.sip.domain.Quotation;
import com.ruoyi.sip.service.IQuotationService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
@ -28,6 +29,7 @@ public class QuotationController extends BaseController {
@GetMapping("/list")
@RequiresPermissions("sip:quotation:list")
public TableDataInfo list(Quotation quotation) {
startPage();
List<Quotation> list = quotationService.queryAll(quotation);
@ -43,6 +45,7 @@ public class QuotationController extends BaseController {
@PostMapping("/insert")
@RequiresPermissions("sip:quotation:add")
public AjaxResult add(@RequestBody Quotation quotation) {
return toAjax(quotationService.insert(quotation));
}
@ -50,6 +53,7 @@ public class QuotationController extends BaseController {
@PutMapping("/update")
@RequiresPermissions("sip:quotation:update")
public AjaxResult edit(@RequestBody Quotation quotation) {
return toAjax(quotationService.update(quotation));
}
@ -57,6 +61,7 @@ public class QuotationController extends BaseController {
@DeleteMapping("/{id}")
@RequiresPermissions("sip:quotation:delete")
public AjaxResult remove(@PathVariable("id") Integer id) {
return toAjax(quotationService.deleteById(id));
}
@ -66,10 +71,12 @@ public class QuotationController extends BaseController {
*/
@DeleteMapping("/remove/batch/{ids}")
@RequiresPermissions("sip:quotation:delete")
public AjaxResult batchRemove(@PathVariable("ids") Integer[] ids) {
return AjaxResult.success(quotationService.batchRemove(ids));
}
@GetMapping("/export/single/{id}")
@RequiresPermissions("sip:quotation:export")
public AjaxResult exportSingle(@PathVariable("id") Integer id) {
return AjaxResult.success(quotationService.exportSingle(id));
}

View File

@ -230,6 +230,7 @@ public class ProjectInfo extends BaseEntity
private List<OmsFileLog> projectFileList;
private String fileId;
private String partnerSystemUserId;
private Integer quotationId;
private List<Integer> quotationIdList;

View File

@ -32,6 +32,7 @@ public class Quotation extends BaseEntity {
*
*/
private String projectCode;
private String projectName;
/**
*
*/
@ -78,6 +79,8 @@ public class Quotation extends BaseEntity {
private List<QuotationProductInfo> hardwareProjectProductInfoList;
// @Excel(name = "服务")
private List<QuotationProductInfo> maintenanceProjectProductInfoList;
//省代
private List<QuotationProductInfo> provinceProductInfoList;
@Getter
public enum QuotationStatusEnum {

View File

@ -11,6 +11,7 @@ import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.ShiroUtils;
@ -69,6 +70,7 @@ public class QuotationServiceImpl implements IQuotationService {
* @return
*/
@Override
@DataScope(deptAlias = "u", userAlias = "u")
public List<Quotation> queryAll(Quotation quotation) {
List<Quotation> dataList = quotationMapper.queryAll(quotation);
PageUtils.clearPage();
@ -95,7 +97,14 @@ public class QuotationServiceImpl implements IQuotationService {
maintenanceProjectProductInfoList.addAll(productListMap.getOrDefault(ProductInfo.ProductTypeEnum.SOFTWARE_MAINTENANCE.getType(), new ArrayList<>()));
maintenanceProjectProductInfoList.addAll(productListMap.getOrDefault(ProductInfo.ProductTypeEnum.OTHER.getType(), new ArrayList<>()));
quotation.setMaintenanceProjectProductInfoList(maintenanceProjectProductInfoList);
quotation.setProvinceProductInfoList(productListMap.getOrDefault(ProductInfo.ProductTypeEnum.PROVINCE_SERVICE.getType(),new ArrayList<>()));
ProjectInfo projectInfo = new ProjectInfo();
projectInfo.setQuotationId(quotation.getId());
List<ProjectInfo> projectInfos = projectInfoService.selectProjectInfoList(projectInfo);
quotation.setProjectCode(projectInfos.stream().map(ProjectInfo::getProjectCode).collect(Collectors.joining(",")));
quotation.setProjectName(projectInfos.stream().map(ProjectInfo::getProjectName).collect(Collectors.joining(",")));
}
return quotation;
}
@ -187,46 +196,42 @@ public class QuotationServiceImpl implements IQuotationService {
row1.add("");
}
rows.add(row1);
rows.add(Collections.emptyList());
// Calculate Date
String validDate = LocalDate.now().plusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// Helper to create padded row
List<Object> row2 = new ArrayList<>();
row2.add("项目名称:");
row2.add(quotation.getQuotationName());
row2.add("");
row2.add("*项目名称:");
row2.add(quotation.getProjectName());
row2.add("");
row2.add("");
row2.add("");
row2.add(""); // Padding
row2.add("*本报价单有效期至:");
row2.add(validDate);
row2.add("*本报价单有效期至:"+validDate);
rows.add(row2);
List<Object> row3 = new ArrayList<>();
row3.add("");
row3.add("*项目ID:");
row3.add(quotation.getProjectCode());
row3.add("");
row3.add("");
row3.add("项目名称和ID在商务申请时必填");
row3.add("");
row3.add("");
row3.add("*云桌面完整报价单必须包含部署服务、现场维保、省代集成服务,此三项由省代进行补充报价,不能缺项");
rows.add(row3);
List<Object> row4 = new ArrayList<>();
row4.add("");
row4.add("国家/地区 :");
row4.add("中国大陆");
row4.add("");
row4.add("");
row4.add("");
row4.add("");
row4.add("*因上游CPU、内存、存储波动较大封标前3天与汇智区域接口人邮件确定商务折扣和供货周期否则报价单无效");
rows.add(row4);
List<Object> row5 = new ArrayList<>();
row5.add("备注:");
row5.add(quotation.getRemark());
rows.add(row5);
rows.add(Collections.emptyList());
@ -236,18 +241,25 @@ public class QuotationServiceImpl implements IQuotationService {
"目录单价(RMB)", "推荐折扣", "折扣单价(RMB)", "总价(RMB)",
"目录总价(RMB)", "CID信息", "备注"
);
// 记录列标题行索引,使其变灰
// 记录列标题行索引
int headerRowIndex = rows.size();
Set<Integer> coloredRowIndices = new HashSet<>();
coloredRowIndices.add(rows.size());
// 标题列填充灰色
coloredRowIndices.add(headerRowIndex);
rows.add(new ArrayList<>(headers));
// 3. 第三部分:数据分组
Set<Integer> aquaRowIndices = new HashSet<>();
AtomicInteger sectionCounter = new AtomicInteger(1);
addSection(rows, "软件", quotation.getSoftwareProjectProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
addSection(rows, "硬件", quotation.getHardwareProjectProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
addSection(rows, "服务", quotation.getMaintenanceProjectProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
// 添加默认分组:云桌面服务器
QuotationProductInfo defaultItem = new QuotationProductInfo();
addSection(rows, "云桌面服务器","VOI/VDI服务器", Collections.singletonList(defaultItem), coloredRowIndices, aquaRowIndices, sectionCounter);
addSection(rows, "云桌面软件", "Workspace/learningspace/NEX",quotation.getSoftwareProjectProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
addSection(rows, "云终端", "VOI/VDI终端",quotation.getHardwareProjectProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
addSection(rows, "省代服务(若从省代出货需计算)","省代服务", quotation.getProvinceProductInfoList(), coloredRowIndices, aquaRowIndices, sectionCounter);
ExcelUtil<Object> util = new ExcelUtil<>(Object.class);
String fileName = util.encodingFilename("报价单_" + quotation.getQuotationCode());
@ -257,15 +269,19 @@ public class QuotationServiceImpl implements IQuotationService {
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setWrapped(true);
contentWriteCellStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
contentWriteCellStyle.setHorizontalAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.LEFT);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(null, contentWriteCellStyle);
EasyExcel.write(filePath)
.registerWriteHandler(horizontalCellStyleStrategy)
.registerWriteHandler(new CustomColumnWidthStrategy())
.registerWriteHandler(new CustomColumnWidthStrategy(headerRowIndex))
.registerWriteHandler(new com.alibaba.excel.write.handler.CellWriteHandler() {
private org.apache.poi.ss.usermodel.CellStyle coloredStyle;
private org.apache.poi.ss.usermodel.CellStyle coloredLeftStyle;
private org.apache.poi.ss.usermodel.CellStyle aquaStyle;
private org.apache.poi.ss.usermodel.CellStyle centerStyle;
private org.apache.poi.ss.usermodel.CellStyle infoStyle;
private org.apache.poi.ss.usermodel.CellStyle redInfoStyle;
@Override
public void beforeCellCreate(com.alibaba.excel.write.metadata.holder.WriteSheetHolder writeSheetHolder, com.alibaba.excel.write.metadata.holder.WriteTableHolder writeTableHolder, org.apache.poi.ss.usermodel.Row row, com.alibaba.excel.metadata.Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
@ -289,6 +305,8 @@ public class QuotationServiceImpl implements IQuotationService {
centerStyle = workbook.createCellStyle();
centerStyle.setAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.CENTER);
centerStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
centerStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.WHITE.getIndex());
centerStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
org.apache.poi.ss.usermodel.Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 16);
@ -297,21 +315,75 @@ public class QuotationServiceImpl implements IQuotationService {
cell.setCellStyle(centerStyle);
// Merge cells for title (0-10, Logo is at 11 usually, but let's merge fewer if needed, or 0-10)
writeSheetHolder.getSheet().addMergedRegionUnsafe(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 10));
}
} else if (cell.getRowIndex() < headerRowIndex) {
// Info rows (0 to headerRowIndex-1) - Excluding 0 handled above?
// Actually handle 0 separately or here. 0 is handled above.
if (cell.getRowIndex() == 0) return;
if (coloredRowIndices.contains(cell.getRowIndex())) {
if (coloredStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
coloredStyle = workbook.createCellStyle();
coloredStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
coloredStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
coloredStyle.setWrapText(true);
coloredStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
org.apache.poi.ss.usermodel.Font font = workbook.createFont();
font.setBold(true);
coloredStyle.setFont(font);
// Check for Red Font Conditions
boolean isRed = false;
if (cell.getRowIndex() == 2 && (cell.getColumnIndex() == 3 || cell.getColumnIndex() == 6)) {
isRed = true;
} else if (cell.getRowIndex() == 3 && (cell.getColumnIndex() == 6 || cell.getColumnIndex() == 3)) {
isRed = true;
} else if (cell.getRowIndex() == 4 && cell.getColumnIndex() == 6) {
isRed = true;
}
if (isRed) {
if (redInfoStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
redInfoStyle = workbook.createCellStyle();
redInfoStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.WHITE.getIndex());
redInfoStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
redInfoStyle.setWrapText(false);
redInfoStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
org.apache.poi.ss.usermodel.Font font = workbook.createFont();
font.setColor(org.apache.poi.ss.usermodel.IndexedColors.RED.getIndex());
redInfoStyle.setFont(font);
}
cell.setCellStyle(redInfoStyle);
} else {
if (infoStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
infoStyle = workbook.createCellStyle();
infoStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.WHITE.getIndex());
infoStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
infoStyle.setWrapText(false);
infoStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
}
cell.setCellStyle(infoStyle);
}
} else if (coloredRowIndices.contains(cell.getRowIndex())) {
if (cell.getRowIndex() > headerRowIndex) {
if (coloredLeftStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
coloredLeftStyle = workbook.createCellStyle();
coloredLeftStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
coloredLeftStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
coloredLeftStyle.setWrapText(true);
coloredLeftStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
coloredLeftStyle.setAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.LEFT);
org.apache.poi.ss.usermodel.Font font = workbook.createFont();
font.setBold(true);
coloredLeftStyle.setFont(font);
}
cell.setCellStyle(coloredLeftStyle);
} else {
if (coloredStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
coloredStyle = workbook.createCellStyle();
coloredStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.GREY_25_PERCENT.getIndex());
coloredStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
coloredStyle.setWrapText(true);
coloredStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
org.apache.poi.ss.usermodel.Font font = workbook.createFont();
font.setBold(true);
coloredStyle.setFont(font);
}
cell.setCellStyle(coloredStyle);
}
cell.setCellStyle(coloredStyle);
} else if (aquaRowIndices.contains(cell.getRowIndex())) {
if (aquaStyle == null) {
org.apache.poi.ss.usermodel.Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
@ -319,6 +391,7 @@ public class QuotationServiceImpl implements IQuotationService {
aquaStyle.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.AQUA.getIndex());
aquaStyle.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
aquaStyle.setWrapText(true);
aquaStyle.setAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.LEFT);
}
cell.setCellStyle(aquaStyle);
}
@ -350,7 +423,7 @@ public class QuotationServiceImpl implements IQuotationService {
quotationMapper.update(quotation);
}
private void addSection(List<List<Object>> rows, String title, List<QuotationProductInfo> list, Set<Integer> coloredRowIndices, Set<Integer> aquaRowIndices, AtomicInteger sectionCounter) {
private void addSection(List<List<Object>> rows, String title, String subTitle,List<QuotationProductInfo> list, Set<Integer> coloredRowIndices, Set<Integer> aquaRowIndices, AtomicInteger sectionCounter) {
if (CollUtil.isEmpty(list)) {
return;
}
@ -367,6 +440,18 @@ public class QuotationServiceImpl implements IQuotationService {
}
rows.add(titleRow);
// 添加标题行 (补齐12列)
List<Object> subTitleRow = new ArrayList<>();
subTitleRow.add(currentSection+"_"+currentSection);
subTitleRow.add(subTitle);
for (int i = 0; i < 10; i++) {
subTitleRow.add("");
}
rows.add(subTitleRow);
double sumAllPrice = 0.0;
double sumCatalogueAllPrice = 0.0;
@ -374,7 +459,7 @@ public class QuotationServiceImpl implements IQuotationService {
int index = 1;
for (QuotationProductInfo item : list) {
List<Object> row = new ArrayList<>();
row.add(currentSection + "_" + index++);
row.add("");
row.add(item.getProductBomCode());
row.add(item.getModel());
row.add(item.getProductDesc());
@ -400,7 +485,7 @@ public class QuotationServiceImpl implements IQuotationService {
coloredRowIndices.add(rows.size());
List<Object> subTotalRow1 = new ArrayList<>();
subTotalRow1.add("");
subTotalRow1.add(title);
subTotalRow1.add(subTitle);
subTotalRow1.add("");
subTotalRow1.add("");
subTotalRow1.add("");
@ -434,9 +519,19 @@ public class QuotationServiceImpl implements IQuotationService {
// 自定义列宽策略:自适应但有最大宽度
public static class CustomColumnWidthStrategy extends AbstractColumnWidthStyleStrategy {
private static final int MAX_COLUMN_WIDTH = 50;
private final int headerRowIndex;
public CustomColumnWidthStrategy(int headerRowIndex) {
this.headerRowIndex = headerRowIndex;
}
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 1-6行不参与列宽计算 (小于标题行的行)
if (cell.getRowIndex() < headerRowIndex) {
return;
}
if (isHead != null && isHead) {
// 如果是表头不需特别处理EasyExcel会自动处理或者这里也可以计算
return;

View File

@ -28,70 +28,74 @@
<!--通过实体作为筛选条件查询-->
<select id="queryAll" resultMap="QuotationMap">
select
<include refid="Base_Column_List"/>
from oms_quotation
t1.id, t1.quotation_code, t1.quotation_name, t1.quotation_amount, t1.discount_amount,
t1.quotation_status, t1.create_time, t1.create_by, t1.update_by, t1.update_time, t1.remark,
t1.agent_code, t1.amount_type, t1.customer_name
from oms_quotation t1
LEFT join sys_user u on t1.create_by = u.user_id
<where>
<if test="id != null">
and id = #{id}
and t1.id = #{id}
</if>
<if test="quotationCode != null and quotationCode != ''">
and quotation_code = #{quotationCode}
and t1.quotation_code = #{quotationCode}
</if>
<if test="quotationName != null and quotationName != ''">
and quotation_name = #{quotationName}
and t1.quotation_name = #{quotationName}
</if>
<if test="quotationAmount != null">
and quotation_amount = #{quotationAmount}
and t1.quotation_amount = #{quotationAmount}
</if>
<if test="discountAmount != null">
and discount_amount = #{discountAmount}
and t1.discount_amount = #{discountAmount}
</if>
<if test="quotationStatus != null and quotationStatus != ''">
and quotation_status = #{quotationStatus}
and t1.quotation_status = #{quotationStatus}
</if>
<if test="createTime != null">
and create_time = #{createTime}
and t1.create_time = #{createTime}
</if>
<if test="createBy != null and createBy != ''">
and create_by = #{createBy}
and t1.create_by = #{createBy}
</if>
<if test="updateBy != null and updateBy != ''">
and update_by = #{updateBy}
and t1.update_by = #{updateBy}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
and t1.update_time = #{updateTime}
</if>
<if test="remark != null and remark != ''">
and remark = #{remark}
and t1.remark = #{remark}
</if>
<if test="agentCode != null and agentCode != ''">
and agent_code = #{agentCode}
and t1.agent_code = #{agentCode}
</if>
<if test="amountType != null and amountType != ''">
and amount_type = #{amountType}
and t1.amount_type = #{amountType}
</if>
<if test="customerName != null and customerName != ''">
and customer_name = #{customerName}
and t1.customer_name = #{customerName}
</if>
<if test="customerName != null and customerName != ''">
and customer_name = #{customerName}
and t1.customer_name = #{customerName}
</if>
<if test="createTimeStart != null or createTimeEnd != null">
<choose>
<when test="createTimeStart != null and createTimeEnd != null">
and create_time between date_format(#{createTimeStart}, '%Y-%m-%d 00:00:00') and date_format(#{createTimeEnd}, '%Y-%m-%d 23:59:59')
and t1.create_time between date_format(#{createTimeStart}, '%Y-%m-%d 00:00:00') and date_format(#{createTimeEnd}, '%Y-%m-%d 23:59:59')
</when>
<when test="createTimeStart != null">
and create_time <![CDATA[ >= ]]> date_format(#{createTimeStart}, '%Y-%m-%d 00:00:00')
and t1.create_time <![CDATA[ >= ]]> date_format(#{createTimeStart}, '%Y-%m-%d 00:00:00')
</when>
<when test="createTimeEnd != null">
and create_time <![CDATA[ <= ]]> date_format(#{createTimeEnd}, '%Y-%m-%d 23:59:59')
and t1.create_time <![CDATA[ <= ]]> date_format(#{createTimeEnd}, '%Y-%m-%d 23:59:59')
</when>
</choose>
</if>
${params.dataScope}
</where>
</select>

View File

@ -91,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
t3.user_name as hz_support_user_name,
t5.level,
t5.contact_email as partner_email,
t6.system_user_id as partner_system_user_id,
t1.update_time ,
ifnull(t4.work_time,t1.update_time) as last_work_update_time
from project_info t1
@ -98,6 +99,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
left join sys_user t3 on t1.hz_support_user=t3.user_id
left join partner_info t5 on t1.partner_code=t5.partner_code
left join (select max(work_time) work_time,project_id from project_work_progress group by project_id) t4 ON t1.id=t4.project_id
left join partner_info t6 on t1.partner_code=t6.partner_code
</sql>