自定义内容修改

v1.2.0
于肖磊 2024-12-25 16:07:48 +08:00
parent 1a0623d6e3
commit 1883237799
7 changed files with 304 additions and 135 deletions

View File

@ -14,7 +14,10 @@
<model-icon :key="item.id" :value="item.com_data" :scale="scale" :source-list="sourceList" :is-custom="isCustom" :is-display-panel="true"></model-icon>
</template>
<template v-else-if="item.key == 'panel'">
<model-panel :key="item.id" :value="item.com_data" :scale="scale" :source-list="sourceList" :is-custom="isCustom" :is-display-panel="true"></model-panel>
<model-panel :key="item.id" :value="item.com_data" :scale="scale" :source-list="sourceList" :is-custom="isCustom" :is-display-panel="true"></model-panel>
</template>
<template v-else-if="item.key == 'custom-group'">
<model-custom-group :key="item.id" :value="item.com_data" :scale="scale" :source-list="sourceList" :data-height="item.com_data.com_height" :data-width="item.com_data.com_width" :is-custom="isCustom" :is-display-panel="true"></model-custom-group>
</template>
</div>
</div>

View File

@ -0,0 +1,60 @@
<template>
<card-container>
<div class="mb-20">显示设置</div>
<el-form-item label="铺满方式">
<el-radio-group v-model="form.data_source_direction">
<el-radio v-for="(item, index) in default_type_data?.show_type" :key="index" :value="item">{{ item == 'vertical' ? '' : item == 'vertical-scroll' ? '' : '' }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="每屏显示">
<el-radio-group v-model="form.data_source_carousel_col">
<el-radio v-for="(item, index) in default_type_data?.show_number" :key="index" :value="item">{{ item }}{{ form.data_source_direction == 'vertical-scroll' ? '' : '' }}</el-radio>
</el-radio-group>
</el-form-item>
</card-container>
<div class="bg-f5 divider-line" />
<card-container>
<div class="mb-20">自定义设置</div>
<el-button class="w" size="large" @click="custom_edit"><icon name="edit" size="12"></icon>自定义编辑</el-button>
</card-container>
</template>
<script setup lang="ts">
const props = defineProps({
value: {
type: Object,
default: () => {},
}
});
const form = ref(props.value);
const default_type_data = {
show_type: ['vertical', 'vertical-scroll', 'horizontal-scroll'],
show_number: [1, 2, 3, 4],
};
const emit = defineEmits(['custom_edit']);
const custom_edit = () => {
emit('custom_edit');
};
</script>
<style lang="scss" scoped>
.card-background {
background: #fff;
padding-left: 1.6rem;
padding-right: 2rem;
padding-top: 4.6rem;
padding-bottom: 1.6rem;
}
.upload_style {
width: 100%;
height: 12.4rem;
}
.col-price-style {
color: red;
}
:deep(.el-checkbox-group .el-checkbox) {
margin-right: 2rem;
}
:deep(.el-radio-group .el-radio) {
margin-right: 2rem;
}
</style>

View File

@ -1,18 +1,59 @@
<template>
<div class="img-outer w h re oh" :style="com_style">
<div :style="text_style" class="text-word-break">
<template v-if="form.is_rich_text == '1'">
<div class="rich-text-content" :innerHTML="text_title"></div>
</template>
<template v-else>
{{ text_title }}
</template>
<div :style="style_container">
<div :style="style_img_container">
<div :style="style_content_container">
<div class="w h re" :style="style_content_img_container">
<template v-if="data_source_content_list.length > 0 && form.data_source_direction == 'vertical'">
<div class="flex-row flex-wrap" :style="`row-gap: ${ new_style.row_gap }px;column-gap: ${ new_style.column_gap }px;`">
<div v-for="(item1, index1) in data_source_content_list" :key="index1" :style="`width: ${ gap_width }`">
<div :style="style_chunk_container">
<div class="w h oh" :style="style_chunk_img_container">
<data-rendering :custom-list="form.custom_list" :source-list="item1" :data-height="dataHeight" :scale="custom_scale" :is-custom="form.is_custom_data == '1'" :show-data="form?.show_data || { data_key: 'id', data_name: 'name' }"></data-rendering>
</div>
</div>
</div>
</div>
</template>
<div v-else-if="data_source_content_list.length > 0 && ['vertical-scroll', 'horizontal'].includes(form.data_source_direction)" class="oh" :style="form.data_source_direction == 'horizontal' ? `height:100%;` : `height: ${ swiper_height }px;`">
<swiper :key="carouselKey" class="w flex" :direction="form.data_source_direction == 'horizontal' ? 'horizontal': 'vertical'" :height="swiper_height" :loop="true" :autoplay="autoplay" :slides-per-view="slides_per_view" :slides-per-group="slides_per_group" :space-between="space_between" :allow-touch-move="false" :pause-on-mouse-enter="true" :modules="modules" @slide-change="slideChange">
<swiper-slide v-for="(item1, index1) in data_source_content_list" :key="index1">
<div :style="style_chunk_container">
<div class="w h oh" :style="style_chunk_img_container">
<data-rendering :custom-list="form.custom_list" :source-list="item1" :data-height="dataHeight" :scale="custom_scale" :is-custom="form.is_custom_data == '1'" :show-data="form?.show_data || { data_key: 'id', data_name: 'name' }"></data-rendering>
</div>
</div>
</swiper-slide>
</swiper>
<div v-if="new_style.is_show == '1' && dot_list.length > 1" :class="['left', 'right'].includes(new_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="indicator_location_style">
<template v-if="new_style.indicator_style == 'num'">
<div :style="indicator_style" class="dot-item">
<span class="num-active">{{ actived_index + 1 }}</span><span>/{{ dot_list.length }}</span>
</div>
</template>
<template v-else>
<div v-for="(item, index) in dot_list" :key="index" :style="indicator_style" :class="{ 'dot-item': true, active: actived_index == index }" />
</template>
</div>
</div>
<template v-else>
<div :style="style_chunk_container">
<div class="w h oh" :style="style_chunk_img_container">
<data-rendering :custom-list="form.custom_list" :source-list="sourceList" :data-height="dataHeight" :scale="custom_scale" :is-custom="isCustom" :show-data="form?.show_data || { data_key: 'id', data_name: 'name' }"></data-rendering>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { radius_computer, padding_computer, gradient_handle, get_nested_property } from '@/utils';
import { common_img_computer, common_styles_computer, get_math, radius_computer } from '@/utils';
import { isEmpty } from 'lodash';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Autoplay } from 'swiper/modules';
const modules = [Autoplay];
const props = defineProps({
value: {
type: Object,
@ -27,6 +68,14 @@ const props = defineProps({
return {};
}
},
dataWidth: {
type: Number,
default: 0,
},
dataHeight: {
type: Number,
default: 0,
},
isDisplayPanel: {
type: Boolean,
default: false
@ -38,121 +87,148 @@ const props = defineProps({
isCustom: {
type: Boolean,
default: false
},
titleParams: {
type: String,
default: ''
},
options: {
type: Array<any>,
default: () => [],
}
});
//
const form = computed(() => props.value);
const text_title = computed(() => {
return getTextTitle(form.value, props);
const new_style = computed(() => props.value.data_style);
//
const style_container = computed(() => common_styles_computer(new_style.value.common_style));
const style_img_container = computed(() => common_img_computer(new_style.value.common_style));
//
const style_content_container = computed(() => common_styles_computer(new_style.value.data_content_style));
const style_content_img_container = computed(() => common_img_computer(new_style.value.data_content_style));
//
const style_chunk_container = computed(() => common_styles_computer(new_style.value.data_style));
const style_chunk_img_container = computed(() => common_img_computer(new_style.value.data_style));
const data_source_content_list = ref([]);
//#region
const custom_scale = ref(1);
//
watchEffect(() => {
const { common_style, data_style, data_content_style } = new_style.value;
const old_width = props.dataWidth * props.scale;
//
const outer_spacing = common_style.margin_left + common_style.margin_right + common_style.padding_left + common_style.padding_right;
//
const content_spacing = data_content_style.margin_left + data_content_style.margin_right + data_content_style.padding_left + data_content_style.padding_right;
//
const internal_spacing = data_style.margin_left + data_style.margin_right + data_style.padding_left + data_style.padding_right;
//
const width = old_width - outer_spacing - internal_spacing - content_spacing;
//
custom_scale.value = width / old_width;
});
/**
* 根据表单值和属性获取文本标题
* 此函数用于根据提供的表单值和组件属性在不同的条件下返回相应的文本标题
* 主要处理逻辑包括检查表单值和数据源列表的存在性处理数据源ID以获取标题处理自定义标题情况错误处理以及最终文本的确定
*
* @param formValue 表单的当前值包含数据源ID和可能的文本标题
* @param props 组件的属性包括数据源列表是否显示面板是否自定义标题等
* @returns {string} 根据不同条件返回的文本标题
*/
const getTextTitle = (formValue: any, props: any): string => {
//
if (!formValue || !props.sourceList) {
if (!props.isDisplayPanel) {
return '请在此输入文字';
} else {
return '';
}
}
// ID
const data_source_id = !isEmpty(formValue?.data_source_field?.id || []) ? formValue?.data_source_field?.id : [];
//
const option = formValue?.data_source_field?.option || [];
//
let text_title = '';
try {
//
if (data_source_id.length > 0) {
//
data_source_id.forEach((source_id: string) => {
const sourceList = option.find((item: any) => item.field == source_id);
// ID
if (source_id.includes(';')) {
const ids = source_id.split(';');
let text = '';
ids.forEach((item: string, index: number) => {
text += data_handling(item) + (index != ids.length - 1 ? (sourceList?.join || '') : '');
});
text_title += (sourceList?.first || '') + (text == '' && !props.isDisplayPanel ? sourceList?.name || '请在此输入文字' : text ) + (sourceList?.last || '');
} else {
text_title += (sourceList?.first || '') + (data_handling(source_id) === '' && !props.isDisplayPanel ? sourceList?.name || '请在此输入文字' : data_handling(source_id) ) + (sourceList?.last || '');
}
});
}
} catch (error) {
if (!props.isDisplayPanel) {
return '请在此输入文字';
} else {
return '';
}
}
// 使使
let text = formValue.text_title || text_title;
if (text === '' && !props.isDisplayPanel) {
text = '请在此输入文字';
}
return text;
}
const data_handling = (data_source_id: string) => {
let text_title = get_nested_property(props.sourceList, data_source_id);
//
if (props.sourceList.data && props.isCustom) {
if (data_source_id === props.titleParams) {
text_title = props.sourceList.new_title || get_nested_property(props.sourceList.data, data_source_id);
} else {
text_title = get_nested_property(props.sourceList.data, data_source_id);
}
}
return text_title;
}
const text_style = computed(() => {
let style = `font-size: ${ form.value.text_size * props.scale }px;line-height: ${ (typeof form.value.line_text_size === "number" ? form.value.line_text_size : form.value.text_size) * props.scale}px;color: ${ form.value.text_color }; text-align: ${ form.value.text_location }; transform: rotate(${form.value.text_rotate}deg);text-decoration: ${ form.value.text_option };${ padding_computer(form.value.text_padding, props.scale) };`;
if (form.value.text_weight == 'italic') {
style += `font-style: italic`;
} else if (form.value.text_weight == '500') {
style += `font-weight: 500`;
}
return style;
//#endregion
//
const gap_width = computed(() => {
const model_number = Number(form.value.data_source_carousel_col);
// (gap * gap) /
let gap = (new_style.value.column_gap * (model_number - 1)) / model_number;
return `calc(${100 / model_number}% - ${gap}px)`;
});
const com_style = computed(() => {
let style = `${ set_count() } ${ gradient_handle(form.value.color_list, form.value.direction) } ${ radius_computer(form.value.bg_radius, props.scale) }`;
if (form.value.border_show == '1') {
style += `border: ${form.value.border_size}px ${form.value.border_style} ${form.value.border_color};`;
}
//
if (form.value.is_rich_text == '1' && form.value.is_up_down == '1') {
style += `overflow-y: auto;`
}
return style;
});
const set_count = () => {
if (props.isDisplayPanel) {
return '';
//#region
// key
const carouselKey = ref('0');
const autoplay = ref<boolean | object>(false);
const slides_per_group = ref(1);
const dot_list = ref<unknown[]>([]);
const swiper_height = ref(390);
const slides_per_view = ref(1);
const space_between = ref(0);
//
watchEffect(() => {
//
if (new_style.value.is_roll == '1') {
autoplay.value = {
delay: (new_style.value.interval_time || 2) * 1000,
pauseOnMouseEnter: true,
};
} else {
return `width: ${ form.value.com_width }px; height: ${ form.value.com_height }px;`;
autoplay.value = false;
}
//
space_between.value = form.value.data_source_direction == 'horizontal' ? new_style.value.column_gap : new_style.value.row_gap;
//
const data_source_carousel_col = Number(form.value.data_source_carousel_col);
//
slides_per_group.value = new_style.value.rolling_fashion == 'translation' ? 1 : Number(data_source_carousel_col);
//
const col = data_source_content_list.value.length > Number(data_source_carousel_col) ? Number(data_source_carousel_col) : data_source_content_list.value.length;
slides_per_view.value = col;
let num = 0;
//
if (!isEmpty(data_source_content_list.value)) {
num = new_style.value.rolling_fashion == 'translation' ? data_source_content_list.value.length : Math.ceil(data_source_content_list.value.length / Number(data_source_carousel_col));
}
const { padding_top, padding_bottom, margin_bottom, margin_top } = new_style.value.data_style;
//
if (form.value.data_source_direction == 'horizontal') {
swiper_height.value = form.value.height * custom_scale.value + padding_top + padding_bottom + margin_bottom + margin_top;
} else {
swiper_height.value = (form.value.height * custom_scale.value + padding_top + padding_bottom + margin_bottom + margin_top) * col + ((data_source_carousel_col - 1) * space_between.value);
}
dot_list.value = Array(num);
// key
carouselKey.value = get_math();
});
//
const actived_color = computed(() => new_style.value?.actived_color || '#2A94FF' );
const indicator_style = computed(() => {
let indicator_styles = '';
if (!isEmpty(new_style.value.indicator_radius)) {
indicator_styles += radius_computer(new_style.value.indicator_radius);
}
const size = new_style.value?.indicator_size || 5;
if (new_style.value.indicator_style == 'num') {
indicator_styles += `color: ${new_style.value?.color || '#DDDDDD'};`;
indicator_styles += `font-size: ${size}px;`;
} else if (new_style.value.indicator_style == 'elliptic') {
indicator_styles += `background: ${new_style.value?.color || '#DDDDDD'};`;
if (['left', 'right'].includes(new_style.value.indicator_new_location)) {
indicator_styles += `width: ${ size }px; height: ${size * 3}px;`;
} else {
indicator_styles += `width: ${size * 3}px; height: ${size}px;`;
}
} else {
indicator_styles += `background: ${new_style.value?.color || '#DDDDDD'};`;
indicator_styles += `width: ${size}px; height: ${size}px;`;
}
return indicator_styles;
});
const actived_index = ref(0);
const slideChange = (swiper: { realIndex: number }) => {
//
actived_index.value = new_style.value.rolling_fashion == 'translation' ? swiper.realIndex : (swiper.realIndex / Number(form.value.data_source_carousel_col)) > 0 ? (swiper.realIndex / Number(form.value.data_source_carousel_col)) : 0;
};
//#endregion
//#region
//
const indicator_location_style = computed(() => {
const { indicator_new_location, indicator_location, indicator_bottom } = new_style.value;
let styles = '';
if (['left', 'right'].includes(indicator_new_location)) {
if (indicator_location == 'flex-start') {
styles += `top: 0px;`;
} else if (indicator_location == 'center') {
styles += `top: 50%; transform: translateY(-50%);`;
} else {
styles += `bottom: 0px;`;
}
} else {
if (indicator_location == 'flex-start') {
styles += `left: 0px;`;
} else if (indicator_location == 'center') {
styles += `left: 50%; transform: translateX(-50%);`;
} else {
styles += `right: 0px;`;
}
}
// 使
styles += `${ !isEmpty(indicator_new_location) ? `${indicator_new_location}: ${ indicator_bottom }px;` : `bottom: ${ indicator_bottom }px;` }`;
return styles;
});
//#endregion
</script>
<style lang="scss" scoped>
.rich-text-content {

View File

@ -12,16 +12,22 @@
</card-container>
<div class="bg-f5 divider-line" />
<card-container>
<div class="mb-20">自定义设置</div>
<el-button class="w" size="large" @click="custom_edit"><icon name="edit" size="12"></icon>自定义编辑</el-button>
<div class="mb-12">容器设置</div>
<el-form-item label="容器宽度">
<slider v-model="form.com_width" :max="390"></slider>
</el-form-item>
<el-form-item label="容器高度">
<slider v-model="form.com_height" :max="1000"></slider>
</el-form-item>
</card-container>
<div class="bg-f5 divider-line" />
<el-tabs v-model="tabs_name" class="content-tabs">
<el-tab-pane label="内容设置" name="content">
<tabs-content :value="form.data_content"></tabs-content>
<custom-tabs-content :value="form" @custom_edit="custom_edit"></custom-tabs-content>
</el-tab-pane>
<el-tab-pane label="样式设置" name="styles">
<model-custom-styles :value="form.data_style" :content="form.data_content"></model-custom-styles>
{{ diy_data.location.x }}
<model-custom-styles :value="form.data_style" :content="form" :is-floating-up="false"></model-custom-styles>
</el-tab-pane>
</el-tabs>
</el-form>
@ -52,7 +58,9 @@ const center_height = defineModel('height', { type: Number, default: 0 });
const emit = defineEmits(['custom_edit']);
const custom_edit = () => {
const { custom_list, com_width, custom_height } = form.value;
emit('custom_edit', diy_data.value.id, custom_list, com_width, custom_height);
//
const width = form.value.data_source_direction != 'vertical-scroll' ? com_width / form.value.data_source_carousel_col : com_width; //
emit('custom_edit', diy_data.value.id, custom_list, width, custom_height);
};
// x
const location_x_change = (val: number) => {
@ -83,4 +91,20 @@ watch(
width: 3rem;
height: 2rem;
}
:deep(.el-tabs.content-tabs) {
.el-tabs__header.is-top {
background: #fff;
margin: 0;
padding-top: 2rem;
}
.el-tabs__item.is-top {
padding: 0;
align-items: center;
width: 10rem;
font-size: 1.4rem;
}
.el-tabs__active-bar{
width: 100%;
}
}
</style>

View File

@ -175,18 +175,16 @@ const panel_com_data = {
}
const custom_group_com_data = {
// 容器高度
com_width: 100,
com_height: 100,
staging_height: 100,
color_list: [{ color: 'rgb(244, 252, 255)', color_percentage: undefined }],
direction: '90deg',
custom_list: [],
custom_list: [], // 自定义内容处理
custom_height: 100, // 自定义高度
data_content: {
},
data_source_direction: 'vertical', // 铺满方式
data_source_carousel_col: 1, // 铺满数量
data_style: {
...defaultCustom
...defaultCustom.style
}
}
// 判断两个矩形是否有交集或者被包裹

View File

@ -84,6 +84,9 @@
<template v-else-if="item.key == 'panel'">
<model-panel :key="item.id" :value="item.com_data" :source-list="props.sourceList" :is-custom="isCustom"></model-panel>
</template>
<template v-else-if="item.key == 'custom-group'">
<model-custom-group :key="item.id" :value="item.com_data" :source-list="props.sourceList" :data-height="item.com_data.com_height" :data-width="item.com_data.com_width" :is-custom="isCustom"></model-custom-group>
</template>
</div>
</Vue3DraggableResizable>
</DraggableContainer>
@ -202,10 +205,7 @@ const on_sort = (item: SortableEvent) => {
};
//#endregion
//#region
const diy_data = ref<any[]>([]);
watch(props.list, (newVal) => {
diy_data.value = newVal;
}, {immediate: true, deep: true});
const diy_data = toRef(props.list);
//
// onMounted(() => {
// //
@ -260,9 +260,13 @@ const edit_close_processing = (index: number) => {
//
const copy = (index: null | number) => {
if (typeof index === 'number' && !isNaN(index)) {
const data = cloneDeep(get_diy_index_data(index));
// key
const list = diy_data.value.filter(item => item.key == data.key);
// , id
const new_data = {
...cloneDeep(get_diy_index_data(index)),
...data,
new_name: data.name + list.length, // ,
id: get_math(),
};
//

View File

@ -68,7 +68,7 @@
</card-container>
</el-form>
<div class="bg-f5 divider-line" />
<common-styles :value="form.common_style" />
<common-styles :value="form.common_style" :is-floating-up="isFloatingUp" />
</div>
</template>
<script setup lang="ts">
@ -80,7 +80,11 @@ const props = defineProps({
content: {
type: Object,
default: () => ({}),
}
},
isFloatingUp: {
type: Boolean,
default: true,
},
});
const state = reactive({
form: props.value,