!211 iframe组件添加定时切换和切换动画效果

Merge pull request !211 from lma/cherry-pick-111
This commit is contained in:
Foming
2025-05-19 08:20:37 +00:00
committed by Gitee
4 changed files with 424 additions and 28 deletions

View File

@@ -192,6 +192,11 @@
:chart-type="item.chartType"
@change="changed($event, item.name)"
/>
<multiIframeManager
v-if="item.type == 'multiIframeManager'"
v-model="formData[item.name]"
@change="(val) => changed(val, item.name)"
/>
</div>
<div v-else-if="isShowForm(item, '[object Array]')" :key="'a-' + index">
<el-collapse accordion>
@@ -347,6 +352,7 @@ import dynamicAddRadar from "./dynamicAddRadar";
import MonacoEditor from "@/components/MonacoEditor/index";
import componentLinkage from './componentLinkage';
import imageSelect from './imageSelect';
import multiIframeManager from './multiIframeManager.vue';
export default {
name: "DynamicForm",
components: {
@@ -360,7 +366,8 @@ export default {
customUpload,
dynamicAddRadar,
MonacoEditor,
componentLinkage
componentLinkage,
multiIframeManager
},
model: {
prop: "value",

View File

@@ -0,0 +1,189 @@
<template>
<div class="multi-iframe-manager">
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
plain
@click="handleAddClick"
>
新增地址
</el-button>
<el-table :data="formData" style="width: 100%">
<el-table-column prop="name" label="名称" width="80">
<template slot-scope="scope">
<span>{{ scope.row.name || `地址${scope.$index + 1}` }}</span>
</template>
</el-table-column>
<el-table-column prop="url" label="地址" show-overflow-tooltip>
<template slot-scope="scope">
<el-tooltip :content="scope.row.url" placement="top">
<span class="url-text">{{ scope.row.url }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<span
class="editor"
@click="handleEditorClick(scope.$index, scope.row)"
>
<i class="el-icon-edit" /> 编辑
</span>
<span
class="delete"
@click="handleDeleteClick(scope.$index, scope.row)"
>
<i class="el-icon-delete" /> 删除
</span>
</template>
</el-table-column>
</el-table>
<el-dialog
:title="isAddFlag ? '新增iframe地址' : '修改iframe地址'"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
>
<el-form ref="iframeForm" :model="iframeForm" label-width="80px">
<el-form-item label="名称">
<el-input v-model="iframeForm.name" size="mini" placeholder="给iframe地址一个名称"></el-input>
</el-form-item>
<el-form-item label="地址" required>
<el-input v-model="iframeForm.url" size="mini" placeholder="请输入iframe地址"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="mini" @click="handleClose"> </el-button>
<el-button size="mini" type="primary" @click="handleSaveClick"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'MultiIframeManager',
model: {
prop: 'formData',
event: 'input'
},
props: {
formData: {
type: Array,
default: () => []
}
},
data() {
return {
isAddFlag: true, // true 新增, false 编辑
indexEditor: -1, // 编辑第几个数据
iframeForm: {
name: '',
url: ''
},
dialogVisible: false // 显示弹窗
}
},
methods: {
// 弹出框关闭
handleClose() {
this.dialogVisible = false
this.resetForm()
},
// 重置表单
resetForm() {
this.iframeForm = {
name: '',
url: ''
}
},
// 新增按钮
handleAddClick() {
this.resetForm()
this.isAddFlag = true
this.dialogVisible = true
},
// 修改按钮
handleEditorClick(index, row) {
this.isAddFlag = false
this.iframeForm = JSON.parse(JSON.stringify(row))
this.dialogVisible = true
this.indexEditor = index
},
// 删除
handleDeleteClick(index) {
this.$confirm('确定要删除该地址吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.formData.splice(index, 1)
this.$emit('input', this.formData)
this.$emit('change', this.formData)
this.$message({
type: 'success',
message: '删除成功!'
})
}).catch(() => {})
},
// 确定
handleSaveClick() {
if (!this.iframeForm.url) {
this.$message.error('请输入iframe地址')
return
}
const obj = JSON.parse(JSON.stringify(this.iframeForm))
// 如果没有设置名称,则使用默认名称
if (!obj.name) {
obj.name = this.isAddFlag
? `地址${this.formData.length + 1}`
: `地址${this.indexEditor + 1}`
}
if (this.isAddFlag) {
// 新增
this.formData.push(obj)
this.dialogVisible = false
} else {
// 编辑
this.formData[this.indexEditor] = obj
this.dialogVisible = false
}
this.$emit('input', this.formData)
this.$emit('change', this.formData)
this.resetForm()
}
}
}
</script>
<style lang="scss" scoped>
.multi-iframe-manager {
margin-bottom: 20px;
.editor,
.delete {
margin: 0 5px;
font-size: 12px;
color: #409EFF;
cursor: pointer;
}
.delete {
color: #F56C6C;
}
.url-text {
display: inline-block;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

View File

@@ -24,12 +24,36 @@ export const widgetIframe = {
value: 'iframe',
},
{
type: 'el-input-text',
label: '地址',
name: 'iframeAdress',
type: 'el-select',
label: '切换动画',
name: 'transitionEffect',
required: false,
placeholder: '',
value: 'https://ajreport.beliefteam.cn/index.html',
placeholder: '请选择切换动画效果',
selectOptions: [
{name: '无动画', code: 'none'},
{name: '淡入淡出', code: 'fade'},
{name: '滑动', code: 'slide'},
{name: '缩放', code: 'zoom'}
],
value: 'none',
},
{
type: 'el-input-number',
label: '切换间隔(秒)',
name: 'autoSwitchInterval',
required: false,
placeholder: '0表示不自动切换',
value: 0,
},
{
type: 'multiIframeManager',
label: 'iframe地址管理',
name: 'iframeUrls',
required: false,
value: [{
name: '默认地址',
url: 'https://ajreport.beliefteam.cn/index.html'
}],
},
],
// 数据

View File

@@ -1,10 +1,29 @@
<template>
<iframe
:style="styleColor"
:src="this.toGetUrl(styleColor.iframeAdress)"
width="100%"
height="100%"
/>
<div class="iframe-container" :style="containerStyle">
<transition :name="transitionName">
<iframe
v-if="hasIframes"
:ref="`iframe-${currentIframeIndex}`"
:key="`iframe-${currentIframeIndex}`"
:style="iframeStyle"
:src="processedUrl"
width="100%"
height="100%"
class="iframe"
/>
</transition>
<!-- 切换按钮只在有多个iframe时显示 -->
<div v-if="hasMultipleUrls" class="iframe-switcher">
<div
v-for="(iframe, index) in iframeUrls"
:key="index"
:class="['switcher-dot', currentIframeIndex === index ? 'active' : '']"
@click="switchToIframe(index)"
:title="iframe.name || `地址${index+1}`">
</div>
</div>
</div>
</template>
<script>
@@ -17,61 +36,218 @@ export default {
},
data() {
return {
options: {}
options: {},
currentIframeIndex: 0,
switchTimer: null,
processedUrl: ''
};
},
computed: {
transStyle() {
return this.objToOne(this.options);
},
styleColor() {
// 容器样式 - 分离样式属性
containerStyle() {
return {
position: this.ispreview ? "absolute" : "static",
width: this.transStyle.width + "px",
height: this.transStyle.height + "px",
left: this.transStyle.left + "px",
top: this.transStyle.top + "px",
right: this.transStyle.right + "px",
iframeAdress: this.transStyle.iframeAdress
right: this.transStyle.right + "px"
};
},
// iframe样式
iframeStyle() {
return {
width: "100%",
height: "100%",
border: "none"
};
},
// 获取iframe地址数组
iframeUrls() {
return this.transStyle.iframeUrls || [];
},
// 判断是否有iframe地址配置
hasIframes() {
return this.iframeUrls.length > 0;
},
// 判断是否有多个iframe地址
hasMultipleUrls() {
return this.iframeUrls.length > 1;
},
// 当前显示的iframe地址
currentIframeUrl() {
if (!this.hasIframes || this.currentIframeIndex >= this.iframeUrls.length) {
return '';
}
return this.iframeUrls[this.currentIframeIndex].url;
},
// 根据选择的动画效果返回transition名称
transitionName() {
const effect = this.transStyle.transitionEffect;
if (!effect || effect === 'none') return '';
return effect;
}
},
watch: {
value: {
handler(val) {
this.options = val;
// 如果配置变化,重新设置自动切换
this.setupAutoSwitch();
// 更新处理后的URL
this.updateUrl();
// 当iframe列表变更时确保当前索引在有效范围内
if (this.hasIframes && this.currentIframeIndex >= this.iframeUrls.length) {
this.currentIframeIndex = 0;
}
},
deep: true
},
currentIframeUrl: {
handler() {
this.updateUrl();
}
}
},
mounted() {
this.options = this.value;
this.setupAutoSwitch();
this.updateUrl();
},
beforeDestroy() {
// 组件销毁前清除定时器
this.clearSwitchTimer();
},
methods: {
// 更新处理后的URL
updateUrl() {
if (this.currentIframeUrl) {
this.processedUrl = this.toGetUrl(this.currentIframeUrl);
} else {
this.processedUrl = '';
}
},
toGetUrl(url) {
if (url.indexOf('{') < 0 && url.indexOf('}' < 0)) {
return url
if (!url || (url.indexOf('{') < 0 && url.indexOf('}') < 0)) {
return url;
}
const reg = /{[a-zA-Z0-9]*\}/g
const list = url.match(reg)
console.log(list)
let result = url
const query = this.$route.query
const reg = /{[a-zA-Z0-9]*\}/g;
const list = url.match(reg);
if (!list) return url;
let result = url;
const query = this.$route.query;
for (let i = 0; i < list.length; i++) {
const sub = list[i]
const key = sub.replace('{', '').replace('}', '')
result = result.replace(sub, query[key])
const sub = list[i];
const key = sub.replace('{', '').replace('}', '');
result = result.replace(sub, query[key] || '');
}
return result;
},
// 切换到指定的iframe
switchToIframe(index) {
if (index === this.currentIframeIndex || index >= this.iframeUrls.length) return;
this.currentIframeIndex = index;
},
// 设置自动切换
setupAutoSwitch() {
this.clearSwitchTimer();
const interval = this.transStyle.autoSwitchInterval;
if (interval && interval > 0 && this.iframeUrls.length > 1) {
this.switchTimer = setInterval(() => {
this.currentIframeIndex = (this.currentIframeIndex + 1) % this.iframeUrls.length;
}, interval * 1000);
}
},
// 清除自动切换定时器
clearSwitchTimer() {
if (this.switchTimer) {
clearInterval(this.switchTimer);
this.switchTimer = null;
}
return result
}
}
};
</script>
<style scoped lang="scss">
iframe {
.iframe-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.iframe {
width: 100%;
height: 100%;
border: none;
position: absolute;
top: 0;
left: 0;
}
/* 切换指示器样式 */
.iframe-switcher {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
z-index: 10;
}
.switcher-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
cursor: pointer;
transition: all 0.3s;
}
.switcher-dot.active {
background-color: #fff;
box-shadow: 0 0 5px rgba(255, 255, 255, 0.8);
}
/* 淡入淡出动画 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
/* 滑动动画 */
.slide-enter-active, .slide-leave-active {
transition: transform 0.5s;
}
.slide-enter {
transform: translateX(100%);
}
.slide-leave-to {
transform: translateX(-100%);
}
/* 缩放动画 */
.zoom-enter-active, .zoom-leave-active {
transition: transform 0.5s, opacity 0.5s;
}
.zoom-enter {
transform: scale(0.8);
opacity: 0;
}
.zoom-leave-to {
transform: scale(1.2);
opacity: 0;
}
</style>