新增公告样式三

This commit is contained in:
于肖磊
2026-05-11 17:31:07 +08:00
parent b0941bd8ac
commit 1e653cb53c
4 changed files with 279 additions and 38 deletions

View File

@ -24,7 +24,7 @@
</div>
</div>
</template>
<template v-else>
<template v-else-if="form.notice_style == 'card'">
<div class="news-card" :style="container_background_style">
<div class="flex-col gap-10" :style="container_background_img_style">
<div class="flex-row w jc-sb">
@ -48,12 +48,68 @@
</div>
</div>
</template>
<template v-else-if="form.notice_style == 'marquee'">
<div class="news-box news-box-marquee" :style="container_background_style">
<div class="news-marquee-row flex-row align-c gap-y-8" :style="container_background_img_style">
<div class="flex-shrink-0 flex-row align-c">
<template v-if="form.title_type == 'img-icon'">
<div v-if="!isEmpty(form.img_src)">
<image-empty v-model="form.img_src[0]" :style="img_style"></image-empty>
</div>
<div v-else>
<icon :name="form.icon_class" :size="new_style.icon_size + ''" :color="new_style.icon_color"></icon>
</div>
</template>
<template v-else>
<div :style="topic_style" class="pl-6 pr-6 radius-sm">{{ form.title }}</div>
</template>
</div>
<template v-if="marquee_scroll_enabled">
<!-- 与销售记录横向平移一致free-mode + linear + delay:0 + speed 用间隔时间 -->
<div class="news-marquee-viewport flex-1 flex-width swiper-free-mode swiper-horizontal-free-mode">
<swiper
:key="marquee_swiper_key"
class="news-marquee-swiper w flex"
direction="horizontal"
:modules="swiper_modules"
:loop="true"
slides-per-view="auto"
:space-between="marquee_scroll_gap"
:allow-touch-move="false"
:autoplay="marquee_swiper_autoplay"
:speed="marquee_swiper_speed"
>
<swiper-slide v-for="i in marquee_swiper_slides" :key="i" class="news-marquee-slide">
<div class="news-marquee-slide-inner flex-row align-c">
<div class="news-marquee-slide-text news-marquee-slide-text--scroll" :style="`${news_style} color: ${new_style.news_color}`">{{ marquee_display_text }}</div>
</div>
</swiper-slide>
</swiper>
</div>
</template>
<div v-else class="news-marquee-static flex-1 flex-width">
<div class="news-marquee-static-text" :style="`${news_style} color: ${new_style.news_color}`">{{ marquee_display_text }}</div>
</div>
<div
v-if="form.is_right_button == '1'"
class="flex-shrink-0 flex-row align-c"
:style="`color: ${new_style.right_button_color}; font-size: ${new_style.right_button_size}px`"
>
{{ form.right_title }}<icon name="arrow-right" :color="new_style.right_button_color || '#999'"></icon>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { background_computer, common_img_computer, common_styles_computer, get_math, gradient_computer, gradient_handle, padding_computer, radius_computer } from '@/utils';
import { isEmpty, cloneDeep, throttle } from 'lodash';
import { isEmpty, cloneDeep } from 'lodash';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Autoplay } from 'swiper/modules';
const swiper_modules = [Autoplay];
const props = defineProps({
value: {
@ -101,7 +157,7 @@ const container_background_img_style = computed(() => {
} else {
// 为空的时候使用默认数据,兼容老数据没有这个值时的处理
let old_padding = { padding: 15, padding_top: 15, padding_right: 15, padding_bottom: 15, padding_left: 15 };
if (form.value.notice_style === 'inherit') {
if (form.value.notice_style === 'inherit' || form.value.notice_style === 'marquee') {
old_padding = { padding: 0, padding_top: 0, padding_right: 10, padding_bottom: 0, padding_left: 10, }
}
padding = padding_computer(old_padding);
@ -139,10 +195,53 @@ const notice_list = computed(() => {
return arry_list.filter((item: { is_show: string }) => item.is_show == '1');
});
const marquee_scroll_enabled = computed(() => form.value.marquee_scroll != '0');
const marquee_display_text = computed(() => {
const raw = String(form.value.marquee_content ?? '').trim();
if (raw) return raw;
const first = notice_list.value[0]?.notice_title;
if (first && String(first).trim()) return String(first).trim();
return '暂无公告';
});
/** 样式三:每条 slide 一整段文案(宽度随内容),多份 slide + space-between 满足 loop 与间距 */
const marquee_repeat = 8;
const marquee_swiper_slides = computed(() => Array.from({ length: marquee_repeat }, (_, i) => i));
const marquee_swiper_key = computed(
() =>
`notice-marquee-${marquee_display_text.value}-${form.value.interval_time}-${form.value.marquee_scroll}-${form.value.notice_style}`
);
/** 与销售记录横向 translation 一致delay 0 + waitForTransition靠 speed 控制平移快慢 */
const marquee_swiper_autoplay = computed(() => {
if (!marquee_scroll_enabled.value) return false;
return {
delay: 0,
waitForTransition: true,
pauseOnMouseEnter: true,
disableOnInteraction: false,
};
});
/** 销售记录横向swiper_speed = interval_time * 1000ms */
const marquee_swiper_speed = computed(() => {
const sec = Number(form.value.interval_time) > 0 ? Number(form.value.interval_time) : 3;
return sec * 1000;
});
/** 开启滚动时,每条 slide 之间的间距(与销售记录 data_spacing 思路一致) */
const marquee_scroll_gap = computed(() => {
const n = Number(new_style.value?.data_spacing);
if (Number.isFinite(n) && n > 0) return n;
return 32;
});
// 内容参数的集合
watchEffect(() => {
//#region 轮播图设置
const time = (new_style.value?.interval_time || 2) * 1000;
const time = (Number(form.value.interval_time) > 0 ? Number(form.value.interval_time) : 3) * 1000;
const direction = form.value.direction;
// 判断长度是否相等
const notice_length = notice_list.value.length;
@ -169,6 +268,11 @@ watchEffect(() => {
height: v-bind(container_height);
overflow: hidden;
}
.news-box-marquee {
display: flex;
flex-direction: column;
justify-content: center;
}
.num {
padding-right: 0.7rem;
color: #999;
@ -197,4 +301,73 @@ watchEffect(() => {
}
}
}
.news-marquee-row {
width: 100%;
align-items: center;
}
.news-marquee-viewport {
flex: 1 1 0;
min-width: 0;
width: 100%;
overflow: hidden;
display: flex;
align-items: center;
align-self: stretch;
position: relative;
/* 供 slide 使用 cqi每条至少占满中间区域宽度短文案不再并排「切片」 */
container-type: inline-size;
container-name: news-marquee-vp;
}
.news-marquee-viewport.swiper-free-mode :deep(.swiper-wrapper) {
transition-timing-function: linear !important;
}
.news-marquee-swiper {
min-width: 0;
height: 100%;
}
.news-marquee-swiper :deep(.swiper),
.news-marquee-swiper :deep(.swiper-wrapper) {
height: 100%;
}
.news-marquee-swiper :deep(.swiper-wrapper) {
align-items: center;
}
.news-marquee-swiper :deep(.swiper-slide) {
width: auto;
height: 100%;
box-sizing: border-box;
/* 短文案slide 至少一屏宽,视口内只看到一整条;长文案:随 max-content 变宽 */
min-width: 100%;
min-width: 100cqi;
}
.news-marquee-slide-inner {
display: flex;
align-items: center;
height: 100%;
width: max-content;
box-sizing: border-box;
}
/* 开启滚动:整段文案单行完整展示,不用省略号 */
.news-marquee-slide-text--scroll {
width: max-content;
max-width: none;
white-space: nowrap;
overflow: visible;
}
.news-marquee-static {
flex: 1 1 0;
min-width: 0;
overflow: hidden;
display: flex;
align-items: center;
align-self: stretch;
}
.news-marquee-static-text {
width: 100%;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>

View File

@ -9,6 +9,7 @@
<el-radio-group v-model="form.notice_style" @change="change_style">
<el-radio value="inherit">样式一</el-radio>
<el-radio value="card">样式二</el-radio>
<el-radio value="marquee">样式三</el-radio>
</el-radio-group>
</el-form-item>
</card-container>
@ -28,15 +29,25 @@
<el-input v-model="form.title" placeholder="请输入标题" maxlength="30" clearable></el-input>
</el-form-item>
<template v-if="!is_card">
<el-form-item label="滚动方式">
<el-radio-group v-model="form.direction">
<el-radio value="vertical">上下滚动</el-radio>
<el-radio value="horizontal">左右滚动</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="间隔时间">
<slider v-model="form.interval_time" :min="1" :max="100"></slider>
</el-form-item>
<template v-if="is_marquee">
<el-form-item label="开启滚动">
<el-switch v-model="form.marquee_scroll" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
<el-form-item v-if="form.marquee_scroll === '1'" label="间隔时间">
<slider v-model="form.interval_time" :min="1" :max="100"></slider>
</el-form-item>
</template>
<template v-else>
<el-form-item label="滚动方式">
<el-radio-group v-model="form.direction">
<el-radio value="vertical">上下滚动</el-radio>
<el-radio value="horizontal">左右滚动</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="间隔时间">
<slider v-model="form.interval_time" :min="1" :max="100"></slider>
</el-form-item>
</template>
</template>
</card-container>
<div class="bg-f5 divider-line" />
@ -57,22 +68,37 @@
<div class="bg-f5 divider-line" />
<card-container>
<div class="mb-12">内容设置</div>
<drag :data="form.notice_list" type="card" :space-col="25" @remove="remove" @on-sort="on_sort">
<template #default="scoped">
<div class="flex-col align-c jc-s gap-20 flex-1">
<el-form-item label="标题" class="w mb-0" label-width="40">
<el-input v-model="scoped.row.notice_title" placeholder="请输入标题" maxlength="100" clearable></el-input>
</el-form-item>
<el-form-item label="链接" class="w mb-0" label-width="40">
<url-value v-model="scoped.row.notice_link"></url-value>
</el-form-item>
<el-form-item label="状态" class="w mb-0" label-width="40">
<el-switch v-model="scoped.row.is_show" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
</div>
</template>
</drag>
<el-button class="mt-20 mb-20 w" @click="add">+添加</el-button>
<template v-if="is_marquee">
<el-form-item label="公告内容" class="mb-0">
<el-input
v-model="form.marquee_content"
type="textarea"
:rows="3"
placeholder="请输入公告文字"
maxlength="500"
show-word-limit
clearable
></el-input>
</el-form-item>
</template>
<template v-else>
<drag :data="form.notice_list" type="card" :space-col="25" @remove="remove" @on-sort="on_sort">
<template #default="scoped">
<div class="flex-col align-c jc-s gap-20 flex-1">
<el-form-item label="标题" class="w mb-0" label-width="40">
<el-input v-model="scoped.row.notice_title" placeholder="请输入标题" maxlength="100" clearable></el-input>
</el-form-item>
<el-form-item label="链接" class="w mb-0" label-width="40">
<url-value v-model="scoped.row.notice_link"></url-value>
</el-form-item>
<el-form-item label="状态" class="w mb-0" label-width="40">
<el-switch v-model="scoped.row.is_show" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
</div>
</template>
</drag>
<el-button class="mt-20 mb-20 w" @click="add">+添加</el-button>
</template>
</card-container>
</el-form>
</div>
@ -87,10 +113,11 @@ const props = defineProps({
noticeStyle: {
type: Object,
default: () => {},
}
},
});
const is_card = computed(() => form.value.notice_style == 'card');
const is_marquee = computed(() => form.value.notice_style == 'marquee');
const is_text = computed(() => form.value.title_type == 'text');
const state = reactive({
@ -99,15 +126,31 @@ const state = reactive({
});
const { form, new_style } = toRefs(state);
if (form.value.marquee_content == null || form.value.marquee_content === undefined) {
form.value.marquee_content = '';
}
if (form.value.marquee_scroll == null || form.value.marquee_scroll === undefined) {
form.value.marquee_scroll = '1';
}
const sync_marquee_from_list = () => {
const t = String(form.value.marquee_content ?? '').trim();
if (t) return;
const row = form.value.notice_list?.find((i: { is_show?: string; notice_title?: string }) => i.is_show == '1' && String(i.notice_title ?? '').trim());
if (row?.notice_title) {
form.value.marquee_content = String(row.notice_title).trim();
}
};
const change_style = (val: string | number | boolean | undefined) => {
if (val === 'inherit') {
if (val === 'inherit' || val === 'marquee') {
new_style.value.container_padding = {
padding: 0,
padding_top: 0,
padding_right: 10,
padding_bottom: 0,
padding_left: 10,
}
};
} else {
new_style.value.container_padding = {
padding: 15,
@ -115,7 +158,12 @@ const change_style = (val: string | number | boolean | undefined) => {
padding_right: 15,
padding_bottom: 15,
padding_left: 15,
}
};
}
if (val === 'marquee') {
sync_marquee_from_list();
// 仅切换到样式三时统一左侧图标为喇叭,样式一/二不改动
form.value.icon_class = 'speaker';
}
};
const add = () => {
@ -130,10 +178,15 @@ const remove = (index: number) => {
form.value.notice_list.splice(index, 1);
};
// 拖拽更新之后,更新数据
const on_sort = (new_list: nav_group[]) => {
form.value.notice_list = new_list;
};
onMounted(() => {
if (is_marquee.value) {
sync_marquee_from_list();
}
});
</script>
<style lang="scss" scoped>
:deep(.size-12.cr-9.mt-10) {

View File

@ -11,7 +11,7 @@
<flex-gradients-create :color-list="form.title_color_list"></flex-gradients-create>
</el-form-item>
</template>
<template v-else>
<template v-else-if="is_img">
<template v-if="!isEmpty(substance.icon_class)">
<el-form-item label="左侧图标">
<div class="flex-col w gap-10">
@ -54,7 +54,7 @@
<div class="bg-f5 divider-line" />
<card-container>
<div class="mb-12">容器设置</div>
<template v-if="substance.notice_style === 'inherit'">
<template v-if="substance.notice_style === 'inherit' || substance.notice_style === 'marquee'">
<el-form-item label="高度">
<slider v-model="form.container_height" :max="1000"></slider>
</el-form-item>
@ -91,11 +91,10 @@ const state = reactive({
form: props.value,
substance: props.content,
});
// 如果需要解构确保使用toRefs
const { form, substance } = toRefs(state);
const is_img = computed(() => substance.value.title_type == 'img-icon');
// 通用样式处理
const common_styles_update = (val: Object) => {
form.value.common_style = val;
};

View File

@ -16,6 +16,13 @@ interface defaultSearch {
title: string;
direction: string;
img_src: uploadList[];
/** 样式三:单条公告文案(替代 notice_list */
marquee_content: string;
/** 样式三是否开启横向滚动1/0 */
marquee_scroll: string;
/** 兼容旧数据,样式三已不再使用 */
notice_left_img: uploadList[];
notice_right_img: uploadList[];
icon_class: string;
right_title: string;
more_link: object;
@ -38,6 +45,9 @@ interface defaultSearch {
container_height: number;
icon_size: number;
icon_color: string;
/** 兼容旧数据 */
marquee_slot_width: number;
marquee_slot_height: number;
container_color_list: color_list[],
container_direction: string,
container_background_img_style: string,
@ -58,6 +68,10 @@ const defaultSearch: defaultSearch = {
title_type: 'img-icon',
title: '测试标题',
img_src: [],
marquee_content: '',
marquee_scroll: '1',
notice_left_img: [],
notice_right_img: [],
// 滚动方式
direction: 'vertical',
interval_time: 3,
@ -100,6 +114,8 @@ const defaultSearch: defaultSearch = {
title_height: 24,
icon_size: 12,
icon_color: '#999',
marquee_slot_width: 24,
marquee_slot_height: 24,
right_button_color: '#999',
right_button_size: 12,
// 容器高度