Merge branch 'main' of zentao/zentaopms (#2969)
This commit is contained in:
@@ -134,6 +134,7 @@ function detailSide(): detailSide {return createWg('detailSide', func_get_args()
|
||||
function detailBody(): detailBody {return createWg('detailBody', func_get_args());}
|
||||
function detailForm(): detailForm {return createWg('detailForm', func_get_args());}
|
||||
function echarts(): echarts {return createWg('echarts', func_get_args());}
|
||||
function graph(): graph {return createWg('graph', func_get_args());}
|
||||
function popovers(): popovers {return createWg('popovers', func_get_args());}
|
||||
function backBtn(): backBtn {return createWg('backBtn', func_get_args());}
|
||||
function collapseBtn(): collapseBtn {return createWg('collapseBtn', func_get_args());}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class graph extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'type?: string', // 'TreeGraph' or 'Graph', default is 'TreeGraph'
|
||||
'responsive?: bool=false',
|
||||
'width?: number|string="100%"',
|
||||
'height?: number|string="500"',
|
||||
);
|
||||
|
||||
protected function build(): zui
|
||||
{
|
||||
list($type, $width, $height, $responsive) = $this->prop(array('type', 'width', 'height', 'responsive'));
|
||||
|
||||
return zui::graph(
|
||||
set::_id('zin_graph_' . uniqid()),
|
||||
set::type($type),
|
||||
set::responsive($responsive),
|
||||
set::_style(array('width' => is_int($width) ? "{$width}px" : $width, 'height' => is_int($height) ? "{$height}px" : $height)),
|
||||
set($this->getRestProps()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
const allMainNavbarItemNames = [];
|
||||
|
||||
/**
|
||||
* @type {Map<string, any>}
|
||||
*/
|
||||
const allMainNavbarItemMap = new Map();
|
||||
|
||||
$(document).ready(
|
||||
function()
|
||||
{
|
||||
allMainNavbarItemNames.length = 0;
|
||||
allMainNavbarItemMap.clear();
|
||||
|
||||
if (typeof allMainNavbarItems !== 'undefined')
|
||||
{
|
||||
for(const item of allMainNavbarItems)
|
||||
{
|
||||
allMainNavbarItemNames.push(item['data-id']);
|
||||
allMainNavbarItemMap.set(item['data-id'], item);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get current main navbar items data.
|
||||
*
|
||||
* @returns {Array<{name: string; order: number;}>}
|
||||
*/
|
||||
function getCurrentMainNavbarItems()
|
||||
{
|
||||
const items = [];
|
||||
const $nav = $('#mainNavbar .nav');
|
||||
$nav.children().each(
|
||||
function(index, element)
|
||||
{
|
||||
const $elm = $(element);
|
||||
$a = $elm.find('a');
|
||||
items.push(
|
||||
{
|
||||
name: $a.attr('data-id'),
|
||||
order: index * 5
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate main menu nav items to be added.
|
||||
*
|
||||
* @param {Cash} $item
|
||||
* @param {(name: string) => void} onClick
|
||||
* @returns {Array<{text: string; onClick: () => void;}>}
|
||||
*/
|
||||
function generateAddMainNavbarItems($item, onClick)
|
||||
{
|
||||
const items = [];
|
||||
const allMainNavbarItemIDSet = new Set(allMainNavbarItemMap.keys());
|
||||
const curMainNavbarItems = getCurrentMainNavbarItems();
|
||||
for(const {name} of curMainNavbarItems)
|
||||
{
|
||||
allMainNavbarItemIDSet.delete(name);
|
||||
}
|
||||
|
||||
if(allMainNavbarItemIDSet.size === 0) return items;
|
||||
for(const name of allMainNavbarItemIDSet)
|
||||
{
|
||||
const item = allMainNavbarItemMap.get(name);
|
||||
items.push(
|
||||
{
|
||||
text: item.text,
|
||||
onClick: () => {
|
||||
onClick(name),
|
||||
saveMainNavbarToServer($item);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current navbar item can be hidden.
|
||||
*
|
||||
* @param {Cash} $item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canHideCurrentNavbar($item)
|
||||
{
|
||||
if($item.is('.active')) return false;
|
||||
|
||||
const $navbarActiveItem = $('#navbar .nav .active');
|
||||
if($item.attr('href') === $navbarActiveItem.attr('href')) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$(document).on(
|
||||
'contextmenu',
|
||||
'#mainNavbar .nav-item > a',
|
||||
function(event)
|
||||
{
|
||||
const $item = $(this);
|
||||
const $nav = $('#mainNavbar .nav');
|
||||
const isMoving = $nav.is('[z-use-sortable]');
|
||||
const hideDisabled = !canHideCurrentNavbar($item);
|
||||
const $li = $item.closest('li');
|
||||
const itemsToAdded = generateAddMainNavbarItems($item, (name) => {
|
||||
const item = allMainNavbarItemMap.get(name);
|
||||
const $a = $('<a></a>')
|
||||
.attr('href', item.url)
|
||||
.attr('data-id', item['data-id'])
|
||||
.attr('data-app', item['data-app'])
|
||||
.append(`<span class="text">${item.text}</span>`);
|
||||
if(item.badge)
|
||||
{
|
||||
$a.append(`<span class="${item.badge.class}">${item.badge.text}</span>`);
|
||||
}
|
||||
const $navItem = $('<li class="nav-item item"></li>');
|
||||
$navItem.append($a);
|
||||
$li.after($navItem);
|
||||
});
|
||||
const items = [
|
||||
isMoving
|
||||
? {
|
||||
text: langData.save,
|
||||
onClick: () => {
|
||||
$item.closest('.nav').zui().destroy();
|
||||
saveMainNavbarToServer($item);
|
||||
}
|
||||
}
|
||||
: {
|
||||
text: langData.sort,
|
||||
onClick: () => {
|
||||
const sortable = new zui.Sortable(
|
||||
'#mainNavbar .nav',
|
||||
{
|
||||
animation: 150,
|
||||
ghostClass: 'bg-primary-pale',
|
||||
onSort: () => {
|
||||
saveMainNavbarToServer($item);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: langData.hide,
|
||||
disabled: hideDisabled,
|
||||
onClick: hideDisabled
|
||||
? null
|
||||
: () => {
|
||||
$li.remove();
|
||||
saveMainNavbarToServer($item);
|
||||
}
|
||||
},
|
||||
itemsToAdded.length === 0
|
||||
? {
|
||||
text: langData.add,
|
||||
disabled: true,
|
||||
}
|
||||
: {
|
||||
text: langData.add,
|
||||
items: itemsToAdded,
|
||||
},
|
||||
{
|
||||
text: langData.restore,
|
||||
onClick: () => {
|
||||
restoreMainNavbarToServer($item, {
|
||||
onSuccess() {
|
||||
loadCurrentPage('#mainNavbar');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
zui.ContextMenu.show(
|
||||
{
|
||||
hideOthers: true,
|
||||
element: $item[0],
|
||||
placement: 'bottom-start',
|
||||
items: items,
|
||||
event: event,
|
||||
onClickItem: (info) => info.event.preventDefault()
|
||||
}
|
||||
);
|
||||
event.preventDefault();
|
||||
}
|
||||
);
|
||||
|
||||
function saveMainNavbarToServer($item)
|
||||
{
|
||||
const items = getCurrentMainNavbarItems();
|
||||
const menu = $item.data('group');
|
||||
const url = $.createLink('custom', 'ajaxSetMenu');
|
||||
$.ajaxSubmit({url, data: {menu, items: JSON.stringify(items)}});
|
||||
}
|
||||
|
||||
function restoreMainNavbarToServer($item, options = {})
|
||||
{
|
||||
const url = $.createLink('custom', 'ajaxRestoreMenu');
|
||||
const menu = $item.data('group');
|
||||
$.ajaxSubmit({url, data: {menu}, ...options});
|
||||
}
|
||||
@@ -63,9 +63,19 @@ class mainNavbar extends nav
|
||||
#mainNavbar .main-navbar-left #switcher .icon-angle-right {display: none;}
|
||||
#mainNavbar .main-navbar-left #switcher .caret {color: rgb(var(--color-link-hover-rgb));}
|
||||
#mainNavbar .main-navbar-left #switcher .text {color: rgb(var(--color-primary-500-rgb));}
|
||||
#mainNavbar .nav[z-use-sortable] > li:hover {cursor: grab !important;}
|
||||
#mainNavbar .nav[z-use-sortable] > li > a:hover {cursor: grab !important;}
|
||||
CSS;
|
||||
}
|
||||
|
||||
public static function getPageJS(): ?string
|
||||
{
|
||||
global $lang, $app;
|
||||
$app->loadLang('index');
|
||||
jsVar('langData', $lang->index->dock);
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function created()
|
||||
{
|
||||
global $app;
|
||||
@@ -102,10 +112,12 @@ class mainNavbar extends nav
|
||||
$item = array();
|
||||
$link = $menuItem['link'];
|
||||
$name = $menuItem['name'];
|
||||
$item['text'] = $menuItem['text'];
|
||||
$item['url'] = commonModel::createMenuLink((object)$menuItem, $app->tab);
|
||||
$item['data-id'] = $name;
|
||||
$item['data-app'] = $app->tab;
|
||||
$item['text'] = $menuItem['text'];
|
||||
$item['url'] = commonModel::createMenuLink((object)$menuItem, $app->tab);
|
||||
$item['hidden'] = !empty($menuItem['hidden']);
|
||||
$item['data-id'] = $name;
|
||||
$item['data-app'] = $app->tab;
|
||||
$item['data-group'] = $app->tab . '-' . $activeMenu;
|
||||
|
||||
$active = '';
|
||||
if($activeItem && $activeItem == $name)
|
||||
@@ -184,6 +196,9 @@ class mainNavbar extends nav
|
||||
}
|
||||
}
|
||||
|
||||
jsVar('allMainNavbarItems', $items);
|
||||
$items = array_filter($items, function($item) { return empty($item['hidden']); });
|
||||
|
||||
$this->setProp('items', $items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
const allNavbarItemNames = [];
|
||||
|
||||
/**
|
||||
* @type {Map<string, any>}
|
||||
*/
|
||||
const allNavbarItemMap = new Map();
|
||||
|
||||
$(document).ready(
|
||||
function()
|
||||
{
|
||||
allNavbarItemNames.length = 0;
|
||||
allNavbarItemMap.clear();
|
||||
|
||||
for(const item of allNavbarItems)
|
||||
{
|
||||
if(item.type === 'divider')
|
||||
{
|
||||
allNavbarItemNames.push('divider');
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = item['data-id'] || item.id;
|
||||
|
||||
allNavbarItemNames.push(name);
|
||||
allNavbarItemMap.set(name, item);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get current navbar items data.
|
||||
*
|
||||
* @returns {Array<{name: string; order: number;}>}
|
||||
*/
|
||||
function getCurrentNavbarItems()
|
||||
{
|
||||
const items = [];
|
||||
const $nav = $('#navbar .nav');
|
||||
$nav.children().each(
|
||||
function(index, element)
|
||||
{
|
||||
const $elm = $(element)
|
||||
const $a = $elm.find('a');
|
||||
items.push(
|
||||
{
|
||||
name: $elm.is('.nav-divider') ? 'divider' : ($a.attr('data-id') || $a.attr('id')),
|
||||
order: index * 5
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate navbar items to be added.
|
||||
* @param {Cash} $item
|
||||
* @param {(item: string) => void} onClick click handler of navbar item.
|
||||
* @returns {Array<{text: string; onClick: () => void;}>}
|
||||
*/
|
||||
function generateAddNavbarItems($item, onClick)
|
||||
{
|
||||
const items = canAddDivider($item)
|
||||
? [{
|
||||
text: langData.divider,
|
||||
onClick: () => {
|
||||
onClick('divider');
|
||||
saveNavbarToServer();
|
||||
}
|
||||
}]
|
||||
: [];
|
||||
|
||||
const allNavbarItemIDSet = new Set(allNavbarItemMap.keys());
|
||||
const curNavbarItems = getCurrentNavbarItems();
|
||||
for(const {name} of curNavbarItems)
|
||||
{
|
||||
if(name === 'divider') continue;
|
||||
allNavbarItemIDSet.delete(name);
|
||||
}
|
||||
|
||||
if(allNavbarItemIDSet.size === 0) return items;
|
||||
for(const name of allNavbarItemIDSet)
|
||||
{
|
||||
const item = allNavbarItemMap.get(name);
|
||||
items.push(
|
||||
{
|
||||
text: item.text,
|
||||
onClick: () => {
|
||||
onClick(name);
|
||||
saveNavbarToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current navbar item can be hidden.
|
||||
*
|
||||
* @param {Cash} $item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canHideCurrentNavbar($item)
|
||||
{
|
||||
if($item.is('.nav-divider')) return true;
|
||||
if($item.is('.active')) return false;
|
||||
|
||||
const app = $.apps.getLastApp();
|
||||
const appDefaultUrl = app.url;
|
||||
const itemUrl = $item.attr('href');
|
||||
if(itemUrl === appDefaultUrl) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu name by items and app.
|
||||
*
|
||||
* @param {string} app
|
||||
* @returns {string}
|
||||
*/
|
||||
function getMenuName(app)
|
||||
{
|
||||
if(typeof projectModel !== 'undefined')
|
||||
{
|
||||
return `project-${projectModel}`;
|
||||
}
|
||||
|
||||
if(isHomeMenu)
|
||||
{
|
||||
return `${app}-home`;
|
||||
}
|
||||
|
||||
if(app == 'admin')
|
||||
{
|
||||
return `admin-${adminMenuKey}`;
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
$(document).on(
|
||||
'contextmenu',
|
||||
'#navbar .nav-item:not(.nav-dropdown) > a, #navbar .nav-divider',
|
||||
function(event)
|
||||
{
|
||||
const $item = $(this);
|
||||
const $nav = $('#navbar .nav');
|
||||
const isMoving = $nav.is('[z-use-sortable]');
|
||||
const hideDisabled = !canHideCurrentNavbar($item);
|
||||
const $li = $item.closest('li');
|
||||
const toAddedItems = generateAddNavbarItems($item, (name) => {
|
||||
if(name === 'divider') return $li.after('<li class="nav-divider item divider"></li>');
|
||||
|
||||
const item = allNavbarItemMap.get(name);
|
||||
const $a = $('<a></a>')
|
||||
.attr('href', item.url)
|
||||
.attr('target', item.target)
|
||||
.attr('data-id', item['data-id'])
|
||||
.append(`<span class="text">${item.text}</span>`);
|
||||
if(item.class) $a.attr('class', item.class);
|
||||
const $navItem = $('<li class="nav-item item"></li>');
|
||||
$navItem.append($a);
|
||||
$li.after($navItem);
|
||||
saveNavbarToServer();
|
||||
});
|
||||
const items = [
|
||||
isMoving
|
||||
? {
|
||||
text: langData.save,
|
||||
onClick: () => {
|
||||
$item.closest('.nav').zui().destroy();
|
||||
saveNavbarToServer();
|
||||
}
|
||||
}
|
||||
: {
|
||||
text: langData.sort,
|
||||
onClick: () => {
|
||||
const sortable = new zui.Sortable(
|
||||
'#navbar .nav',
|
||||
{
|
||||
animation: 150,
|
||||
ghostClass: 'bg-primary-pale',
|
||||
onSort: () => {
|
||||
saveNavbarToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: langData.hide,
|
||||
disabled: hideDisabled,
|
||||
onClick: hideDisabled
|
||||
? null
|
||||
: () => {
|
||||
$li.remove();
|
||||
saveNavbarToServer();
|
||||
}
|
||||
},
|
||||
toAddedItems.length === 0
|
||||
? {
|
||||
text: langData.add,
|
||||
disabled: true,
|
||||
}
|
||||
: {
|
||||
text: langData.add,
|
||||
items: toAddedItems,
|
||||
},
|
||||
{
|
||||
text: langData.restore,
|
||||
onClick: () => {
|
||||
restoreNavbarToServer({
|
||||
onSuccess() {
|
||||
loadCurrentPage('#navbar');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
zui.ContextMenu.show(
|
||||
{
|
||||
hideOthers: true,
|
||||
element: $item[0],
|
||||
placement: 'bottom-start',
|
||||
items: items,
|
||||
event: event,
|
||||
onClickItem: (info) => info.event.preventDefault()
|
||||
}
|
||||
);
|
||||
event.preventDefault();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Save navbar to server.
|
||||
*/
|
||||
function saveNavbarToServer()
|
||||
{
|
||||
const url = $.createLink('custom', 'ajaxSetMenu');
|
||||
const items = getCurrentNavbarItems();
|
||||
const app = $.apps.getLastApp().code;
|
||||
const menu = getMenuName(app);
|
||||
$.ajaxSubmit({url, data: {menu, items: JSON.stringify(items)}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore navbar to server.
|
||||
*/
|
||||
function restoreNavbarToServer(options = {})
|
||||
{
|
||||
const url = $.createLink('custom', 'ajaxRestoreMenu');
|
||||
const app = $.apps.getLastApp().code;
|
||||
const menu = getMenuName(app);
|
||||
$.ajaxSubmit({url, data: {menu}, ...options});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether current element can add a divider.
|
||||
* @param {Cash} $item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canAddDivider($item)
|
||||
{
|
||||
$item = $item.closest('li');
|
||||
if($item.is('.divider')) return false;
|
||||
if($item.next().is('.divider')) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -10,6 +10,23 @@ class navbar extends wg
|
||||
'items?: array'
|
||||
);
|
||||
|
||||
public static function getPageCSS(): ?string
|
||||
{
|
||||
return <<<'CSS'
|
||||
#navbar .nav[z-use-sortable] > li:hover {cursor: grab !important;}
|
||||
#navbar .nav[z-use-sortable] > li > a:hover {cursor: grab !important;}
|
||||
#navbar .nav li.nav-divider.divider {border: none; width: 1px; background: currentColor; margin: 0; padding-left: var(--nav-divider-margin); padding-right: var(--nav-divider-margin); box-sizing: content-box; background-clip: content-box;}
|
||||
CSS;
|
||||
}
|
||||
|
||||
public static function getPageJS(): ?string
|
||||
{
|
||||
global $lang, $app;
|
||||
$app->loadLang('index');
|
||||
jsVar('langData', $lang->index->dock);
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function getExecutionMoreItem($executionID)
|
||||
{
|
||||
if(defined('TUTORIAL')) return;
|
||||
@@ -61,6 +78,7 @@ class navbar extends wg
|
||||
'text' => $lang->more,
|
||||
'trigger' => 'hover',
|
||||
'id' => 'navbarMoreMenu',
|
||||
'data-id' => 'more',
|
||||
'menu' => array('style' => array('max-width' => '300px'))
|
||||
);
|
||||
}
|
||||
@@ -111,11 +129,19 @@ class navbar extends wg
|
||||
if(!empty($items)) return $items;
|
||||
|
||||
global $app, $lang, $config;
|
||||
if($app->tab == 'admin') $app->control->loadModel('admin')->setMenu();
|
||||
if($app->tab == 'admin')
|
||||
{
|
||||
$app->control->loadModel('admin')->setMenu();
|
||||
$adminMenuKey = $app->control->loadModel('admin')->getMenuKey();
|
||||
jsVar('adminMenuKey', $adminMenuKey);
|
||||
}
|
||||
|
||||
commonModel::replaceMenuLang();
|
||||
commonModel::setMainMenu();
|
||||
$isHomeMenu = commonModel::setMainMenu();
|
||||
commonModel::checkMenuVarsReplaced();
|
||||
|
||||
jsVar('isHomeMenu', $isHomeMenu);
|
||||
|
||||
$isTutorialMode = commonModel::isTutorialMode();
|
||||
$currentModule = $app->rawModule;
|
||||
$currentMethod = $app->rawMethod;
|
||||
@@ -123,15 +149,14 @@ class navbar extends wg
|
||||
if($isTutorialMode and defined('WIZARD_MODULE')) $currentModule = WIZARD_MODULE;
|
||||
if($isTutorialMode and defined('WIZARD_METHOD')) $currentMethod = WIZARD_METHOD;
|
||||
|
||||
$menu = \customModel::getMainMenu();
|
||||
$tab = $app->tab;
|
||||
$menu = \customModel::getMainMenu($isHomeMenu);
|
||||
$activeMenu = '';
|
||||
$activeMenuID = data('activeMenuID');
|
||||
$items = array();
|
||||
$flows = $config->edition != 'open' ? $app->control->loadModel('my')->getFlowPairs() : array();
|
||||
foreach($menu as $menuItem)
|
||||
{
|
||||
if(isset($menuItem->hidden) and $menuItem->hidden and (!isset($menuItem->tutorial) or !$menuItem->tutorial)) continue;
|
||||
if(isset($menuItem->class) && strpos($menuItem->class, 'automation-menu'))
|
||||
{
|
||||
if($menuItem->divider) $items[] = array('type' => 'divider');
|
||||
@@ -147,7 +172,7 @@ class navbar extends wg
|
||||
}
|
||||
if(empty($menuItem->link)) continue;
|
||||
|
||||
if($menuItem->divider) $items[] = array('type' => 'divider');
|
||||
if($menuItem->divider && empty($menuItem->hidden)) $items[] = array('type' => 'divider');
|
||||
|
||||
/* Init the these vars. */
|
||||
$subModule = isset($menuItem->subModule) ? explode(',', $menuItem->subModule) : array();
|
||||
@@ -164,6 +189,13 @@ class navbar extends wg
|
||||
$isActive = true;
|
||||
}
|
||||
|
||||
if($menuItem->link['module'] == 'project' and $menuItem->link['method'] == 'index')
|
||||
{
|
||||
$projectID = str_replace('project=', '', $menuItem->link['vars']);
|
||||
$projectModel = $app->dbh->query("SELECT `model` FROM " . TABLE_PROJECT . " WHERE `id` = '$projectID'")->fetch();
|
||||
if($projectModel) jsVar('projectModel', $projectModel->model);
|
||||
}
|
||||
|
||||
if($menuItem->link['module'] == 'execution' and $menuItem->link['method'] == 'more')
|
||||
{
|
||||
$executionID = $menuItem->link['vars'];
|
||||
@@ -274,7 +306,8 @@ class navbar extends wg
|
||||
'active' => $isActive,
|
||||
'target' => $target,
|
||||
'data-id' => $menuItem->name,
|
||||
'data-app' => $dataApp
|
||||
'data-app' => $dataApp,
|
||||
'hidden' => (isset($menuItem->hidden) && $menuItem->hidden && (!isset($menuItem->tutorial) || !$menuItem->tutorial))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -286,6 +319,9 @@ class navbar extends wg
|
||||
|
||||
/* Set active menu to global data, make it accessible to other widgets */
|
||||
data('activeMenu', $activeMenu);
|
||||
jsVar('allNavbarItems', $items);
|
||||
|
||||
$items = array_filter($items, function($item) { return empty($item['hidden']); });
|
||||
|
||||
return $items;
|
||||
}
|
||||
@@ -297,12 +333,13 @@ class navbar extends wg
|
||||
*/
|
||||
protected function build()
|
||||
{
|
||||
$items = $this->getItems();
|
||||
return h::nav
|
||||
(
|
||||
set::id('navbar'),
|
||||
new nav
|
||||
(
|
||||
set::items($this->getItems()),
|
||||
set::items($items),
|
||||
$this->children()
|
||||
)
|
||||
);
|
||||
|
||||
@@ -111,6 +111,9 @@ class adminModel extends model
|
||||
$menuKey = $this->getMenuKey();
|
||||
if(empty($menuKey)) return;
|
||||
|
||||
$customKey = "admin-$menuKey";
|
||||
$customMenu = isset($this->config->customMenu->{$customKey}) ? json_decode($this->config->customMenu->{$customKey}) : array();
|
||||
|
||||
$this->setSwitcher($menuKey);
|
||||
if(isset($this->lang->admin->menuList->$menuKey))
|
||||
{
|
||||
@@ -146,6 +149,22 @@ class adminModel extends model
|
||||
}
|
||||
}
|
||||
|
||||
if($customMenu)
|
||||
{
|
||||
$this->lang->admin->menuList->{$menuKey}['menuOrder'] = array();
|
||||
$this->lang->admin->menuList->{$menuKey}['dividerMenu'] = '';
|
||||
$prev = '';
|
||||
foreach($customMenu as $item)
|
||||
{
|
||||
$this->lang->admin->menuList->{$menuKey}['menuOrder'][$item->order] = $item->name;
|
||||
if($prev == 'divider') $this->lang->admin->menuList->{$menuKey}['dividerMenu'] .= ',' . $item->name;
|
||||
$prev = $item->name;
|
||||
}
|
||||
|
||||
if($this->lang->admin->menuList->{$menuKey}['dividerMenu']) $this->lang->admin->menuList->{$menuKey}['dividerMenu'] = ',' . trim($this->lang->admin->menuList->{$menuKey}['dividerMenu']) . ',';
|
||||
ksort($this->lang->admin->menuList->{$menuKey}['menuOrder']);
|
||||
}
|
||||
|
||||
if(isset($this->lang->admin->menuList->{$menuKey}['menuOrder'])) $this->lang->admin->menuOrder = $this->lang->admin->menuList->{$menuKey}['menuOrder'];
|
||||
if(isset($this->lang->admin->menuList->{$menuKey}['dividerMenu'])) $this->lang->admin->dividerMenu = $this->lang->admin->menuList->{$menuKey}['dividerMenu'];
|
||||
if(isset($this->lang->admin->menuList->{$menuKey}['tabMenu'])) $this->lang->admin->tabMenu = $this->lang->admin->menuList->{$menuKey}['tabMenu'];
|
||||
|
||||
@@ -401,6 +401,7 @@ $lang->execution->menuOrder[55] = 'build';
|
||||
$lang->execution->menuOrder[60] = 'release';
|
||||
$lang->execution->menuOrder[65] = 'action';
|
||||
$lang->execution->menuOrder[70] = 'settings';
|
||||
$lang->execution->menuOrder[75] = 'more';
|
||||
|
||||
$lang->execution->menu->view['subMenu'] = new stdclass();
|
||||
$lang->execution->menu->view['subMenu']->groupTask = "$lang->groupView|execution|grouptask|executionID=%s";
|
||||
|
||||
+41
-16
@@ -605,12 +605,13 @@ class commonModel extends model
|
||||
* Get main nav items list
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @param bool $useDefault 是否使用语言项中的默认值
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function getMainNavList(string $moduleName): array
|
||||
public static function getMainNavList(string $moduleName, bool $useDefault = false): array
|
||||
{
|
||||
global $lang, $app, $config;
|
||||
|
||||
@@ -619,7 +620,18 @@ class commonModel extends model
|
||||
/* Ensure user has latest rights set. */
|
||||
$app->user->rights = $app->control->loadModel('user')->authorize($app->user->account);
|
||||
|
||||
$menuOrder = $lang->mainNav->menuOrder;
|
||||
$menuOrder = array();
|
||||
$hasCustomMenu = false;
|
||||
if(isset($config->customMenu->nav) && !$useDefault)
|
||||
{
|
||||
$items = json_decode($config->customMenu->nav);
|
||||
foreach($items as $item) $menuOrder[$item->order] = $item->name;
|
||||
$hasCustomMenu = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$menuOrder = $lang->mainNav->menuOrder;
|
||||
}
|
||||
ksort($menuOrder);
|
||||
|
||||
$items = array();
|
||||
@@ -628,18 +640,29 @@ class commonModel extends model
|
||||
|
||||
foreach($menuOrder as $key => $group)
|
||||
{
|
||||
// 如果有自定义菜单,则直接用自定义后的divider分隔符
|
||||
if($hasCustomMenu && $group == 'divider')
|
||||
{
|
||||
$items[] = 'divider';
|
||||
continue;
|
||||
}
|
||||
|
||||
if($group != 'my' && !empty($app->user->rights['acls']['views']) && !isset($app->user->rights['acls']['views'][$group])) continue; // 后台权限分组中没有给导航视图
|
||||
if(!isset($lang->mainNav->$group)) continue;
|
||||
|
||||
$nav = $lang->mainNav->$group;
|
||||
list($title, $currentModule, $currentMethod, $vars) = explode('|', $nav);
|
||||
|
||||
/* When last divider is not used in mainNav, use it next menu. */
|
||||
$printDivider = ($printDivider or ($lastItem != $key) and strpos($lang->dividerMenu, ",{$group},") !== false) ? true : false;
|
||||
if($printDivider and !empty($items))
|
||||
// 没有自定义过菜单,用默认语言项中的divider分隔符
|
||||
if(!$hasCustomMenu)
|
||||
{
|
||||
$items[] = 'divider';
|
||||
$printDivider = false;
|
||||
/* When last divider is not used in mainNav, use it next menu. */
|
||||
$printDivider = ($printDivider or ($lastItem != $key) and strpos($lang->dividerMenu, ",{$group},") !== false) ? true : false;
|
||||
if($printDivider and !empty($items))
|
||||
{
|
||||
$items[] = 'divider';
|
||||
$printDivider = false;
|
||||
}
|
||||
}
|
||||
|
||||
$display = false;
|
||||
@@ -707,8 +730,8 @@ class commonModel extends model
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
/* Fix bug 14574. */
|
||||
if(end($items) == 'divider') array_pop($items);
|
||||
// 如果最后一个是分割线,则删除
|
||||
while(!empty($items) && end($items) === 'divider') { array_pop($items); }
|
||||
return $items;
|
||||
}
|
||||
|
||||
@@ -2039,9 +2062,9 @@ eof;
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
public static function setMainMenu()
|
||||
public static function setMainMenu(): bool
|
||||
{
|
||||
global $app, $lang;
|
||||
$tab = $app->tab;
|
||||
@@ -2055,12 +2078,12 @@ eof;
|
||||
$lang->menu = isset($lang->$tab->menu) ? $lang->$tab->menu : array();
|
||||
$lang->menuOrder = isset($lang->$tab->menuOrder) ? $lang->$tab->menuOrder : array();
|
||||
|
||||
if(!isset($lang->$tab->homeMenu)) return;
|
||||
if(!isset($lang->$tab->homeMenu)) return false;
|
||||
|
||||
if($currentModule == $tab and $currentMethod == 'create')
|
||||
{
|
||||
$lang->menu = $lang->$tab->homeMenu;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the method is in homeMenu, display homeMenu. */
|
||||
@@ -2073,7 +2096,7 @@ eof;
|
||||
if($method == $currentMethod)
|
||||
{
|
||||
$lang->menu = $lang->$tab->homeMenu;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$alias = isset($menu['alias']) ? explode(',', strtolower($menu['alias'])) : array();
|
||||
@@ -2081,15 +2104,17 @@ eof;
|
||||
if(in_array($currentMethod, $alias) && !in_array("{$currentModule}-{$currentMethod}", $exclude))
|
||||
{
|
||||
$lang->menu = $lang->$tab->homeMenu;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isset($menu['subModule']) and strpos(",{$menu['subModule']},", ",$currentModule,") !== false)
|
||||
{
|
||||
$lang->menu = $lang->$tab->homeMenu;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -552,4 +552,42 @@ class custom extends control
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax set menu
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function ajaxSetMenu()
|
||||
{
|
||||
if($_POST)
|
||||
{
|
||||
$menu = $this->post->menu; // 导航类型,nav(左侧主导航)|$app(顶部一级导航)|$app-home(项目集、项目的首页导航)|$app-$subMenu(顶部二级导航)|admin-$menuKey(后台导航)
|
||||
$items = $this->post->items; // 导航项
|
||||
$account = $this->app->user->account;
|
||||
|
||||
if($menu && $items) $this->loadModel('setting')->setItem("$account.common.customMenu.$menu@{$this->config->vision}", $items);
|
||||
}
|
||||
|
||||
$this->send(array('result' => 'success'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax restore menu
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function ajaxRestoreMenu()
|
||||
{
|
||||
if($_POST)
|
||||
{
|
||||
$account = $this->app->user->account;
|
||||
$menu = $this->post->menu;
|
||||
$this->loadModel('setting')->deleteItems("owner={$account}&module=common§ion=customMenu&key=$menu");
|
||||
}
|
||||
|
||||
$this->send(array('result' => 'success'));
|
||||
}
|
||||
}
|
||||
|
||||
+23
-53
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* The model file of custom module of ZenTaoPMS.
|
||||
*
|
||||
@@ -226,7 +225,7 @@ class customModel extends model
|
||||
else
|
||||
{
|
||||
$isFirst = false;
|
||||
if(!isset($menu[$item->order]->divider))$menu[$item->order]->divider = false;
|
||||
if(!isset($menu[$item->order]->divider)) $menu[$item->order]->divider = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,39 +247,17 @@ class customModel extends model
|
||||
global $lang;
|
||||
$customMenuMap = array();
|
||||
$order = 1;
|
||||
if($customMenu)
|
||||
if($customMenu && is_array($customMenu))
|
||||
{
|
||||
if(is_string($customMenu))
|
||||
$prev = '';
|
||||
foreach($customMenu as $customMenuItem)
|
||||
{
|
||||
$customMenuItems = explode(',', $customMenu);
|
||||
foreach($customMenuItems as $customMenuItem)
|
||||
{
|
||||
$item = new stdclass();
|
||||
$item->name = $customMenuItem;
|
||||
$item->order = $order ++;
|
||||
$item->hidden = false;
|
||||
$customMenuMap[$item->name] = $item;
|
||||
}
|
||||
foreach($allMenu as $name => $item)
|
||||
{
|
||||
if(!isset($customMenuMap[$name]))
|
||||
{
|
||||
$item = new stdclass();
|
||||
$item->name = $name;
|
||||
$item->hidden = true;
|
||||
$item->order = $order ++;
|
||||
$customMenuMap[$name] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif(is_array($customMenu))
|
||||
{
|
||||
foreach($customMenu as $customMenuItem)
|
||||
{
|
||||
if(!isset($customMenuItem->order)) $customMenuItem->order = $order;
|
||||
$customMenuMap[$customMenuItem->name] = $customMenuItem;
|
||||
$order ++;
|
||||
}
|
||||
$name = $customMenuItem->name;
|
||||
if(!isset($customMenuItem->order)) $customMenuItem->order = $order;
|
||||
if($prev == 'divider') $customMenuItem->divider = true;
|
||||
if($name != 'divider') $customMenuMap[$name] = $customMenuItem;
|
||||
$prev = $name;
|
||||
$order ++;
|
||||
}
|
||||
}
|
||||
elseif($module)
|
||||
@@ -387,6 +364,8 @@ class customModel extends model
|
||||
/* Process menu item's order and hidden attirbute. */
|
||||
$menuItem = static::buildMenuItem($item, $customMenuMap, $name, $label, $itemLink, $isTutorialMode, $subMenu);
|
||||
$menuItem->order = (isset($customMenuMap[$name]) && isset($customMenuMap[$name]->order) ? $customMenuMap[$name]->order : $order ++);
|
||||
if(!empty($customMenuMap) && !isset($customMenuMap[$name])) $menuItem->hidden = true; // 自定义过滤掉的菜单不显示。
|
||||
if(isset($customMenuMap[$name]) && isset($customMenuMap[$name]->divider)) $menuItem->divider = true;
|
||||
if($app->viewType == 'mhtml' && isset($config->custom->moblieHidden[$menuModuleName]) && in_array($name, $config->custom->moblieHidden[$menuModuleName])) $menuItem->hidden = 1; // Hidden menu by config in mobile.
|
||||
while(isset($menu[$menuItem->order])) $menuItem->order ++;
|
||||
$menu[$menuItem->order] = $menuItem;
|
||||
@@ -412,13 +391,6 @@ class customModel extends model
|
||||
*/
|
||||
public static function buildMenuItem(array|string $item, $customMenuMap, string $name = '', string $label = '', string|array $itemLink = '', bool $isTutorialMode = false, array $subMenu = array()): object
|
||||
{
|
||||
if($item === '-')
|
||||
{
|
||||
$menuItem = new stdclass();
|
||||
$menuItem->type = 'divider';
|
||||
return $menuItem;
|
||||
}
|
||||
|
||||
if(is_array($item) && (isset($item['subMenu']) || isset($item['dropMenu'])))
|
||||
{
|
||||
foreach(array('subMenu', 'dropMenu') as $key)
|
||||
@@ -428,13 +400,6 @@ class customModel extends model
|
||||
{
|
||||
if(isset($subItem->link['module']) && isset($subItem->link['method'])) $subItem->hidden = !common::hasPriv($subItem->link['module'], $subItem->link['method']);
|
||||
}
|
||||
if(isset($customMenuMap[$name]->$key))
|
||||
{
|
||||
foreach($customMenuMap[$name]->$key as $subItem)
|
||||
{
|
||||
if(isset($subItem->hidden) && isset($item[$key][$subItem->name])) $item[$key][$subItem->name]->hidden = $subItem->hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,10 +426,11 @@ class customModel extends model
|
||||
* Get module menu data, if module is 'main' then return main menu.
|
||||
*
|
||||
* @param string $module
|
||||
* @param bool $isHomeMenu
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function getModuleMenu($module = 'main'): array
|
||||
public static function getModuleMenu($module = 'main', $isHomeMenu = false): array
|
||||
{
|
||||
global $app, $lang, $config;
|
||||
|
||||
@@ -474,8 +440,11 @@ class customModel extends model
|
||||
if($module == 'main' and !empty($lang->menu)) $allMenu = $lang->menu;
|
||||
if($module != 'main' and isset($lang->menu->$module) and isset($lang->menu->{$module}['subMenu'])) $allMenu = $lang->menu->{$module}['subMenu'];
|
||||
if($module == 'product' and isset($allMenu->branch)) $allMenu->branch = str_replace('@branch@', $lang->custom->branch, $allMenu->branch);
|
||||
$flowModule = $config->global->flow . '_' . $module;
|
||||
$customMenu = isset($config->customMenu->$flowModule) ? $config->customMenu->$flowModule : array();
|
||||
|
||||
/* 获取自定义过的导航。 */
|
||||
$customKey = $isHomeMenu ? $app->tab . '-home' : ($module == 'main' ? $app->tab : $app->tab . '-' . $module);
|
||||
$customMenu = isset($config->customMenu->{$customKey}) ? $config->customMenu->{$customKey}: array();
|
||||
|
||||
if(!empty($customMenu) && is_string($customMenu) && substr($customMenu, 0, 1) === '[') $customMenu = json_decode($customMenu);
|
||||
if($module == 'my' && empty($config->global->scoreStatus)) unset($allMenu->score);
|
||||
|
||||
@@ -486,12 +455,13 @@ class customModel extends model
|
||||
* 获取主菜单数据。
|
||||
* Get main menu data.
|
||||
*
|
||||
* @param bool $isHomeMenu
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public static function getMainMenu(): array
|
||||
public static function getMainMenu($isHomeMenu = false): array
|
||||
{
|
||||
return static::getModuleMenu('main');
|
||||
return static::getModuleMenu('main', $isHomeMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,6 +45,7 @@ class index extends control
|
||||
$this->view->showFeatures = $this->indexZen->checkShowFeatures();
|
||||
$this->view->latestVersionList = $latestVersionList;
|
||||
$this->view->appsItems = commonModel::getMainNavList($this->app->rawModule);
|
||||
$this->view->allAppsItems = commonModel::getMainNavList($this->app->rawModule, true);
|
||||
$this->view->browserMessage = $this->loadModel('message')->getBrowserMessageConfig();
|
||||
|
||||
$this->display();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#menu {position: fixed; left: 0; top: 0; bottom: 0; background: var(--zt-menu-bg); width: var(--zt-menu-width); color: rgba(var(--color-canvas-rgb), .8); transition: width .1s; user-select: none; z-index: 10;}
|
||||
#menu .nav {flex-direction: column; align-items: stretch;}
|
||||
#menu .nav > li {display: block; height: var(--zt-menu-height, 38px); padding: 4px 8px; transition: padding .2s;}
|
||||
#menu .nav > .divider {background: var(--color-canvas); opacity: .12; height: 1px; padding: 0; margin: 6px 12px; align-self: stretch}
|
||||
#menu .nav > .divider {background: var(--color-canvas); opacity: .12; height: 1px; padding: 6px 4px; align-self: stretch; background-clip: content-box; box-sizing: content-box; border: none;}
|
||||
#menu .nav > li > a {color: inherit; display: flex; align-items: center; gap: 4px; padding: 0 6px; height: calc(var(--zt-menu-height, 38px) - 8px); transition: color .2s, background-color .2s; border-radius: var(--radius-md);}
|
||||
#menu .nav > li > a.active,
|
||||
#menu .nav > li > a:hover {background: var(--zt-menu-hover-bg, rgba(var(--color-primary-400-rgb), .4)); color: var(--color-canvas);}
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
.hide-menu #menu {width: var(--zt-menu-fold-width)!important;}
|
||||
.hide-menu #menu .nav > li {padding: 4px}
|
||||
.hide-menu #menu .nav > .divider {padding: 0; margin: 6px}
|
||||
.hide-menu #menu .nav > .divider {padding: 6px;}
|
||||
.hide-menu #menu .nav > li > a {justify-content: center;}
|
||||
.hide-menu #menu .nav > li > a > .text {display: none;}
|
||||
.hide-menu #menu #menuMoreNav .nav > li > a > .text {display: flex;}
|
||||
@@ -83,3 +83,6 @@
|
||||
#upgradeContent {display: none; position: absolute; bottom: var(--zt-apps-bar-height); right: 10px; width: 370px; height: 322px; background-color: #fff; padding: 5px 0; border: 1px solid rgba(0,0,0,.15); border-color: rgba(0,0,0,.1); opacity: 1; border-radius: 4px; box-shadow: 0 6px 12px rgba(0,0,0,.12), 0 1px 3px rgba(0,0,0,.1);}
|
||||
.version-upgrade {width: 14px; height: 16px; float: left; background-repeat: no-repeat; background-position: center ; background-size: cover; display: block;}
|
||||
#latestVersionList {height: 270px; overflow: auto;}
|
||||
|
||||
#menuMainNav[z-use-sortable] a[data-app]:hover {cursor: grab;}
|
||||
#menuMainNav[z-use-sortable] li.divider:hover {cursor: grab;}
|
||||
+346
-3
@@ -819,6 +819,180 @@ setTimeout(refreshMenu, 500);
|
||||
$(document).on('click', '.menu-toggle', () => toggleMenu());
|
||||
toggleMenu(!$('body').hasClass('hide-menu'));
|
||||
|
||||
/**
|
||||
* Get current menu nav data.
|
||||
*
|
||||
* @returns {Array<{name: string; order: number;}>}
|
||||
*/
|
||||
function getMenuNavData()
|
||||
{
|
||||
const data = [];
|
||||
const $nav = $('#menuMainNav');
|
||||
$nav.children().each(function(index, element) {
|
||||
const $elm = $(element);
|
||||
data.push(
|
||||
{
|
||||
name: $elm.is('.divider') ? 'divider' : $elm.data('app'),
|
||||
order: index * 5
|
||||
}
|
||||
);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save menu nav to server.
|
||||
*/
|
||||
function saveMenuNavToServer()
|
||||
{
|
||||
const url = $.createLink('custom', 'ajaxSetMenu');
|
||||
const data = getMenuNavData();
|
||||
$.ajaxSubmit({url, data: {menu: 'nav', items: JSON.stringify(data)}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore menu nav to server.
|
||||
*/
|
||||
function restoreMenuNavToServer()
|
||||
{
|
||||
const url = $.createLink('custom', 'ajaxRestoreMenu');
|
||||
$.ajaxSubmit({url, data: {menu: 'nav'}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate menu nav items to be added.
|
||||
*
|
||||
* @param {Cash} $item
|
||||
* @param {(item: string) => void} onClick click handler of menu item.
|
||||
* @returns {Array<{icon: string; text: string; onClick: () => void;}>}
|
||||
*/
|
||||
function generateAddMenuNavItems($item, onClick)
|
||||
{
|
||||
const items = canAddDivider($item)
|
||||
? [
|
||||
{
|
||||
icon: 'icon-minus',
|
||||
text: langData.divider,
|
||||
onClick: () => {
|
||||
onClick('divider');
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
const data = getMenuNavData();
|
||||
const allAppCodeSet = new Set(allAppsItemsMap.keys());
|
||||
for(const {name} of data)
|
||||
{
|
||||
if(name === 'divider') continue;
|
||||
allAppCodeSet.delete(name);
|
||||
}
|
||||
|
||||
if(allAppCodeSet.size === 0) return items;
|
||||
for(const name of allAppCodeSet)
|
||||
{
|
||||
const [icon, title] = getAppItemIconAndTitle(name);
|
||||
items.push(
|
||||
{
|
||||
icon,
|
||||
text: title,
|
||||
onClick: () => {
|
||||
onClick(name);
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
$(document).on('contextmenu', '#menuMainNav .divider', function(event)
|
||||
{
|
||||
const $divider = $(this);
|
||||
const $nav = $divider.closest('.nav');
|
||||
const isMoving = $nav.is('[z-use-sortable]');
|
||||
const items = [];
|
||||
if(isMoving)
|
||||
{
|
||||
items.push(
|
||||
{
|
||||
text: langData.save,
|
||||
onClick: () => {
|
||||
$divider.closest('.nav').zui().destroy();
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
items.push(
|
||||
{
|
||||
text: langData.sort,
|
||||
onClick: () => {
|
||||
const sortable = new zui.Sortable(
|
||||
'#menuMainNav',
|
||||
{
|
||||
animation: 150,
|
||||
ghostClass: 'bg-primary-pale',
|
||||
onSort: () => {
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
items.push(
|
||||
{
|
||||
text: langData.hide,
|
||||
onClick: () => {
|
||||
const $li = $divider.closest('li');
|
||||
$li.remove();
|
||||
refreshMenu();
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
const toAddedItems = generateAddMenuNavItems($divider, addMenuToMainNavCb($divider));
|
||||
items.push(
|
||||
toAddedItems.length === 0
|
||||
? {
|
||||
text: langData.add,
|
||||
disabled: true,
|
||||
}
|
||||
: {
|
||||
text: langData.add,
|
||||
items: toAddedItems,
|
||||
}
|
||||
);
|
||||
items.push(
|
||||
{
|
||||
text: langData.restore,
|
||||
onClick: () => {
|
||||
initAppsMenu(allAppsItems);
|
||||
refreshMenu();
|
||||
restoreMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if(apps.openedMenu) apps.openedMenu.hide();
|
||||
apps.openedMenu = zui.ContextMenu.show(
|
||||
{
|
||||
hideOthers: true,
|
||||
element: $divider[0],
|
||||
placement: 'right-start',
|
||||
items: items,
|
||||
event: event,
|
||||
onClickItem: (info) => info.event.preventDefault()
|
||||
}
|
||||
);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
/* Bind events for app trigger */
|
||||
$(document).on('click', '.open-in-app,.show-in-app', function(e)
|
||||
{
|
||||
@@ -837,11 +1011,88 @@ $(document).on('click', '.open-in-app,.show-in-app', function(e)
|
||||
if(!code) return;
|
||||
|
||||
const app = apps.openedMap[code];
|
||||
const items = [{text: langData.open, disabled: app && getLastAppCode() === code, onClick: function(){showApp(code)}}];
|
||||
const items = [{text: langData.open, disabled: app && getLastAppCode() === code, onClick: () => showApp(code)}];
|
||||
if(app)
|
||||
{
|
||||
items.push({text: langData.reload, onClick: function(){reloadApp(code)}});
|
||||
if(code !== 'my') items.push({text: langData.close, onClick: function(){closeApp(code)}});
|
||||
items.push({text: langData.reload, onClick: () => reloadApp(code)});
|
||||
if(code !== 'my') items.push({text: langData.close, onClick: () => closeApp(code)});
|
||||
}
|
||||
|
||||
if($btn.closest('#menuMainNav').length !== 0)
|
||||
{
|
||||
const $nav = $btn.closest('.nav');
|
||||
const isMoving = $nav.is('[z-use-sortable]');
|
||||
if(isMoving)
|
||||
{
|
||||
items.push(
|
||||
{
|
||||
text: langData.save,
|
||||
onClick: () => {
|
||||
$btn.closest('.nav').zui().destroy();
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
items.push(
|
||||
{
|
||||
text: langData.sort,
|
||||
onClick: () => {
|
||||
const sortable = new zui.Sortable(
|
||||
'#menuMainNav',
|
||||
{
|
||||
animation: 150,
|
||||
ghostClass: 'bg-primary-pale',
|
||||
onSort: () => {
|
||||
saveMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const hideDisabled = code === 'my' || $btn.is('.active');
|
||||
items.push(
|
||||
{
|
||||
text: langData.hide,
|
||||
onClick: hideDisabled
|
||||
? null
|
||||
: () => {
|
||||
closeApp(code);
|
||||
const $li = $btn.closest('li');
|
||||
$li.remove();
|
||||
refreshMenu();
|
||||
saveMenuNavToServer();
|
||||
},
|
||||
disabled: hideDisabled,
|
||||
}
|
||||
);
|
||||
const toAddedItems = generateAddMenuNavItems($btn, addMenuToMainNavCb($btn.closest('li')));
|
||||
items.push(
|
||||
toAddedItems.length === 0
|
||||
? {
|
||||
text: langData.add,
|
||||
disabled: true,
|
||||
}
|
||||
: {
|
||||
text: langData.add,
|
||||
items: toAddedItems,
|
||||
}
|
||||
);
|
||||
items.push(
|
||||
{
|
||||
text: langData.restore,
|
||||
onClick: () => {
|
||||
initAppsMenu(allAppsItems);
|
||||
refreshMenu();
|
||||
restoreMenuNavToServer();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(apps.openedMenu) apps.openedMenu.hide();
|
||||
@@ -1066,3 +1317,95 @@ $(document).on('click', e =>
|
||||
$('#bizLink').removeClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
const allAppsItemsMap = new Map();
|
||||
$(document).ready(
|
||||
function()
|
||||
{
|
||||
for(const item of allAppsItems)
|
||||
{
|
||||
if(item === 'divider') continue;
|
||||
|
||||
allAppsItemsMap.set(item.code, item);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get icon and title of app item.
|
||||
*
|
||||
* @param {string} name app name
|
||||
* @returns {[string, string]}
|
||||
*/
|
||||
function getAppItemIconAndTitle(name)
|
||||
{
|
||||
if(!allAppsItemsMap.has(name)) return[];
|
||||
const item = allAppsItemsMap.get(name);
|
||||
const str = item.title;
|
||||
const regex = /class=["']icon (\S*)["']\>\<\/i\>\s(\S*)/;
|
||||
const matches = str.match(regex);
|
||||
|
||||
if(matches)
|
||||
{
|
||||
const icon = matches[1];
|
||||
const text = matches[2].trim();
|
||||
return [icon, text];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu item to #mainNav callback, used by generateAddMenuNavItems.
|
||||
*
|
||||
* @param {Cash} $li menu item li
|
||||
* @returns {(name: string) => void}
|
||||
*/
|
||||
function addMenuToMainNavCb($li) {
|
||||
return (name) => {
|
||||
if(name === 'divider')
|
||||
{
|
||||
$li.after('<li class="divider"></li>');
|
||||
refreshMenu();
|
||||
return
|
||||
}
|
||||
|
||||
let item = allAppsItemsMap.get(name);
|
||||
const oldItem = apps.map[item.code];
|
||||
if(oldItem !== item && oldItem) item = $.extend({}, oldItem, item, {active: false});
|
||||
item.external = item.external || item.url && item.url.includes('://');
|
||||
|
||||
const $link= $('<a data-pos="menu"></a>')
|
||||
.attr('data-app', item.notApp ? undefined : item.code)
|
||||
.attr('href', item.url || '#')
|
||||
.attr('target', item.notApp ? '_blank' : undefined)
|
||||
.addClass('rounded' + (item.notApp ? '' : ' show-in-app'))
|
||||
.html(item.title, false);
|
||||
|
||||
item.icon = item.icon || ($link.find('.icon').attr('class') || '').replace('icon ', '');
|
||||
item.text = $link.text().trim();
|
||||
$link.html('<i class="icon ' + item.icon + '"></i><span class="text">' + item.text + '</span>', false);
|
||||
if(['devops', 'bi', 'safe'].includes(item.code)) $link.find('.text').addClass('font-brand');
|
||||
apps.map[item.code] = item;
|
||||
|
||||
$('<li class="hint-right"></li>')
|
||||
.attr({'data-app': item.code, 'data-hint': item.text})
|
||||
.append($link)
|
||||
.insertAfter($li);
|
||||
|
||||
refreshMenu();
|
||||
if(!apps.defaultCode) apps.defaultCode = item.code;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether current element can add a divider.
|
||||
* @param {Cash} $item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canAddDivider($item)
|
||||
{
|
||||
$item = $item.closest('li');
|
||||
if($item.is('.divider')) return false;
|
||||
if($item.next().is('.divider')) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -6,9 +6,15 @@ $lang->index->pleaseInput = 'Please input';
|
||||
$lang->index->search = 'Search';
|
||||
|
||||
$lang->index->dock = new stdClass();
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->sort = 'Sort';
|
||||
$lang->index->dock->save = 'Exit sort';
|
||||
$lang->index->dock->hide = 'Hide';
|
||||
$lang->index->dock->add = 'Add';
|
||||
$lang->index->dock->divider = 'Divider';
|
||||
$lang->index->dock->restore = 'Restore defaults';
|
||||
|
||||
$lang->index->upgradeVersion = 'Upgradable version';
|
||||
$lang->index->upgradeNow = 'Upgrade now';
|
||||
|
||||
@@ -6,9 +6,15 @@ $lang->index->pleaseInput = 'Enter';
|
||||
$lang->index->search = 'Search';
|
||||
|
||||
$lang->index->dock = new stdClass();
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->sort = 'Sort';
|
||||
$lang->index->dock->save = 'Exit sort';
|
||||
$lang->index->dock->hide = 'Hide';
|
||||
$lang->index->dock->add = 'Add';
|
||||
$lang->index->dock->divider = 'Divider';
|
||||
$lang->index->dock->restore = 'Restore defaults';
|
||||
|
||||
$lang->index->upgradeVersion = 'Upgradable version';
|
||||
$lang->index->upgradeNow = 'Upgrade now';
|
||||
|
||||
@@ -6,9 +6,15 @@ $lang->index->pleaseInput = 'Please input';
|
||||
$lang->index->search = 'Search';
|
||||
|
||||
$lang->index->dock = new stdClass();
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->open = 'Open';
|
||||
$lang->index->dock->reload = 'Reload';
|
||||
$lang->index->dock->close = 'Close';
|
||||
$lang->index->dock->sort = 'Sort';
|
||||
$lang->index->dock->save = 'Exit sort';
|
||||
$lang->index->dock->hide = 'Hide';
|
||||
$lang->index->dock->add = 'Add';
|
||||
$lang->index->dock->divider = 'Divider';
|
||||
$lang->index->dock->restore = 'Restore defaults';
|
||||
|
||||
$lang->index->upgradeVersion = 'Upgradable version';
|
||||
$lang->index->upgradeNow = 'Upgrade now';
|
||||
|
||||
@@ -6,9 +6,15 @@ $lang->index->pleaseInput = '请输入';
|
||||
$lang->index->search = '搜索';
|
||||
|
||||
$lang->index->dock = new stdClass();
|
||||
$lang->index->dock->open = '打开';
|
||||
$lang->index->dock->reload = '刷新';
|
||||
$lang->index->dock->close = '关闭';
|
||||
$lang->index->dock->open = '打开';
|
||||
$lang->index->dock->reload = '刷新';
|
||||
$lang->index->dock->close = '关闭';
|
||||
$lang->index->dock->sort = '排序';
|
||||
$lang->index->dock->save = '退出排序';
|
||||
$lang->index->dock->hide = '隐藏';
|
||||
$lang->index->dock->add = '添加';
|
||||
$lang->index->dock->divider = '分割线';
|
||||
$lang->index->dock->restore = '恢复默认';
|
||||
|
||||
$lang->index->upgradeVersion = '可升级版本';
|
||||
$lang->index->upgradeNow = '现在升级';
|
||||
|
||||
@@ -6,9 +6,15 @@ $lang->index->pleaseInput = '請輸入';
|
||||
$lang->index->search = '搜索';
|
||||
|
||||
$lang->index->dock = new stdClass();
|
||||
$lang->index->dock->open = '打開';
|
||||
$lang->index->dock->reload = '刷新';
|
||||
$lang->index->dock->close = '關閉';
|
||||
$lang->index->dock->open = '打開';
|
||||
$lang->index->dock->reload = '刷新';
|
||||
$lang->index->dock->close = '關閉';
|
||||
$lang->index->dock->sort = '排序';
|
||||
$lang->index->dock->save = '退出排序';
|
||||
$lang->index->dock->hide = '隱藏';
|
||||
$lang->index->dock->add = '添加';
|
||||
$lang->index->dock->divider = '分割線';
|
||||
$lang->index->dock->restore = '恢復預設';
|
||||
|
||||
$lang->index->upgradeVersion = '可升級版本';
|
||||
$lang->index->upgradeNow = '現在升級';
|
||||
|
||||
@@ -106,6 +106,7 @@ jsVar('vision', $config->vision);
|
||||
jsVar('navGroup', $lang->navGroup);
|
||||
jsVar('oldPages', $config->index->oldPages);
|
||||
jsVar('appsItems', $appsItems);
|
||||
jsVar('allAppsItems', $allAppsItems);
|
||||
jsVar('defaultOpen', !empty($open) ? $open : '');
|
||||
jsVar('manualText', $lang->manual);
|
||||
jsVar('manualUrl', ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme']);
|
||||
|
||||
+19
-1
@@ -976,7 +976,7 @@ class projectTao extends projectModel
|
||||
*/
|
||||
protected function setMenuByModel(string $projectModel): bool
|
||||
{
|
||||
global $lang;
|
||||
global $lang, $config;
|
||||
$model = 'scrum';
|
||||
if(in_array($projectModel, $this->config->project->waterfallList))
|
||||
{
|
||||
@@ -1000,6 +1000,24 @@ class projectTao extends projectModel
|
||||
|
||||
if(isset($lang->$model))
|
||||
{
|
||||
$key = 'project-' . $model;
|
||||
if(isset($config->customMenu->$key))
|
||||
{
|
||||
$lang->{$model}->menuOrder = array();
|
||||
$lang->{$model}->dividerMenu = '';
|
||||
|
||||
$items = json_decode($config->customMenu->$key);
|
||||
$prevItem = '';
|
||||
foreach($items as $item)
|
||||
{
|
||||
$lang->{$model}->menuOrder[$item->order] = $item->name;
|
||||
if($prevItem == 'divider') $lang->{$model}->dividerMenu .= $item->name . ',';
|
||||
$prevItem = $item->name;
|
||||
}
|
||||
|
||||
if($lang->{$model}->dividerMenu) $lang->{$model}->dividerMenu = ',' . trim($lang->{$model}->dividerMenu, ',') . ',';
|
||||
}
|
||||
|
||||
$lang->project->menu = $lang->{$model}->menu;
|
||||
$lang->project->menuOrder = $lang->{$model}->menuOrder;
|
||||
$lang->project->dividerMenu = $lang->{$model}->dividerMenu;
|
||||
|
||||
Reference in New Issue
Block a user