You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

706 lines
20 KiB
Vue

<template>
<div class="page-top-setting-wrap">
<div class="logo-wrap item-wrap">
<img
class="menu-img"
src="../BigScreenDesign/images/app.png"
alt="返回"
@click="goBackManage"
>
<span class="logo-text name-span">{{ pageInfo.name }}</span>
</div>
<div class="head-btn-group">
<span style="margin-right:8px;font-size:12px">缩放</span>
<el-input-number
class="bs-el-input-number"
ref="zoomInput"
style="margin-right:10px"
:value="zoom"
:min="1"
label="描述文字"
:controls="true"
@change="changeZoom"
/>
<CusBtn
:loading="saveAndPreviewLoading"
@click.native="changeZoom('auto')"
>
自适应
</CusBtn>
<el-dropdown
trigger="click"
class="align-list-dropdown"
>
<CusBtn type="primary">
对齐方式<i class="el-icon-arrow-down el-icon--right" />
</CusBtn>
<el-dropdown-menu
slot="dropdown"
class="align-dropdown-menu"
>
<el-dropdown-item
v-for="(mode, index) in alignList"
:key="mode.value"
@click.native="setAlign(mode.value)"
>
<icon-svg
style="padding:3px 8px"
:name="iconList[index]"
/>
<span style="color: #bcc9d4">{{ mode.label }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<CusBtn
:loading="saveAndPreviewLoading"
@click.native="designAssign()"
>
设计分工
</CusBtn>
<CusBtn @click.native="showHostory">
历史操作
</CusBtn>
<CusBtn
:disabled="undoDisabled"
@click.native="undo(true)"
>
<i class="iconfont-bigscreen icon-jiantouqianjin icon-reverse" />
</CusBtn>
<CusBtn
:disabled="redoDisabled"
@click.native="undo(false)"
>
<i class="iconfont-bigscreen icon-jiantouqianjin" />
</CusBtn>
<CusBtn
:loading="saveAndPreviewLoading"
@click.native="createdImg()"
>
生成图片
</CusBtn>
<CusBtn
:loading="saveAndPreviewLoading"
@click.native="execRun()"
>
预览
</CusBtn>
<CusBtn
:loading="saveLoading"
@click="save('saveLoading')"
>
保存
</CusBtn>
<CusBtn @click="empty">
清空
</CusBtn>
<CusBtn @click="showPageInfo">
设置
</CusBtn>
<CusBtn @click="updateRightVisiable">
<i
class="iconfont-bigscreen"
:class="rightFold ? 'icon-zhankaicaidan' : 'icon-shouqicaidan'"
/>
</CusBtn>
</div>
<ChooseTemplateDialog
ref="ChooseTemplateDialog"
:has-create="false"
:page-info="pageInfo"
@replaceItByTemplate="replaceItByTemplate"
/>
<AssignDialog ref="AssignDialog" />
<HistoryList ref="HistoryList" />
</div>
</template>
<script>
import { EventBus } from 'data-room-ui/js/utils/eventBus'
import { toJpeg, toPng } from 'html-to-image'
import { mapMutations, mapActions, mapState } from 'vuex'
import { saveScreen } from 'data-room-ui/js/api/bigScreenApi'
import ChooseTemplateDialog from 'data-room-ui/BigScreenManagement/ChooseTemplateDialog.vue'
// import _ from 'lodash'
import cloneDeep from 'lodash/cloneDeep'
import uniqBy from 'lodash/uniqBy'
import { stringifyObjectFunctions } from 'data-room-ui/js/utils/evalFunctions'
import AssignDialog from 'data-room-ui/BigScreenDesign/AssignDialog/index.vue'
import HistoryList from 'data-room-ui/BigScreenDesign/HistoryList/index.vue'
import CusBtn from './BtnLoading'
import icons from 'data-room-ui/assets/images/alignIcon/export'
import IconSvg from 'data-room-ui/SvgIcon'
import {
showSize,
dataURLtoBlob,
translateBlobToBase64
} from 'data-room-ui/js/utils/compressImg'
import * as imageConversion from 'image-conversion'
export default {
name: 'PageTopSetting',
components: {
IconSvg,
ChooseTemplateDialog,
AssignDialog,
CusBtn,
HistoryList
},
props: {
code: {
type: String,
default: ''
},
rightFold: {
type: Boolean,
default: false
}
},
data () {
return {
iconList: icons.getNameList(),
alignList: [
{
label: '左侧对齐',
value: 'left'
},
{
label: '居中对齐',
value: 'center'
},
{
label: '右侧对齐',
value: 'right'
},
{
label: '顶部对齐',
value: 'top'
},
{
label: '中部对齐',
value: 'middle'
},
{
label: '底部对齐',
value: 'bottom'
},
{
label: '水平均分',
value: 'levelAround'
},
{
label: '垂直均分',
value: 'verticalAround'
}
],
appInfo: '',
saveLoading: false,
createdImgLoading: false,
saveAndPreviewLoading: false
}
},
computed: {
...mapState({
pageInfo: (state) => state.bigScreen.pageInfo,
timelineStore: (state) => state.bigScreen.timelineStore,
currentTimeLine: (state) => state.bigScreen.currentTimeLine,
activeCodes: state => state.bigScreen.activeCodes,
zoom: (state) => state.bigScreen.zoom
}),
pageCode () {
return this.$route.query.code || this.code
},
undoDisabled () {
return Boolean(this.currentTimeLine <= 1)
},
redoDisabled () {
return Boolean(
!this.timelineStore?.length ||
(
this.currentTimeLine &&
this.currentTimeLine === this.timelineStore?.length
)
)
}
},
mounted() {
this.$refs.zoomInput.$el.addEventListener('mousewheel', this.handleMouseWheel);
},
beforeDestroy() {
this.$refs.zoomInput.$el.removeEventListener('mousewheel', this.handleMouseWheel);
},
methods: {
...mapActions({
initLayout: 'bigScreen/initLayout'
}),
...mapMutations({
changeActiveCode: 'bigScreen/changeActiveCode',
changeActiveItem: 'bigScreen/changeActiveItem',
changePageInfo: 'bigScreen/changePageInfo',
undoTimeLine: 'bigScreen/undoTimeLine',
saveTimeLine: 'bigScreen/saveTimeLine'
}),
handleMouseWheel() {
const delta = Math.sign(event.deltaY);
// 限制最小缩放比例为10
if (this.zoom <= 10 && delta > 0) return;
event.preventDefault();
let zoom1 = this.zoom - delta
this.$emit('changeZoom', zoom1)
},
changeZoom (val) {
this.$emit('changeZoom', val)
},
setAlign (command) {
const pageInfo = cloneDeep(this.pageInfo)
// 获取所有选中的组件
let activeChartList = pageInfo.chartList.filter((chart) => {
return this.activeCodes.some(code => (code === chart.code))
})
// 找到选中组件内的xy最大最小值
const maxXW = Math.max.apply(Math, activeChartList.map(item => { return item.x + item.w }))
let maxX = Math.max.apply(Math, activeChartList.map(item => { return item.x }))
const minX = Math.min.apply(Math, activeChartList.map(item => { return item.x }))
const maxYH = Math.max.apply(Math, activeChartList.map(item => { return item.y + item.h }))
const maxY = Math.max.apply(Math, activeChartList.map(item => { return item.y }))
const minY = Math.min.apply(Math, activeChartList.map(item => { return item.y }))
const centerW = maxXW - minX
const centerH = maxY - minY
switch (command) {
case 'left':
activeChartList.forEach((chart) => {
chart.x = minX
})
break
case 'center':
// eslint-disable-next-line no-case-declarations
activeChartList.forEach((chart) => {
chart.x = (centerW - chart.w) / 2 + minX
})
break
case 'right':
activeChartList.forEach((chart) => {
chart.x = maxXW - chart.w
})
break
case 'top':
activeChartList.forEach((chart) => {
chart.y = minY
})
break
case 'middle':
activeChartList.forEach((chart) => {
chart.y = (centerH - chart.h) / 2 + minY
})
break
case 'bottom':
activeChartList.forEach((chart) => {
chart.y = maxYH - chart.h
})
break
case 'levelAround':
// 先让数组根据x的属性进行排序
activeChartList = activeChartList.sort(this.compare('x'))
// eslint-disable-next-line no-case-declarations
const minXW = activeChartList[0].x + activeChartList[0].w
maxX = Math.max.apply(Math, activeChartList.map(item => { return item.x }))
// 中间总的宽度
// eslint-disable-next-line no-case-declarations
let totalW = 0
for (let i = 1; i < activeChartList.length - 1; i++) {
totalW = totalW + activeChartList[i].w
}
// 中间剩余的空格
// eslint-disable-next-line no-case-declarations
const padding = (maxX - minXW - totalW) / (activeChartList.length - 1)
// eslint-disable-next-line no-case-declarations
let useW = 0
for (let i = 1; i < activeChartList.length - 1; i++) {
activeChartList[i].x = minXW + padding * i + useW
useW = useW + activeChartList[i].w
}
break
case 'verticalAround':
// 先让数组根据y的属性进行排序
activeChartList = activeChartList.sort(this.compare('y'))
// eslint-disable-next-line no-case-declarations
const minYH = activeChartList[0].y + activeChartList[0].h
// eslint-disable-next-line no-case-declarations
let totalH = 0
for (let i = 1; i < activeChartList.length - 1; i++) {
totalH = totalH + activeChartList[i].h
}
// eslint-disable-next-line no-case-declarations
const paddingBottom = (maxY - minYH - totalH) / (activeChartList.length - 1)
// eslint-disable-next-line no-case-declarations
let useH = 0
for (let i = 1; i < activeChartList.length - 1; i++) {
activeChartList[i].y = minYH + paddingBottom * i + useH
useH = useH + activeChartList[i].h
}
break
}
pageInfo.chartList = [...pageInfo.chartList, ...activeChartList]
pageInfo.chartList = uniqBy(pageInfo.chartList, 'code')
this.changePageInfo(pageInfo)
},
compare (property) {
return function (obj1, obj2) {
const value1 = obj1[property]
const value2 = obj2[property]
return value1 - value2 // 升序
}
},
goBackManage () {
this.$confirm('确定返回主页面吗?未保存的配置将会丢失。', '提示', {
distinguishCancelAndClose: true,
confirmButtonText: '保存后离开页面',
cancelButtonText: '离开页面',
cancelButtonClass: 'cancel-btn',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(async () => {
const flag = await this.save()
if (flag){
await this.backManagement()
}
}).catch((action) => {
if (action === 'cancel') {
this.backManagement()
}
})
},
backManagement () {
const data = { componentsManagementType: 'component' }
this.$router.app.$options.globalData = data // 将数据存储在全局变量中
this.$router.push({ path: this.pageInfo.type === 'component' ? (window.BS_CONFIG?.routers?.componentUrl || '/big-screen-components') : (window.BS_CONFIG?.routers?.pageManagementUrl || '/home') })
},
undo (isUndo) {
this.undoTimeLine(isUndo)
},
// 清空
empty () {
this.changeActiveCode('')
this.$emit('empty')
},
// 预览
async execRun () {
this.save('preview').then((res) => {
this.preview(res)
})
},
// 预览
preview (previewCode) {
const { href } = this.$router.resolve({
path: window.BS_CONFIG?.routers?.previewUrl || '/big-screen/preview',
query: {
code: previewCode || this.pageCode
}
})
window.open(href, '_blank')
},
// 保存时判断tabs组件里面的元素是否符合要求
validateTabs(chartList){
let isValid = true
if(chartList.length){
for(let chart of chartList){
if(chart.type === 'chartTab' && chart.customize.tabList.length !== 0 ){
for(let tab of chart.customize.tabList){
if((!tab.name) || (!tab.chartCode)){
isValid = false
return isValid
}
}
}
}
}
return isValid
},
// 保存
async save (type, hasPageTemplateId = false) {
const pageInfo = cloneDeep(this.handleSaveData())
//保存时判断tabs组件里面的元素是否符合要求
const flag = this.validateTabs(pageInfo?.chartList)
if (!flag){
this.$message.warning('请完成tab项配置')
return false
}
// 保存页面
try {
if (!hasPageTemplateId) {
delete pageInfo.pageTemplateId
}
if (type === 'preview') {
pageInfo.isPreview = true
const res = await saveScreen(pageInfo)
return res
} else {
pageInfo.isPreview = false
this.saveLoading = true
const node = document.querySelector('.render-theme-wrap')
const dataUrl = await toJpeg(node, { quality: 0.2 })
if (showSize(dataUrl) > 200) {
const url = dataURLtoBlob(dataUrl)
// 压缩到500KB,这里的500就是要压缩的大小,可自定义
const imgRes = await imageConversion.compressAccurately(url, {
size: 200, // 图片大小压缩到100kb
width: 1280, // 宽度压缩到1280
height: 720 // 高度压缩到720
})
const base64 = await translateBlobToBase64(imgRes)
pageInfo.coverPicture = base64.result
const res = await saveScreen(pageInfo)
this.$message.success('保存成功')
return res
} else {
pageInfo.coverPicture = dataUrl
const res = await saveScreen(pageInfo)
this.$message.success('保存成功')
return res
}
}
} catch (error) {
console.error(error)
this.saveLoading = false
throw error
} finally {
this.saveLoading = false
}
},
goBack (path) {
this.$router.push({
path: `/${path}`
})
},
// 得到模板列表
getTemplateList (type) {
this.$nextTick(() => {
this.$refs.ChooseTemplateDialog.init(undefined, type)
})
},
// 选择模版后覆盖配置
selectTemplate (template) {
this.pageInfo.pageTemplateId = template.id
this.save('saveLoading', true).then(() => {
this.initLayout(this.pageCode)
})
},
replaceItByTemplate (config) {
this.changePageInfo(config)
},
// 处理保存数据
handleSaveData () {
const pageInfo = cloneDeep(this.pageInfo)
const chartList = cloneDeep(this.pageInfo.chartList)
pageInfo.pageConfig.cacheDataSets =
pageInfo.pageConfig.cacheDataSets?.map((cache) => ({
name: cache.name,
dataSetId: cache.dataSetId
})) || []
const newChartList = chartList?.map((chart) => {
// 如果是自定义组件需要将option转换为json字符串因为其中可能有函数
if (['customComponent', 'remoteComponent', 'echartsComponent'].includes(chart.type)) {
// chart.option.data = []
chart.option = stringifyObjectFunctions(chart.option)
}
return chart
})
return cloneDeep({
...this.pageInfo,
chartList: newChartList
})
},
updateRightVisiable () {
this.$emit('updateRightVisiable', !this.rightFold)
},
showPageInfo () {
this.$emit('showPageInfo')
},
designAssign () {
this.$refs.AssignDialog.init()
},
showHostory () {
this.$refs.HistoryList.init()
},
createdImg () {
this.saveAndPreviewLoading = true
// 暂停跑马灯动画
EventBus.$emit('stopMarquee')
const node = document.querySelector('.render-theme-wrap')
toPng(node)
.then((dataUrl) => {
const link = document.createElement('a')
link.download = `${this.pageInfo.name}.png`
link.href = dataUrl
link.click()
link.addEventListener('click', () => {
link.remove()
})
this.saveAndPreviewLoading = false
// 恢复跑马灯动画
EventBus.$emit('startMarquee')
})
.catch(() => {
this.$message.warning('出现未知错误,请重试')
this.saveAndPreviewLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
@import '../BigScreenDesign/fonts/iconfont.css';
.default-layout-box {
display: flex;
flex-wrap: wrap;
.default-layout-item {
cursor: pointer;
width: 42%;
margin: 9px;
display: flex;
flex-direction: column;
align-items: center;
.component-name {
font-size: 12px;
}
.sampleImg {
margin: 0 auto;
width: 102px;
height: 73px;
display: block;
}
.img_dispaly {
margin: 0 auto;
text-align: center;
width: 100px;
height: 70px;
line-height: 70px;
background-color: #d7d7d7;
color: #999;
}
.demonstration {
text-align: center;
}
}
.default-layout-item:hover {
cursor: pointer;
}
::v-deep .el-radio__label {
display: none;
}
}
.page-top-setting-wrap {
height: 40px;
background-color: var(--bs-background-2);
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
color: #ffffff;
padding: 0 5px;
.app-name {
cursor: pointer;
}
.head-btn-group {
display: flex;
margin-left: 50px;
align-items: center;
i {
font-size: 14px;
}
.icon-reverse {
transform: rotate(180deg);
}
}
.item-wrap {
display: flex;
align-items: center;
.menu-img {
width: 18px;
height: 18px;
margin-left: 9px;
margin-right: 15px;
cursor: pointer;
}
.logo-text {
user-select: none;
margin-left: 9px;
font-size: 14px;
color: #ffffff;
}
.name-span {
max-width: 300px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.theme-switch {
margin-right: 10px;
/deep/.el-switch__label {
color: #bcc9d4 !important;
}
/deep/.el-switch__label.is-active {
color: var(--bs-el-color-primary) !important;
}
}
.align-list-dropdown {
width: 100px !important;
color: #ffffff !important;
}
}
// 自定义dropdown的样式
.align-dropdown-menu {
background-color: var(--bs-background-2) !important;
border: 1px solid var(--bs-border-1);
/deep/ .el-dropdown-menu__item {
background-color: var(--bs-background-2) !important;
&:hover {
color: var(--bs-el-color-primary) !important;
background-color: var(--bs-el-background-3) !important;
}
}
}
::v-deep .el-input__inner,
::v-deep .el-color-picker__color-inner,
::v-deep .el-input-number--mini,
::v-deep .el-textarea__inner,
::v-deep .el-input-group__append {
background: var(--bs-el-background-1);
color: var(--bs-el-text);
border: 0 !important;
width: 100px;
}
// .bs-el-input-number{
// }
</style>