Merge branch 'main' of http://192.168.65.198:3000/Front_Team/SPDM
This commit is contained in:
135
src/components/common/report/reportEditer/components/form.vue
Normal file
135
src/components/common/report/reportEditer/components/form.vue
Normal 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>
|
||||
325
src/components/common/report/reportEditer/components/img.vue
Normal file
325
src/components/common/report/reportEditer/components/img.vue
Normal 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>
|
||||
191
src/components/common/report/reportEditer/components/table.vue
Normal file
191
src/components/common/report/reportEditer/components/table.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
285
src/components/common/report/reportEditer/editItem.vue
Normal file
285
src/components/common/report/reportEditer/editItem.vue
Normal 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>
|
||||
204
src/components/common/report/reportEditer/index.vue
Normal file
204
src/components/common/report/reportEditer/index.vue
Normal 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>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user