提交 6a2c6981 编写于 作者: aaronchen2k2k's avatar aaronchen2k2k

add context menu for tree component

上级 11583115
package service
import (
"errors"
configHelper "github.com/easysoft/zentaoatf/internal/comm/helper/config"
zentaoHelper "github.com/easysoft/zentaoatf/internal/comm/helper/zentao"
"github.com/easysoft/zentaoatf/internal/pkg/domain"
......@@ -8,6 +9,7 @@ import (
serverDomain "github.com/easysoft/zentaoatf/internal/server/modules/v1/domain"
"github.com/easysoft/zentaoatf/internal/server/modules/v1/model"
"github.com/easysoft/zentaoatf/internal/server/modules/v1/repo"
"regexp"
"strings"
)
......@@ -42,6 +44,12 @@ func (s *SiteService) GetDomainObject(id uint) (site serverDomain.ZentaoSite, er
}
func (s *SiteService) Create(site model.Site) (id uint, isDuplicate bool, err error) {
site.Url = fixSiteUlt(site.Url)
if site.Url == "" {
err = errors.New("url not right")
return
}
site.Url = fileUtils.AddUrlPathSepIfNeeded(site.Url)
config := configHelper.LoadBySite(site)
......@@ -56,6 +64,12 @@ func (s *SiteService) Create(site model.Site) (id uint, isDuplicate bool, err er
}
func (s *SiteService) Update(site model.Site) (isDuplicate bool, err error) {
site.Url = fixSiteUlt(site.Url)
if site.Url == "" {
err = errors.New("url not right")
return
}
site.Url = fileUtils.AddUrlPathSepIfNeeded(site.Url)
config := configHelper.LoadBySite(site)
......@@ -137,3 +151,22 @@ func (s *SiteService) CreateEmptySite(lang string) (err error) {
return
}
func fixSiteUlt(url string) (ret string) {
regx := regexp.MustCompile(`(http|https):\/\/.+`)
result := regx.FindStringSubmatch(url)
if result == nil {
return
}
regx = regexp.MustCompile(`[^:\/]\/`)
result = regx.FindStringSubmatch(url)
if result == nil { // without /
ret = url
} else {
index := strings.LastIndex(url, "/")
ret = url[:index+1]
}
return
}
......@@ -7,6 +7,7 @@
v-bind="_convertNodeData(data, undefined, 0)"
:childrenConverter="_convertNodeData"
@click="_handleClick"
@rightClick="emit('rightClick', $event)"
@toggle="_handleToggle"
@clickToolbar="emit('clickToolbar', $event)"
@check="_handleCheck"
......@@ -86,6 +87,7 @@ const emit = defineEmits<{
(type: 'collapse', event: {collapsed: Record<string, boolean>}) : void,
(type: 'check', event: {checked: Record<string, boolean | 'indeterminate'>}) : void,
(type: 'active', event: {activeID: string}) : void,
(type: 'rightClick', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
(type: 'clickToolbar', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
}>();
......
......@@ -6,6 +6,7 @@
@mouseenter="_handleMouseEnter"
@mouseleave="_handleMouseLeave"
@click.stop="emit('click', {node: props, event: $event})"
@contextmenu.prevent="emit('rightClick', {node: props, event: $event})"
>
<template v-if="children">
<Button v-if="isCollapsed" size="sm" :icon="collapsedIcon" :class="collapsedIconClass" :style="collapsedIconStyle" class="tree-node-toggle" @click="emit('toggle', {node: props, event: $event})" />
......@@ -19,10 +20,12 @@
size="sm"
@click="emit('check', {node: props, event: $event})"
/>
<div class="tree-node-icon">
<Icon v-if="icon" :icon="icon" :class="iconClass" :style="iconStyle" />
</div>
<div class="tree-node-title" :class="titleClass" :style="titleClass">{{title}}</div>
<Toolbar
v-if="toolbarItems && showToolbar"
class="tree-node-toolbar"
......@@ -37,6 +40,7 @@
v-bind="childrenConverter ? childrenConverter(child, props, index) : child"
:childrenConverter="childrenConverter"
@click="emit('click', {parent: props, ...$event})"
@rightClick="emit('rightClick', {parent: props, ...$event})"
@toggle="emit('toggle', {parent: props, ...$event})"
@clickToolbar="emit('clickToolbar', {parent: props, ...$event})"
@check="emit('check', {parent: props, ...$event})"
......@@ -100,6 +104,7 @@ const showToolbar = ref(!props.toolbarShowOnHover);
const emit = defineEmits<{
(type: 'click', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
(type: 'rightClick', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
(type: 'check', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
(type: 'toggle', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
(type: 'clickToolbar', event: {node: TreeNodeData, parent?: TreeNodeData, event: any}) : void,
......@@ -118,6 +123,7 @@ function _handleMouseEnter() {
function _handleMouseLeave() {
showToolbar.value = false;
}
</script>
<style scoped>
......
......@@ -216,8 +216,16 @@ export default {
'test_type_other': 'Other Automated or Unit Test',
'input_keyword_to_search': 'Input Keyword To Search',
'tips_test_cmd': 'Please run "{cmd}" with command line.',
'pls_add_zentao_site': 'Please add zentao site.',
'sync-from-zentao': 'Sync From Zentao',
'sync-to-zentao': 'Sync To Zentao',
'copy': 'Copy',
'cut': 'Cut',
'paste': 'Paste',
'open-in-explore': 'Open In Explore',
'open-in-terminal': 'Open In Terminal',
'pls_add_zentao_site': 'Please add zentao site.',
'pls_create_workspace': 'Please create workspace to continue.',
'pls_product': 'Please select product.',
'pls_lang': 'Please select language.',
......@@ -235,7 +243,7 @@ export default {
'pls_workspace_path': 'Please input workspace full path.',
'pls_workspace_type': 'Please input workspace type.',
'pls_zentao_url': 'Please input ZenTao URL',
'pls_zentao_url': 'Please input right ZenTao URL',
'pls_username': 'Please input user name.',
'pls_password': 'Please input password.',
'pls_input_lang': 'Please input language.',
......
......@@ -228,6 +228,14 @@ export default {
'switch_display_bottom_side': '切换显示底部边栏',
'switch_display_right_side': '切换显示右侧边栏',
'sync-from-zentao': '从禅道同步',
'sync-to-zentao': '同步到禅道',
'copy': '复制',
'cut': '剪切',
'paste': '粘贴',
'open-in-explore': '在资源管理器中显示',
'open-in-terminal': '从此位置打开命令行',
'pls_add_zentao_site': '初次使用,请点击右上按钮新建禅道站点。',
'pls_create_workspace': '请点击右上角链接新建工作目录',
'pls_product': '请选择产品',
......@@ -246,7 +254,7 @@ export default {
'pls_workspace_path': '请输入工作目录完整路径',
'pls_workspace_type': '请选择工作目录类型',
'pls_zentao_url': '请输入禅道地址',
'pls_zentao_url': '请输入正确的禅道地址',
'pls_username': '请输入用户名',
'pls_password': '请输入密码',
'pls_input_lang': '请输入语言',
......
<template>
<div class="tree-context-menu">
<div class="menu">
<div @click="menuClick('sync-from-zentao')" class="menu-item">
<span>{{t('sync-from-zentao')}} {{ treeNode.type }} - {{ treeNode.workspaceType }}</span>
</div>
<div @click="menuClick('sync-to-zentao')" class="menu-item">
<span>{{t('sync-to-zentao')}} {{ treeNode.type }} - {{ treeNode.workspaceType }}</span>
</div>
<div @click="menuClick('exec')" class="menu-item">
<span>{{t('exec')}}</span>
</div>
<div @click="menuClick('copy')" class="menu-item">
<span>{{t('copy')}}</span>
</div>
<div @click="menuClick('cut')" class="menu-item">
<span>{{t('cut')}}</span>
</div>
<div @click="menuClick('paste')" class="menu-item">
<span>{{t('paste')}}</span>
</div>
<div @click="menuClick('open-in-explore')" class="menu-item">
<span>{{t('open-in-explore')}}</span>
</div>
<div @click="menuClick('open-in-terminal')" class="menu-item">
<span>{{t('open-in-terminal')}}</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import {defineComponent, PropType, Ref} from "vue";
import {useI18n} from "vue-i18n";
export default defineComponent({
name: 'TreeContextMenu',
props: {
treeNode: {
type: Object,
required: true
},
onMenuClick: {
type: Function as PropType<(menuKey: string, targetId: number) => void>,
required: true
}
},
components: {
},
setup(props) {
const { t } = useI18n();
const menuClick = (menuKey) => {
props.onMenuClick(menuKey, props.treeNode.id);
};
return {
t,
menuClick
}
}
})
</script>
<style lang="less">
.tree-context-menu {
.menu {
padding: 0;
border: 1px solid #dedfe1;
background-color: #fff;
.menu-item {
margin: 0;
padding: 5px 10px;
height: 22px;
line-height: 22px;
cursor: pointer;
&:hover {
background-color: #f5f5f5;
}
}
}
}
</style>
\ No newline at end of file
......@@ -4,7 +4,8 @@
:data="treeData"
:checkable="checkable"
ref="treeRef"
@active="selectNode"
@active="selectNode"
@rightClick="onRightClick"
@check="checkNode"
@clickToolbar="onToolbarClicked"
@collapse="expandNode"
......@@ -18,6 +19,10 @@
:label="t('exec_selected')"
@click="execSelected"
/>
<div v-if="contextNode.id && rightVisible" :style="menuStyle">
<TreeContextMenu :treeNode="contextNode" :onMenuClick="menuClick"/>
</div>
</div>
</template>
......@@ -33,6 +38,7 @@ import Tree from "@/components/Tree.vue";
import notification from "@/utils/notification";
import { computed, defineExpose, onMounted, onUnmounted, ref, watch } from "vue";
import Button from '@/components/Button.vue';
import TreeContextMenu from './TreeContextMenu.vue';
import bus from "@/utils/eventBus";
import {
......@@ -355,8 +361,6 @@ const execSelected = () => {
bus.emit(settings.eventExec, { execType: 'ztf', scripts: arr });
}
let contextNode = ref({} as any)
let menuStyle = ref({} as any)
const editedData = ref<any>({})
const nameFormVisible = ref(false)
......@@ -417,6 +421,52 @@ const expandNode = (expandedKeysMap) => {
setExpandedKeys(currSite.value.id, currProduct.value.id, expandedKeys.value)
}
let menuStyle = ref({} as any)
let contextNode = ref({} as any)
let targetModelId = 0
let rightVisible = ref(false)
const onRightClick = (e) => {
console.log('onRightClick', e)
const {event, node} = e
const y = event.currentTarget.getBoundingClientRect().top
const x = event.currentTarget.getBoundingClientRect().right
const contextNodeData = treeDataMap.value[node.id]
contextNode.value = {
id: contextNodeData.id,
title: contextNodeData.title,
type: contextNodeData.type,
isLeaf: contextNodeData.isLeaf,
workspaceId: contextNodeData.workspaceId,
workspaceType: contextNodeData.workspaceType,
}
let top = y
if (y + 260 > document.body.clientHeight)
top = document.body.clientHeight - 260
menuStyle.value = {
zIndex: 9,
position: 'fixed',
left: `${x + 10}px`,
top: `${top}px`,
}
rightVisible.value = true
}
const menuClick = (menuKey: string, targetId: number) => {
console.log('menuClick', menuKey, targetId)
targetModelId = targetId
clearMenu()
}
const clearMenu = () => {
console.log('clearMenu')
contextNode.value = ref(null)
}
defineExpose({
get isCheckable() {
return checkable.value;
......@@ -431,12 +481,20 @@ defineExpose({
onToolbarClicked,
loadScripts
});
onMounted(() => {
console.log('onMounted')
document.addEventListener("click", clearMenu)
})
onUnmounted(() => {
document.removeEventListener("click", clearMenu)
})
</script>
<style lang="less" scoped>
.workdir {
height: calc(100vh - 80px);
position: relative;
.run-selected{
max-width: 100px;
......
@echo off
start gui\ztf.exe
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册