Skip to content

Commit

Permalink
feat: add canvas route bar (#967)
Browse files Browse the repository at this point in the history
* feat: add route link bar above the canvas

* feat: route bar support switch route

* add TODO

* change css variables

* fix: fix switch page error after create one

* feat: support select router page on RouterLink

* feat: RouterLink support navigate by right-click menu operation in canvas

* feat: navigation operation place to first

* feat: support setting default page

* fix: hide route bar in block canvas

* fix: hide route bar in block canvas

* feat: add confirm modal before switch page

* fix: remove TODO comment

* fix: Modified based on review comments

* fix some issues

* fix wrong type of route id
  • Loading branch information
gene9831 authored Jan 7, 2025
1 parent 170fd41 commit 3bf93ff
Show file tree
Hide file tree
Showing 13 changed files with 488 additions and 99 deletions.
9 changes: 8 additions & 1 deletion packages/canvas/DesignCanvas/src/DesignCanvas.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<component :is="CanvasLayout">
<template #header>
<component v-if="!isBlock()" :is="CanvasRouteBar"></component>
</template>
<template #container>
<component
:is="CanvasContainer.entry"
Expand Down Expand Up @@ -52,7 +55,7 @@ export default {
setup() {
const registry = getMergeRegistry('canvas')
const materialsPanel = getMergeMeta('engine.plugins.materials')?.entry
const { CanvasBreadcrumb } = registry.components
const { CanvasRouteBar, CanvasBreadcrumb } = registry.components
const CanvasLayout = registry.layout.entry
const [CanvasContainer] = registry.metas
const footData = ref([])
Expand All @@ -74,6 +77,8 @@ export default {
pageState.properties = null
}
const isBlock = useCanvas().isBlock
watch(
[() => useCanvas().isSaved(), () => useLayout().layoutState.pageStatus, () => useCanvas().getPageSchema()],
([isSaved, pageStatus, pageSchema], [oldIsSaved, _oldPageStatus, oldPageSchema]) => {
Expand Down Expand Up @@ -221,9 +226,11 @@ export default {
addHistoryDataChangedCallback,
ast
},
isBlock,
CanvasLayout,
canvasRef,
CanvasContainer,
CanvasRouteBar,
CanvasBreadcrumb
}
}
Expand Down
38 changes: 33 additions & 5 deletions packages/canvas/container/src/components/CanvasMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div v-show="menuState.show" ref="menuDom" class="context-menu" :style="menuState.position">
<ul class="menu-item">
<li
v-for="(item, index) in menus"
v-for="(item, index) in filteredMenus"
:key="index"
:class="{
'li-item': item.items,
Expand Down Expand Up @@ -32,9 +32,9 @@
</template>

<script lang="jsx">
import { ref, reactive, nextTick } from 'vue'
import { ref, reactive, nextTick, computed } from 'vue'
import { canvasState, getConfigure, getController, getCurrent, copyNode, removeNodeById } from '../container'
import { useLayout, useModal, useCanvas, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { useLayout, useModal, useCanvas, usePage, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { iconRight } from '@opentiny/vue-icon'
const menuState = reactive({
Expand Down Expand Up @@ -121,6 +121,25 @@ export default {
menus.value.push({ name: '新建区块', code: 'createBlock' })
}
menus.value.unshift({
name: '路由跳转',
code: 'route',
show: () => getCurrent()?.schema?.componentName === 'RouterLink',
check: () => {
const targetPageId = getCurrent().schema.props?.to?.name
return typeof targetPageId === 'number' || targetPageId
}
})
const filteredMenus = computed(() =>
menus.value.filter((item) => {
if (typeof item.show === 'function') {
return item.show()
}
return true
})
)
const boxVisibility = ref(false)
// 计算上下文菜单位置,右键时显示,否则关闭
Expand Down Expand Up @@ -205,10 +224,19 @@ export default {
status: 'error'
})
}
},
route() {
// check中验证过了 targetPageId 是有效值
const targetPageId = getCurrent().schema.props.to.name
usePage().switchPageWithConfirm(targetPageId)
}
}
const actionDisabled = (actionItem) => {
if (typeof actionItem.check === 'function' && !actionItem.check()) {
return true
}
const actions = ['del', 'copy', 'addParent']
return actions.includes(actionItem.code) && !getCurrent().schema?.id
}
Expand All @@ -221,7 +249,7 @@ export default {
boxVisibility.value = false
}
const doOperation = (item) => {
if ((item.check && !item.check?.()) || actionDisabled(item)) {
if (actionDisabled(item)) {
return
}
Expand All @@ -234,7 +262,7 @@ export default {
return {
SaveNewBlock,
menuState,
menus,
filteredMenus,
doOperation,
boxVisibility,
close,
Expand Down
2 changes: 2 additions & 0 deletions packages/canvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { CanvasRouteBar } from './route-bar'
import { CanvasBreadcrumb } from './breadcrumb'

// meta app
Expand All @@ -21,6 +22,7 @@ export { CanvasContainer, CanvasLayout, DesignCanvas }
export default {
...DesignCanvas,
components: {
CanvasRouteBar,
CanvasBreadcrumb
},
layout: CanvasLayout,
Expand Down
18 changes: 13 additions & 5 deletions packages/canvas/layout/src/CanvasLayout.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div id="canvas-wrap" ref="canvasRef">
<slot name="header"></slot>
<div ref="siteCanvas" class="site-canvas" :style="siteCanvasStyle">
<slot name="container"></slot>
</div>
Expand All @@ -8,13 +9,20 @@
</template>
<script setup>
import { computed } from 'vue'
import { useLayout } from '@opentiny/tiny-engine-meta-register'
import { useCanvas, useLayout } from '@opentiny/tiny-engine-meta-register'
const ROUTE_BAR_HEIGHT = 32
const { isBlock } = useCanvas()
const dimension = useLayout().getDimension()
const siteCanvasStyle = computed(() => {
const { scale } = useLayout().getDimension()
const { scale } = dimension
const routeBarHeight = isBlock() ? 0 : ROUTE_BAR_HEIGHT
return {
height: `calc((100% - var(--base-bottom-panel-height, 30px) - 36px) / ${scale})`,
transform: `scale(${scale})`
height: `calc((100% - var(--base-bottom-panel-height, 30px) - ${36 + routeBarHeight}px) / ${scale})`,
transform: `scale(${scale})`,
marginTop: `${18 + routeBarHeight}px`
}
})
</script>
Expand All @@ -32,7 +40,7 @@ const siteCanvasStyle = computed(() => {
background: var(--ti-lowcode-breadcrumb-hover-bg);
position: absolute;
overflow: hidden;
margin: 18px 0;
margin-bottom: 18px;
transform-origin: top;
}
}
Expand Down
29 changes: 21 additions & 8 deletions packages/canvas/render/src/builtin/CanvasRouterLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</span>
</template>
<script lang="ts">
import { computed, inject } from 'vue'
import { computed, inject, PropType, Ref } from 'vue'
export default {
props: {
activeClass: {
Expand All @@ -22,17 +22,30 @@ export default {
default: ''
},
to: {
type: String
// TODO: 待改成页面选择器
// type: Object as PropType<{
// pageId: string
// }>
// TODO: 支持绝对路径,类型为String
type: Object as PropType<{
name: string
}>
}
},
setup(props) {
const pageAncestor = (inject('page-ancestors') as Ref<string[] | null>).value
const active = computed(() => pageAncestor?.length && pageAncestor.indexOf(props.to) > -1)
const exactActive = computed(() => pageAncestor?.length && props.to === pageAncestor[pageAncestor.length - 1])
const active = computed(() => {
if (!Array.isArray(pageAncestor) || !props.to?.name) {
return false
}
return pageAncestor.includes(props.to.name)
})
const exactActive = computed(() => {
if (!Array.isArray(pageAncestor) || !props.to?.name) {
return false
}
return props.to.name === pageAncestor[pageAncestor.length - 1]
})
return {
active,
exactActive
Expand Down
6 changes: 2 additions & 4 deletions packages/canvas/render/src/builtin/builtin.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
},
"cols": 12,
"widget": {
"component": "InputConfigurator",
"component": "RouterSelectConfigurator",
"props": {}
}
},
Expand Down Expand Up @@ -630,9 +630,7 @@
"icon": "RouterLink",
"schema": {
"componentName": "RouterLink",
"props": {
"to": ""
},
"props": {},
"children": [
{
"componentName": "Text",
Expand Down
1 change: 1 addition & 0 deletions packages/canvas/route-bar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as CanvasRouteBar } from './src/CanvasRouteBar.vue'
121 changes: 121 additions & 0 deletions packages/canvas/route-bar/src/CanvasRouteBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<template>
<div id="canvas-route-bar" :style="sizeStyle">
<div class="address-bar">
<template v-for="route in routes" :key="route.id">
<span class="slash">/</span>
<span :class="[{ route: route.isPage && route.id !== pageId }]" @click="handleClickRoute(route)">{{
route.route
}}</span>
</template>
</div>
</div>
</template>

<script setup>
import { getMetaApi, META_SERVICE, useLayout, useMessage, usePage } from '@opentiny/tiny-engine-meta-register'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
const sizeStyle = computed(() => {
const { width } = useLayout().getDimension()
return { width }
})
const { pageSettingState, getAncestors, switchPageWithConfirm } = usePage()
const pageId = ref(getMetaApi(META_SERVICE.GlobalService).getBaseInfo().pageId)
const { subscribe, unsubscribe } = useMessage()
let subscriber = null
onMounted(() => {
subscriber = subscribe({
topic: 'locationHistoryChanged',
callback: (data) => {
if (data.pageId) {
pageId.value = data.pageId
}
},
subscriber: 'routeBar'
})
})
onUnmounted(() => {
if (subscriber) {
unsubscribe(subscriber)
}
})
/**
* @typedef {Object} Route
* @property {string | number} id
* @property {string} route
* @property {boolean} isPage
*/
/** @type {import('vue').Ref<Route[]>} */
const routes = ref([])
watch(
pageId,
async (value) => {
if (!value) {
routes.value = []
return
}
const ancestors = await getAncestors(value, true)
routes.value = ancestors.concat(value).map((id) => {
const { route, isPage } = pageSettingState.treeDataMapping[id]
return {
id,
route: route
.replace(/\/+/g, '/') // 替换连续的 '/' 为单个 '/'
.replace(/^\/|\/$/g, ''), // 去掉开头和结尾的 '/'
isPage
}
})
},
{ immediate: true }
)
/**
* @param route {Route}
*/
const handleClickRoute = (route) => {
switchPageWithConfirm(route.id)
}
</script>

<style lang="less" scoped>
#canvas-route-bar {
position: absolute;
top: 18px;
height: 32px;
max-width: 100%;
background-color: var(--te-common-bg-prompt);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
display: flex;
align-items: center;
padding: 0 8px;
}
.address-bar {
display: flex;
align-items: center;
gap: 2px;
background-color: var(--te-common-bg-container);
height: 20px;
width: 100%;
border-radius: 999px;
padding: 0 10px;
cursor: default;
}
.route {
cursor: pointer;
&:hover {
text-decoration: underline;
color: var(--te-common-text-link);
}
}
</style>
2 changes: 2 additions & 0 deletions packages/configurator/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import RadioConfigurator from './radio-configurator/RadioConfigurator.vue'
import RadioGroupConfigurator from './radio-group-configurator/RadioGroupConfigurator.vue'
import RelatedColumnsConfigurator from './related-columns-configurator/RelatedColumnsConfigurator.vue'
import RelatedEditorConfigurator from './related-editor-configurator/RelatedEditorConfigurator.vue'
import RouterSelectConfigurator from './router-select-configurator/RouterSelectConfigurator.vue'
import SelectConfigurator from './select-configurator/SelectConfigurator.vue'
import SelectIconConfigurator from './select-icon-configurator/SelectIconConfigurator.vue'
import SliderConfigurator from './slider-configurator/SliderConfigurator.vue'
Expand Down Expand Up @@ -54,6 +55,7 @@ export {
RadioGroupConfigurator,
RelatedColumnsConfigurator,
RelatedEditorConfigurator,
RouterSelectConfigurator,
SelectConfigurator,
SelectIconConfigurator,
SliderConfigurator,
Expand Down
Loading

0 comments on commit 3bf93ff

Please sign in to comment.