forked from github/dataease
feat: 移动端
This commit is contained in:
parent
916a7ec456
commit
0b74a8ec36
13
core/core-frontend/mobile.html
Normal file
13
core/core-frontend/mobile.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>DataEase</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/pages/mobile/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -42,6 +42,7 @@
|
|||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"snowflake-id": "^1.1.0",
|
"snowflake-id": "^1.1.0",
|
||||||
"tinymce": "^5.8.2",
|
"tinymce": "^5.8.2",
|
||||||
|
"vant": "^4.8.2",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
|
@ -182,6 +182,10 @@ const multiplexingCanvasOpen = () => {
|
|||||||
multiplexingRef.value.dialogInit()
|
multiplexingRef.value.dialogInit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mobileConfig = () => {
|
||||||
|
useEmitt().emitter.emit('mobileConfig')
|
||||||
|
}
|
||||||
|
|
||||||
eventBus.on('preview', previewInner)
|
eventBus.on('preview', previewInner)
|
||||||
eventBus.on('save', saveCanvasWithCheck)
|
eventBus.on('save', saveCanvasWithCheck)
|
||||||
eventBus.on('clearCanvas', clearCanvas)
|
eventBus.on('clearCanvas', clearCanvas)
|
||||||
@ -417,6 +421,12 @@ onMounted(() => {
|
|||||||
is-label
|
is-label
|
||||||
@customClick="multiplexingCanvasOpen"
|
@customClick="multiplexingCanvasOpen"
|
||||||
></component-button-label>
|
></component-button-label>
|
||||||
|
<component-button-label
|
||||||
|
icon-name="icon_copy_filled"
|
||||||
|
title="移动端"
|
||||||
|
is-label
|
||||||
|
@customClick="mobileConfig"
|
||||||
|
></component-button-label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="bar-main"
|
class="bar-main"
|
||||||
|
v-if="!mobileInPc"
|
||||||
:class="[
|
:class="[
|
||||||
showEditPosition,
|
showEditPosition,
|
||||||
{
|
{
|
||||||
@ -249,7 +250,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { element, index, showPosition, canvasId } = toRefs(props)
|
const { element, index, showPosition, canvasId } = toRefs(props)
|
||||||
const { batchOptStatus, pcMatrixCount, curComponent, componentData, canvasViewInfo } =
|
const { batchOptStatus, pcMatrixCount, curComponent, componentData, canvasViewInfo, mobileInPc } =
|
||||||
storeToRefs(dvMainStore)
|
storeToRefs(dvMainStore)
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
@ -118,7 +118,7 @@ import DeCustomTab from '@/custom-component/de-tabs/DeCustomTab.vue'
|
|||||||
import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
|
import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
|
||||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
const dvMainStore = dvMainStoreWithOut()
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
const { tabMoveInActiveId, bashMatrixInfo, editMode } = storeToRefs(dvMainStore)
|
const { tabMoveInActiveId, bashMatrixInfo, editMode, mobileInPc } = storeToRefs(dvMainStore)
|
||||||
const tabComponentRef = ref(null)
|
const tabComponentRef = ref(null)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -168,7 +168,7 @@ const editableTabsValue = ref(null)
|
|||||||
const noBorderColor = ref('none')
|
const noBorderColor = ref('none')
|
||||||
let currentInstance
|
let currentInstance
|
||||||
|
|
||||||
const isEditMode = computed(() => editMode.value === 'edit' && isEdit.value)
|
const isEditMode = computed(() => editMode.value === 'edit' && isEdit.value && !mobileInPc.value)
|
||||||
|
|
||||||
const calcTabLength = () => {
|
const calcTabLength = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -53,7 +53,7 @@ const props = defineProps({
|
|||||||
const { element, view, scale } = toRefs(props)
|
const { element, view, scale } = toRefs(props)
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const dvMainStore = dvMainStoreWithOut()
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
const { curComponent, canvasViewInfo } = storeToRefs(dvMainStore)
|
const { curComponent, canvasViewInfo, mobileInPc } = storeToRefs(dvMainStore)
|
||||||
const canEdit = ref(false)
|
const canEdit = ref(false)
|
||||||
const queryConfig = ref()
|
const queryConfig = ref()
|
||||||
const defaultStyle = {
|
const defaultStyle = {
|
||||||
@ -346,7 +346,11 @@ const autoStyle = computed(() => {
|
|||||||
<div v-if="!listVisible.length" class="no-list-label flex-align-center">
|
<div v-if="!listVisible.length" class="no-list-label flex-align-center">
|
||||||
<div class="container flex-align-center">
|
<div class="container flex-align-center">
|
||||||
将右侧的字段拖拽到这里 或 点击
|
将右侧的字段拖拽到这里 或 点击
|
||||||
<el-button :disabled="showPosition === 'preview'" @click="addCriteriaConfigOut" text>
|
<el-button
|
||||||
|
:disabled="showPosition === 'preview' || mobileInPc"
|
||||||
|
@click="addCriteriaConfigOut"
|
||||||
|
text
|
||||||
|
>
|
||||||
添加查询条件
|
添加查询条件
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
8
core/core-frontend/src/pages/mobile/App.vue
Normal file
8
core/core-frontend/src/pages/mobile/App.vue
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import configGlobal from '@/components/config-global/src/ConfigGlobal.vue'
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<config-global>
|
||||||
|
<router-view />
|
||||||
|
</config-global>
|
||||||
|
</template>
|
28
core/core-frontend/src/pages/mobile/main.ts
Normal file
28
core/core-frontend/src/pages/mobile/main.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import '../../assets/font/index.css'
|
||||||
|
import '@/style/index.less'
|
||||||
|
import '@/plugins/svg-icon'
|
||||||
|
import 'normalize.css/normalize.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { setupI18n } from '@/plugins/vue-i18n'
|
||||||
|
import { setupStore } from '@/store'
|
||||||
|
import { setupRouter } from '@/router/mobile'
|
||||||
|
import { setupElementPlus, setupElementPlusIcons } from '@/plugins/element-plus'
|
||||||
|
// 注册数据大屏组件
|
||||||
|
import { setupCustomComponent } from '@/custom-component'
|
||||||
|
import { installDirective } from '@/directive'
|
||||||
|
import '@/utils/DateUtil'
|
||||||
|
import '@/permissionMobile'
|
||||||
|
const setupAll = async () => {
|
||||||
|
const app = createApp(App)
|
||||||
|
installDirective(app)
|
||||||
|
await setupI18n(app)
|
||||||
|
setupStore(app)
|
||||||
|
setupRouter(app)
|
||||||
|
setupElementPlus(app)
|
||||||
|
setupCustomComponent(app)
|
||||||
|
setupElementPlusIcons(app)
|
||||||
|
app.mount('#app')
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAll()
|
44
core/core-frontend/src/permissionMobile.ts
Normal file
44
core/core-frontend/src/permissionMobile.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import router from './router/mobile'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||||
|
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { getRoleRouters } from '@/api/common'
|
||||||
|
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||||
|
import { interactiveStoreWithOut } from '@/store/modules/interactive'
|
||||||
|
const permissionStore = usePermissionStoreWithOut()
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
|
||||||
|
const { start, done } = useNProgress()
|
||||||
|
const interactiveStore = interactiveStoreWithOut()
|
||||||
|
|
||||||
|
const { loadStart, loadDone } = usePageLoading()
|
||||||
|
|
||||||
|
router.beforeEach(async (to, _, next) => {
|
||||||
|
start()
|
||||||
|
loadStart()
|
||||||
|
if (wsCache.get('user.token')) {
|
||||||
|
if (!userStore.getUid) {
|
||||||
|
await userStore.setUser()
|
||||||
|
}
|
||||||
|
if (to.path === '/login') {
|
||||||
|
next({ path: '/index' })
|
||||||
|
} else {
|
||||||
|
const roleRouters = (await getRoleRouters()) || []
|
||||||
|
const routers: any[] = roleRouters as AppCustomRouteRecordRaw[]
|
||||||
|
routers.forEach(item => (item['top'] = true))
|
||||||
|
await permissionStore.generateRoutes(routers as AppCustomRouteRecordRaw[])
|
||||||
|
permissionStore.setIsAddRouters(true)
|
||||||
|
await interactiveStore.initInteractive(true)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next('/login') // 否则全部重定向到登录页
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
done()
|
||||||
|
loadDone()
|
||||||
|
})
|
45
core/core-frontend/src/router/mobile.ts
Normal file
45
core/core-frontend/src/router/mobile.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
|
export const routes: AppRouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: '/',
|
||||||
|
redirect: '/index',
|
||||||
|
hidden: true,
|
||||||
|
meta: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/index',
|
||||||
|
name: 'index',
|
||||||
|
component: () => import('@/views/mobile/index.vue'),
|
||||||
|
hidden: true,
|
||||||
|
meta: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
hidden: true,
|
||||||
|
meta: {},
|
||||||
|
component: () => import('@/views/mobile/login/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/panel',
|
||||||
|
name: 'panel',
|
||||||
|
hidden: true,
|
||||||
|
meta: {},
|
||||||
|
component: () => import('@/views/mobile/panel/index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: routes as RouteRecordRaw[]
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setupRouter = (app: App<Element>) => {
|
||||||
|
app.use(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router
|
@ -29,6 +29,7 @@ export const dvMainStore = defineStore('dataVisualization', {
|
|||||||
datasetAreaCollapse: false
|
datasetAreaCollapse: false
|
||||||
},
|
},
|
||||||
editMode: 'edit', // 编辑器模式 edit preview
|
editMode: 'edit', // 编辑器模式 edit preview
|
||||||
|
mobileInPc: false,
|
||||||
canvasStyleData: { ...deepCopy(DEFAULT_CANVAS_STYLE_DATA_DARK), backgroundColor: null },
|
canvasStyleData: { ...deepCopy(DEFAULT_CANVAS_STYLE_DATA_DARK), backgroundColor: null },
|
||||||
// 当前展示画布缓存数据
|
// 当前展示画布缓存数据
|
||||||
componentDataCache: null,
|
componentDataCache: null,
|
||||||
@ -196,6 +197,9 @@ export const dvMainStore = defineStore('dataVisualization', {
|
|||||||
setEditMode(mode) {
|
setEditMode(mode) {
|
||||||
this.editMode = mode
|
this.editMode = mode
|
||||||
},
|
},
|
||||||
|
setMobileInPc(mobileInPc) {
|
||||||
|
this.mobileInPc = mobileInPc
|
||||||
|
},
|
||||||
|
|
||||||
setInEditorStatus(status) {
|
setInEditorStatus(status) {
|
||||||
this.isInEditor = status
|
this.isInEditor = status
|
||||||
|
@ -104,6 +104,7 @@ const hasCurrentRouter = (locations, routers, index) => {
|
|||||||
kids = router.children
|
kids = router.children
|
||||||
return router.path === location || '/' + location === router.path
|
return router.path === location || '/' + location === router.path
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isvalid && index < locations.length - 1) {
|
if (isvalid && index < locations.length - 1) {
|
||||||
return hasCurrentRouter(locations, kids, index + 1)
|
return hasCurrentRouter(locations, kids, index + 1)
|
||||||
}
|
}
|
||||||
|
205
core/core-frontend/src/views/dashboard/MobileConfigPanel.vue
Normal file
205
core/core-frontend/src/views/dashboard/MobileConfigPanel.vue
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, unref, onBeforeUnmount, computed } from 'vue'
|
||||||
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { getStyle } from '@/utils/style'
|
||||||
|
import findComponent from '@/utils/components'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const { componentData, canvasStyleData, canvasViewInfo, dvInfo } = storeToRefs(dvMainStore)
|
||||||
|
const mobileLoading = ref(true)
|
||||||
|
const activeName = ref('hide')
|
||||||
|
|
||||||
|
const emits = defineEmits(['pcMode'])
|
||||||
|
|
||||||
|
const getComponentStyleDefault = style => {
|
||||||
|
return getStyle(style, ['top', 'left', 'width', 'height', 'rotate'])
|
||||||
|
}
|
||||||
|
const mobileStatusChange = (type, value) => {
|
||||||
|
const iframe = document.querySelector('iframe')
|
||||||
|
if (iframe) {
|
||||||
|
iframe.contentWindow.postMessage(
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
value
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLoad = () => {
|
||||||
|
mobileLoading.value = false
|
||||||
|
console.log('dvInfo', JSON.parse(JSON.stringify(unref(dvInfo))))
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
mobileStatusChange(
|
||||||
|
'panelInit',
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify({
|
||||||
|
componentData: JSON.parse(JSON.stringify(unref(componentData))),
|
||||||
|
canvasStyleData: JSON.parse(JSON.stringify(unref(canvasStyleData))),
|
||||||
|
canvasViewInfo: JSON.parse(JSON.stringify(unref(canvasViewInfo))),
|
||||||
|
dvInfo: JSON.parse(JSON.stringify(unref(dvInfo)))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}, 800)
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentDataNotInMobile = computed(() => {
|
||||||
|
return componentData.value.filter(ele => !ele.inMobile)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
dvMainStore.setMobileInPc(true)
|
||||||
|
useEmitt({
|
||||||
|
name: 'onMobileStatusChange',
|
||||||
|
callback: (type, value) => {
|
||||||
|
mobileStatusChange(type, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
dvMainStore.setMobileInPc(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
const addToMobile = com => {
|
||||||
|
com.inMobile = true
|
||||||
|
mobileStatusChange('addToMobile', JSON.parse(JSON.stringify(unref(com))))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile-config-panel">
|
||||||
|
<div class="mobile-to-pc">
|
||||||
|
<el-icon class="custom-el-icon back-icon" @click="emits('pcMode')">
|
||||||
|
<Icon class="toolbar-icon" name="icon_left_outlined" />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-canvas">
|
||||||
|
<div class="config-panel-title">{{ dvInfo.name }}</div>
|
||||||
|
<div class="config-panel-content" v-loading="mobileLoading">
|
||||||
|
<iframe
|
||||||
|
src="./mobile.html#/panel"
|
||||||
|
frameborder="0"
|
||||||
|
width="360"
|
||||||
|
height="570"
|
||||||
|
@load="handleLoad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="config-panel-foot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-com-list">
|
||||||
|
<el-tabs v-model="activeName">
|
||||||
|
<el-tab-pane label="隐藏的组件" name="hide" />
|
||||||
|
<el-tab-pane label="样式设置" name="style" />
|
||||||
|
</el-tabs>
|
||||||
|
<template v-if="activeName === 'hide'">
|
||||||
|
<div
|
||||||
|
:style="{ height: '200px', width: '31%' }"
|
||||||
|
class="mobile-wrapper-inner-adaptor"
|
||||||
|
v-for="config in componentDataNotInMobile"
|
||||||
|
:key="config.id"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="findComponent(config['component'])"
|
||||||
|
ref="component"
|
||||||
|
class="component"
|
||||||
|
:view="canvasViewInfo[config.id]"
|
||||||
|
:canvas-style-data="canvasStyleData"
|
||||||
|
:dv-info="dvInfo"
|
||||||
|
:dv-type="dvInfo.type"
|
||||||
|
:canvas-view-info="canvasViewInfo"
|
||||||
|
:prop-value="config?.propValue"
|
||||||
|
:element="config"
|
||||||
|
:request="config?.request"
|
||||||
|
:style="getComponentStyleDefault(config?.style)"
|
||||||
|
:linkage="config?.linkage"
|
||||||
|
show-position="preview"
|
||||||
|
:disabled="true"
|
||||||
|
:is-edit="false"
|
||||||
|
/>
|
||||||
|
<div class="mobile-com-mask"></div>
|
||||||
|
|
||||||
|
<div class="pc-select-to-mobile" @click="addToMobile(config)"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.mobile-config-panel {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
.mobile-canvas {
|
||||||
|
border-radius: 30px;
|
||||||
|
width: 370px;
|
||||||
|
min-height: 600px;
|
||||||
|
max-height: 700px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #000;
|
||||||
|
background-size: 100% 100% !important;
|
||||||
|
padding: 5px;
|
||||||
|
height: 777px;
|
||||||
|
position: absolute;
|
||||||
|
top: 100px;
|
||||||
|
left: 100px;
|
||||||
|
|
||||||
|
.config-panel-title {
|
||||||
|
margin-top: 30px;
|
||||||
|
background: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-com-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 100px;
|
||||||
|
left: 500px;
|
||||||
|
width: calc(100% - 600px);
|
||||||
|
max-height: 700px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
background: #f5f6f7;
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
:deep(.ed-tabs) {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-wrapper-inner-adaptor {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-com-mask {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-select-to-mobile {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 11;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,6 +5,8 @@ import { storeToRefs } from 'pinia'
|
|||||||
import findComponent from '../../utils/components'
|
import findComponent from '../../utils/components'
|
||||||
import DvSidebar from '../../components/visualization/DvSidebar.vue'
|
import DvSidebar from '../../components/visualization/DvSidebar.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
import MobileConfigPanel from './MobileConfigPanel.vue'
|
||||||
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
import DbToolbar from '@/components/dashboard/DbToolbar.vue'
|
import DbToolbar from '@/components/dashboard/DbToolbar.vue'
|
||||||
import ViewEditor from '@/views/chart/components/editor/index.vue'
|
import ViewEditor from '@/views/chart/components/editor/index.vue'
|
||||||
import { getDatasetTree } from '@/api/dataset'
|
import { getDatasetTree } from '@/api/dataset'
|
||||||
@ -29,7 +31,6 @@ const eventCheck = e => {
|
|||||||
}
|
}
|
||||||
const dvMainStore = dvMainStoreWithOut()
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
const snapshotStore = snapshotStoreWithOut()
|
const snapshotStore = snapshotStoreWithOut()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
componentData,
|
componentData,
|
||||||
curComponent,
|
curComponent,
|
||||||
@ -68,8 +69,20 @@ const checkPer = async resourceId => {
|
|||||||
await interactiveStore.setInteractive(request)
|
await interactiveStore.setInteractive(request)
|
||||||
return check(wsCache.get('panel-weight'), resourceId, 4)
|
return check(wsCache.get('panel-weight'), resourceId, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mobileConfig = ref(false)
|
||||||
|
|
||||||
|
const onMobileConfig = () => {
|
||||||
|
mobileConfig.value = true
|
||||||
|
}
|
||||||
// 全局监听按键事件
|
// 全局监听按键事件
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
useEmitt({
|
||||||
|
name: 'mobileConfig',
|
||||||
|
callback: () => {
|
||||||
|
onMobileConfig()
|
||||||
|
}
|
||||||
|
})
|
||||||
window.addEventListener('storage', eventCheck)
|
window.addEventListener('storage', eventCheck)
|
||||||
const { resourceId, opt, pid, createType } = window.DataEaseBi || router.currentRoute.value.query
|
const { resourceId, opt, pid, createType } = window.DataEaseBi || router.currentRoute.value.query
|
||||||
const checkResult = await checkPer(resourceId)
|
const checkResult = await checkPer(resourceId)
|
||||||
@ -123,7 +136,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dv-common-layout dv-teleport-query">
|
<div class="dv-common-layout dv-teleport-query" v-if="!mobileConfig">
|
||||||
<DbToolbar />
|
<DbToolbar />
|
||||||
<el-container
|
<el-container
|
||||||
class="dv-layout-container"
|
class="dv-layout-container"
|
||||||
@ -189,6 +202,7 @@ onUnmounted(() => {
|
|||||||
</dv-sidebar>
|
</dv-sidebar>
|
||||||
</el-container>
|
</el-container>
|
||||||
</div>
|
</div>
|
||||||
|
<MobileConfigPanel @pcMode="mobileConfig = false" v-else></MobileConfigPanel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
117
core/core-frontend/src/views/mobile/directory/index.vue
Normal file
117
core/core-frontend/src/views/mobile/directory/index.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { BusiTreeRequest } from '@/models/tree/TreeNode'
|
||||||
|
import { interactiveStoreWithOut } from '@/store/modules/interactive'
|
||||||
|
import VanCell from 'vant/es/cell'
|
||||||
|
import VanSticky from 'vant/es/sticky'
|
||||||
|
import VanSearch from 'vant/es/search'
|
||||||
|
import VanCellGroup from 'vant/es/cell-group'
|
||||||
|
import VanNavBar from 'vant/es/nav-bar'
|
||||||
|
import 'vant/es/cell/style'
|
||||||
|
import 'vant/es/cell-group/style'
|
||||||
|
import 'vant/es/nav-bar/style'
|
||||||
|
import 'vant/es/sticky/style'
|
||||||
|
import 'vant/es/search/style'
|
||||||
|
const keywords = ref()
|
||||||
|
const anyManage = ref(false)
|
||||||
|
const rootManage = ref(false)
|
||||||
|
const tableData = ref([])
|
||||||
|
const directName = ref([])
|
||||||
|
const directId = ref([])
|
||||||
|
const activeDirectName = ref('')
|
||||||
|
const interactiveStore = interactiveStoreWithOut()
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const { dvInfo } = storeToRefs(dvMainStore)
|
||||||
|
|
||||||
|
const dfsTree = (ids, arr) => {
|
||||||
|
const id = ids.shift()
|
||||||
|
return arr.reduce((pre, ele) => {
|
||||||
|
if (id && ele.id === id) {
|
||||||
|
pre = ele.children
|
||||||
|
}
|
||||||
|
const children = dfsTree([...ids], ele.children || [])
|
||||||
|
if (children?.length) {
|
||||||
|
pre = children
|
||||||
|
}
|
||||||
|
return pre
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTableData = computed(() => {
|
||||||
|
return directId.value.length ? dfsTree([...directId.value], tableData.value) : tableData.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['hiddentabbar', 'setLoading'])
|
||||||
|
const onClickLeft = () => {
|
||||||
|
activeDirectName.value = directName.value.pop()
|
||||||
|
directId.value.pop()
|
||||||
|
if (!!directName.value.length) {
|
||||||
|
emits('hiddentabbar', false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataClick = val => {
|
||||||
|
directName.value.push(val.name)
|
||||||
|
activeDirectName.value = val.name
|
||||||
|
directId.value.push(val.id)
|
||||||
|
if (directName.value.length === 1) {
|
||||||
|
emits('hiddentabbar', true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTree = async () => {
|
||||||
|
const request = { busiFlag: 'dashboard' } as BusiTreeRequest
|
||||||
|
await interactiveStore.setInteractive(request)
|
||||||
|
const interactiveData = interactiveStore.getPanel
|
||||||
|
const nodeData = interactiveData.treeNodes
|
||||||
|
rootManage.value = interactiveData.rootManage
|
||||||
|
anyManage.value = interactiveData.anyManage
|
||||||
|
if (dvInfo.value && dvInfo.value.id && !JSON.stringify(nodeData).includes(dvInfo.value.id)) {
|
||||||
|
dvMainStore.resetDvInfo()
|
||||||
|
}
|
||||||
|
if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') {
|
||||||
|
tableData.value = nodeData[0]['children'] || []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableData.value = nodeData
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTree()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<van-sticky>
|
||||||
|
<van-search
|
||||||
|
v-if="!directName.length"
|
||||||
|
v-model="keywords"
|
||||||
|
shape="round"
|
||||||
|
placeholder="请输入仪表板名称"
|
||||||
|
/>
|
||||||
|
<van-nav-bar
|
||||||
|
v-else
|
||||||
|
:title="activeDirectName"
|
||||||
|
left-text="返回"
|
||||||
|
left-arrow
|
||||||
|
@click-left="onClickLeft"
|
||||||
|
/>
|
||||||
|
</van-sticky>
|
||||||
|
<van-cell-group>
|
||||||
|
<van-cell
|
||||||
|
v-for="ele in activeTableData"
|
||||||
|
:key="ele.id"
|
||||||
|
size="large"
|
||||||
|
@click="dataClick(ele)"
|
||||||
|
:title="ele.name"
|
||||||
|
:is-link="!ele.leaf"
|
||||||
|
:icon="ele.leaf ? 'bar-chart-o' : 'list-switch'"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
114
core/core-frontend/src/views/mobile/home/index.vue
Normal file
114
core/core-frontend/src/views/mobile/home/index.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, reactive } from 'vue'
|
||||||
|
import { interactiveStoreWithOut } from '@/store/modules/interactive'
|
||||||
|
import { shortcutOption } from '@/views/workbranch/ShortcutOption'
|
||||||
|
import { XpackComponent } from '@/components/plugin'
|
||||||
|
import VanTabs from 'vant/es/tabs'
|
||||||
|
import VanTab from 'vant/es/tab'
|
||||||
|
import VanCell from 'vant/es/cell'
|
||||||
|
import VanSticky from 'vant/es/sticky'
|
||||||
|
import VanCellGroup from 'vant/es/cell-group'
|
||||||
|
import 'vant/es/sticky/style'
|
||||||
|
import 'vant/es/tab/style'
|
||||||
|
import 'vant/es/tabs/style'
|
||||||
|
import 'vant/es/cell/style'
|
||||||
|
import 'vant/es/cell-group/style'
|
||||||
|
|
||||||
|
const activeTab = ref('recent')
|
||||||
|
const state = reactive({
|
||||||
|
tableData: [],
|
||||||
|
curTypeList: []
|
||||||
|
})
|
||||||
|
const interactiveStore = interactiveStoreWithOut()
|
||||||
|
|
||||||
|
const emits = defineEmits(['setLoading'])
|
||||||
|
const loadTableData = () => {
|
||||||
|
emits('setLoading', true)
|
||||||
|
shortcutOption
|
||||||
|
.loadData({ type: 'panel', keyword: '', asc: false })
|
||||||
|
.then(res => {
|
||||||
|
state.tableData = res.data
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
emits('setLoading', false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const tablePaneList = ref([
|
||||||
|
{ title: '最近使用', name: 'recent', disabled: false },
|
||||||
|
{ title: '我的收藏', name: 'store', disabled: false }
|
||||||
|
])
|
||||||
|
|
||||||
|
const panelLoad = paneInfo => {
|
||||||
|
tablePaneList.value.push({
|
||||||
|
title: paneInfo.title,
|
||||||
|
name: paneInfo.name,
|
||||||
|
disabled: tablePaneList.value[1].disabled
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const busiDataMap = computed(() => interactiveStore.getData)
|
||||||
|
|
||||||
|
const getBusiListWithPermission = () => {
|
||||||
|
const baseFlagList = ['panel', 'screen', 'dataset', 'datasource']
|
||||||
|
const busiFlagList: string[] = []
|
||||||
|
for (const key in busiDataMap.value) {
|
||||||
|
if (busiDataMap.value[key].menuAuth) {
|
||||||
|
busiFlagList.push(baseFlagList[parseInt(key)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tablePaneList.value[0].disabled = !busiFlagList?.length
|
||||||
|
tablePaneList.value[1].disabled =
|
||||||
|
!busiFlagList.includes('panel') && !busiFlagList.includes('screen')
|
||||||
|
return busiFlagList
|
||||||
|
}
|
||||||
|
|
||||||
|
const busiAuthList = getBusiListWithPermission()
|
||||||
|
|
||||||
|
const handleClick = ({ name, disabled }) => {
|
||||||
|
if (disabled) return
|
||||||
|
if (name === 'recent' || name === 'store') {
|
||||||
|
emits('setLoading', true)
|
||||||
|
shortcutOption.setBusiFlag(name)
|
||||||
|
loadTableData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
!!busiAuthList.length &&
|
||||||
|
handleClick({
|
||||||
|
name: 'recent',
|
||||||
|
disabled: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatterTime = val => {
|
||||||
|
return new Date(val).toLocaleString()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile-panel-list">
|
||||||
|
<van-sticky>
|
||||||
|
<van-tabs @click-tab="handleClick" v-model:active="activeTab">
|
||||||
|
<van-tab
|
||||||
|
v-for="item in tablePaneList"
|
||||||
|
:key="item.name"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:name="item.name"
|
||||||
|
:title="item.title"
|
||||||
|
></van-tab>
|
||||||
|
</van-tabs>
|
||||||
|
</van-sticky>
|
||||||
|
<van-cell-group>
|
||||||
|
<van-cell
|
||||||
|
v-for="ele in state.tableData"
|
||||||
|
:key="ele.id"
|
||||||
|
size="large"
|
||||||
|
:title="ele.name"
|
||||||
|
:value="formatterTime(ele.lastEditTime)"
|
||||||
|
icon="bar-chart-o"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
<div style="width: 100%; height: 50px"></div>
|
||||||
|
<XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" />
|
||||||
|
</div>
|
||||||
|
</template>
|
51
core/core-frontend/src/views/mobile/index.vue
Normal file
51
core/core-frontend/src/views/mobile/index.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Home from './home/index.vue'
|
||||||
|
import Directory from './directory/index.vue'
|
||||||
|
import VanTabbar from 'vant/es/tabbar'
|
||||||
|
import VanTabbarItem from 'vant/es/tabbar-item'
|
||||||
|
import VanCell from 'vant/es/cell'
|
||||||
|
import VanSticky from 'vant/es/sticky'
|
||||||
|
import VanOverlay from 'vant/es/overlay'
|
||||||
|
import VanLoading from 'vant/es/loading'
|
||||||
|
import VanCellGroup from 'vant/es/cell-group'
|
||||||
|
import 'vant/es/tabbar-item/style'
|
||||||
|
import 'vant/es/tabbar/style'
|
||||||
|
import 'vant/es/sticky/style'
|
||||||
|
import 'vant/es/overlay/style'
|
||||||
|
import 'vant/es/cell/style'
|
||||||
|
import 'vant/es/loading/style'
|
||||||
|
import 'vant/es/cell-group/style'
|
||||||
|
|
||||||
|
const activeTabbar = ref('home')
|
||||||
|
const showLoading = ref(false)
|
||||||
|
const hiddenTabbar = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile-index">
|
||||||
|
<Home v-if="activeTabbar === 'home'" @setLoading="val => (showLoading = val)"></Home>
|
||||||
|
<Directory
|
||||||
|
v-else-if="activeTabbar === 'direct'"
|
||||||
|
@setLoading="val => (showLoading = val)"
|
||||||
|
@hiddenTabbar="val => (hiddenTabbar = val)"
|
||||||
|
></Directory>
|
||||||
|
<van-tabbar v-if="!hiddenTabbar" v-model="activeTabbar">
|
||||||
|
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
|
||||||
|
<van-tabbar-item name="direct" icon="bars">目录</van-tabbar-item>
|
||||||
|
<van-tabbar-item name="user" icon="user-o">我的</van-tabbar-item>
|
||||||
|
</van-tabbar>
|
||||||
|
<van-overlay v-model:show="showLoading">
|
||||||
|
<div style="display: flex; align-items: center; justify-content: center; height: 100%">
|
||||||
|
<van-loading type="spinner" />
|
||||||
|
</div>
|
||||||
|
</van-overlay>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.mobile-index {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
9
core/core-frontend/src/views/mobile/login/index.vue
Normal file
9
core/core-frontend/src/views/mobile/login/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
33
core/core-frontend/src/views/mobile/panel/MobileInPc.vue
Normal file
33
core/core-frontend/src/views/mobile/panel/MobileInPc.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import DeCanvas from '@/views/canvas/DeCanvas.vue'
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
|
||||||
|
const { componentData, canvasStyleData, canvasViewInfo } = storeToRefs(dvMainStore)
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
canvasId: 'canvas-main'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dv-common-layout-mobile">
|
||||||
|
<de-canvas
|
||||||
|
style="overflow-x: hidden"
|
||||||
|
:canvas-id="state.canvasId"
|
||||||
|
:component-data="componentData"
|
||||||
|
:canvas-style-data="canvasStyleData"
|
||||||
|
:canvas-view-info="canvasViewInfo"
|
||||||
|
></de-canvas>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.dv-common-layout-mobile {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
56
core/core-frontend/src/views/mobile/panel/index.vue
Normal file
56
core/core-frontend/src/views/mobile/panel/index.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { onBeforeMount, ref, onBeforeUnmount } from 'vue'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
import DePreviewMobile from './MobileInPc.vue'
|
||||||
|
const panelInit = ref(false)
|
||||||
|
|
||||||
|
const checkItemPosition = component => {
|
||||||
|
component.x = 1
|
||||||
|
component.sizeX = 36
|
||||||
|
component.y = dvMainStore.componentData.reduce((pre, next) => {
|
||||||
|
return Math.max(pre, next.y + next.sizeY)
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hanedleMessage = event => {
|
||||||
|
if (event.data.type === 'panelInit') {
|
||||||
|
const { componentData, canvasStyleData, dvInfo, canvasViewInfo } = event.data.value
|
||||||
|
dvMainStore.setComponentData(componentData.filter(ele => !!ele.inMobile))
|
||||||
|
dvMainStore.setMobileInPc(true)
|
||||||
|
dvMainStore.setCanvasStyle(canvasStyleData)
|
||||||
|
dvMainStore.updateCurDvInfo(dvInfo)
|
||||||
|
dvMainStore.setCanvasViewInfo(canvasViewInfo)
|
||||||
|
panelInit.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.type === 'addToMobile') {
|
||||||
|
console.log('event.data.value', event.data.value)
|
||||||
|
const component = event.data.value
|
||||||
|
checkItemPosition(component)
|
||||||
|
dvMainStore.componentData.push(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
window.addEventListener('message', hanedleMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('message', hanedleMessage)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="panel-mobile">
|
||||||
|
<de-preview-mobile v-if="panelInit"></de-preview-mobile>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.panel-mobile {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
9
core/core-frontend/src/views/mobile/personal/index.vue
Normal file
9
core/core-frontend/src/views/mobile/personal/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
Loading…
Reference in New Issue
Block a user