| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- <template>
- <div class="preview-program-layout">
- <!-- 只保留中央预览区 -->
- <div class="main-preview">
- <div class="preview-canvas" ref="previewCanvasRef">
- <CanvasBoard v-if="canvasItem" :width="canvasItem.width" :height="canvasItem.height" :bg="canvasItem.bg"
- :scale="canvasScale">
- <template #default>
- <template
- v-for="item in editorContent.elements.filter((el) => el.type !== 'canvas').sort((a, b) => a.depth - b.depth)"
- :key="item.depth + '-' + item.type">
- <div :style="{
- position: 'absolute',
- left: (item.x || 0) * canvasScale + 'px',
- top: (item.y || 0) * canvasScale + 'px',
- width: (item.width || 200) * canvasScale + 'px',
- height: (item.height || 40) * canvasScale + 'px',
- zIndex: 10,
- pointerEvents: 'none' // 禁用所有交互
- }">
- <TextBoard v-if="item.type === 'text'" :text="item.text" :color="item.color" :font-size="item.fontSize"
- :font-weight="item.fontWeight" :align="item.align" :width="item.width * canvasScale"
- :height="item.height * canvasScale" />
- <ScrollingTextBoard v-if="item.type === 'scrollingText'" :text="item.text" :color="item.color"
- :font-size="item.fontSize" :font-weight="item.fontWeight" :align="item.align"
- :width="item.width * canvasScale" :height="item.height * canvasScale" :speed="item.speed" />
- <MediaAssetBoard v-if="item.type === 'mediaAsset'" :width="item.width * canvasScale"
- :height="item.height * canvasScale" :media-id="item.mediaId" v-model="item.mediaGroup" />
- <LiveBoard v-if="item.type === 'live'" :width="item.width * canvasScale"
- :height="item.height * canvasScale" :live-url="item.liveUrl" :play-audio="item.playAudio" />
- <WebPageBoard v-if="item.type === 'webPage'" :width="item.width * canvasScale"
- :height="item.height * canvasScale" :url="item.url" />
- <ClockBoard v-if="item.type === 'clock'" :width="item.width * canvasScale"
- :height="item.height * canvasScale" :format="item.format" />
- </div>
- </template>
- </template>
- </CanvasBoard>
- </div>
- <div class="preview-controls">
- <el-button type="primary" @click="goBack" class="back-btn">返回</el-button>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, nextTick, computed } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import { ElMessage } from 'element-plus';
- import { getItemProgram, listItemProgram } from '@/api/smsb/source/item_program';
- import CanvasBoard from './component/CanvasBoard.vue';
- import TextBoard from './component/TextBoard.vue';
- import ScrollingTextBoard from './component/ScrollingTextBoard.vue';
- import MediaAssetBoard from './component/MediaAssetBoard.vue';
- import LiveBoard from './component/LiveBoard.vue';
- import WebPageBoard from './component/WebPageBoard.vue';
- import ClockBoard from './component/ClockBoard.vue';
- const router = useRouter();
- const route = useRoute();
- // 自动修正 id 类型,确保为 string 或 number
- const rawId = route.params.id;
- const id = ref<string | number>(Array.isArray(rawId) ? rawId[0] : rawId);
- // 节目信息
- const programName = ref('');
- const programResolution = ref('');
- // 画布缩放
- const previewCanvasRef = ref<HTMLElement | null>(null);
- const containerSize = ref({ width: 0, height: 0 });
- const canvasScale = ref(1);
- // 编辑器内容
- interface EditorContent {
- elements: any[];
- }
- const editorContent = ref<EditorContent>({ elements: [] });
- const canvasItem = computed(() => {
- return editorContent.value.elements.find((el) => el.type === 'canvas');
- });
- // 更新容器尺寸和缩放
- const updateContainerSize = () => {
- if (previewCanvasRef.value) {
- const container = previewCanvasRef.value;
- containerSize.value = {
- width: container.clientWidth,
- height: container.clientHeight
- };
- if (canvasItem.value) {
- const scaleX = (container.clientWidth - 40) / canvasItem.value.width;
- const scaleY = (container.clientHeight - 40) / canvasItem.value.height;
- canvasScale.value = Math.min(scaleX, scaleY, 1); // 限制最大缩放为1
- console.log('Canvas scale updated:', {
- scale: canvasScale.value,
- container: containerSize.value,
- canvas: { width: canvasItem.value.width, height: canvasItem.value.height }
- });
- }
- }
- };
- // 获取节目详情
- const fetchProgramDetail = async () => {
- try {
- console.log('Fetching program detail for ID:', id.value);
- // 使用列表接口获取节目数据,因为详情接口可能返回 null
- const listRes = await listItemProgram({ programId: id.value });
- console.log('Program list response:', listRes);
- if (listRes.code === 200 && listRes.rows && listRes.rows.length > 0) {
- const programData = listRes.rows[0];
- programName.value = programData.name || '未命名节目';
- programResolution.value = programData.resolutionRatio || '1920x1080';
- // 尝试从 itemJsonStr 或 content 中获取节目内容
- let contentStr = programData.itemJsonStr || programData.content;
- if (!contentStr) {
- console.warn('No content found in program data');
- ElMessage.warning('该节目没有可预览的内容');
- return;
- }
- try {
- // 处理可能的转义字符
- if (typeof contentStr === 'string') {
- // 先尝试直接解析
- try {
- contentStr = JSON.parse(contentStr);
- } catch (e) {
- // 如果解析失败,可能是双重转义的 JSON 字符串
- try {
- contentStr = JSON.parse(JSON.parse(`"${contentStr}"`));
- } catch (e2) {
- console.error('Failed to parse content string:', e2);
- throw new Error('内容格式不正确');
- }
- }
- }
- // 确保 elements 数组存在
- if (!contentStr.elements || !Array.isArray(contentStr.elements)) {
- contentStr.elements = [];
- }
- editorContent.value = contentStr;
- // 确保有画布元素
- if (!editorContent.value.elements.some((el) => el.type === 'canvas')) {
- console.warn('No canvas element found in program content, adding default canvas');
- editorContent.value.elements.unshift({
- type: 'canvas',
- width: 1920,
- height: 1080,
- bg: { type: 'color', value: '#ffffff' },
- depth: 0
- });
- }
- // 更新容器尺寸
- nextTick(updateContainerSize);
- } catch (e) {
- console.error('Failed to parse program content:', e);
- ElMessage.error('节目内容解析失败: ' + (e as Error).message);
- }
- } else {
- throw new Error(listRes.msg || '获取节目详情失败,未找到对应节目');
- }
- } catch (error) {
- console.error('Error fetching program detail:', error);
- ElMessage.error('获取节目详情失败: ' + (error as Error).message);
- }
- };
- // 返回上一页
- const goBack = () => {
- router.go(-1);
- };
- onMounted(async () => {
- await fetchProgramDetail();
- window.addEventListener('resize', updateContainerSize);
- // 添加页面标题
- document.title = `预览 - ${programName.value || '节目预览'}`;
- return () => {
- window.removeEventListener('resize', updateContainerSize);
- };
- });
- </script>
- <style scoped>
- .preview-program-layout {
- display: flex;
- height: 100vh;
- background-color: #f0f2f5;
- overflow: hidden;
- }
- .main-preview {
- flex: 1;
- display: flex;
- flex-direction: column;
- height: 100%;
- padding: 20px;
- box-sizing: border-box;
- overflow: hidden;
- }
- .preview-canvas {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #f5f5f5;
- border: 1px solid #ddd;
- border-radius: 4px;
- overflow: hidden;
- margin-bottom: 16px;
- }
- .preview-controls {
- display: flex;
- justify-content: center;
- padding: 16px 0;
- background-color: #fff;
- border-top: 1px solid #eee;
- }
- .back-btn {
- min-width: 120px;
- }
- </style>
|