DdataeaseShufix: 样式优化
6b3f1a84创建于 4月17日历史提交
<script lang="ts" setup>
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
import icon_pc_outlined from '@/assets/svg/icon_pc_outlined.svg'
import { ref, onMounted, unref, onBeforeUnmount, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import MobileBackgroundSelector from './MobileBackgroundSelector.vue'
import ComponentWrapper from '@/components/data-visualization/canvas/ComponentWrapper.vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { useEmbedded } from '@/store/modules/embedded'
import { canvasSave, findComponentById } from '@/utils/canvasUtils'
import { useEmitt } from '@/hooks/web/useEmitt'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { backCanvasData } from '@/utils/canvasUtils'
import { storeToRefs } from 'pinia'
import { debounce } from 'lodash-es'
import mobileHeader from '@/assets/img/mobile-header.png'
import ComponentStyleEditor from '@/views/common/ComponentStyleEditor.vue'
import { deepCopy } from '@/utils/utils'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()

const dvMainStore = dvMainStoreWithOut()
const { componentData, canvasStyleData, canvasViewInfo, dvInfo } = storeToRefs(dvMainStore)
const mobileLoading = ref(true)
const mobileStyle = ref(null)
const emits = defineEmits(['pcMode'])
const snapshotStore = snapshotStoreWithOut()
const canvasViewInfoMobile = ref({})

const mobileStatusChange = (type, value) => {
  if (type === 'componentStyleChange') {
    changeTimes.value++
  }
  const iframe = document.querySelector('iframe')
  if (iframe) {
    iframe.contentWindow.postMessage(
      {
        type,
        value
      },
      '*'
    )
  }
}
const embeddedStore = useEmbedded()

const iframeSrc = computed(() => {
  return embeddedStore.baseUrl
    ? `${embeddedStore.baseUrl}mobile.html#/panel`
    : './mobile.html#/panel'
})
const handleLoad = () => {
  const mobileViewInfo = JSON.parse(JSON.stringify(unref(canvasViewInfo)))
  // 移动端初始化话
  if (!!mobileViewInfo) {
    Object.keys(mobileViewInfo).forEach(key => {
      const { customAttrMobile, customStyleMobile, customAttr, customStyle } = mobileViewInfo[key]
      mobileViewInfo[key]['customAttr'] = customAttrMobile || customAttr
      mobileViewInfo[key]['customStyle'] = customStyleMobile || customStyle
    })
  }
  canvasViewInfoMobile.value = mobileViewInfo
  mobileStatusChange(
    'panelInit',
    JSON.parse(
      JSON.stringify({
        componentData: JSON.parse(
          JSON.stringify(unref(componentData.value.filter(ele => !!ele.inMobile)))
        ),
        canvasStyleData: JSON.parse(JSON.stringify(unref(canvasStyleData))),
        canvasViewInfo: deepCopy(mobileViewInfo),
        dvInfo: JSON.parse(JSON.stringify(unref(dvInfo))),
        isEmbedded: !!embeddedStore.baseUrl
      })
    )
  )
}

const componentDataNotInMobile = computed(() => {
  return componentData.value.filter(ele => !ele.inMobile)
})

const newWindow = ref()

const hanedleMessage = event => {
  if (
    event.data?.msgOrigin === 'de-fit2cloud' &&
    !!embeddedStore.token &&
    !!embeddedStore.baseUrl
  ) {
    const params = {
      embeddedToken: embeddedStore.token
    }
    params['de-embedded'] = true
    const contentWindow = newWindow.value.contentWindow
    contentWindow.postMessage(params, '*')
    return
  }
  if (event.data.type === 'panelInit') {
    loadCanvasData()
  }

  if (event.data.type === 'curComponentChange') {
    // 移动端CurComponent引用不在主dvMain中
    dvMainStore.setCurComponentMobileConfig(event.data.value)
    if (!!event.data.value) {
      activeCollapse.value = 'componentStyle'
    } else {
      activeCollapse.value = 'com'
    }
  }

  if (event.data.type === 'delFromMobile') {
    changeTimes.value++
    componentData.value.some(ele => {
      if (ele.id === event.data.value) {
        ele.inMobile = false
        return true
      }
      return false
    })
  }

  if (event.data.type === 'syncPcDesign') {
    const targetComponent = findComponentById(event.data.value)
    if (targetComponent) {
      changeTimes.value++
      let targetViewInfo
      const sourceViewInfo = canvasViewInfo.value[targetComponent.id]
      if (sourceViewInfo) {
        targetViewInfo = deepCopy(sourceViewInfo)
        targetViewInfo.customStyleMobile = null
        targetViewInfo.customAttrMobile = null
        canvasViewInfoMobile.value[targetComponent.id] = targetViewInfo
      }
      snapshotStore.recordSnapshotCacheToMobile('syncPcDesign', targetComponent, targetViewInfo)
    }
  }

  if (['mobileSaveFromMobile', 'mobilePatchFromMobile'].includes(event.data.type)) {
    componentData.value.forEach(ele => {
      const com = event.data.value[ele.id]
      if (!!com) {
        const { x, y, sizeX, sizeY, style, propValue, events, commonBackground } = com
        ele.mx = x
        ele.my = y
        ele.mSizeX = sizeX
        ele.mSizeY = sizeY
        ele.mStyle = style
        ele.mEvents = events
        ele.mCommonBackground = commonBackground

        if (ele.component === 'VQuery') {
          ele.propValue = propValue
          ele.propValue?.forEach(queryItem => {
            queryItem['mPlaceholder'] = queryItem.placeholder
            queryItem['mQueryConditionWidth'] = queryItem.queryConditionWidth
            queryItem.placeholder = queryItem.tempPlaceholder
            queryItem.queryConditionWidth = queryItem.tempQueryConditionWidth
          })
        }
        if (ele.component === 'DeTabs') {
          ele.propValue.forEach(tabItem => {
            tabItem.componentData?.forEach(tabComponent => {
              const {
                style: tStyle,
                propValue: tPropValue,
                events: tEvents,
                commonBackground: tCommonBackground
              } = com.tab[tabComponent.id]
              tabComponent.mStyle = tStyle
              tabComponent.mEvents = tEvents
              tabComponent.mCommonBackground = tCommonBackground
            })
          })
        }
      }
    })
    // 将图表的修改信息还原
    if (!!canvasViewInfoMobile.value) {
      Object.keys(canvasViewInfoMobile.value).forEach(key => {
        const { customAttr, customStyle } = canvasViewInfoMobile.value[key]
        canvasViewInfo.value[key]['customAttrMobile'] = customAttr
        canvasViewInfo.value[key]['customStyleMobile'] = customStyle
      })
    }
  }
  if (event.data.type === 'mobileSaveFromMobile') {
    saveCanvasWithCheckFromMobile()
  }

  if (event.data.type === 'mobilePatchFromMobile') {
    emits('pcMode')
  }
}

const saveCanvasWithCheckFromMobile = () => {
  snapshotStore.resetStyleChangeTimes()
  canvasSave(() => {
    ElMessage.success(t('visualization.save_success'))
  })
}
const loadCanvasData = () => {
  handleLoad()
  mobileLoading.value = false
}

const setMobileStyle = debounce(() => {
  const height = window.innerHeight
  if (height > 1032) {
    mobileStyle.value = {
      transform: 'translateY(-50%)'
    }
    return
  }
  const scale = height / 1032
  mobileStyle.value = {
    transform: `scale(${scale}) translateY(-50%)`,
    transformOrigin: '0 0'
  }
}, 100)
const curComponentChangeHandle = () => {
  // do change
}
onMounted(() => {
  window.addEventListener('message', hanedleMessage)
  window.addEventListener('resize', setMobileStyle)
  dvMainStore.setMobileInPc(true)
  useEmitt({
    name: 'onMobileStatusChange',
    callback: ({ type, value }) => {
      mobileStatusChange(type, value)
    }
  })
  useEmitt({
    name: 'curComponentChange',
    callback: info => {
      curComponentChangeHandle(info)
    }
  })
  setMobileStyle()
})

onBeforeUnmount(() => {
  dvMainStore.setMobileInPc(false)
  window.removeEventListener('message', hanedleMessage)
  window.removeEventListener('resize', setMobileStyle)
})

const addToMobile = com => {
  if (mobileLoading.value) return
  com.inMobile = true
  changeTimes.value++
  mobileStatusChange('addToMobile', JSON.parse(JSON.stringify(unref(com))))
}

const changeTimes = ref(0)
const activeCollapse = ref('com')
const handleBack = () => {
  dvMainStore.setCurComponent({ component: null, index: null })
  if (!changeTimes.value) {
    mobileStatusChange('mobilePatch', undefined)
    return
  }
  ElMessageBox.confirm(t('visualization.change_save_tips'), {
    confirmButtonType: 'primary',
    type: 'warning',
    autofocus: false,
    showClose: false
  }).then(() => {
    setTimeout(() => {
      backCanvasData(dvInfo.value.id, canvasViewInfoMobile.value, 'dashboard', () => {
        changeTimes.value = 0
        emits('pcMode')
      })
    }, 100)
  })
}

const save = () => {
  changeTimes.value = 0
  mobileStatusChange('mobileSave', undefined)
}
</script>

<template>
  <div class="mobile-config-panel">
    <div class="top-bar">
      <div class="mobile-to-pc">
        <el-icon @click="handleBack">
          <Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
        </el-icon>
        {{ dvInfo.name }}
      </div>
      <div class="mobile-save">
        <span class="open-mobile">{{ t('common.openMobileTerminal') }}</span>
        <el-switch size="small" v-model="dvInfo.mobileLayout" />
        <span class="open-mobile-line"></span>
        <el-tooltip :offset="14" effect="dark" content="切换至PC端布局" placement="bottom">
          <el-icon @click="handleBack" class="switch-pc">
            <Icon name="icon_pc_outlined"><icon_pc_outlined class="svg-icon" /></Icon>
          </el-icon>
        </el-tooltip>
        <el-button type="primary" @click="save">保存</el-button>
      </div>
    </div>
    <div class="mobile-canvas" :style="mobileStyle" v-if="mobileStyle">
      <div class="mobile-header">
        <img :src="mobileHeader" alt="" srcset="" />
      </div>
      <div class="config-panel-title">
        <el-icon>
          <Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
        </el-icon>
        {{ dvInfo.name }}
      </div>
      <div class="config-panel-content" v-loading="mobileLoading">
        <iframe ref="newWindow" :src="iframeSrc" frameborder="0" width="375" />
      </div>
      <div class="config-panel-foot"></div>
    </div>
    <div class="mobile-com-list">
      <div class="config-mobile-sidebar">{{ t('visualization.mobile_config') }}</div>
      <el-tabs size="small" v-model="activeCollapse">
        <el-tab-pane :label="t('visualization.visualization_component')" name="com"> </el-tab-pane>
        <el-tab-pane :label="t('visualization.component_style')" name="componentStyle">
        </el-tab-pane>
        <el-tab-pane :label="t('visualization.whole_style')" name="style"> </el-tab-pane>
      </el-tabs>
      <template v-if="!mobileLoading">
        <div class="config-mobile-tab" v-show="activeCollapse === 'style'">
          <MobileBackgroundSelector @styleChange="changeTimes++"></MobileBackgroundSelector>
        </div>
        <div class="config-mobile-tab-style" v-show="activeCollapse === 'componentStyle'">
          <component-style-editor
            :canvas-view-info-mobile="canvasViewInfoMobile"
          ></component-style-editor>
        </div>
        <div class="config-mobile-tab" v-if="activeCollapse === 'com'">
          <div
            :style="{ height: '196px', width: '196px' }"
            class="mobile-wrapper-inner-adaptor"
            v-for="item in componentDataNotInMobile"
            :key="item.id"
          >
            <div class="component-outer">
              <ComponentWrapper
                v-show="item.isShow"
                canvas-id="canvas-main"
                :canvas-style-data="canvasStyleData"
                :dv-info="dvInfo"
                :canvas-view-info="canvasViewInfoMobile"
                :view-info="canvasViewInfoMobile[item.id]"
                :config="item"
                class="wrapper-design"
                show-position="preview"
                :search-count="0"
                :scale="65"
              />
            </div>
            <div class="mobile-com-mask" @click="addToMobile(item)">
              <span v-show="item.component === 'DeStreamMedia'" style="color: #909399">{{
                t('visualization.mobile_ios_tips')
              }}</span>
            </div>
            <div class="pc-select-to-mobile" @click="addToMobile(item)" v-if="!mobileLoading"></div>
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<style lang="less" scoped>
.mobile-config-panel {
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  position: relative;
  background: #f5f6f7;

  .top-bar {
    position: sticky;
    top: 0;
    left: 0;
    z-index: 10;
    background: #050e21;
    box-shadow: 0px 2px 4px 0px #1f23291f;
    height: 64px;
    padding: 0 24px 0 20px;
    display: flex;
    align-items: center;
    justify-content: space-between;

    .mobile-save {
      display: flex;
      align-items: center;
      .switch-pc {
        &::after {
          content: '';
          border-radius: 6px;
          display: none;
          position: absolute;
          width: calc(100% + 10px);
          height: calc(100% + 10px);
          top: -5px;
          left: -5px;
        }

        &:hover {
          &::after {
            display: block;
            background: rgba(255, 255, 255, 0.1);
          }
        }
        position: relative;
      }
      .open-mobile-line {
        background: #ffffff4d;
        width: 1px;
        height: 18px;
        margin: 0 20px;
      }

      .open-mobile {
        font-size: 14px;
        font-weight: 400;
        line-height: 22px;
        color: #ffffff99;
        margin-right: 8px;
      }
      .ed-icon {
        font-size: 20px;
        cursor: pointer;
        margin-right: 20px;
        color: #ffffff99;
      }
    }

    .mobile-to-pc {
      display: flex;
      align-items: center;
      font-size: 16px;
      font-weight: 400;
      line-height: 24px;
      color: #ffffffe5;
      .ed-icon {
        font-size: 20px;
        cursor: pointer;
        margin-right: 12px;
        color: #ffffffcc;
      }
    }
  }

  .mobile-canvas {
    border-radius: 30px;
    width: 419px;
    height: 852px;
    overflow: hidden;
    background-size: 100% 100% !important;
    position: absolute;
    top: calc(50% + 32px);
    left: calc(50% - 419px);
    background-image: url(../../assets/img/mobile-bg-pc.png);
    padding: 0 22px;

    .mobile-header {
      margin-top: 20px;
      height: 43px;
      display: flex;
      img {
        height: 100%;
        width: 100%;
      }
    }

    .config-panel-title {
      height: 44px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #fff;
      position: relative;
      border-top-right-radius: 4px;
      border-top-left-radius: 4px;

      .ed-icon {
        font-size: 20px;
        position: absolute;
        left: 16px;
        top: 12px;
      }
    }

    .config-panel-content {
      width: 100%;
      height: calc(100% - 127px);
      border-bottom-left-radius: 45px;
      border-bottom-right-radius: 45px;
      overflow: hidden;
      iframe {
        height: 100%;
      }
    }
  }

  .mobile-com-list {
    float: right;
    width: 420px;
    background-color: #fff;
    position: relative;
    border-left: rgba(31, 35, 41, 0.15) 1px solid;
    height: calc(100% - 64px);
    overflow-y: auto;
    :deep(.ed-collapse) {
      --ed-collapse-header-font-size: 12px;
      --ed-collapse-content-font-size: 12px;
    }

    & > :deep(.ed-tabs) {
      --ed-tabs-header-height: 36px;
      border-top: 1px solid #1f232926;
      position: sticky;
      top: 38px;
      left: 0;
      background: #fff;
      z-index: 25;
      .ed-tabs__header {
        padding-left: 8px;
        &::before {
          content: '';
          width: 8px;
          height: 1px;
          position: absolute;
          bottom: 0;
          left: 0;
          background: #1f232926;
        }
      }
    }

    :deep(.ed-tabs__item) {
      font-size: 12px;
    }

    :deep(.ed-tabs__item):not(.is-active) {
      color: #646a73;
    }

    .config-mobile-sidebar {
      font-size: 14px;
      font-weight: 500;
      line-height: 22px;
      padding: 8px;
      position: sticky;
      top: 0;
      left: 0;
      background: #fff;
      z-index: 25;
    }

    .config-mobile-tab {
      padding: 16px 0;
    }
    .config-mobile-tab-style {
      padding: 0;
      overflow-y: auto;
      overflow-x: hidden;
      ::v-deep(.editor-light) {
        border-left: none !important;
      }
    }
    .mobile-wrapper-inner-adaptor {
      position: relative;
      margin-left: 8px;
      margin-bottom: 8px;
      float: left;
      background: #fff;
      padding: 4px;
      border-radius: 6px;
      border: 1px solid #dee0e3;
      &:nth-child(2n) {
        margin-right: -1px;
      }

      .component-outer {
        position: relative;
        width: 100%;
        height: 100%;
      }

      &:hover {
        border-color: var(--ed-color-primary-99, #3370ff99);
      }
    }

    .mobile-com-mask {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      z-index: 10;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .pc-select-to-mobile {
      position: absolute;
      width: 16px;
      height: 16px;
      top: 12px;
      right: 12px;
      border: 2px solid #8f959e;
      border-radius: 6px;
      z-index: 24;
      cursor: pointer;
      &:hover {
        border-color: var(--ed-color-primary-99, #3370ff99);
      }
    }
  }
}

.wrapper-design {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</style>

<style lang="less">
.mobile-config-panel {
  .title-container {
    width: 90% !important;
  }
}
</style>