Browse Source

1. 修复 "低代码平台" 中 "报表模版管理" 前端的 bug

main
wangshaoping 1 month ago
parent
commit
d66e6e3766
  1. 11
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateConfig.vue
  2. 358
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateConfigDialog.vue
  3. 131
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateGrid.vue
  4. 464
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateParamsDialog.vue
  5. 234
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateParamsSelectDialog.vue
  6. 34
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateReportDialog.vue
  7. 91
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateSetColumnParamsDialog.vue
  8. 250
      io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateSetForeachPropsDialog.vue
  9. 339
      io.sc.platform.lcdp.frontend/src/views/excel-template/luckysheet/LuckySheet.vue
  10. 319
      io.sc.platform.lcdp.frontend/src/views/excel-template/luckysheet/exportExcel.ts
  11. 27
      io.sc.platform.lcdp.frontend/src/views/excel-template/template.ts

11
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateConfig.vue

@ -0,0 +1,11 @@
<template>
<div>
<LuckySheet></LuckySheet>
</div>
</template>
<script setup lang="ts">
import LuckySheet from './luckysheet/LuckySheet.vue';
</script>
<style scoped></style>

358
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateConfigDialog.vue

@ -0,0 +1,358 @@
<template>
<w-dialog ref="dialogRef" :title="state.dialogTitle" :maximized="true" :buttons="dialog.buttons" body-padding="0px 0px 0px 0px">
<div class="flex justify-end gap-2 p-1.5">
<q-file v-model="impFile" label="导入Excel文件" input-class="w-[400px]" filled dense accept=".xlsx,.xls" @update:model-value="loadExcel($event)">
<template #append>
<q-icon name="attachment" />
</template>
</q-file>
<div class="flex flex-none">
<q-btn label="选取变量" :icon="PlatformIconEnum.选取变量" @click="selectParams"></q-btn>
</div>
<div class="flex flex-none">
<q-btn-dropdown :icon="PlatformIconEnum.设置" label="循环属性配置" unelevated outline>
<q-list>
<q-item v-close-popup clickable>
<q-item-section>
<q-item-label @click="openConfigDialog('main')"><q-icon :name="PlatformIconEnum.更多查询"></q-icon> ()</q-item-label>
</q-item-section>
</q-item>
<q-item v-close-popup clickable>
<q-item-section>
<q-item-label @click="openConfigDialog('column')"><q-icon :name="PlatformIconEnum.更多查询"></q-icon> ()</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<TemplateParamsSelectDialog ref="paramsSelectDialogRef" @set-params="setParams"></TemplateParamsSelectDialog>
<TemplateSetForeachPropsDialog ref="setForeachPropsDialogRef" @set-for-each-params="setForEachParams"></TemplateSetForeachPropsDialog>
<TemplateSetColumnParamsDialog ref="setColumnParamsDialogRef" @set-for-each-params="setForEachParams"></TemplateSetColumnParamsDialog>
</div>
<div id="luckysheet"></div>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick, onBeforeMount } from 'vue';
import LuckyExcel from 'luckyexcel';
import { exportExcel } from './luckysheet/exportExcel';
import { useQuasar } from 'quasar';
import { axios, Environment, NotifyManager, PlatformIconEnum, Tools } from 'platform-core';
import TemplateParamsSelectDialog from './TemplateParamsSelectDialog.vue';
import TemplateSetForeachPropsDialog from './TemplateSetForeachPropsDialog.vue';
import TemplateSetColumnParamsDialog from './TemplateSetColumnParamsDialog.vue';
const $q = useQuasar();
const dialogRef = ref();
const state = reactive({
dialogTitle: '模板制定',
dataId: '',
});
const dialog = {
buttons: [
{
icon: PlatformIconEnum.保存,
label: '保存',
click: () => {
save();
},
},
],
};
const show = async (tableSelectedRow: any) => {
state.dialogTitle = '模板制定——' + tableSelectedRow.templateName + '(' + tableSelectedRow.templateCode + ')';
state.dataId = tableSelectedRow.id;
dialogRef.value.show();
nextTick(async () => {
if (window.luckysheet) {
window.luckysheet.destroy();
}
const resp = await axios.get(Environment.apiContextPath('api/excel/template/') + state.dataId);
if (resp?.data?.templateConfig) {
luckysheetOptions.data = [eval('(' + resp.data.templateConfig + ')')];
}
// eslint-disable-next-line
luckysheet.create(luckysheetOptions);
});
};
const hide = () => {
dialogRef.value.hide();
};
const impFile = ref(null);
const paramsSelectDialogRef = ref();
const setForeachPropsDialogRef = ref();
const setColumnParamsDialogRef = ref();
const loadExcel = (value) => {
const { name } = value;
const suffixArr = name.split('.');
const suffix = suffixArr[suffixArr.length - 1];
if (suffix !== 'xlsx') {
NotifyManager.warn('只能导入 .xlsx 格式的Excel文件');
return;
}
LuckyExcel.transformExcelToLucky(value, (exportJson) => {
if (exportJson.sheets === null || exportJson.sheets.length === 0) {
NotifyManager.warn('导入的Excel为空文件');
return;
}
window.luckysheet.destroy();
const data = [
{
...exportJson.sheets[0],
...{
column: 26,
row: 30,
},
},
];
luckysheetOptions.data = data;
window.luckysheet.create({
lang: 'zh',
container: 'luckysheet',
showinfobar: false,
showtoolbar: true, //
sheetFormulaBar: true, //
showsheetbar: false, // sheet
data: data,
title: exportJson.info.name,
userInfo: exportJson.info.name.creator,
});
});
};
const downloadExcel = () => {
// eslint-disable-next-line
exportExcel(luckysheet.getAllSheets(), '下载');
};
const luckysheetData = [
{
name: 'Cell', //
color: '', //
index: 0, //
status: 1, //
order: 0, //
hide: 0, //
defaultRowHeight: 19, //
defaultColWidth: 73, //
column: 26,
row: 30,
celldata: [], // 使
config: {
merge: {}, //
rowlen: {}, //
columnlen: {}, //
rowhidden: {}, //
colhidden: {}, //
borderInfo: [], //
authority: {}, //
},
scrollLeft: 0, //
scrollTop: 0, //
luckysheet_select_save: [], //
calcChain: [], //
isPivotTable: false, //
pivotTable: {}, //
filter_select: {}, //
filter: null, //
luckysheet_alternateformat_save: [], //
luckysheet_alternateformat_save_modelCustom: [], //
luckysheet_conditionformat_save: {}, //
frozen: {}, //
chart: [], //
zoomRatio: 1, //
image: [], //
showGridLines: 1, // 线
dataVerification: {}, //
},
];
const luckysheetOptions = {
container: 'luckysheet', // DOMid
lang: 'zh', //
title: '测试', //
allowEdit: true, //
rowHeaderWidth: 46, // 0A,B,C46
columnHeaderHeight: 20, // 020
showtoolbar: true, //
showinfobar: false, // luckysheetlogo
userInfo: 'likunming', // showinfobar使
sheetFormulaBar: true, //
defaultFontSize: 11, // ,11
showsheetbar: false, // sheet
data: luckysheetData, //
};
const selectParams = () => {
// eslint-disable-next-line
const selectRange = luckysheet.getRange();
if (!selectRange || selectRange.length === 0) {
NotifyManager.warn('请选择要插入变量的Excel单元格');
return;
} else if (selectRange && selectRange.length > 1) {
NotifyManager.warn('只支持单个选区选取变量');
return;
}
paramsSelectDialogRef.value.show(state.dataId);
};
const setParams = (paramsCode: string | Array<string>) => {
if (typeof paramsCode === 'string') {
// eslint-disable-next-line
let rangeValue = luckysheet.getRangeValue();
rangeValue[0][0] = { ...rangeValue[0][0], ...{ m: paramsCode, v: paramsCode } };
// eslint-disable-next-line
luckysheet.setRangeValue(rangeValue);
} else {
// eslint-disable-next-line
const currRange = luckysheet.getRange();
paramsCode.forEach((item, index) => {
const tmpRangeValue = [[{ m: item, v: item }]];
const tmp = {
row: [currRange[0].row[0], currRange[0].row[1]],
column: [currRange[0].column[0] + index, currRange[0].column[1] + index],
};
// eslint-disable-next-line
luckysheet.setRangeValue(tmpRangeValue, { range: tmp });
});
}
};
// eslint-disable-next-line
const psReg = /\@\{(.*)\}\@/;
const openConfigDialog = (configType) => {
// eslint-disable-next-line
const range = luckysheet.getRange();
// eslint-disable-next-line
const rangeValue = luckysheet.getRangeValue();
if (!range) {
NotifyManager.info('请选择要配置属性的单元格');
return;
} else if (range && range.length > 1) {
NotifyManager.warn('多个选区无法配置属性,选择单元格既可');
return;
}
let data = {};
if (rangeValue[0] && rangeValue[0][0] && rangeValue[0][0].ps && rangeValue[0][0].ps.value) {
let tmp = rangeValue[0][0].ps.value.replaceAll('\n', '');
data = JSON.parse(psReg.exec(tmp)[1]);
}
if (configType === 'main') {
setForeachPropsDialogRef.value.show(state.dataId, data);
} else {
setColumnParamsDialogRef.value.show(data);
}
};
const setForEachParams = (formData) => {
// eslint-disable-next-line
const range = luckysheet.getRange();
let tmp = '@{{\n';
Object.keys(formData).forEach((item) => {
if (!Tools.isEmpty(formData[item])) {
tmp += '"' + item + '"' + ':' + '"' + formData[item] + '"' + ',' + '\n';
}
});
tmp = tmp.substring(0, tmp.length - 2);
tmp += '\n}}@';
// eslint-disable-next-line
luckysheet.insertComment(range[0].row[0], range[0].column[0], tmp);
//
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.width = 300;
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.height = 150;
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.isShow = false;
// eslint-disable-next-line
luckysheet.refresh();
};
const save = async () => {
// eslint-disable-next-line
if (!luckysheet.getAllSheets() || luckysheet.getAllSheets().length === 0) {
NotifyManager.warn('系统异常,未获取到需要保存的Sheet数据');
return;
}
// celldata使data
// eslint-disable-next-line
const saveData = { ...luckysheet.getAllSheets()[0], ...{ celldata: [] } };
//
let requestParams = {
method: 'POST',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: { templateConfig: JSON.stringify(saveData) },
url: Environment.apiContextPath('api/excel/template/updateConfig/') + state.dataId,
};
const resp = await axios(requestParams).catch((error) => {
console.info('error====', error);
NotifyManager.error('保存失败');
});
NotifyManager.info('保存成功');
// dialogRef.value.dialogHide();
};
const buttons = ref([
{
name: 'selectParams',
icon: 'find_in_page',
label: '选取变量',
click: selectParams,
},
[
{
name: 'foreachPropsConfig',
icon: 'settings',
label: '循环属性配置',
},
{
name: 'mainConfig',
icon: 'build_circle',
label: '整体配置(首列)',
click: () => {
openConfigDialog('main');
},
},
{
name: 'columnConfig',
icon: 'build_circle',
label: '列配置(非首列)',
click: () => {
openConfigDialog('column');
},
},
],
{
name: 'download',
icon: 'download',
label: '导出文件',
click: downloadExcel,
},
]);
onBeforeMount(() => {
window.setInputZIndex = () => {
return true;
};
window.setRightClickMenuZIndex = () => {
return true;
};
});
defineExpose({
show,
hide,
});
</script>
<style scoped>
#luckysheet {
margin: 0px;
padding: 0px;
position: absolute;
width: 100%;
left: 0px;
top: 50px;
bottom: 0px;
}
</style>

131
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateGrid.vue

@ -0,0 +1,131 @@
<template>
<div style="height: 100%">
<w-grid
ref="templateGridRef"
title="Excel模板列表"
sort-no
:data-url="Environment.apiContextPath('api/excel/template')"
:query-form-cols-num="2"
:query-form-fields="templateGrid.queryFormFields"
:toolbar-actions="templateGrid.toolbarActions"
:columns="templateGrid.columns"
:editor="templateGrid.editor"
:viewer="templateGrid.viewer"
:sort-by="['-lastModifyDate']"
></w-grid>
<TemplateParamsDialog ref="templateParamsDialogRef"></TemplateParamsDialog>
<TemplateConfigDialog ref="templateConfigDialogRef"></TemplateConfigDialog>
<TemplateReportDialog ref="templateReportDialogRef"></TemplateReportDialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Environment } from 'platform-core';
import TemplateParamsDialog from './TemplateParamsDialog.vue';
import TemplateConfigDialog from './TemplateConfigDialog.vue';
import TemplateReportDialog from './TemplateReportDialog.vue';
const templateGridRef = ref();
const templateParamsDialogRef = ref();
const templateConfigDialogRef = ref();
const templateReportDialogRef = ref();
const templateGrid = {
queryFormFields: [
{ label: '模板编码', name: 'templateCode', type: 'w-text' },
{ label: '模板名称', name: 'templateName', type: 'w-text' },
],
toolbarActions: [
'query',
'reset',
'separator',
[
{
name: 'operate',
icon: 'difference',
label: '操作',
},
'add',
{
extend: 'edit',
afterClick: (args) => {
args.grid.setEditDataUrl(Environment.apiContextPath('api/excel/template/updateField'));
},
},
'remove',
],
{
name: 'params',
icon: 'settings',
label: '变量维护',
click: (args) => {
templateParamsDialogRef.value.show(args.selecteds);
},
},
{
name: 'templateConfig',
icon: 'build_circle',
label: '模板制定',
enableIf: (args) => {
if (args.selected) {
return true;
} else {
return false;
}
},
click: (args) => {
templateConfigDialogRef.value.show(args.selected);
},
},
'separator',
{
name: 'reportView',
icon: 'visibility',
label: '报表查看',
enableIf: (args) => {
if (args.selected) {
return true;
} else {
return false;
}
},
click: (args) => {
templateReportDialogRef.value.show(args.selected);
},
},
'separator',
],
columns: [
{ name: 'templateCode', label: '模板编码' },
{ name: 'templateName', label: '模板名称' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
editor: {
dialog: {
width: '50%',
height: '30%',
},
form: {
colsNum: 1,
fields: [
{ label: '模板编码', name: 'templateCode', type: 'w-text', requiredIf: true },
{ label: '模板名称', name: 'templateName', type: 'w-text', requiredIf: true },
],
},
},
viewer: {
panel: {
fields: [
{ name: 'id', label: '模板ID' },
{ name: 'templateCode', label: '模板编码' },
{ name: 'templateName', label: '模板名称' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
},
},
};
</script>

464
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateParamsDialog.vue

@ -0,0 +1,464 @@
<template>
<w-dialog ref="dialogRef" title="变量维护" :maximized="true">
<w-grid
ref="templateParamsGridRef"
title="模板变量列表"
:auto-fetch-data="false"
:data-url="Environment.apiContextPath('api/excel/template/params')"
:sort-no="true"
:columns="templateParamsGrid.columns"
:toolbar-actions="templateParamsGrid.toolbarActions"
:query-form-cols-num="4"
:query-form-fields="templateParamsGrid.queryFormFields"
:pagination="{
sortBy: 'lastModifyDate',
descending: true,
}"
></w-grid>
<w-dialog ref="templateParamsDialogRef" :title="state.templateParamsDialogTitle" width="80%" height="80%" :buttons="templateParamsDialog.buttons">
<w-form ref="templateParamsAddEditFormRef" :cols-num="3" :fields="templateParamsDialog.formFields" class="p-1.5"> </w-form>
<w-grid
v-show="state.paramsType && state.paramsType === 'LIST'"
ref="templateParamsListFieldGridRef"
title="字段列表"
dense
:draggable="true"
:pageable="false"
:config-button="false"
:auto-fetch-data="false"
:fetch-data-url="Environment.apiContextPath('api/excel/template/params/list?sortBy=sortNo')"
:toolbar-actions="templateParamsListFieldGrid.toolbarActions"
:columns="templateParamsListFieldGrid.columns"
></w-grid>
<w-dialog
ref="templateParamsListFieldDialogRef"
:title="state.templateParamsListFieldDialogTitle"
width="60%"
height="50%"
:buttons="templateParamsListFieldDialog.buttons"
>
<w-form ref="templateParamsListFieldAddEditFormRef" :cols-num="1" :fields="templateParamsListFieldDialog.formFields" class="p-1.5"> </w-form>
</w-dialog>
</w-dialog>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { axios, Environment, NotifyManager } from 'platform-core';
const dialogRef = ref();
const templateParamsGridRef = ref();
const templateParamsDialogRef = ref();
const templateParamsAddEditFormRef = ref();
const templateParamsListFieldGridRef = ref();
const templateParamsListFieldDialogRef = ref();
const templateParamsListFieldAddEditFormRef = ref();
const state = reactive({
templateArray: [],
tableSelectedTemplateId: null,
paramsType: null,
templateParamsDialogTitle: '新增变量',
templateParamsListFieldDialogTitle: '新增字段',
});
const templateParamsGrid = {
queryFormFields: [
{ label: '变量编码', name: 'paramsCode', type: 'w-text' },
{ label: '变量名称', name: 'paramsName', type: 'w-text' },
{
label: '变量类型',
name: 'paramsType',
type: 'w-select',
queryOperator: 'equals',
options: [
{ label: '单值', value: 'SING' },
{ label: '列表', value: 'LIST' },
],
},
{
label: '所属模板',
name: 'reportExcelTemplate',
queryOperator: 'equals',
type: 'w-select',
clearable: true,
colspan: 2,
options: [],
},
],
toolbarActions: [
'query',
'reset',
'separator',
{
extend: 'add',
click: () => {
templateParamsDialogRef.value.show();
},
afterClick: () => {
templateParamsAddEditFormRef.value.setStatus('add');
state.templateParamsDialogTitle = '新增变量';
templateParamsAddEditFormRef.value.getFields()['reportExcelTemplate'].options = state.templateArray;
state.paramsType = null;
if (state.tableSelectedTemplateId) {
templateParamsAddEditFormRef.value.setFieldValue('paramsIsCommon', false);
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', state.tableSelectedTemplateId);
}
},
},
{
extend: 'edit',
click: () => {
templateParamsDialogRef.value.show();
},
afterClick: (args) => {
templateParamsAddEditFormRef.value.setStatus('edit');
state.templateParamsDialogTitle = '编辑变量';
templateParamsAddEditFormRef.value.getFields()['reportExcelTemplate'].options = state.templateArray;
templateParamsAddEditFormRef.value.setData(args.selected);
state.paramsType = args.selected.paramsType;
if (args.selected['paramsIsCommon']) {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', null);
} else if (args.selected['reportExcelTemplate']) {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', args.selected['reportExcelTemplate']);
}
if (state.paramsType === 'LIST') {
const urlSearchParams = new URLSearchParams();
urlSearchParams.append('criteria', JSON.stringify({ fieldName: 'reportExcelTemplateParams', operator: 'equals', value: args.selected.id }));
axios
.get(Environment.apiContextPath('api/excel/template/params/list?pageable=false&sortBy=sortNo'), { params: urlSearchParams })
.then((resp) => {
if (resp.data.content) {
templateParamsListFieldGridRef.value.setLocalData(resp.data.content);
}
})
.catch((error) => {
console.info('error====', error);
});
}
},
},
{
extend: 'clone',
click: () => {
templateParamsDialogRef.value.show();
},
afterClick: (args) => {
templateParamsAddEditFormRef.value.setStatus('clone');
state.templateParamsDialogTitle = '复制变量';
templateParamsAddEditFormRef.value.getFields()['reportExcelTemplate'].options = state.templateArray;
templateParamsAddEditFormRef.value.setData(args.selected);
state.paramsType = args.selected.paramsType;
if (args.selected['paramsIsCommon']) {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', null);
} else if (args.selected['reportExcelTemplate']) {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', args.selected['reportExcelTemplate']);
}
if (state.paramsType === 'LIST') {
const urlSearchParams = new URLSearchParams();
urlSearchParams.append('criteria', JSON.stringify({ fieldName: 'reportExcelTemplateParams', operator: 'equals', value: args.selected.id }));
axios
.get(Environment.apiContextPath('api/excel/template/params/list?pageable=false&sortBy=sortNo'), { params: urlSearchParams })
.then((resp) => {
if (resp.data.content) {
templateParamsListFieldGridRef.value.setLocalData(resp.data.content);
}
})
.catch((error) => {
console.info('error====', error);
});
}
},
},
'remove',
'separator',
],
columns: [
{ name: 'paramsCode', label: '变量编码' },
{ name: 'paramsName', label: '变量名称' },
{
name: 'paramsType',
label: '变量类型',
format: (val, row) => {
if (val === 'SING') {
return '单值';
} else if (val === 'LIST') {
return '列表';
} else {
return val;
}
},
},
{
name: 'paramsIsCommon',
label: '变量是否通用',
format: (val, row) => {
if (val === true) {
return '所有模板均可使用';
} else if (val === false) {
return '指定模板可使用';
} else {
return val;
}
},
},
{ name: 'reportExcelTemplateName', label: '所属模板名称' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
};
const templateParamsDialog = {
buttons: [
{
icon: 'beenhere',
label: '提交',
click: async () => {
const validate = await templateParamsAddEditFormRef.value.validate();
if (validate) {
const formStatus = templateParamsAddEditFormRef.value.getStatus();
const formData = templateParamsAddEditFormRef.value.getData();
if (formData.reportExcelTemplate) {
formData.reportExcelTemplate = { id: formData.reportExcelTemplate };
} else {
formData.reportExcelTemplate = null;
}
const fieldRows = templateParamsListFieldGridRef.value.getRows();
let url = Environment.apiContextPath('api/excel/template/params');
if (formStatus === 'edit') {
url += '/edit';
formData.id = templateParamsGridRef.value.getSelectedRows()[0].id;
fieldRows.forEach((item) => {
delete item.reportExcelTemplateParams;
});
} else {
url += '/add';
}
const resp = await axios
.post(url, {
params: formData,
list: formData.paramsType === 'LIST' ? fieldRows : [],
})
.catch((error) => {
console.info('error====', error);
NotifyManager.error('操作失败');
});
NotifyManager.info('保存成功');
templateParamsDialogRef.value.hide();
templateParamsGridRef.value.refresh();
}
},
},
{
icon: 'published_with_changes',
label: 'SQL校验',
click: async () => {
const paramsSqlValue = templateParamsAddEditFormRef.value.getFieldValue('paramsSql');
if (!paramsSqlValue) {
NotifyManager.warn('取值SQL为空');
return;
}
const requestParams = {
method: 'POST',
headers: { 'content-type': 'text/plain;charset=utf-8;' },
data: paramsSqlValue,
url: Environment.apiContextPath('api/jdbc/fetchMetaDataBySql'),
};
const resp = await axios(requestParams);
if (resp && resp.data) {
NotifyManager.info('校验通过');
} else {
NotifyManager.error('校验失败');
}
},
},
],
formFields: [
{ label: '变量编码', name: 'paramsCode', type: 'w-text', requiredIf: true },
{ label: '变量名称', name: 'paramsName', type: 'w-text', requiredIf: true },
{
label: '变量类型',
name: 'paramsType',
type: 'w-select',
requiredIf: true,
options: [
{ label: '单值', value: 'SING' },
{ label: '列表', value: 'LIST' },
],
'onUpdate:modelValue': (value) => {
state.paramsType = value;
},
},
{
label: '变量是否通用',
name: 'paramsIsCommon',
type: 'w-select',
requiredIf: true,
options: [
{ label: '所有模板均可使用', value: true },
{ label: '指定模板可使用', value: false },
],
'onUpdate:modelValue': (value) => {
if (typeof value === 'boolean' && value === false) {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', state.tableSelectedTemplateId);
} else {
templateParamsAddEditFormRef.value.setFieldValue('reportExcelTemplate', null);
}
},
},
{
label: '所属模板',
name: 'reportExcelTemplate',
type: 'w-select',
colspan: 2,
requiredIf: true,
options: [],
showIf: (args) => {
const value = args.form?.getFieldValue('paramsIsCommon');
if (typeof value === 'boolean' && value === false) {
return true;
} else {
return false;
}
},
},
{
label: '取值SQL',
name: 'paramsSql',
type: 'w-textarea',
requiredIf: true,
colspan: 'full',
},
],
};
const templateParamsListFieldGrid = {
toolbarActions: [
{
name: 'generateFieldsBySql',
icon: 'playlist_add_circle',
label: '根据取值SQL生成',
click: async () => {
const paramsSqlValue = templateParamsAddEditFormRef.value.getFieldValue('paramsSql');
if (!paramsSqlValue) {
NotifyManager.warn('取值SQL为空,无法生成');
return;
}
const requestParams = {
method: 'POST',
headers: { 'content-type': 'text/plain;charset=utf-8;' },
data: paramsSqlValue,
url: Environment.apiContextPath('api/jdbc/fetchMetaDataBySql'),
};
const resp: any = await axios(requestParams).catch((error) => {
console.info('error====', error);
NotifyManager.error('操作失败');
});
const listFieldsRow = { name: '', desc: '' };
const listFields = <any>[];
if (resp.data) {
resp.data.forEach((item, index) => {
listFields.push({ ...listFieldsRow, ...{ sortNo: index + 1, name: item.columnName, type: item.columnTypeName } });
});
templateParamsListFieldGridRef.value.setLocalData(listFields);
}
},
},
{
extend: 'edit',
click: () => {
state.templateParamsListFieldDialogTitle = '编辑字段';
templateParamsListFieldDialogRef.value.show();
},
afterClick: (args) => {
templateParamsListFieldAddEditFormRef.value.setStatus('edit');
templateParamsListFieldAddEditFormRef.value.setData(args.selected);
},
},
{
extend: 'remove',
click: (args) => {
if (args.tickeds) {
templateParamsListFieldGridRef.value.removeLocalData(args.tickeds);
} else {
templateParamsListFieldGridRef.value.removeLocalData(args.selecteds);
}
},
},
],
columns: [
{ name: 'sortNo', label: '排序号' },
{ name: 'name', label: '字段名' },
{ name: 'desc', label: '字段描述' },
],
};
const templateParamsListFieldDialog = {
buttons: [
{
icon: 'save',
label: '确定',
click: async () => {
const validateResult = await templateParamsListFieldAddEditFormRef.value.validate();
if (validateResult) {
const formData = templateParamsListFieldAddEditFormRef.value.getData();
const formStatus = templateParamsListFieldAddEditFormRef.value.getStatus();
if (formStatus === 'add') {
templateParamsListFieldGridRef.value.addRow(formData, null);
} else {
templateParamsListFieldGridRef.value.replaceRow({
...formData,
_rowKey_: templateParamsListFieldGridRef.value.getSelectedRows()[0]['_rowKey_'],
});
}
templateParamsListFieldDialogRef.value.hide();
}
},
},
],
formFields: [
{ label: '字段名', name: 'name', type: 'w-text', requiredIf: true },
{ label: '字段描述', name: 'desc', type: 'w-text' },
],
};
const getTemplateListFun = async () => {
const resp = await axios.get(Environment.apiContextPath('api/excel/template?pageable=false'));
if (resp && resp.data?.content) {
const options = <any>[];
resp.data.content.forEach((item) => {
options.push({ label: item.templateName, value: item.id });
});
state.templateArray = options;
templateParamsGridRef.value.getQueryForm().getFields()['reportExcelTemplate'].options = options;
setTimeout(() => {
if (state.tableSelectedTemplateId) {
templateParamsGridRef.value.getQueryForm().setFieldValue('reportExcelTemplate', state.tableSelectedTemplateId);
}
//
templateParamsGridRef.value.refresh();
}, 100);
}
};
const show = (tableSelected) => {
dialogRef.value.show();
if (tableSelected && tableSelected.length > 0) {
state.tableSelectedTemplateId = tableSelected[0].id;
} else {
state.tableSelectedTemplateId = null;
}
nextTick(() => {
getTemplateListFun();
});
};
const hide = () => {
dialogRef.value.hide();
};
defineExpose({
show,
hide,
});
</script>

234
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateParamsSelectDialog.vue

@ -0,0 +1,234 @@
<template>
<w-dialog ref="dialogRef" title="选取变量" width="80%" height="80%" :buttons="dialog.buttons">
<div class="h-full">
<div class="h-[300px]">
<w-grid
ref="templateParamsGridRef"
dense
sort-no
title="模板变量列表"
:auto-fetch-data="false"
:data-url="Environment.apiContextPath('api/excel/template/params')"
:columns="templateParamsGrid.columns"
:toolbar-actions="templateParamsGrid.toolbarActions"
:config-button="false"
:query-form-cols-num="3"
:query-form-fields="templateParamsGrid.queryFormFields"
:sort-by="['-lastModifyDate']"
@row-click="templateParamsGridClick"
></w-grid>
</div>
<div style="height: calc(100% - 300px)">
<w-grid
v-show="state.showListFieldGrid"
ref="templateParamsListFieldGridRef"
dense
sort-no
:config-button="false"
:hide-bottom="false"
:pageable="false"
title="字段列表"
:auto-fetch-data="false"
:fetch-data-url="Environment.apiContextPath('api/excel/template/params/list')"
:columns="templateParamsListFieldGrid.columns"
></w-grid>
</div>
</div>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { useQuasar } from 'quasar';
import { axios, Environment, NotifyManager, OperatorTypeEnum } from 'platform-core';
const $q = useQuasar();
const emit = defineEmits<{
(
e: 'setParams', //
paramsCode: string | Array<string>, //
): void;
}>();
const dialogRef = ref();
const templateParamsGridRef = ref();
const templateParamsListFieldGridRef = ref();
const state = reactive({
templateArray: [],
tableSelectedTemplateId: null,
showListFieldGrid: false,
});
const dialog = {
buttons: [
{
icon: 'save',
label: '确定',
click: async () => {
const paramsSelected = templateParamsGridRef.value.getSelectedRows();
const listFieldSelected = templateParamsListFieldGridRef.value.getSelectedRows();
if (paramsSelected && paramsSelected.length > 0) {
if (paramsSelected[0].paramsType === 'LIST' && (!listFieldSelected || listFieldSelected.length === 0)) {
const listFieldRows = templateParamsListFieldGridRef.value.getRows();
const arr = <string[]>[];
listFieldRows.forEach((item) => {
arr.push('#{' + item.name + '}');
});
emit('setParams', arr);
hide();
return;
} else if (paramsSelected[0].paramsType === 'LIST' && listFieldSelected && listFieldSelected.length > 0) {
emit('setParams', '#{' + listFieldSelected[0].name + '}');
hide();
return;
}
} else {
NotifyManager.warn('请选择要插入的变量');
return;
}
},
},
],
};
const templateParamsGrid = reactive({
queryFormFields: [
{ label: '变量名称', name: 'paramsName', type: 'w-text' },
{
label: '变量类型',
name: 'paramsType',
type: 'w-select',
queryOperator: 'equals',
options: [
{ label: '单值', value: 'SING' },
{ label: '列表', value: 'LIST' },
],
},
{
label: '所属模板',
name: 'reportExcelTemplate',
type: 'w-select',
queryOperator: 'equals',
clearable: true,
colspan: 2,
options: [],
},
],
toolbarActions: ['query', 'reset'],
columns: [
{ name: 'paramsCode', label: '变量编码' },
{ name: 'paramsName', label: '变量名称' },
{
name: 'paramsType',
label: '变量类型',
format: (val) => {
if (val === 'SING') {
return '单值';
} else if (val === 'LIST') {
return '列表';
} else {
return val;
}
},
},
{
name: 'paramsIsCommon',
label: '变量是否通用',
format: (val) => {
if (val === true) {
return '所有模板均可使用';
} else if (val === false) {
return '指定模板可使用';
} else {
return val;
}
},
},
{ name: 'reportExcelTemplateName', label: '所属模板名称' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
});
const templateParamsListFieldGrid = reactive({
columns: [
{ name: 'name', label: '字段名' },
{ name: 'desc', label: '字段描述' },
{
name: 'formatType',
label: '格式化类型',
format: (val) => {
if (val && OperatorTypeEnum[val]) {
return OperatorTypeEnum[val];
} else {
return val;
}
},
},
{ name: 'formatValue', label: '格式化值' },
],
});
const templateParamsGridClick = (args) => {
const { evt, row, index } = args;
if (row.paramsType === 'LIST') {
state.showListFieldGrid = true;
const urlSearchParams = new URLSearchParams();
urlSearchParams.append('criteria', JSON.stringify({ fieldName: 'reportExcelTemplateParams', operator: 'equals', value: row.id }));
axios
.get(Environment.apiContextPath('api/excel/template/params/list?pageable=false&sortBy=sortNo'), { params: urlSearchParams })
.then((resp) => {
if (resp.data.content) {
templateParamsListFieldGridRef.value.setLocalData(resp.data.content);
}
})
.catch((error) => {
console.info('error====', error);
});
} else {
//
templateParamsListFieldGridRef.value.setLocalData([]);
//
state.showListFieldGrid = false;
}
};
const getTemplateListFun = async () => {
const resp = await axios.get(Environment.apiContextPath('api/excel/template?pageable=false'));
if (resp && resp.data?.content) {
const options = <any>[];
resp.data.content.forEach((item) => {
options.push({ label: item.templateName, value: item.id });
});
state.templateArray = options;
templateParamsGridRef.value.getQueryForm().getFields()['reportExcelTemplate'].options = options;
setTimeout(() => {
if (state.tableSelectedTemplateId) {
templateParamsGridRef.value.getQueryForm().setFieldValue('reportExcelTemplate', state.tableSelectedTemplateId);
}
//
templateParamsGridRef.value.refresh();
}, 100);
}
};
const show = (templateId) => {
dialogRef.value.show();
if (templateId) {
state.tableSelectedTemplateId = templateId;
} else {
state.tableSelectedTemplateId = null;
}
nextTick(() => {
getTemplateListFun();
});
};
const hide = () => {
dialogRef.value.hide();
};
defineExpose({
show,
hide,
});
</script>

34
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateReportDialog.vue

@ -0,0 +1,34 @@
<template>
<w-dialog ref="dialogRef" :title="state.dialogTitle" width="100%" height="100%" :maximized="true" :can-maximize="false" body-padding="0px 0px 0px 0px">
<ExcelReport v-if="state.dataId" ref="excelReportRef" :template-id="state.dataId" style="height: 100%"></ExcelReport>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import ExcelReport from './ExcelReport.vue';
const dialogRef = ref();
const excelReportRef = ref();
const state = reactive({
dialogTitle: '模板制定',
dataId: '',
});
const show = (tableSelectedRow: any) => {
state.dataId = tableSelectedRow.id;
state.dialogTitle = '模板报表查看——' + tableSelectedRow.templateName + '(' + tableSelectedRow.templateCode + ')';
dialogRef.value.show();
};
const hide = () => {
dialogRef.value.hide();
};
defineExpose({
show,
hide,
});
</script>
<style scoped></style>

91
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateSetColumnParamsDialog.vue

@ -0,0 +1,91 @@
<template>
<w-dialog ref="dialogRef" title="属性配置" width="80%" height="80%" :buttons="dialog.buttons">
<w-form ref="formRef" :cols-num="1" :fields="form.fields" class="p-1.5"> </w-form>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue';
import { NotifyManager, Tools } from 'platform-core';
const emit = defineEmits<{
(
e: 'setForEachParams', //
formData: any, //
): void;
}>();
const dialogRef = ref();
const formRef = ref();
const dialog = {
buttons: [
{
icon: 'save',
label: '确定',
click: async () => {
const validate = await formRef.value.validate();
if (validate) {
const formData = formRef.value.getData();
let flag = false;
Object.keys(formData).forEach((item) => {
if (!Tools.isEmpty(formData[item])) {
flag = true;
}
});
if (flag) {
emit('setForEachParams', formData);
dialogRef.value.hide();
} else {
NotifyManager.warn('未配置任何属性');
}
}
},
},
],
};
const form = {
fields: [
{
label: '单元格格式',
name: 'cellFormat',
type: 'w-select',
options: [
{ label: '纯文本', value: '1' },
{ label: '数字格式-整数', value: '2' },
{ label: '数字格式-两位小数', value: '3' },
{ label: '数字格式-四位小数', value: '4' },
{ label: '百分比整数', value: '5' },
{ label: '百分比两位小数', value: '6' },
{ label: '万元两位小数(示例:12万3456.00)', value: '7' },
{ label: '千位符整数(示例:1,235)', value: '8' },
{ label: '千位符两位小数(示例:1,234.56)', value: '9' },
],
},
{
label: '单元格码值转换',
name: 'cellValueTransformed',
type: 'w-text',
hint: `当值匹配上时显示转换后的值,等号左边为原始值,右边为转换值,位置需对应,英文逗号分隔。示例:1,2=年,月 转换为空示例:-999=NULL`,
},
{ label: '单元格批注', name: 'cellComment', type: 'w-textarea' },
],
};
const show = (data) => {
dialogRef.value.show();
nextTick(() => {
if (data) {
formRef.value.setData(data);
}
});
};
const hide = () => {
dialogRef.value.hide();
};
defineExpose({
show,
hide,
});
</script>

250
io.sc.platform.lcdp.frontend/src/views/excel-template/TemplateSetForeachPropsDialog.vue

@ -0,0 +1,250 @@
<template>
<w-dialog ref="dialogRef" title="属性配置" width="80%" height="80%" :buttons="dialog.buttons">
<w-form ref="formRef" :cols-num="1" :fields="form.fields" class="p-1.5"></w-form>
<w-dialog ref="listParamsDialogRef" dialog-title="选择循环的列表名" width="70%" height="70%" :buttons="listParamsDialog.buttons">
<w-grid
ref="templateParamsGridRef"
title="模板变量列表"
sort-no
dense
:checkbox-selection="false"
:auto-fetch-data="false"
:data-url="Environment.apiContextPath('api/excel/template/params')"
:columns="templateParamsGrid.columns"
:toolbar-actions="templateParamsGrid.toolbarActions"
:query-form-cols-num="2"
:query-form-row-num="2"
:query-form-fields="templateParamsGrid.queryFormFields"
:sort-by="['-lastModifyDate']"
></w-grid>
</w-dialog>
</w-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { axios, Environment, NotifyManager, Tools } from 'platform-core';
const emit = defineEmits<{
(
e: 'setForEachParams', //
formData: any, //
): void;
}>();
const dialogRef = ref();
const formRef = ref();
const listParamsDialogRef = ref();
const templateParamsGridRef = ref();
const state = reactive({
templateArray: [],
tableSelectedTemplateId: null,
});
const dialog = {
buttons: [
{
icon: 'save',
label: '确定',
click: async () => {
const validate = await formRef.value.validate();
if (validate) {
const formData = formRef.value.getData();
let flag = false;
Object.keys(formData).forEach((item) => {
if (!Tools.isEmpty(formData[item])) {
flag = true;
}
});
if (flag) {
emit('setForEachParams', formData);
dialogRef.value.hide();
} else {
NotifyManager.warn('未配置任何属性');
}
}
},
},
],
};
const templateParamsGrid = {
queryFormFields: [
{ label: '变量编码', name: 'paramsCode', type: 'w-text' },
{ label: '变量名称', name: 'paramsName', type: 'w-text' },
{
label: '所属模板',
name: 'reportExcelTemplate',
type: 'w-select',
clearable: true,
queryOperator: 'equals',
options: [],
},
{
label: '变量类型',
name: 'paramsType',
type: 'w-select',
queryOperator: 'equals',
options: [
{ label: '单值', value: 'SING' },
{ label: '列表', value: 'LIST' },
],
},
],
toolbarActions: ['query', 'reset', 'separator'],
columns: [
{ name: 'paramsCode', label: '变量编码' },
{ name: 'paramsName', label: '变量名称' },
{
name: 'paramsType',
label: '变量类型',
format: (val) => {
if (val === 'SING') {
return '单值';
} else if (val === 'LIST') {
return '列表';
} else {
return val;
}
},
},
{
name: 'paramsIsCommon',
label: '变量是否通用',
format: (val) => {
if (val === true) {
return '所有模板均可使用';
} else if (val === false) {
return '指定模板可使用';
} else {
return val;
}
},
},
{ name: 'reportExcelTemplateName', label: '所属模板名称' },
{ name: 'lastModifier', label: '最后修改人' },
{ name: 'lastModifyDate', label: '最后修改时间' },
],
};
const form = {
fields: [
{
label: '循环的列表名',
name: 'listName',
type: 'w-grid-select',
requiredIf: true,
displayValue: 'paramsName',
grid: {
title: '模板变量列表',
primaryKey: 'paramsCode',
sortNo: true,
dense: true,
dataUrl: Environment.apiContextPath('api/excel/template/params'),
columns: templateParamsGrid.columns,
queryFormFields: templateParamsGrid.queryFormFields,
queryFormColsNum: 2,
queryFormRowNum: 2,
sortBy: ['-lastModifyDate'],
},
},
{ label: '循环列表中一行数据占几行', name: 'dataRowNum', type: 'w-number' },
{
label: '单元格值相等时字体加粗',
name: 'fontWeight',
type: 'w-text',
hint: `当值匹配上任意值字体加粗,英文逗号分隔。示例:合计,不适用`,
},
{
label: '单元格值相等时字体加粗-该行后续列都加粗',
name: 'fontWeightRow',
type: 'w-select',
options: [
{ label: '是', value: 'true' },
{ label: '否', value: 'false' },
],
},
{
label: '单元格根据值设置背景色',
name: 'cellBgColor',
type: 'w-text',
hint: `当值匹配上时设置对应的背景色,等号左边为单元格值,右边为设置的背景色,位置需对应,英文逗号分隔。示例:不达标,达标,其他=#f44336,#2196f3,#bdbdbd`,
},
{
label: '单元格根据值设置背景色-该行后续列都设置相同背景色',
name: 'cellBgColorRow',
type: 'w-select',
options: [
{ label: '是', value: 'true' },
{ label: '否', value: 'false' },
],
},
{
label: '单元格码值转换',
name: 'cellValueTransformed',
type: 'w-text',
hint: `当值匹配上时显示转换后的值,等号左边为原始值,右边为转换值,位置需对应,英文逗号分隔。示例:1,2=年,月 转换为空示例:-999=NULL`,
},
{ label: '单元格批注', name: 'cellComment', type: 'w-textarea', rows: 2 },
],
};
const listParamsDialog = {
buttons: [
{
icon: 'save',
label: '确定',
click: () => {
const tableSelected = templateParamsGridRef.value.getSelectedRows();
if (!tableSelected || tableSelected.length === 0) {
NotifyManager.info('请选择模板变量');
return;
}
formRef.value.setFieldValue('listName', tableSelected[0].paramsCode);
listParamsDialogRef.value.hide();
},
},
],
};
const getTemplateListFun = async () => {
const resp = await axios.get(Environment.apiContextPath('api/excel/template?pageable=false'));
if (resp.data?.content) {
const options = <any>[];
resp.data.content.forEach((item) => {
options.push({ label: item.templateName, value: item.id });
});
state.templateArray = options;
templateParamsGridRef.value.getQueryForm().getFields()['reportExcelTemplate'].options = options;
setTimeout(() => {
if (state.tableSelectedTemplateId) {
templateParamsGridRef.value.getQueryForm().setFieldValue('reportExcelTemplate', state.tableSelectedTemplateId);
templateParamsGridRef.value.getQueryForm().setFieldValue('paramsType', 'LIST');
}
templateParamsGridRef.value.refresh();
}, 100);
}
};
const show = (templateId, data) => {
dialogRef.value.show();
if (templateId) {
state.tableSelectedTemplateId = templateId;
} else {
state.tableSelectedTemplateId = null;
}
nextTick(() => {
if (data) {
formRef.value.setData(data);
}
});
};
const hide = () => {
dialogRef.value.hide();
};
defineExpose({
show,
hide,
});
</script>

339
io.sc.platform.lcdp.frontend/src/views/excel-template/luckysheet/LuckySheet.vue

@ -0,0 +1,339 @@
<template>
<q-card ref="cardRef" flat bordered :style="cardHeightComponent">
<q-card-section class="p-2.5">
<div class="flex gap-2">
<div class="grow">
<q-file v-model="impFile" label="导入Excel文件" outlined dense accept=".xlsx,.xls" @update:model-value="loadExcel($event)">
<template #append>
<q-icon name="attachment" />
</template>
</q-file>
</div>
<div class="flex flex-none">
<q-btn label="选取变量" :icon="PlatformIconEnum.选取变量" @click="selectParams"></q-btn>
</div>
<div class="flex flex-none">
<q-btn-dropdown :icon="PlatformIconEnum.设置" label="循环属性配置" unelevated outline>
<q-list>
<q-item v-close-popup clickable>
<q-item-section>
<q-item-label @click="openConfigDialog('main')"><q-icon :name="PlatformIconEnum.更多查询"></q-icon> ()</q-item-label>
</q-item-section>
</q-item>
<q-item v-close-popup clickable>
<q-item-section>
<q-item-label @click="openConfigDialog('column')"><q-icon :name="PlatformIconEnum.更多查询"></q-icon> ()</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
<div class="flex flex-none">
<q-btn label="保存并关闭" :icon="PlatformIconEnum.保存" @click="save"></q-btn>
</div>
<div class="flex flex-none">
<q-btn label="导出文件" :icon="PlatformIconEnum.下载" @click="downloadExcel"></q-btn>
</div>
</div>
</q-card-section>
<TemplateParamsSelectDialog ref="paramsSelectDialogRef" @set-params="setParams"></TemplateParamsSelectDialog>
<TemplateSetForEachParamsDialog ref="setForEachParamsDialogRef" @set-for-each-params="setForEachParams"></TemplateSetForEachParamsDialog>
<TemplateSetColumnParamsDialog ref="setColumnParamsDialogRef" @set-for-each-params="setForEachParams"></TemplateSetColumnParamsDialog>
<div id="luckysheet"></div>
</q-card>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, reactive } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import LuckyExcel from 'luckyexcel';
import { exportExcel } from './exportExcel';
import { useQuasar } from 'quasar';
import { axios, TagViewManager, Environment, PlatformIconEnum, Tools, NotifyManager } from 'platform-core';
import TemplateParamsSelectDialog from '@/views/excel_template/TemplateParamsSelectDialog.vue';
import TemplateSetForEachParamsDialog from '@/views/excel_template/TemplateSetForeachPropsDialog.vue';
import TemplateSetColumnParamsDialog from '@/views/excel_template/TemplateSetColumnParamsDialog.vue';
const gc = Environment.getConfigure();
const $q = useQuasar();
const impFile = ref(null);
const paramsSelectDialogRef = ref();
const setForEachParamsDialogRef = ref();
const setColumnParamsDialogRef = ref();
const route = useRoute();
const cardRef = ref();
console.info('route------------', route);
//
onBeforeRouteUpdate((to, from, next) => {
console.info('to------------', to);
initExcel(to.query.templateId);
next();
});
const state = reactive({
cardY: 0,
});
const cardHeightComponent = computed(() => {
//
const screenHeight = $q.screen.height;
// XX
const footerHeight = gc.theme.footer.enable ? gc.theme.footer.height : 0;
const cardHeight = screenHeight - footerHeight - state.cardY - 10;
return {
height: cardHeight + 'px',
};
});
const loadExcel = (value) => {
const { name } = value;
const suffixArr = name.split('.');
const suffix = suffixArr[suffixArr.length - 1];
if (suffix !== 'xlsx') {
NotifyManager.info('只能导入xlsx格式的Excel文件');
return;
}
LuckyExcel.transformExcelToLucky(value, (exportJson) => {
if (exportJson.sheets === null || exportJson.sheets.length === 0) {
NotifyManager.info(`只能导入'xlsx'格式的Excel文件`);
return;
}
window.luckysheet.destroy();
const data = [
{
...exportJson.sheets[0],
...{
column: 26,
row: 30,
},
},
];
luckysheetOptions.data = data;
window.luckysheet.create({
lang: 'zh',
container: 'luckysheet',
showinfobar: false,
showtoolbar: true, //
sheetFormulaBar: true, //
showsheetbar: false, // sheet
data: data,
title: exportJson.info.name,
userInfo: exportJson.info.name.creator,
});
});
};
const downloadExcel = () => {
// eslint-disable-next-line
exportExcel(luckysheet.getAllSheets(), '下载');
};
const luckysheetData = [
{
name: 'Cell', //
color: '', //
index: 0, //
status: 1, //
order: 0, //
hide: 0, //
defaultRowHeight: 19, //
defaultColWidth: 73, //
column: 26,
row: 30,
celldata: [], // 使
config: {
merge: {}, //
rowlen: {}, //
columnlen: {}, //
rowhidden: {}, //
colhidden: {}, //
borderInfo: [], //
authority: {}, //
},
scrollLeft: 0, //
scrollTop: 0, //
luckysheet_select_save: [], //
calcChain: [], //
isPivotTable: false, //
pivotTable: {}, //
filter_select: {}, //
filter: null, //
luckysheet_alternateformat_save: [], //
luckysheet_alternateformat_save_modelCustom: [], //
luckysheet_conditionformat_save: {}, //
frozen: {}, //
chart: [], //
zoomRatio: 1, //
image: [], //
showGridLines: 1, // 线
dataVerification: {}, //
},
];
const luckysheetOptions = {
container: 'luckysheet', // DOMid
lang: 'zh', //
title: '测试', //
allowEdit: true, //
rowHeaderWidth: 46, // 0A,B,C46
columnHeaderHeight: 20, // 020
showtoolbar: true, //
showinfobar: false, // luckysheetlogo
userInfo: 'likunming', // showinfobar使
sheetFormulaBar: true, //
defaultFontSize: 11, // ,11
showsheetbar: false, // sheet
data: luckysheetData, //
};
const luckySheetEdit = () => {
luckysheetOptions.allowEdit = true;
luckysheetOptions.showtoolbar = true;
luckysheetOptions.sheetFormulaBar = true;
// eslint-disable-next-line
luckysheet.create(luckysheetOptions);
};
const luckySheetSave = () => {
luckysheetOptions.allowEdit = false;
luckysheetOptions.showtoolbar = false;
luckysheetOptions.sheetFormulaBar = false;
// eslint-disable-next-line
luckysheet.create(luckysheetOptions);
};
const selectParams = () => {
// eslint-disable-next-line
const selectRange = luckysheet.getRange();
if (!selectRange || selectRange.length === 0) {
platformNotify($q, '请选择要插入变量的Excel单元格');
return;
} else if (selectRange && selectRange.length > 1) {
platformNotify($q, '只支持单个选区选取变量');
return;
}
paramsSelectDialogRef.value.dialogShow(route.query?.templateId);
};
const setParams = (paramsCode: string | Array<string>) => {
if (typeof paramsCode === 'string') {
// eslint-disable-next-line
let rangeValue = luckysheet.getRangeValue();
rangeValue[0][0] = { ...rangeValue[0][0], ...{ m: paramsCode, v: paramsCode } };
// eslint-disable-next-line
luckysheet.setRangeValue(rangeValue);
} else {
// eslint-disable-next-line
const currRange = luckysheet.getRange();
paramsCode.forEach((item, index) => {
const tmpRangeValue = [[{ m: item, v: item }]];
const tmp = {
row: [currRange[0].row[0], currRange[0].row[1]],
column: [currRange[0].column[0] + index, currRange[0].column[1] + index],
};
// eslint-disable-next-line
luckysheet.setRangeValue(tmpRangeValue, { range: tmp });
});
}
};
// eslint-disable-next-line
const psReg = /\@\{(.*)\}\@/;
const openConfigDialog = (configType) => {
// eslint-disable-next-line
const range = luckysheet.getRange();
// eslint-disable-next-line
const rangeValue = luckysheet.getRangeValue();
if (!range) {
NotifyManager.info('请选择要配置属性的单元格');
return;
} else if (range && range.length > 1) {
NotifyManager.info('多个选区无法配置属性,选择单元格既可');
return;
}
let data = {};
if (rangeValue[0] && rangeValue[0][0] && rangeValue[0][0].ps && rangeValue[0][0].ps.value) {
let tmp = rangeValue[0][0].ps.value.replaceAll('\n', '');
data = JSON.parse(psReg.exec(tmp)[1]);
}
if (configType === 'main') {
setForEachParamsDialogRef.value.dialogShow(route.query?.templateId, data);
} else {
setColumnParamsDialogRef.value.dialogShow(data);
}
};
const setForEachParams = (formData) => {
// eslint-disable-next-line
const range = luckysheet.getRange();
let tmp = '@{{\n';
Object.keys(formData).forEach((item) => {
if (!Tools.isEmpty(formData[item])) {
tmp += '"' + item + '"' + ':' + '"' + formData[item] + '"' + ',' + '\n';
}
});
tmp = tmp.substring(0, tmp.length - 2);
tmp += '\n}}@';
// eslint-disable-next-line
luckysheet.insertComment(range[0].row[0], range[0].column[0], tmp);
//
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.width = 300;
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.height = 150;
luckysheetOptions.data[0].data[range[0].row[0]][range[0].column[0]].ps.isShow = false;
// eslint-disable-next-line
luckysheet.refresh();
};
const save = async () => {
// eslint-disable-next-line
if (!luckysheet.getAllSheets() || luckysheet.getAllSheets().length === 0) {
NotifyManager.info('系统异常,未获取到需要保存的Sheet数据');
return;
}
// celldata使data
// eslint-disable-next-line
const saveData = { ...luckysheet.getAllSheets()[0], ...{ celldata: [] } };
//
let requestParams = {
method: 'POST',
headers: { 'content-type': 'application/json;charset=utf-8;' },
data: { templateConfig: JSON.stringify(saveData) },
url: Environment.apiContextPath('api/excel/template/updateConfig/') + route.query.templateId,
};
const resp = await axios(requestParams).catch((error) => {
console.info('error====', error);
NotifyManager.error('保存失败');
});
NotifyManager.error('保存成功');
//
// const index = TagViewManager.getTagViews().findIndex((item) => item.fullPath.indexOf(route.query.templateId) > -1);
TagViewManager.removeTagView({ mode: 'current' });
};
const initExcel = async (templateId?: any) => {
if (window.luckysheet) {
window.luckysheet.destroy();
}
const resp = await axios.get(Environment.apiContextPath('api/excel/template/') + (templateId ?? route.query.templateId));
if (resp?.data?.templateConfig) {
luckysheetOptions.data = [eval('(' + resp.data.templateConfig + ')')];
}
// eslint-disable-next-line
luckysheet.create(luckysheetOptions);
};
onMounted(async () => {
state.cardY = cardRef.value.$el.getBoundingClientRect()?.y;
initExcel();
});
</script>
<style scoped>
#luckysheet {
margin: 0px;
padding: 0px;
position: absolute;
width: 100%;
left: 0px;
top: 70px;
bottom: 0px;
}
</style>

319
io.sc.platform.lcdp.frontend/src/views/excel-template/luckysheet/exportExcel.ts

@ -0,0 +1,319 @@
import Excel from 'exceljs';
import FileSaver from 'file-saver';
const exportExcel = function (luckysheet, value) {
// 参数为luckysheet.getluckysheetfile()获取的对象
// 1.创建工作簿,可以为工作簿添加属性
const workbook = new Excel.Workbook();
// 2.创建表格,第二个参数可以配置创建什么样的工作表
if (Object.prototype.toString.call(luckysheet) === '[object Object]') {
luckysheet = [luckysheet];
}
luckysheet.forEach(function (table) {
if (table.data.length === 0) return true;
// ws.getCell('B2').fill = fills.
const worksheet = workbook.addWorksheet(table.name);
const merge = (table.config && table.config.merge) || {};
const borderInfo = (table.config && table.config.borderInfo) || {};
// 3.设置单元格合并,设置单元格边框,设置单元格样式,设置值
setStyleAndValue(table.data, worksheet);
setMerge(merge, worksheet);
setBorder(borderInfo, worksheet);
return true;
});
// return
// 4.写入 buffer
const buffer = workbook.xlsx.writeBuffer().then((data) => {
// console.log('data', data)
const blob = new Blob([data], {
type: 'application/vnd.ms-excel;charset=utf-8',
});
console.log('导出成功!');
FileSaver.saveAs(blob, `${value}.xlsx`);
});
return buffer;
};
const setMerge = function (luckyMerge = {}, worksheet) {
const mergearr = Object.values(luckyMerge);
mergearr.forEach(function (elem) {
// elem格式:{r: 0, c: 0, rs: 1, cs: 2}
// 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)
worksheet.mergeCells(elem.r + 1, elem.c + 1, elem.r + elem.rs, elem.c + elem.cs);
});
};
const setBorder = function (luckyBorderInfo, worksheet) {
if (!Array.isArray(luckyBorderInfo)) return;
// console.log('luckyBorderInfo', luckyBorderInfo)
luckyBorderInfo.forEach(function (elem) {
// 现在只兼容到borderType 为range的情况
// console.log('ele', elem)
if (elem.rangeType === 'range') {
const border = borderConvert(elem.borderType, elem.style, elem.color);
const rang = elem.range[0];
// console.log('range', rang)
const row = rang.row;
const column = rang.column;
for (let i = row[0] + 1; i < row[1] + 2; i++) {
for (let y = column[0] + 1; y < column[1] + 2; y++) {
worksheet.getCell(i, y).border = border;
}
}
}
if (elem.rangeType === 'cell') {
// col_index: 2
// row_index: 1
// b: {
// color: '#d0d4e3'
// style: 1
// }
const { col_index, row_index } = elem.value;
const borderData = Object.assign({}, elem.value);
delete borderData.col_index;
delete borderData.row_index;
const border = addborderToCell(borderData, row_index, col_index);
// console.log('bordre', border, borderData)
worksheet.getCell(row_index + 1, col_index + 1).border = border;
}
// console.log(rang.column_focus + 1, rang.row_focus + 1)
// worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border
});
};
const setStyleAndValue = function (cellArr, worksheet) {
if (!Array.isArray(cellArr)) return;
cellArr.forEach(function (row, rowid) {
row.every(function (cell, columnid) {
if (!cell) return true;
const fill = fillConvert(cell.bg);
const font = fontConvert(cell.ff, cell.fc, cell.bl, cell.it, cell.fs, cell.cl, cell.ul);
const alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr);
let value = '';
if (cell.f) {
value = { formula: cell.f, result: cell.v };
} else if (!cell.v && cell.ct && cell.ct.s) {
// xls转为xlsx之后,内部存在不同的格式,都会进到富文本里,即值不存在与cell.v,而是存在于cell.ct.s之后
// value = cell.ct.s[0].v
cell.ct.s.forEach((arr) => {
value += arr.v;
});
} else {
value = cell.v;
}
// style 填入到_value中可以实现填充色
const letter = createCellPos(columnid);
const target = worksheet.getCell(letter + (rowid + 1));
// console.log('1233', letter + (rowid + 1))
for (const key in fill) {
target.fill = fill;
break;
}
target.font = font;
target.alignment = alignment;
target.value = value;
return true;
});
});
};
const fillConvert = function (bg) {
if (!bg) {
return {};
}
// const bgc = bg.replace('#', '')
const fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: bg.replace('#', '') },
};
return fill;
};
const fontConvert = function (ff = 0, fc = '#000000', bl = 0, it = 0, fs = 10, cl = 0, ul = 0) {
// luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)
const luckyToExcel = {
0: '微软雅黑',
1: '宋体(Song)',
2: '黑体(ST Heiti)',
3: '楷体(ST Kaiti)',
4: '仿宋(ST FangSong)',
5: '新宋体(ST Song)',
6: '华文新魏',
7: '华文行楷',
8: '华文隶书',
9: 'Arial',
10: 'Times New Roman ',
11: 'Tahoma ',
12: 'Verdana',
num2bl: function (num) {
return num === 0 ? false : true;
},
};
// 出现Bug,导入的时候ff为luckyToExcel的val
const font = {
name: typeof ff === 'number' ? luckyToExcel[ff] : ff,
family: 1,
size: fs,
color: { argb: fc.replace('#', '') },
bold: luckyToExcel.num2bl(bl),
italic: luckyToExcel.num2bl(it),
underline: luckyToExcel.num2bl(ul),
strike: luckyToExcel.num2bl(cl),
};
return font;
};
const alignmentConvert = function (vt = 'default', ht = 'default', tb = 'default', tr = 'default') {
// luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)
const luckyToExcel = {
vertical: {
0: 'middle',
1: 'top',
2: 'bottom',
default: 'top',
},
horizontal: {
0: 'center',
1: 'left',
2: 'right',
default: 'left',
},
wrapText: {
0: false,
1: false,
2: true,
default: false,
},
textRotation: {
0: 0,
1: 45,
2: -45,
3: 'vertical',
4: 90,
5: -90,
default: 0,
},
};
const alignment = {
vertical: luckyToExcel.vertical[vt],
horizontal: luckyToExcel.horizontal[ht],
wrapText: luckyToExcel.wrapText[tb],
textRotation: luckyToExcel.textRotation[tr],
};
return alignment;
};
const borderConvert = function (borderType, style = 1, color = '#000') {
// 对应luckysheet的config中borderinfo的的参数
if (!borderType) {
return {};
}
const luckyToExcel = {
type: {
'border-all': 'all',
'border-top': 'top',
'border-right': 'right',
'border-bottom': 'bottom',
'border-left': 'left',
},
style: {
0: 'none',
1: 'thin',
2: 'hair',
3: 'dotted',
4: 'dashDot', // 'Dashed',
5: 'dashDot',
6: 'dashDotDot',
7: 'double',
8: 'medium',
9: 'mediumDashed',
10: 'mediumDashDot',
11: 'mediumDashDotDot',
12: 'slantDashDot',
13: 'thick',
},
};
const template = {
style: luckyToExcel.style[style],
color: { argb: color.replace('#', '') },
};
const border = {};
if (luckyToExcel.type[borderType] === 'all') {
border['top'] = template;
border['right'] = template;
border['bottom'] = template;
border['left'] = template;
} else {
border[luckyToExcel.type[borderType]] = template;
}
// console.log('border', border)
return border;
};
function addborderToCell(borders, row_index, col_index) {
const border = {};
const luckyExcel = {
type: {
l: 'left',
r: 'right',
b: 'bottom',
t: 'top',
},
style: {
0: 'none',
1: 'thin',
2: 'hair',
3: 'dotted',
4: 'dashDot', // 'Dashed',
5: 'dashDot',
6: 'dashDotDot',
7: 'double',
8: 'medium',
9: 'mediumDashed',
10: 'mediumDashDot',
11: 'mediumDashDotDot',
12: 'slantDashDot',
13: 'thick',
},
};
// console.log('borders', borders)
for (const bor in borders) {
// console.log(bor)
if (borders[bor].color.indexOf('rgb') === -1) {
border[luckyExcel.type[bor]] = {
style: luckyExcel.style[borders[bor].style],
color: { argb: borders[bor].color.replace('#', '') },
};
} else {
border[luckyExcel.type[bor]] = {
style: luckyExcel.style[borders[bor].style],
color: { argb: borders[bor].color },
};
}
}
return border;
}
function createCellPos(n) {
const ordA = 'A'.charCodeAt(0);
const ordZ = 'Z'.charCodeAt(0);
const len = ordZ - ordA + 1;
let s = '';
while (n >= 0) {
s = String.fromCharCode((n % len) + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}
export { exportExcel };

27
io.sc.platform.lcdp.frontend/src/views/excel-template/template.ts

@ -0,0 +1,27 @@
const cellFormatMap = new Map();
// 纯文本
cellFormatMap.set('1', { fa: '@', t: 's' });
// 数字格式-整数
cellFormatMap.set('2', { fa: '0', t: 'n' });
// 数字格式-两位小数
cellFormatMap.set('3', { fa: '0.00', t: 'n' });
// 数字格式-四位小数
cellFormatMap.set('4', { fa: '0.0000', t: 'n' });
// 百分比整数
cellFormatMap.set('5', { fa: '0%', t: 'n' });
// 百分比两位小数
cellFormatMap.set('6', { fa: '0.00%', t: 'n' });
// 万元两位小数(示例:12万3456.00)
cellFormatMap.set('7', { fa: 'w0.00', t: 'n' });
// 千位符整数(示例:1,235)
cellFormatMap.set('8', { fa: '#,##0', t: 'n' });
// 千位符两位小数(示例:1,234.56)
cellFormatMap.set('9', { fa: '#,##0.00', t: 'n' });
/**
*
* @param index
*/
export function getExcelCellFormatObject(index: string) {
return cellFormatMap.get(index);
}
Loading…
Cancel
Save