feat: 可视化大屏
@ -1,30 +1,40 @@
|
||||
const Layout = () => import("@/layout/index.vue");
|
||||
const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
export default [
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
meta: {
|
||||
title: "登录",
|
||||
showLink: false,
|
||||
rank: 101
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/redirect",
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: "加载中...",
|
||||
showLink: false,
|
||||
rank: 102
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
showLink: false,
|
||||
rank: 101,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '加载中...',
|
||||
showLink: false,
|
||||
rank: 102,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
name: 'Redirect',
|
||||
component: () => import('@/layout/redirect.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/screen',
|
||||
name: 'Screen',
|
||||
component: () => import('@/views/screen/index.vue'),
|
||||
meta: {
|
||||
title: '可视化大屏',
|
||||
showLink: false,
|
||||
rank: 103,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/redirect/:path(.*)",
|
||||
name: "Redirect",
|
||||
component: () => import("@/layout/redirect.vue")
|
||||
}
|
||||
]
|
||||
}
|
||||
] satisfies Array<RouteConfigsTable>;
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import CountUp from "./count-up.vue"
|
||||
export default CountUp
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref ,onBeforeUpdate, nextTick} from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import type { PropType } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
color: {
|
||||
type: Array as unknown as PropType<[string, string]>,
|
||||
default: () => [],
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "transparent",
|
||||
},
|
||||
});
|
||||
const defaultColor = ["#6586ec", "#2cf7fe"];
|
||||
const domRef = ref(null);
|
||||
const { width, height } = useElementSize(domRef,{width:0,height:0}, { box: 'border-box' });
|
||||
const mergedColor = computed<[string, string]>(() => {
|
||||
return merge(defaultColor, props.color);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-13 dv-border-box" ref="domRef">
|
||||
<svg :width="width" :height="height" class="dv-border-svg-container">
|
||||
<path
|
||||
:fill="backgroundColor"
|
||||
:stroke="mergedColor[0]"
|
||||
:d="`
|
||||
M 5 20 L 5 10 L 12 3 L 60 3 L 68 10
|
||||
L ${width - 20} 10 L ${width - 5} 25
|
||||
L ${width - 5} ${height - 5} L 20 ${height - 5}
|
||||
L 5 ${height - 20} L 5 20
|
||||
`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="10, 5"
|
||||
:stroke="mergedColor[0]"
|
||||
:d="`M 16 9 L 61 9`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
stroke="{mergedColor[1]}"
|
||||
:d="`M 5 20 L 5 10 L 12 3 L 60 3 L 68 10`"
|
||||
/>
|
||||
|
||||
<path
|
||||
fill="transparent"
|
||||
:stroke="mergedColor[1]"
|
||||
:d="`M ${width - 5} ${height - 30} L ${width - 5} ${height - 5} L ${
|
||||
width - 30
|
||||
} ${height - 5}`"
|
||||
/>
|
||||
</svg>
|
||||
<div class="dv-border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dv-border-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: block;
|
||||
}
|
||||
.dv-border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,3 @@
|
||||
import BorderBox13 from "./border-box-13.vue"
|
||||
|
||||
export default BorderBox13
|
||||
@ -0,0 +1,186 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import type { DefaultConfigType } from "./index.d";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { merge } from "lodash-es";
|
||||
const mergedConfig = ref<any>(null);
|
||||
const capsuleLength = ref<any>([]);
|
||||
const capsuleValue = ref<any>([]);
|
||||
const labelData = ref<any>([]);
|
||||
// const labelDataLength = ref<any>([]);
|
||||
|
||||
const defaultConfig = reactive<DefaultConfigType>({
|
||||
// Colors (hex|rgb|rgba|color keywords) ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red']
|
||||
colors: [
|
||||
"#37a2da",
|
||||
"#32c5e9",
|
||||
"#67e0e3",
|
||||
"#9fe6b8",
|
||||
"#ffdb5c",
|
||||
"#ff9f7f",
|
||||
"#fb7293",
|
||||
],
|
||||
unit: "",
|
||||
showValue: false, // Show item value
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config: object | any;
|
||||
data: Array<{
|
||||
name: string;
|
||||
value: string | number;
|
||||
}>;
|
||||
}>(),
|
||||
{
|
||||
config: () => { },
|
||||
data: () => [],
|
||||
}
|
||||
);
|
||||
const calcData = () => {
|
||||
mergeConfig();
|
||||
calcCapsuleLengthAndLabelData();
|
||||
};
|
||||
const mergeConfig = () => {
|
||||
mergedConfig.value = merge(cloneDeep(defaultConfig), props.config || {});
|
||||
};
|
||||
const calcCapsuleLengthAndLabelData = () => {
|
||||
if (!props.data.length) return;
|
||||
const newcapsuleValue = props.data.map((item: any) => item.value);
|
||||
const maxValue = Math.max(...newcapsuleValue);
|
||||
capsuleValue.value = newcapsuleValue;
|
||||
capsuleLength.value = newcapsuleValue.map((v: any) =>
|
||||
maxValue ? v / maxValue : 0
|
||||
);
|
||||
const oneFifth = maxValue / 5;
|
||||
const newlabelData = Array.from(
|
||||
new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth)))
|
||||
);
|
||||
labelData.value = newlabelData;
|
||||
// labelDataLength.value = Array.from(newlabelData).map((v) =>
|
||||
// maxValue ? v / maxValue : 0
|
||||
// );
|
||||
// console.log(labelDataLength.value);
|
||||
};
|
||||
watch(
|
||||
() => props.data,
|
||||
(newval: any) => {
|
||||
calcData();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.config,
|
||||
(newval: any) => {
|
||||
calcData();
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
calcData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-capsule-chart">
|
||||
<template v-if="mergedConfig">
|
||||
<div class="label-column">
|
||||
<div v-for="item in data" :key="item.name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div> </div>
|
||||
</div>
|
||||
<div class="capsule-container">
|
||||
<div class="capsule-item" v-for="(capsule, index) in capsuleLength" :key="index">
|
||||
<div class="capsule-item-column" :style="`width: ${capsule * 100}%; background-color: ${mergedConfig.colors[index % mergedConfig.colors.length]
|
||||
};`">
|
||||
<div v-if="mergedConfig.showValue" class="capsule-item-value">
|
||||
{{ capsuleValue[index] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unit-label">
|
||||
<div v-for="(label, index) in labelData" :key="label + index">
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unit-text" v-if="mergedConfig.unit">
|
||||
{{ mergedConfig.unit }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dv-capsule-chart {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
|
||||
.label-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
|
||||
div {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.capsule-item {
|
||||
box-shadow: 0 0 3px #999;
|
||||
height: 10px;
|
||||
margin: 5px 0px;
|
||||
border-radius: 5px;
|
||||
|
||||
.capsule-item-column {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
margin-top: 1px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.capsule-item-value {
|
||||
font-size: 12px;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unit-label {
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unit-text {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,6 @@
|
||||
export interface DefaultConfigType {
|
||||
|
||||
colors: Array<String>;
|
||||
unit:string,
|
||||
showValue:Boolean
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import CapsuleChart from "./capsule-chart.vue"
|
||||
export * from "./index.d"
|
||||
export default CapsuleChart
|
||||
@ -0,0 +1,2 @@
|
||||
import ItemWrap from "./item-wrap.vue"
|
||||
export default ItemWrap
|
||||
@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import BorderBox13 from '../datav/border-box-13';
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 标题
|
||||
title: number | string;
|
||||
}>(),
|
||||
{
|
||||
title: '',
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BorderBox13>
|
||||
<div
|
||||
class="item_title"
|
||||
v-if="title !== ''"
|
||||
>
|
||||
<div class="zuo"></div>
|
||||
<span class="title-inner"> {{ title }} </span>
|
||||
<div class="you"></div>
|
||||
</div>
|
||||
<div :class="title !== '' ? 'item_title_content' : 'item_title_content_def'">
|
||||
<slot></slot></div
|
||||
></BorderBox13>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$item-title-height: 38px;
|
||||
$item_title_content-height: calc(100% - 38px);
|
||||
|
||||
.item_title {
|
||||
height: $item-title-height;
|
||||
line-height: $item-title-height;
|
||||
width: 100%;
|
||||
color: #31abe3;
|
||||
text-align: center;
|
||||
// background: linear-gradient(to right, transparent, #0f0756, transparent);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.zuo,
|
||||
.you {
|
||||
width: 58px;
|
||||
height: 14px;
|
||||
background-image: url('@/assets/img/titles/zuo.png');
|
||||
}
|
||||
|
||||
.you {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.title-inner {
|
||||
font-weight: 900;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(92deg, #0072ff 0%, #00eaff 48.8525390625%, #01aaff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.dv-border-box-content) {
|
||||
box-sizing: border-box;
|
||||
padding: 6px 16px 0px;
|
||||
}
|
||||
|
||||
.item_title_content {
|
||||
height: $item_title_content-height;
|
||||
}
|
||||
|
||||
.item_title_content_def {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,3 @@
|
||||
import ScaleScreen from './scale-screen.vue'
|
||||
|
||||
export default ScaleScreen
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, onBeforeMount, onMounted, ref, watch, nextTick } from 'vue';
|
||||
import { computed, onBeforeMount, onMounted, ref, watch, nextTick } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { throttle } from 'lodash-es';
|
||||
type propsType = {
|
||||
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="d-flex jc-center title_wrap">
|
||||
<div class="zuojuxing"></div>
|
||||
<div class="youjuxing"></div>
|
||||
<div class="guang"></div>
|
||||
<div class="d-flex jc-center">
|
||||
<div class="title">
|
||||
<span class="title-text">{{ currentOrg.name }}可视化大屏</span>
|
||||
<IconifyIconOffline
|
||||
:style="{ transform: `rotate(${ratation}deg)` }"
|
||||
class="select-icon"
|
||||
width="30px"
|
||||
height="30px"
|
||||
:icon="CaretRight"
|
||||
@click="toggleShowSelectOrg"
|
||||
/>
|
||||
<ul :class="['select-ul', { fade: isShow }]">
|
||||
<li
|
||||
v-for="item in orgList"
|
||||
:key="item.id"
|
||||
:class="{ selected: item.id === currentOrg.id }"
|
||||
@click="toggleSelectOrg(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timers">
|
||||
<span> {{ dateData.dateYear }} {{ dateData.dateWeek }} {{ dateData.dateDay }}</span>
|
||||
<LaySidebarFullScreen id="full-screen" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref, reactive, inject, computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import LaySidebarFullScreen from '@/layout/components/lay-sidebar/components/SidebarFullScreen.vue';
|
||||
import CaretRight from '@iconify-icons/ep/caret-right';
|
||||
|
||||
const props = defineProps({
|
||||
orgList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const currentOrg = inject('currentOrg');
|
||||
const emits = defineEmits(['changeOrg']);
|
||||
const isShow = ref(false);
|
||||
const toggleShowSelectOrg = () => {
|
||||
isShow.value = !isShow.value;
|
||||
};
|
||||
const toggleSelectOrg = (item) => {
|
||||
isShow.value = false;
|
||||
emits('changeOrg', item);
|
||||
};
|
||||
const ratation = computed(() => (isShow.value ? 90 : 0));
|
||||
|
||||
const weekday = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const dateData = reactive({
|
||||
dateDay: '',
|
||||
dateYear: '',
|
||||
dateWeek: '',
|
||||
timing: null,
|
||||
});
|
||||
const timeFn = () => {
|
||||
dateData.timing = setInterval(() => {
|
||||
dateData.dateDay = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
dateData.dateWeek = weekday[dayjs().day()];
|
||||
}, 1000);
|
||||
};
|
||||
timeFn();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title_wrap {
|
||||
height: 60px;
|
||||
background-image: url('../img/headers/top.png');
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.guang {
|
||||
position: absolute;
|
||||
bottom: -26px;
|
||||
background-image: url('../img/headers/guang.png');
|
||||
background-position: 80px center;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.zuojuxing,
|
||||
.youjuxing {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 140px;
|
||||
height: 6px;
|
||||
background-image: url('../img/headers/juxing1.png');
|
||||
}
|
||||
|
||||
.zuojuxing {
|
||||
left: 11%;
|
||||
}
|
||||
|
||||
.youjuxing {
|
||||
right: 11%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.timers {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 30px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
& > span:first-child {
|
||||
line-height: 1;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.fullscreen-icon {
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
// width: 500px;
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
color: transparent;
|
||||
height: 60px;
|
||||
line-height: 46px;
|
||||
|
||||
.title-text {
|
||||
font-size: 38px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 6px;
|
||||
width: 100%;
|
||||
background: linear-gradient(92deg, #0072ff 0%, #00eaff 48.8525390625%, #01aaff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: #0072ff;
|
||||
vertical-align: text-bottom;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.select-ul {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
color: #f1f1f1;
|
||||
background: rgb(48, 49, 51);
|
||||
border-radius: 10px;
|
||||
width: 150px;
|
||||
z-index: 999;
|
||||
left: 50%;
|
||||
margin-left: 150px;
|
||||
transition: all 0.3s ease;
|
||||
padding: 5px 0;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background: rgb(104, 104, 105);
|
||||
}
|
||||
li.selected {
|
||||
background: rgb(104, 104, 105);
|
||||
}
|
||||
}
|
||||
|
||||
.select-ul.fade {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<scale-screen
|
||||
width="1920"
|
||||
height="1080"
|
||||
:delay="300"
|
||||
:full-screen="false"
|
||||
:box-style="{
|
||||
background: '#03050c',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
:wrapper-style="{}"
|
||||
>
|
||||
<div class="content_wrap">
|
||||
<Headers
|
||||
:orgList="orgList"
|
||||
@changeOrg="changeOrg"
|
||||
/>
|
||||
</div>
|
||||
</scale-screen>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref, provide } from 'vue';
|
||||
import ScaleScreen from './components/common/scale-screen';
|
||||
import Headers from './components/header.vue';
|
||||
|
||||
const orgList = ref([
|
||||
{
|
||||
id: 'all',
|
||||
name: '全局',
|
||||
},
|
||||
{
|
||||
id: 'org1',
|
||||
name: '机构一',
|
||||
},
|
||||
{
|
||||
id: 'org2',
|
||||
name: '机构二',
|
||||
},
|
||||
]);
|
||||
const currentOrg = ref({
|
||||
id: 'all',
|
||||
name: '全局',
|
||||
});
|
||||
provide('currentOrg', currentOrg);
|
||||
const changeOrg = (item) => {
|
||||
currentOrg.value = item;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content_wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
background-image: url('./img/pageBg.png');
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
:src="src"
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref } from 'vue';
|
||||
defineOptions({
|
||||
name: 'TemplateScreen',
|
||||
});
|
||||
|
||||
const origin = window.location.origin;
|
||||
const src = ref(`${origin}/#/screen`);
|
||||
</script>
|
||||