This commit is contained in:
2026-02-28 17:43:55 +08:00
10 changed files with 1244 additions and 3 deletions

View File

@@ -0,0 +1,135 @@
<template>
<div class="comp-edit-form">
<div class="form">
<div v-for="(item, index) in formData" :key="index" class="form-item">
<div class="title">
<el-input
v-if="item.params"
v-model="item.params.title"
placeholder="请输入标题"
clearable
:disabled="disabled"
/>
</div>
<div class="key">
<el-input
v-if="mode === 'input'"
v-model="item.value"
placeholder="请输入"
clearable
:disabled="disabled"
/>
<el-input
v-else
v-model="item.key"
placeholder="请输入key值"
clearable
:disabled="disabled"
/>
</div>
<div v-if="mode === 'edit'" class="option">
<el-link type="danger" @click="delFun(index)">删除</el-link>
</div>
</div>
</div>
<div v-if="mode === 'edit'" class="add-btn">
<el-button :icon="CirclePlus" type="primary" size="small" @click="addFun">添加</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { CirclePlus } from '@element-plus/icons-vue';
import { cloneDeep } from 'lodash-es';
interface Props {
children: any;
mode: any;
disabled?: any;
}
const props = withDefaults(defineProps<Props>(), {
children: '',
mode: '',
disabled: false,
});
const emit = defineEmits(['update:children']);
const formData = ref<any>([]);
watch(
() => props.children,
(val: any, oldVal: any) => {
if (JSON.stringify(val) !== JSON.stringify(oldVal)) {
formData.value = cloneDeep(val) || [];
}
},
{ deep: true, immediate: true }
);
watch(
() => formData.value,
(val: any) => {
emit('update:children', val);
},
{
deep: true,
immediate: true,
}
);
const addFun = () => {
formData.value.push({
id: new Date().getTime(),
type: 'text',
key: '', // 报告生成对应key
value: '', // 内容
params: {
// 其他参数
title: '',
},
children: [], // 子节点
});
};
const delFun = (index: any) => {
formData.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
.comp-edit-form {
.form {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.form-item {
width: 48%;
display: flex;
justify-content: space-between;
padding: 5px 0;
.title {
flex: 1;
margin-right: 10px;
}
.key {
flex: 1;
margin-right: 10px;
}
.option {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
}
}
}
.add-btn {
display: flex;
align-items: center;
justify-content: center;
padding-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,325 @@
<template>
<div class="comp-edit-img">
<template v-if="mode === 'edit'">
<div v-for="(item, index) in Array(paramsData.colNum)" :key="index" class="pic">
<el-icon :size="100"><Picture /></el-icon>
<div class="title-item">{{ paramsData.title }}</div>
</div>
</template>
<div
v-else
ref="PasteContentRef"
class="paste-content"
:class="{ active: isFocus }"
:contenteditable="disabled ? false : contenteditable"
@click="clickFun"
@paste="pasteFun"
>
<template v-if="fileList.length > 0">
<el-row :gutter="20">
<el-col v-for="(item, index) in fileList" :key="index" :span="24 / paramsData.colNum">
<div class="img-item">
<div class="img">
<img :src="item.src" />
</div>
<div class="tip">
<el-input
class="img-tip-inp"
v-model="item.title"
:placeholder="params.placeholder || '请输入图例名称'"
clearable
@input="inputFun"
/>
</div>
<div class="del-btn" @click.stop="delFun(index)">
<el-icon :size="22"><DeleteFilled /></el-icon>
</div>
</div>
</el-col>
</el-row>
</template>
<div v-else class="preview">
<div class="no-data">单击此处粘贴图片...</div>
<div class="placeholder">{{ paramsData.title }}</div>
</div>
</div>
<Dialog v-model="diaShow" diaTitle="图片设置" :width="400" @close="closeFun">
<div class="content">
<el-form label-width="auto">
<el-form-item label="图片key">
<el-input v-model="titleKey" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="默认名称">
<el-input v-model="paramsData.title" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="输入提示">
<el-input v-model="paramsData.placeholder" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="展示列数">
<el-input-number
v-model="paramsData.colNum"
:min="1"
:max="4"
:step="1"
step-strictly
/>
</el-form-item>
</el-form>
</div>
<template #footer>
<div>
<el-button type="primary" @click="closeFun">确定</el-button>
</div>
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { Picture, DeleteFilled } from '@element-plus/icons-vue';
import Dialog from '@/components/common/dialog/index.vue';
import { cloneDeep } from 'lodash-es';
const emit = defineEmits(['update:keyValue', 'update:value', 'update:params']);
interface Props {
keyValue: any;
value: any;
params: any;
mode: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
keyValue: '',
value: [],
params: {},
mode: '',
disabled: false,
});
const diaShow = ref(false);
const titleKey = ref(props.keyValue);
const fileList = ref<any>([]);
const isFocus = ref(false);
const PasteContentRef = ref();
const contenteditable = ref(true);
const paramsData = ref(cloneDeep(props.params));
if (!paramsData.value.colNum) {
paramsData.value.colNum = 1;
}
emit('update:value', fileList.value);
emit('update:params', paramsData.value);
watch(
() => titleKey.value,
(val: any) => {
emit('update:keyValue', val);
},
{ deep: true }
);
watch(
() => paramsData.value,
(val: any) => {
emit('update:params', val);
},
{
deep: true,
}
);
watch(
() => props.value,
(val: any) => {
fileList.value = cloneDeep(val) || [];
},
{ deep: true, immediate: true }
);
onMounted(() => {
document.addEventListener('click', focusFun);
});
onBeforeUnmount(() => {
document.removeEventListener('click', focusFun);
});
const clickFun = () => {
if (props.disabled) {
return;
}
isFocus.value = true;
};
const focusFun = (event: any) => {
if (props.disabled) {
return;
}
if (PasteContentRef.value && !PasteContentRef.value.contains(event.target)) {
isFocus.value = false;
}
};
const delFun = (index: any) => {
contenteditable.value = false;
nextTick(() => {
PasteContentRef.value.click();
PasteContentRef.value.focus();
fileList.value.splice(index, 1);
contenteditable.value = true;
emit('update:value', fileList.value);
});
};
const pasteFun = (event: any) => {
if (props.disabled) {
return;
}
const dom = event.target;
const tagName = dom.tagName;
if (tagName.toLowerCase() !== 'input') {
event.preventDefault();
}
const win: any = window;
const clipboardData = event.clipboardData || win.clipboardData;
const items = clipboardData.items;
if (!items) return;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind === 'file' && item.type.startsWith('image/')) {
const file = item.getAsFile();
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result;
fileList.value.push({
src: base64,
title: props.params.title,
});
emit('update:value', fileList.value);
};
reader.readAsDataURL(file);
break;
}
}
};
const inputFun = () => {
emit('update:value', fileList.value);
};
const openFun = () => {
diaShow.value = true;
};
const closeFun = () => {
diaShow.value = false;
};
defineExpose({
openFun,
});
</script>
<style lang="scss">
.comp-edit-img {
.img-tip-inp {
.el-input__inner {
text-align: center;
}
}
}
</style>
<style lang="scss" scoped>
.comp-edit-img {
position: relative;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: flex-start;
border-radius: 4px;
padding: 20px 10px 10px;
border: solid 1px var(--el-border-color);
.pic {
width: 100%;
height: 200px;
border-radius: 4px;
background-color: var(--el-bg-color);
color: var(--el-text-color-placeholder);
display: flex;
align-items: center;
justify-content: center;
align-content: center;
flex-wrap: wrap;
.title-item {
width: 100%;
text-align: center;
}
}
.paste-content {
padding: 20px;
width: 100%;
min-height: 200px;
background-color: var(--el-bg-color);
border-radius: 4px;
border: dashed 4px var(--el-bg-color);
&.active {
border-color: var(--el-color-primary);
}
.img-item {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 20px;
&:hover {
.del-btn {
opacity: 1;
}
}
.img {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin-bottom: 10px;
img {
max-width: 100%;
}
}
.tip {
width: 100%;
max-width: 300px;
}
.del-btn {
opacity: 0;
position: absolute;
top: 0;
right: 0;
color: var(--el-color-danger);
cursor: pointer;
}
}
.preview {
width: 100%;
height: 148px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-content: space-between;
.no-data {
color: var(--el-text-color-placeholder);
}
.placeholder {
text-align: center;
color: var(--el-text-color-primary);
}
}
}
}
</style>

View File

@@ -0,0 +1,191 @@
<template>
<div class="comp-edit-table">
<el-table :data="tableData">
<el-table-column
v-for="(item, index) in head"
:key="index"
:prop="item.key"
:label="item.title"
>
<template #default="scope">
<el-input v-model="scope.row[item.key]" clearable @input="inputFun" />
</template>
</el-table-column>
<el-table-column v-if="mode === 'input'" prop="actions" label="操作" fixed="right" width="60">
<template #default="scope">
<el-link type="danger" @click="delDataFun(scope.$index)">删除</el-link>
</template>
</el-table-column>
</el-table>
<div v-if="mode === 'input' && !disabled" class="add-column">
<el-button :icon="CirclePlus" type="primary" size="small" @click="addDataFun">添加</el-button>
</div>
<Dialog v-model="diaShow" diaTitle="表格设置" :width="500" @close="closeFun">
<div class="content">
<el-form label-width="auto">
<el-form-item label="表格key">
<el-input v-model="titleKey" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="表格类型">
<el-select v-model="paramsData.tableType">
<el-option label="指标" value="performance" />
<el-option label="普通" value="normal" />
</el-select>
</el-form-item>
</el-form>
<div class="table">
<el-table :data="headData">
<el-table-column prop="title" label="表头名称">
<template #default="scope">
<el-input v-model="scope.row.title" clearable />
</template>
</el-table-column>
<el-table-column prop="key" label="表头key值">
<template #default="scope">
<el-input v-model="scope.row.key" clearable />
</template>
</el-table-column>
<el-table-column prop="actions" label="操作" width="60">
<template #default="scope">
<el-link type="danger" @click="delHeadFun(scope.$index)">删除</el-link>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<div>
<el-button @click="addFun">新增列头</el-button>
<el-button type="primary" @click="saveFun">确定</el-button>
</div>
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { CirclePlus } from '@element-plus/icons-vue';
import Dialog from '@/components/common/dialog/index.vue';
import { cloneDeep } from 'lodash-es';
const emit = defineEmits(['update:keyValue', 'update:value', 'update:params', 'update:head']);
interface Props {
keyValue: any;
value: any;
params: any;
mode: string;
head?: any;
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
keyValue: '',
value: [],
params: {},
mode: '',
head: [],
disabled: false,
});
const diaShow = ref(false);
const titleKey = ref(props.keyValue);
const tableData = ref<any>([]);
const headData = ref<any>(cloneDeep(props.head));
const paramsData = ref(cloneDeep(props.params));
emit('update:value', tableData.value);
emit('update:head', headData.value);
emit('update:params', paramsData.value);
watch(
() => titleKey.value,
(val: any) => {
emit('update:keyValue', val);
},
{ deep: true }
);
watch(
() => paramsData.value,
(val: any) => {
emit('update:params', val);
},
{
deep: true,
}
);
watch(
() => props.head,
(val: any) => {
headData.value = cloneDeep(val) || [];
},
{ deep: true, immediate: true }
);
watch(
() => props.value,
(val: any) => {
tableData.value = cloneDeep(val) || [];
},
{ deep: true, immediate: true }
);
const inputFun = () => {
emit('update:value', tableData.value);
};
const addFun = () => {
headData.value.push({
title: '',
key: '',
});
};
const addDataFun = () => {
tableData.value.push({});
emit('update:value', tableData.value);
};
const delDataFun = (index: any) => {
tableData.value.splice(index, 1);
emit('update:value', tableData.value);
};
const delHeadFun = (index: any) => {
headData.value.splice(index, 1);
};
const saveFun = () => {
emit('update:head', headData.value);
closeFun();
};
const openFun = () => {
diaShow.value = true;
};
const closeFun = () => {
diaShow.value = false;
};
defineExpose({
openFun,
});
</script>
<style lang="scss" scoped>
.comp-edit-table {
position: relative;
border-radius: 4px;
padding: 20px 10px 10px;
border: solid 1px var(--el-border-color);
}
.add-column {
display: flex;
align-items: center;
justify-content: center;
padding-top: 10px;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="comp-edit-text">
<el-input
v-model="textData"
type="textarea"
:placeholder="placeholderFun('请输入文本')"
:disabled="disabled"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
interface Props {
keyValue: any;
value: any;
mode: any;
disabled?: any;
}
const props = withDefaults(defineProps<Props>(), {
keyValue: '',
value: '',
mode: '',
disabled: false,
});
const emit = defineEmits(['update:keyValue', 'update:value']);
const textData = ref('');
watch(
() => props.value,
(val: any) => {
textData.value = val;
}
);
watch(
() => textData.value,
(val: any) => {
if (props.mode === 'edit') {
emit('update:keyValue', val);
} else {
emit('update:value', val);
}
}
);
const placeholderFun = (text: string) => {
return `${text}${props.mode === 'edit' ? 'key' : ''}`;
};
</script>

View File

@@ -0,0 +1,48 @@
<template>
<div class="comp-edit-title">
<el-input
v-if="mode === 'edit'"
v-model="titleData"
type="input"
placeholder="请输入标题"
clearable
:disabled="disabled"
/>
<div v-else class="title">{{ titleData }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
interface Props {
value: any;
mode: any;
disabled?: any;
}
const props = withDefaults(defineProps<Props>(), {
value: '',
mode: '',
disabled: false,
});
const emit = defineEmits(['update:value']);
const titleData = ref(props.value);
watch(
() => titleData.value,
(val: any) => {
emit('update:value', val);
}
);
</script>
<style lang="scss" scoped>
.comp-edit-title {
.title {
font-size: 14px;
color: var(--el-text-color-secondary);
}
}
</style>

View File

@@ -0,0 +1,285 @@
<template>
<Draggable :list="documentData" :animation="250" item-key="id" handle=".drag-btn">
<template #item="{ element, index }">
<div class="comp-edit-item">
<div class="toper">
<div class="title">
<div v-if="mode === 'edit'" class="drag-btn">
<el-icon :size="20"><DCaret /></el-icon>
</div>
<div class="type-name">
{{ titleMap[element.type] }}{{ element.key ? `-${element.key}` : '' }}
</div>
<div class="danger-tip">
<template v-if="element.type === 'img'">
<el-link
v-if="!element.key"
class="tip-item"
type="danger"
@click="editFun(element)"
>
<el-icon :size="14"><Warning /></el-icon>
未设置图片key
</el-link>
</template>
<template v-if="element.type === 'table'">
<el-link
v-if="!element.key"
class="tip-item"
type="danger"
@click="editFun(element)"
>
<el-icon :size="14"><Warning /></el-icon>
未设置表格key
</el-link>
<el-link
v-if="element.head?.length === 0"
class="tip-item"
type="danger"
@click="editFun(element)"
>
<el-icon :size="14"><Warning /></el-icon>
未设置表头
</el-link>
</template>
</div>
</div>
<div v-if="mode === 'edit'" class="options">
<el-dropdown
v-if="paragraphType.includes(element.type)"
:teleported="false"
trigger="click"
>
<el-link v-if="paragraphType.includes(element.type)" class="item" type="primary">
<el-icon :size="14"><CirclePlus /></el-icon>新增
</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="addFun('title', index)">标题</el-dropdown-item>
<el-dropdown-item @click="addFun('text', index)">文本</el-dropdown-item>
<el-dropdown-item @click="addFun('form', index)">表单</el-dropdown-item>
<el-dropdown-item @click="addFun('img', index)">图片</el-dropdown-item>
<el-dropdown-item @click="addFun('table', index)">表格</el-dropdown-item>
<el-dropdown-item @click="addFun('section', index)">小节</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown :teleported="false" trigger="click">
<el-link class="item" type="primary">
<el-icon :size="14"><Setting /></el-icon>编辑
</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="editType.includes(element.type)">
<el-link type="primary" @click="editFun(element)">设置</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link type="danger" @click="delFun(index)">删除</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div v-if="paragraphType.includes(element.type)" class="content">
<EditItem
v-if="element.children?.length > 0"
v-model:data="element.children"
:mode="mode"
:preview="preview"
/>
</div>
<div v-else class="content">
<CompTitle
v-if="element.type === 'title'"
v-model:value="element.value"
:mode="mode"
:disabled="preview"
/>
<CompText
v-if="element.type === 'text'"
v-model:keyValue="element.key"
v-model:value="element.value"
:mode="mode"
:disabled="preview"
/>
<CompForm
v-if="element.type === 'form'"
v-model:children="element.children"
:mode="mode"
:disabled="preview"
/>
<CompImg
v-if="element.type === 'img'"
:ref="(el) => (CompRefs[element.id] = el)"
v-model:keyValue="element.key"
v-model:value="element.value"
v-model:params="element.params"
:mode="mode"
:disabled="preview"
/>
<CompTable
v-if="element.type === 'table'"
:ref="(el) => (CompRefs[element.id] = el)"
v-model:keyValue="element.key"
v-model:value="element.value"
v-model:params="element.params"
v-model:head="element.head"
:mode="mode"
:disabled="preview"
/>
</div>
</div>
</template>
</Draggable>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import Draggable from 'vuedraggable';
import { CirclePlus, Setting, Warning, DCaret } from '@element-plus/icons-vue';
import CompTitle from './components/title.vue';
import CompText from './components/text.vue';
import CompForm from './components/form.vue';
import CompImg from './components/img.vue';
import CompTable from './components/table.vue';
defineOptions({
name: 'EditItem',
});
interface Props {
data: any;
mode: any;
preview?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
data: [],
mode: '',
preview: false,
});
const CompRefs = ref<any>({});
const documentData = ref<any>(props.data);
const paragraphType = ref(['paragraph', 'section', 'empty']);
const editType = ref(['img', 'table']);
const titleMap = ref<any>({
paragraph: '章节',
section: '小节',
empty: '小节',
title: '标题',
text: '文本',
form: '表单',
img: '图片',
table: '表格',
});
watch(
() => props.data,
(val: any, oldVal) => {
if (JSON.stringify(val) !== JSON.stringify(oldVal)) {
documentData.value = val;
}
},
{ deep: true }
);
const addFun = (type: any, index: number) => {
const data: any = {
id: new Date().getTime(),
type, // 类型
key: '', // 报告生成对应key
value: '', // 内容
params: {}, // 其他参数
children: [], // 子节点
};
documentData.value[index]?.children?.push(data);
};
const editFun = (data: any) => {
CompRefs.value[data.id]?.openFun();
};
const delFun = (index: number) => {
documentData.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
.comp-edit-item {
position: relative;
width: 100%;
padding: 25px 10px 10px;
margin-bottom: 10px;
border: solid 1px var(--el-border-color);
border-radius: 4px;
&:hover {
> .toper {
> .options {
opacity: 1;
}
}
}
&:last-child {
margin-bottom: 0;
}
.toper {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 20px;
.drag-btn {
display: flex;
align-items: center;
background-color: var(--el-border-color-light);
color: var(--el-text-color-placeholder);
cursor: -webkit-grab;
}
.title {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
height: 20px;
font-size: 12px;
.type-name {
display: flex;
align-items: center;
height: 20px;
color: var(--el-text-color-secondary);
background-color: var(--el-border-color-light);
padding: 0 10px;
border-bottom-right-radius: 4px;
margin-right: 5px;
}
.danger-tip {
display: flex;
align-items: center;
color: var(--el-color-error);
.tip-item {
padding-right: 5px;
font-size: 12px;
}
}
}
.options {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
height: 20px;
font-size: 12px;
color: var(--el-text-color-secondary);
padding: 0 10px;
opacity: 0;
.item {
font-size: 12px;
margin-left: 10px;
}
}
}
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<div class="comp-report">
<div class="content">
<div class="preview">
<KkfileView :fileId="fileId" />
</div>
<div class="edit">
<EditItem v-model:data="documentDataList" :mode="mode" :preview="preview" />
<div v-if="mode === 'edit'" class="add-paragraph">
<el-button :icon="DocumentAdd" type="primary" size="small" @click="addParagraphFun">
新增章节
</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import EditItem from './editItem.vue';
import { DocumentAdd } from '@element-plus/icons-vue';
import KkfileView from '@/components/common/filePreview/kkfileView.vue';
import { cloneDeep } from 'lodash-es';
import { ElMessage } from 'element-plus';
interface Props {
data: any;
valueList?: any;
fileId: any;
mode: string;
preview?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
data: [],
valueList: [],
fileId: '',
mode: 'input', // input输入模式 edit编辑模板模式
preview: false,
});
const emit = defineEmits(['update:data']);
const documentDataList = ref<any>([]);
watch(
() => props.data,
(val: any, oldVal: any) => {
if (JSON.stringify(val) !== JSON.stringify(oldVal)) {
documentDataList.value = val;
}
},
{ deep: true, immediate: true }
);
watch(
() => documentDataList.value,
(val: any) => {
emit('update:data', val);
},
{ deep: true }
);
const formatDataFun = (data: any) => {
data.forEach((item: any) => {
const matchData = props.valueList?.find((i: any) => i.key === item.key);
if (item.key && matchData) {
// 更具key值将历史数据的value赋值给模板
item.value = matchData.value;
}
if (item.children?.length > 0) {
formatDataFun(item.children);
}
});
};
watch(
() => props.valueList,
() => {
const formatData = cloneDeep(props.data);
formatDataFun(formatData);
emit('update:data', formatData);
},
{ deep: true, immediate: true }
);
// 新增段落/小结
const addParagraphFun = () => {
const data: any = {
id: new Date().getTime(),
type: 'paragraph', // 类型
key: '', // 报告生成对应key
value: '', // 内容
params: {}, // 其他参数
children: [], // 子节点
};
documentDataList.value.push(data);
};
const formatFun = (data: any) => {
data.forEach((item: any, index: number) => {
if (item.type !== 'form') {
const itemData: any = {
type: item.type,
key: item.key,
value: item.value,
params: item.params,
};
if (item.type === 'img') {
itemData.value.forEach((val: any, valIndex: number) => {
let picName = '';
if (val.title) {
picName = `${val.title.replace(/\s/g, '')}_${new Date().getTime()}_${index + 1}_${valIndex + 1}`;
} else {
picName = `图片_${new Date().getTime()}_${index + 1}_${valIndex + 1}`;
}
val.picName = picName;
});
}
if (item.type === 'table') {
itemData.head = item.head.map((i: any) => i.title);
}
formatData.value.push(itemData);
if (item.children?.length > 0) {
formatFun(item.children);
}
} else {
item.children.forEach((val: any) => {
const valData = {
type: val.type,
key: val.key,
value: val.value,
params: val.params,
};
formatData.value.push(valData);
});
}
});
};
const formatData = ref<any>([]);
const getFormatDataFun = () => {
formatData.value = [];
formatFun(documentDataList.value);
return formatData.value;
};
const checkKeysFun = () => {
const dataList = getFormatDataFun();
const keys: any = [];
let repeatKey = '';
dataList.some((item: any) => {
if (item.key) {
if (keys.includes(item.key)) {
repeatKey = item.key;
return true;
} else {
keys.push(item.key);
}
}
});
if (repeatKey) {
ElMessage.warning(`${repeatKey}重复,请更改后再提交`);
return false;
} else {
return true;
}
};
defineExpose({
getFormatDataFun,
checkKeysFun,
});
</script>
<style lang="scss" scoped>
.comp-report {
width: 100%;
height: 100%;
.content {
display: flex;
width: 100%;
height: 100%;
.preview {
width: 50%;
height: 100%;
padding: 20px;
overflow-y: auto;
}
.edit {
width: 50%;
height: 100%;
padding: 20px;
overflow-y: auto;
}
.add-paragraph {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@@ -47,7 +47,7 @@ import Dialog from '@/components/common/dialog/index.vue';
import { queryReportTemplateApi } from '@/api/capability/report';
import { editReportApi, editReportAndDownloadApi, queryTaskRunApi } from '@/api/project/run';
import { getTaskDetailApi } from '@/api/project/task';
import ReportEdit from '@/components/common/report/reportEdit/index.vue';
import ReportEdit from '@/components/common/report/reportEditer/index.vue';
import { ElMessage } from 'element-plus';
interface Props {

View File

@@ -42,7 +42,7 @@ import { onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { useDict } from '@/utils/useDict';
import { queryReportTemplateInfoApi, queryReportTemplateVersionApi } from '@/api/capability/report';
import ReportEdit from './reportEdit/index.vue';
import ReportEdit from './reportEditer/index.vue';
const props = defineProps({
reportUuid: {

View File

@@ -53,7 +53,7 @@
import { onMounted, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import ApproveDialog from './approveDialog.vue';
import ReportEdit from '@/components/common/report/reportEdit/index.vue';
import ReportEdit from '@/components/common/report/reportEditer/index.vue';
import { FLOW_OPERATION_TYPE } from '@/utils/enum/flow';
import { useDict } from '@/utils/useDict';
import {