Merge branch '0.8.X' of http://49.232.154.205:10100/DeXun/klp-oa into 0.8.X
This commit is contained in:
@@ -26,10 +26,15 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Excel相关处理
|
||||
@@ -353,4 +358,67 @@ public class ExcelUtil {
|
||||
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel(按指定顺序的列导出,使用动态表头)
|
||||
*
|
||||
* @param list 导出数据集合
|
||||
* @param sheetName 工作表的名称
|
||||
* @param orderedFields 按导出顺序排列的Java字段名列表
|
||||
* @param fieldLabelMap Java字段名 -> Excel列头中文名 映射
|
||||
* @param response 响应体
|
||||
*/
|
||||
public static <T> void exportExcelOrdered(List<T> list, String sheetName,
|
||||
List<String> orderedFields,
|
||||
Map<String, String> fieldLabelMap,
|
||||
HttpServletResponse response) {
|
||||
if (orderedFields == null || orderedFields.isEmpty()) {
|
||||
throw new IllegalArgumentException("导出列不能为空");
|
||||
}
|
||||
// 构建动态表头
|
||||
List<List<String>> heads = orderedFields.stream()
|
||||
.map(f -> Collections.singletonList(fieldLabelMap.getOrDefault(f, f)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 构建数据行
|
||||
List<List<Object>> data = new ArrayList<>(list.size());
|
||||
if (!list.isEmpty()) {
|
||||
Map<String, Field> fieldCache = new HashMap<>();
|
||||
Class<?> clazz = list.get(0).getClass();
|
||||
for (T vo : list) {
|
||||
List<Object> row = new ArrayList<>(orderedFields.size());
|
||||
for (String fieldName : orderedFields) {
|
||||
Field field = fieldCache.computeIfAbsent(fieldName, k -> {
|
||||
try {
|
||||
Field f = clazz.getDeclaredField(k);
|
||||
f.setAccessible(true);
|
||||
return f;
|
||||
} catch (NoSuchFieldException e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
row.add(field != null ? field.get(vo) : null);
|
||||
} catch (IllegalAccessException e) {
|
||||
row.add(null);
|
||||
}
|
||||
}
|
||||
data.add(row);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
resetResponse(sheetName, response);
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
EasyExcel.write(os)
|
||||
.head(heads)
|
||||
.autoCloseStream(false)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.sheet(sheetName)
|
||||
.doWrite(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("导出Excel异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 自定义导出列选择弹窗 -->
|
||||
<el-dialog title="自定义导出 - 选择导出列" :visible.sync="customExportVisible" width="750px">
|
||||
<el-dialog title="自定义导出 - 选择导出列" :visible.sync="customExportVisible" width="850px" top="5vh">
|
||||
<div class="custom-export-toolbar">
|
||||
<el-input v-model="columnSearch" placeholder="搜索列名" prefix-icon="el-icon-search" clearable size="small" style="width: 220px" />
|
||||
<el-input v-model="columnSearch" placeholder="搜索列名" prefix-icon="el-icon-search" clearable size="small" style="width: 200px" />
|
||||
<div class="custom-export-actions">
|
||||
<el-button size="small" @click="selectAllColumns">全选</el-button>
|
||||
<el-button size="small" @click="invertColumns">反选</el-button>
|
||||
@@ -97,25 +97,47 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-export-body">
|
||||
<el-checkbox-group v-model="selectedColumns">
|
||||
<div v-for="(group, gName) in groupedColumns" :key="gName" class="column-group">
|
||||
<div class="column-group-title">{{ gName }}</div>
|
||||
<div class="column-group-items">
|
||||
<el-checkbox
|
||||
v-for="field in group"
|
||||
:key="field.key"
|
||||
:label="field.key"
|
||||
:style="{ display: columnSearch && !filterMatch(field) ? 'none' : '' }"
|
||||
>{{ field.label }}</el-checkbox>
|
||||
</div>
|
||||
<div class="export-left">
|
||||
<div class="export-panel-title">可选列</div>
|
||||
<div class="export-left-scroll">
|
||||
<el-checkbox-group v-model="selectedColumns">
|
||||
<div v-for="(group, gName) in groupedColumns" :key="gName" class="column-group">
|
||||
<div class="column-group-title">{{ gName }}</div>
|
||||
<div class="column-group-items">
|
||||
<el-checkbox
|
||||
v-for="field in group"
|
||||
:key="field.key"
|
||||
:label="field.key"
|
||||
:style="{ display: columnSearch && !filterMatch(field) ? 'none' : '' }"
|
||||
>{{ field.label }}</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<div class="export-right">
|
||||
<div class="export-panel-title">
|
||||
导出顺序
|
||||
<span class="order-count">{{ orderedColumns.length }}</span>
|
||||
</div>
|
||||
<div class="export-right-scroll">
|
||||
<draggable v-model="orderedColumns" class="ordered-list" ghost-class="ghost" handle=".drag-handle">
|
||||
<div v-for="field in orderedColumns" :key="field" class="ordered-item">
|
||||
<i class="el-icon-rank drag-handle"></i>
|
||||
<span class="order-index">{{ orderedColumns.indexOf(field) + 1 }}</span>
|
||||
<span class="order-label">{{ exportColumns[field] || field }}</span>
|
||||
<i class="el-icon-close order-remove" @click.stop="removeOrderedField(field)"></i>
|
||||
</div>
|
||||
</draggable>
|
||||
<div v-if="orderedColumns.length === 0" class="empty-tip">勾选左侧列后出现在此处,可拖拽排序</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="custom-export-footer">
|
||||
<span class="selected-tip">已选 <b>{{ selectedColumns.length }}</b> / {{ flatColumns.length }} 列</span>
|
||||
<span class="selected-tip">已选 <b>{{ orderedColumns.length }}</b> / {{ flatColumns.length }} 列</span>
|
||||
<el-button @click="customExportVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="doCustomExport" :disabled="selectedColumns.length === 0">
|
||||
导出选中列
|
||||
<el-button type="primary" @click="doCustomExport" :disabled="orderedColumns.length === 0">
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -140,6 +162,7 @@ import TimeRangePicker from "@/views/wms/report/components/timeRangePicker.vue";
|
||||
import HierarchicalPivot from "@/views/wms/report/components/hierarchicalPivot/index.vue";
|
||||
import CrossTable from "@/views/wms/report/components/crossTable/index.vue";
|
||||
import { saveReportFile } from "@/views/wms/report/js/reportFile";
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
|
||||
export default {
|
||||
@@ -155,6 +178,7 @@ export default {
|
||||
TimeRangePicker,
|
||||
HierarchicalPivot,
|
||||
CrossTable,
|
||||
draggable,
|
||||
},
|
||||
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer', 'coil_quality_status'],
|
||||
data() {
|
||||
@@ -184,6 +208,7 @@ export default {
|
||||
customExportVisible: false,
|
||||
exportColumns: {},
|
||||
selectedColumns: [],
|
||||
orderedColumns: [],
|
||||
columnSearch: '',
|
||||
columnGroups: {
|
||||
'基本信息': ['itemTypeDesc', 'warehouseName', 'actualWarehouseName', 'dataTypeText'],
|
||||
@@ -287,6 +312,22 @@ export default {
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
selectedColumns: {
|
||||
immediate: false,
|
||||
handler(nv, ov) {
|
||||
const newSet = new Set(nv)
|
||||
const oldSet = ov ? new Set(ov) : new Set()
|
||||
// 移除取消勾选的列
|
||||
this.orderedColumns = this.orderedColumns.filter(f => newSet.has(f))
|
||||
// 新勾选的追加到末尾
|
||||
const added = nv.filter(f => !oldSet.has(f))
|
||||
if (added.length) {
|
||||
this.orderedColumns.push(...added)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载列设置
|
||||
loadColumns() {
|
||||
@@ -354,14 +395,17 @@ export default {
|
||||
this.customExportVisible = true
|
||||
})
|
||||
},
|
||||
// 执行自定义导出
|
||||
// 执行自定义导出(按 orderedColumns 顺序)
|
||||
doCustomExport() {
|
||||
this.customExportVisible = false
|
||||
this.download('wms/materialCoil/exportCustom', {
|
||||
this.download('wms/materialCoil/exportCustomOrdered', {
|
||||
coilIds: this.coilIds,
|
||||
columns: this.selectedColumns.join(','),
|
||||
columnsOrdered: this.orderedColumns.join(','),
|
||||
}, `materialCoil_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
removeOrderedField(field) {
|
||||
this.selectedColumns = this.selectedColumns.filter(f => f !== field)
|
||||
},
|
||||
filterMatch(field) {
|
||||
const keyword = this.columnSearch.toLowerCase()
|
||||
return !keyword || field.label.toLowerCase().includes(keyword) || field.key.toLowerCase().includes(keyword)
|
||||
@@ -406,8 +450,8 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.custom-export-actions {
|
||||
@@ -415,29 +459,134 @@ export default {
|
||||
gap: 8px;
|
||||
}
|
||||
.custom-export-body {
|
||||
max-height: 420px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: 420px;
|
||||
}
|
||||
.export-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.export-right {
|
||||
width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid #ebeef5;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.export-panel-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.order-count {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.export-left-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.export-right-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.column-group {
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.column-group-title {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 2px;
|
||||
border-left: 3px solid #409eff;
|
||||
color: #909399;
|
||||
margin-bottom: 6px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #dcdfe6;
|
||||
}
|
||||
.column-group-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 4px 12px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2px 8px;
|
||||
}
|
||||
.column-group-items .el-checkbox {
|
||||
margin-right: 0;
|
||||
}
|
||||
.ordered-list {
|
||||
min-height: 60px;
|
||||
}
|
||||
.ordered-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
margin-bottom: 4px;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
transition: background .2s;
|
||||
}
|
||||
.ordered-item:hover {
|
||||
background: #ecf5ff;
|
||||
border-color: #c6e2ff;
|
||||
}
|
||||
.ordered-item.ghost {
|
||||
opacity: 0.4;
|
||||
background: #409eff;
|
||||
}
|
||||
.drag-handle {
|
||||
color: #c0c4cc;
|
||||
cursor: grab;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.order-index {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
background: #e4e7ed;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.order-label {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.order-remove {
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.order-remove:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.empty-tip {
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.custom-export-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -5,8 +5,6 @@ import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.HashMap;
|
||||
@@ -33,7 +31,6 @@ import com.klp.common.core.validate.AddGroup;
|
||||
import com.klp.common.core.validate.EditGroup;
|
||||
import com.klp.common.enums.BusinessType;
|
||||
import com.klp.common.utils.poi.ExcelUtil;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import com.klp.domain.bo.WmsMaterialCoilBo;
|
||||
import com.klp.domain.bo.WmsMaterialCoilReportSummaryBo;
|
||||
import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo;
|
||||
@@ -135,25 +132,6 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilExportVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 个性化导出:前端传入要导出的字段名,仅导出选中列
|
||||
* columns 参数为逗号分隔的 Java 字段名,如 "itemTypeDesc,enterCoilNo,netWeight"
|
||||
* 不传 columns 时等同于 /exportAll 导出全部字段
|
||||
*/
|
||||
@Log(title = "钢卷物料表", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/exportCustom")
|
||||
public void exportCustom(WmsMaterialCoilBo bo,
|
||||
@RequestParam(required = false) String columns,
|
||||
HttpServletResponse response) {
|
||||
List<WmsMaterialCoilAllExportVo> list = iWmsMaterialCoilService.queryExportListAll(bo);
|
||||
if (StringUtils.isNotBlank(columns)) {
|
||||
Set<String> includeFields = new HashSet<>(Arrays.asList(columns.split(",")));
|
||||
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, includeFields, response);
|
||||
} else {
|
||||
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可导出的列元数据(供前端列选择器使用)
|
||||
* 返回 { "fieldName": "中文列名" } 的映射,基于完整导出字段
|
||||
@@ -198,6 +176,35 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
return R.ok(columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义导出(指定列顺序):前端传入按导出顺序排列的字段名
|
||||
* columnsOrdered 参数为逗号分隔的有序字段名,如 "team,enterCoilNo,netWeight,remark"
|
||||
*/
|
||||
@Log(title = "钢卷物料表", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/exportCustomOrdered")
|
||||
public void exportCustomOrdered(WmsMaterialCoilBo bo,
|
||||
@RequestParam String columnsOrdered,
|
||||
HttpServletResponse response) {
|
||||
List<WmsMaterialCoilAllExportVo> list = iWmsMaterialCoilService.queryExportListAll(bo);
|
||||
List<String> orderedFields = Arrays.asList(columnsOrdered.split(","));
|
||||
ExcelUtil.exportExcelOrdered(list, "钢卷物料表", orderedFields,
|
||||
getAllExportFieldLabelMap(), response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 WmsMaterialCoilAllExportVo 注解中提取字段名->中文列名映射
|
||||
*/
|
||||
private Map<String, String> getAllExportFieldLabelMap() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
for (java.lang.reflect.Field field : WmsMaterialCoilAllExportVo.class.getDeclaredFields()) {
|
||||
com.alibaba.excel.annotation.ExcelProperty ep = field.getAnnotation(com.alibaba.excel.annotation.ExcelProperty.class);
|
||||
if (ep != null && ep.value().length > 0) {
|
||||
map.put(field.getName(), ep.value()[0]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出钢卷物料表列表(完整字段版本)
|
||||
* 导出全部字段
|
||||
|
||||
Reference in New Issue
Block a user