311 lines
7.2 KiB
Vue
311 lines
7.2 KiB
Vue
<template>
|
|
<div class="img-content">
|
|
<Toper
|
|
title="图片"
|
|
:btns="['edit', 'del']"
|
|
:disabled="mode !== 'edit'"
|
|
@edit="diaShow = true"
|
|
@del="removeFun"
|
|
/>
|
|
<div v-if="mode === 'edit'" class="pic">
|
|
<div class="title-item">{{ titleKey || '请输入图片key' }}</div>
|
|
<el-icon :size="100"><Picture /></el-icon>
|
|
<div class="title-item">{{ paramsData.title }}</div>
|
|
</div>
|
|
<div
|
|
v-else
|
|
ref="PasteContentRef"
|
|
class="paste-content"
|
|
:class="{ active: isFocus }"
|
|
:contenteditable="contenteditable"
|
|
@click="clickFun"
|
|
@paste="pasteFun"
|
|
>
|
|
<template v-if="fileList.length > 0">
|
|
<div v-for="(item, index) in fileList" :key="index" class="img-item">
|
|
<div class="img">
|
|
<img :src="item.src" />
|
|
</div>
|
|
<div class="tip">
|
|
<el-input
|
|
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>
|
|
</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">
|
|
<div class="option-item">
|
|
<div class="label">图片key</div>
|
|
<el-input v-model="titleKey" placeholder="请输入" clearable />
|
|
</div>
|
|
<div class="option-item">
|
|
<div class="label">默认名称</div>
|
|
<el-input v-model="paramsData.title" placeholder="请输入" clearable />
|
|
</div>
|
|
<div class="option-item">
|
|
<div class="label">输入提示</div>
|
|
<el-input v-model="paramsData.placeholder" placeholder="请输入" clearable />
|
|
</div>
|
|
</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 Toper from './toper.vue';
|
|
import { cloneDeep } from 'lodash-es';
|
|
|
|
const emit = defineEmits(['update:keyValue', 'update:value', 'update:params', 'del']);
|
|
|
|
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(false);
|
|
const paramsData = ref(cloneDeep(props.params));
|
|
|
|
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 removeFun = () => {
|
|
emit('del');
|
|
};
|
|
|
|
const delFun = (index: any) => {
|
|
contenteditable.value = true;
|
|
nextTick(() => {
|
|
PasteContentRef.value.click();
|
|
PasteContentRef.value.focus();
|
|
fileList.value.splice(index, 1);
|
|
contenteditable.value = false;
|
|
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 closeFun = () => {
|
|
diaShow.value = false;
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.img-content {
|
|
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: 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.content {
|
|
.option-item {
|
|
display: flex;
|
|
align-items: center;
|
|
width: 300px;
|
|
margin-bottom: 10px;
|
|
.label {
|
|
width: 80px;
|
|
}
|
|
}
|
|
}
|
|
</style>
|