vr-uniapp/src/components/model-data-magic/index.vue

389 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div ref="container" :style="style_container">
<div class="img-magic" :style="style_img_container">
<div class="w h re outer-style">
<!-- 风格9 -->
<template v-if="form.style_actived == 7">
<div class="flex-row align-c jc-c style-size flex-wrap">
<div v-for="(item, index) in data_magic_list" :key="index" :style="`${ item.data_style.background_style } ${ content_radius }`" :class="['img-spacing-border', { 'style9-top': [0, 1].includes(index), 'style9-bottom': ![0, 1].includes(index) }]">
<div class="w h" :style="`${ item.data_style.background_img_style }`">
<template v-if="item.data_content.data_type == 'goods'">
<div :class="`w h flex-col ${ 'gap-' + item.title_text_gap }`" :style="`${ [0, 1].includes(index) ? padding_computer(item.data_style.chunk_padding) : '' }`">
<div v-if="(!isEmpty(item.data_content.heading_title) || !isEmpty(item.data_content.subtitle)) && [0, 1].includes(index)" class="flex-col gap-5 tl">
<p class="ma-0 w txet-word-break text-line-1" :style="trends_config(item.data_style, 'heading')">{{ item.data_content.heading_title || '' }}</p>
<p class="ma-0 w txet-word-break text-line-1" :style="trends_config(item.data_style, 'subtitle')">{{ item.data_content.subtitle || '' }}</p>
</div>
<div class="w h">
<magic-carousel :value="item" :good-style="item.data_style" :actived="form.style_actived" type="product" @carousel_change="carousel_change($event, index)"></magic-carousel>
</div>
</div>
</template>
<template v-else>
<magic-carousel :value="item" type="img" :actived="form.style_actived" @carousel_change="carousel_change($event, index)"></magic-carousel>
</template>
<div v-if="item.data_style.is_show == '1' && item.data_content.list.length > 1" :class="{'dot-center': item.data_style?.indicator_location == 'center', 'dot-right': item.data_style?.indicator_location == 'flex-end' }" class="dot flex abs" :style="`bottom: ${item.data_style?.indicator_bottom}px;`">
<template v-if="item.data_style.indicator_style == 'num'">
<div :style="item.data_style.indicator_styles" class="dot-item">
<span class="num-active" :style="`color: ${ item.data_style.actived_color }`">{{ item.actived_index + 1 }}</span><span>/{{ item.data_content.list.length }}</span>
</div>
</template>
<template v-else>
<div v-for="(item3, index3) in item.data_content.list" :key="index3" :style="`${ item.data_style.indicator_styles }; ${ style_actived_color(item, index3)}`" class="dot-item" />
</template>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<div v-for="(item, index) in data_magic_list" :key="index" class="cube-selected img-spacing-border" :style="`${ selected_style(item) } ${ item.data_style.background_style } ${ content_radius }`">
<div class="w h" :style="`${ item.data_style.background_img_style }`">
<template v-if="item.data_content.data_type == 'goods'">
<div :class="`w h flex-col ${ 'gap-' + item.title_text_gap }`" :style="`${ padding_computer(item.data_style.chunk_padding) }`">
<div v-if="!isEmpty(item.data_content.heading_title) || !isEmpty(item.data_content.subtitle)" class="flex-col gap-5 tl">
<p class="ma-0 w txet-word-break text-line-1" :style="trends_config(item.data_style, 'heading')">{{ item.data_content.heading_title || '' }}</p>
<p class="ma-0 w txet-word-break text-line-1" :style="trends_config(item.data_style, 'subtitle')">{{ item.data_content.subtitle || '' }}</p>
</div>
<div class="w h">
<magic-carousel :value="item" :good-style="item.data_style" type="product" :actived="form.style_actived" @carousel_change="carousel_change($event, index)"></magic-carousel>
</div>
</div>
</template>
<template v-else>
<magic-carousel :value="item" type="img" :actived="form.style_actived" @carousel_change="carousel_change($event, index)"></magic-carousel>
</template>
<div v-if="item.data_style.is_show == '1' && item.data_content.list.length > 1" :class="{'dot-center': item.data_style?.indicator_location == 'center', 'dot-right': item.data_style?.indicator_location == 'flex-end' }" class="dot flex abs" :style="`bottom: ${item.data_style?.indicator_bottom}px;`">
<template v-if="item.data_style.indicator_style == 'num'">
<div :style="item.data_style.indicator_styles" class="dot-item">
<span class="num-active" :style="`color: ${ item.data_style.actived_color }`">{{ item.actived_index + 1 }}</span><span>/{{ item.data_content.list.length }}</span>
</div>
</template>
<template v-else>
<div v-for="(item3, index3) in item.data_content.list" :key="index3" :style="`${ item.data_style.indicator_styles }; ${ style_actived_color(item, index3)}`" class="dot-item" />
</template>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { background_computer, common_styles_computer, get_math, gradient_computer, percentage_count, radius_computer, padding_computer, common_img_computer } from '@/utils';
import { isEmpty, cloneDeep } from 'lodash';
const props = defineProps({
value: {
type: Object,
default: () => {
return {};
},
},
});
// 用于页面判断显示
const state = reactive({
form: props.value.content,
new_style: props.value.style,
});
// 如果需要解构确保使用toRefs
const { form, new_style } = toRefs(state);
const outer_spacing = computed(() => (new_style.value?.image_spacing || 0) + 'px');
const outer_sx = computed(() => -((new_style.value?.image_spacing || 0) / 2) + 'px');
// 图片间距设置
const spacing = computed(() => (new_style.value?.image_spacing || 0) / 2 + 'px');
// 内容圆角设置
const content_radius = computed(() => radius_computer(new_style.value.data_radius));
//#region 容器大小计算
const div_width = ref(0);
const container_size = computed(() => div_width.value + 'px');
const container = ref<HTMLElement | null>(null);
onMounted(() => {
if (container.value) {
div_width.value = container.value.clientWidth;
}
});
//#endregion
//#region 图片位置计算
//计算选中层的宽度。
interface data_magic {
start: {
x: number;
y: number;
};
end: {
x: number;
y: number;
};
num: number;
flex: string;
title_text_gap: number;
outerflex: string;
heading_title?: string;
subtitle?: string;
actived_index: number;
data_content: any;
data_style: any;
}
// 屏幕分块
const density = ref('4');
//单元魔方宽度。
const cubeCellWidth = computed(() => div_width.value / parseInt(density.value));
//单元魔方高度。
const cubeCellHeight = computed(() => div_width.value / parseInt(density.value));
const getSelectedWidth = (item: data_magic) => {
return (item.end.x - item.start.x + 1) * cubeCellWidth.value;
};
//计算选中层的高度。
const getSelectedHeight = (item: data_magic) => {
return (item.end.y - item.start.y + 1) * cubeCellHeight.value;
};
//计算选中层的右边距离。
const getSelectedTop = (item: data_magic) => {
return (item.start.y - 1) * cubeCellHeight.value;
};
//计算选中层的左边距离。
const getSelectedLeft = (item: data_magic) => {
return (item.start.x - 1) * cubeCellWidth.value;
};
// 根据当前页面大小计算成百分比
const selected_style = (item: data_magic) => {
return `overflow: hidden;width: calc(${percentage(getSelectedWidth(item))} - ${ outer_spacing.value } ); height: calc(${percentage(getSelectedHeight(item))} - ${ outer_spacing.value } ); top: ${percentage(getSelectedTop(item))}; left: ${percentage(getSelectedLeft(item))};`;
};
// 计算成百分比
const percentage = (num: number) => {
return percentage_count(num, div_width.value);
};
//#endregion
//#region 组装页面显示的数据
// 指示器的样式
const indicator_style = (item: any) => {
let styles = '';
if (!isEmpty(item.indicator_radius)) {
styles += radius_computer(item.indicator_radius)
}
const size = item?.indicator_size || 5;
if (item.indicator_style == 'num') {
styles += `color: ${item?.color || '#DDDDDD'};`;
styles += `font-size: ${size}px;`;
} else if (item.indicator_style == 'elliptic') {
styles += `background: ${item?.color || '#DDDDDD'};`;
styles += `width: ${size * 3}px; height: ${size}px;`;
} else {
styles += `background: ${item?.color || '#DDDDDD'};`;
styles += `width: ${size}px; height: ${size}px;`;
}
return styles;
};
const style_actived_color = (item: any, index: number) => {
return item.actived_index == index ? `background: ${ item.data_style.actived_color };` : ''
}
// 根据传递的参数,从对象中取值
const goods_trends_config = (style: any, key: string) => {
return text_style(style[`goods_${key}_typeface`], style[`goods_${key}_size`], style[`goods_${key}_color`]);
}
const trends_config = (style: any, key: string) => {
return text_style(style[`${key}_typeface`], style[`${key}_size`], style[`${key}_color`]);
}
const text_style = (typeface: string, size: number, color: string) => {
return `font-weight:${ typeface }; font-size: ${ size }px; color: ${ color };`;
}
// 设置默认值
const default_list = {
title: '测试商品标题',
min_original_price: '41.2',
show_original_price_symbol: '¥',
show_original_price_unit: '/ 台',
min_price: '51',
show_price_symbol: '¥',
show_price_unit: '/ 台',
sales_count: '1000',
images: '',
new_cover: [],
plugins_view_icon_data: [
{
name: '满减活动',
bg_color: '#EA3323',
br_color: '',
color: '#fff',
url: '',
},
{
name: '包邮',
bg_color: '',
br_color: '#EA3323',
color: '#EA3323',
url: '',
},
{
name: '领劵',
bg_color: '',
br_color: '#EA9223',
color: '#EA9223',
url: '',
},
],
};
/*
** 组装产品的数据
** @param {Array} list 商品列表
** @param {Number} num 显示数量
** @return {Array}
*/
const commodity_list = (list: any[], num: number) => {
if (list.length > 0) {
// 深拷贝一下,确保不会出现问题
const goods_list = cloneDeep(list).map((item: any) => ({
...item.data,
title: !isEmpty(item.new_title) ? item.new_title : item.data.title,
new_cover: item.new_cover,
}));
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(goods_list.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({ split_list: goods_list.slice(i * num, (i + 1) * num) });
}
return nav_list;
} else {
return [{ split_list: Array(num).fill(default_list)}];
}
}
const old_list = ref<any>({});
const data_magic_list = ref<data_magic[]>([]);
watch(props.value.content, (val) => {
const data = cloneDeep(val.data_magic_list);
data.forEach((item: data_magic) => {
const data_content = item.data_content;
const data_style = item.data_style;
const key = data_style.carouselKey;
// 指示器设置为0
item.actived_index = 0;
// 指示器样式
data_style.indicator_styles = indicator_style(data_style);
data_style.background_style = gradient_computer(data_style);
data_style.background_img_style = background_computer(data_style);
const radius = !isEmpty(data_style.img_radius) ? data_style.img_radius : { radius: 4, radius_top_left: 4, radius_top_right: 4, radius_bottom_left: 4, radius_bottom_right: 4 };
data_style.get_img_radius = radius_computer(radius);
// 商品名称和价格样式
data_style.goods_title_style = goods_trends_config(item.data_style, 'title');
data_style.goods_price_style = goods_trends_config(item.data_style, 'price');
const { is_roll, rotation_direction, interval_time } = data_style;
const { goods_list, images_list } = data_content;
if (data_content.data_type == 'goods') {
data_content.list = commodity_list(data_content.goods_list, item.num);
} else {
data_content.list = data_content.images_list;
}
const new_data = {
data_type: data_content.data_type,
interval_time: interval_time,
is_roll: is_roll,
rotation_direction: rotation_direction,
goods_list: [...goods_list], // 确保深拷贝
images_list: [...images_list] // 确保深拷贝
};
// let old_data = old_list.value[key];
// 获取旧数据
if (!isEmpty(old_list.value[key])) {
// 旧数据
const oldDataStringified = JSON.stringify(old_list.value[key]);
// 新数据
const newDataStringified = JSON.stringify(new_data);
if (oldDataStringified != newDataStringified) {
// 更新旧数据
old_list.value[key] = JSON.parse(newDataStringified);
// 更新轮播图的key确保更换时能重新更新轮播图
data_style.carouselKey = get_math();
}
} else {
old_list.value[key] = new_data;
}
});
data_magic_list.value = data;
}, {immediate: true, deep: true})
//#endregion
const carousel_change = (index: number, key: number) => {
if (data_magic_list.value[key]) {
data_magic_list.value[key].actived_index = index;
}
}
// 公共样式
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));
</script>
<style lang="scss" scoped>
// 图片魔方是一个正方形,根据宽度计算高度
.img-magic {
height: v-bind(container_size);
width: 100%;
overflow: hidden;
}
.cube-selected {
position: absolute;
text-align: center;
color: $cr-main;
box-sizing: border-box;
}
.outer-style {
width: calc(100% + v-bind(outer_spacing));
height: calc(100% + v-bind(outer_spacing));
margin: v-bind(outer_sx);
}
.img-spacing-border {
margin: v-bind(spacing);
}
.style-size {
height: 100%;
width: 100%;
.style9-top {
width: calc(50% - v-bind(outer_spacing));
height: calc(50% - v-bind(outer_spacing));
position: relative;
overflow: hidden;
}
.style9-bottom {
width: calc((100% / 3) - v-bind(outer_spacing));
height: calc(50% - v-bind(outer_spacing));
position: relative;
overflow: hidden;
}
}
.dot-center {
left: 50%;
transform: translateX(-50%);
}
.dot-right {
right: 0;
}
.dot {
padding-right: 10px;
padding-left: 10px;
z-index: 3;
.dot-item {
margin: 0 0.3rem;
}
}
// 轮播高度自适应
:deep(.el-carousel) {
height: 100%;
.el-carousel__container {
height: 100%;
}
}
:deep(.el-image) {
height: 100%;
width: 100%;
.el-image__inner {
object-fit: cover;
}
.image-slot img {
width: 6rem;
}
}
</style>