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",
|
||||
"snowflake-id": "^1.1.0",
|
||||
"tinymce": "^5.8.2",
|
||||
"vant": "^4.8.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
|
@ -182,6 +182,10 @@ const multiplexingCanvasOpen = () => {
|
||||
multiplexingRef.value.dialogInit()
|
||||
}
|
||||
|
||||
const mobileConfig = () => {
|
||||
useEmitt().emitter.emit('mobileConfig')
|
||||
}
|
||||
|
||||
eventBus.on('preview', previewInner)
|
||||
eventBus.on('save', saveCanvasWithCheck)
|
||||
eventBus.on('clearCanvas', clearCanvas)
|
||||
@ -417,6 +421,12 @@ onMounted(() => {
|
||||
is-label
|
||||
@customClick="multiplexingCanvasOpen"
|
||||
></component-button-label>
|
||||
<component-button-label
|
||||
icon-name="icon_copy_filled"
|
||||
title="移动端"
|
||||
is-label
|
||||
@customClick="mobileConfig"
|
||||
></component-button-label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="bar-main"
|
||||
v-if="!mobileInPc"
|
||||
:class="[
|
||||
showEditPosition,
|
||||
{
|
||||
@ -249,7 +250,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const { element, index, showPosition, canvasId } = toRefs(props)
|
||||
const { batchOptStatus, pcMatrixCount, curComponent, componentData, canvasViewInfo } =
|
||||
const { batchOptStatus, pcMatrixCount, curComponent, componentData, canvasViewInfo, mobileInPc } =
|
||||
storeToRefs(dvMainStore)
|
||||
|
||||
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 { useEmitt } from '@/hooks/web/useEmitt'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { tabMoveInActiveId, bashMatrixInfo, editMode } = storeToRefs(dvMainStore)
|
||||
const { tabMoveInActiveId, bashMatrixInfo, editMode, mobileInPc } = storeToRefs(dvMainStore)
|
||||
const tabComponentRef = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
@ -168,7 +168,7 @@ const editableTabsValue = ref(null)
|
||||
const noBorderColor = ref('none')
|
||||
let currentInstance
|
||||
|
||||
const isEditMode = computed(() => editMode.value === 'edit' && isEdit.value)
|
||||
const isEditMode = computed(() => editMode.value === 'edit' && isEdit.value && !mobileInPc.value)
|
||||
|
||||
const calcTabLength = () => {
|
||||
setTimeout(() => {
|
||||
|
@ -53,7 +53,7 @@ const props = defineProps({
|
||||
const { element, view, scale } = toRefs(props)
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { curComponent, canvasViewInfo } = storeToRefs(dvMainStore)
|
||||
const { curComponent, canvasViewInfo, mobileInPc } = storeToRefs(dvMainStore)
|
||||
const canEdit = ref(false)
|
||||
const queryConfig = ref()
|
||||
const defaultStyle = {
|
||||
@ -346,7 +346,11 @@ const autoStyle = computed(() => {
|
||||
<div v-if="!listVisible.length" class="no-list-label 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>
|
||||
</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
|
||||
},
|
||||
editMode: 'edit', // 编辑器模式 edit preview
|
||||
mobileInPc: false,
|
||||
canvasStyleData: { ...deepCopy(DEFAULT_CANVAS_STYLE_DATA_DARK), backgroundColor: null },
|
||||
// 当前展示画布缓存数据
|
||||
componentDataCache: null,
|
||||
@ -196,6 +197,9 @@ export const dvMainStore = defineStore('dataVisualization', {
|
||||
setEditMode(mode) {
|
||||
this.editMode = mode
|
||||
},
|
||||
setMobileInPc(mobileInPc) {
|
||||
this.mobileInPc = mobileInPc
|
||||
},
|
||||
|
||||
setInEditorStatus(status) {
|
||||
this.isInEditor = status
|
||||
|
@ -104,6 +104,7 @@ const hasCurrentRouter = (locations, routers, index) => {
|
||||
kids = router.children
|
||||
return router.path === location || '/' + location === router.path
|
||||
})
|
||||
|
||||
if (isvalid && index < locations.length - 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 DvSidebar from '../../components/visualization/DvSidebar.vue'
|
||||
import router from '@/router'
|
||||
import MobileConfigPanel from './MobileConfigPanel.vue'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import DbToolbar from '@/components/dashboard/DbToolbar.vue'
|
||||
import ViewEditor from '@/views/chart/components/editor/index.vue'
|
||||
import { getDatasetTree } from '@/api/dataset'
|
||||
@ -29,7 +31,6 @@ const eventCheck = e => {
|
||||
}
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
|
||||
const {
|
||||
componentData,
|
||||
curComponent,
|
||||
@ -68,8 +69,20 @@ const checkPer = async resourceId => {
|
||||
await interactiveStore.setInteractive(request)
|
||||
return check(wsCache.get('panel-weight'), resourceId, 4)
|
||||
}
|
||||
|
||||
const mobileConfig = ref(false)
|
||||
|
||||
const onMobileConfig = () => {
|
||||
mobileConfig.value = true
|
||||
}
|
||||
// 全局监听按键事件
|
||||
onMounted(async () => {
|
||||
useEmitt({
|
||||
name: 'mobileConfig',
|
||||
callback: () => {
|
||||
onMobileConfig()
|
||||
}
|
||||
})
|
||||
window.addEventListener('storage', eventCheck)
|
||||
const { resourceId, opt, pid, createType } = window.DataEaseBi || router.currentRoute.value.query
|
||||
const checkResult = await checkPer(resourceId)
|
||||
@ -123,7 +136,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-common-layout dv-teleport-query">
|
||||
<div class="dv-common-layout dv-teleport-query" v-if="!mobileConfig">
|
||||
<DbToolbar />
|
||||
<el-container
|
||||
class="dv-layout-container"
|
||||
@ -189,6 +202,7 @@ onUnmounted(() => {
|
||||
</dv-sidebar>
|
||||
</el-container>
|
||||
</div>
|
||||
<MobileConfigPanel @pcMode="mobileConfig = false" v-else></MobileConfigPanel>
|
||||
</template>
|
||||
|
||||
<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