Merge branch 'main' into feature/task

This commit is contained in:
sunguangming
2024-11-20 00:39:30 +00:00
443 changed files with 12025 additions and 3987 deletions
+2
View File
@@ -1,3 +1,5 @@
config/cache.php liugang@easycorp.ltd zhujinyong@easycorp.ltd
framework/** liugang@easycorp.ltd zhujinyong@easycorp.ltd
lib/base/** liugang@easycorp.ltd zhujinyong@easycorp.ltd
lib/cache/** liugang@easycorp.ltd zhujinyong@easycorp.ltd
CODEOWNERS liugang@easycorp.ltd zhujinyong@easycorp.ltd qishiyao@easycorp.ltd
+3
View File
@@ -1,6 +1,7 @@
VERSION = $(shell head -n 1 VERSION)
XUANVERSION = $(shell jq -r .pkg.xuanxuan.gitVersion < ci.json)
XVERSION = $(shell jq -r .pkg.xuanxuan.version < ci.json)
SUITEVERSION = $(shell jq -r .pkg.blocksuite.version < ci.json)
XHPROF_VERSION = 2.3.9
XUANPATH := $(XUANXUAN_SRC_PATH)
@@ -28,6 +29,8 @@ clean:
rm -f *.sh
rm -f *.deb *.rpm
common:
curl https://$(GITFOX_HOST)/_artifacts/zentao/raw/zui3/static/blocksuite/$(SUITEVERSION)/blocksuite-$(SUITEVERSION).tar.gz | tar zxf - -C www/js/zui3/editor/
mkdir zentaopms
cp -fr api zentaopms/
cp -fr bin zentaopms/
+1 -1
View File
@@ -1 +1 @@
21.0
21.1
+7 -12
View File
@@ -23,25 +23,20 @@ class docEntry extends entry
$this->resetOpenApp($this->param('tab', 'doc'));
$control = $this->loadController('doc', 'view');
$control->view($docID);
$data = $this->getData();
if(!$data or !isset($data->status)) return $this->send400('error');
if(isset($data->status) and $data->status == 'fail') return $this->sendError(zget($data, 'code', 400), $data->message);
$doc = $data->data->doc;
$doc = $this->loadModel('doc')->getByID($docID, 0, true);
if(!$doc) return $this->send400('error');
unset($doc->draft);
if(!empty($doc->files)) $doc->files = array_values((array)$doc->files);
/* Set lib name */
$doc->libName = $data->data->lib->name;
$lib = null;
if($doc->lib) $lib = $this->doc->getLibByID((int)$doc->lib);
$doc->libName = $lib ? $lib->name : '';
$preAndNext = $data->data->preAndNext;
$doc->preAndNext = array();
$doc->preAndNext['pre'] = $preAndNext->pre ? $preAndNext->pre->id : '';
$doc->preAndNext['next'] = $preAndNext->next ? $preAndNext->next->id : '';
$doc->preAndNext['pre'] = '';
$doc->preAndNext['next'] = '';
return $this->send(200, $this->format($doc, 'addedBy:user,addedDate:time,assignedTo:user,assignedDate:date,editedBy:user,editedDate:time,mailto:userList'));
}
+3
View File
@@ -15,6 +15,9 @@
},
"downgradeExt": {
"gitRepo": "ci/zentaoext-downgrade"
},
"blocksuite": {
"version": "0.17.32"
}
},
"image": {
+1 -1
View File
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
$config->version = '21.0'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->version = '21.1'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
+6 -3
View File
@@ -296,7 +296,12 @@ $config->logonMethods[] = 'instance.install';
$config->logonMethods[] = 'instance.setting';
$config->logonMethods[] = 'instance.upgrade';
$config->logonMethods[] = 'instance.view';
$config->logonMethods[] = 'instance.backuplist';
$config->logonMethods[] = 'instance.visit';
$config->logonMethods[] = 'instance.manualbackup';
$config->logonMethods[] = 'instance.backupsettings';
$config->logonMethods[] = 'instance.autobackup';
$config->logonMethods[] = 'instance.ajaxRestore';
$config->logonMethods[] = 'jenkins.create';
$config->logonMethods[] = 'jenkins.edit';
$config->logonMethods[] = 'kanban.activatecard';
@@ -461,7 +466,6 @@ define('TABLE_MR', '`' . $config->db->prefix . 'mr`');
define('TABLE_MRAPPROVAL', '`' . $config->db->prefix . 'mrapproval`');
define('TABLE_SERVERROOM', '`' . $config->db->prefix . 'serverroom`');
define('TABLE_ACCOUNT', '`' . $config->db->prefix . 'account`');
define('TABLE_HOST', '`' . $config->db->prefix . 'host`');
define('TABLE_REPO', '`' . $config->db->prefix . 'repo`');
define('TABLE_RELATION', '`' . $config->db->prefix . 'relation`');
@@ -518,7 +522,6 @@ define('TABLE_BUDGET', '`' . $config->db->prefix . 'budget`');
define('TABLE_CMCL', '`' . $config->db->prefix . 'cmcl`');
define('TABLE_DEPLOY', '`' . $config->db->prefix . 'deploy`');
define('TABLE_DEPLOYPRODUCT', '`' . $config->db->prefix . 'deployproduct`');
define('TABLE_DEPLOYSCOPE', '`' . $config->db->prefix . 'deployscope`');
define('TABLE_DEPLOYSTEP', '`' . $config->db->prefix . 'deploystep`');
define('TABLE_DERIVEMEAS', '`' . $config->db->prefix . 'derivemeas`');
define('TABLE_DOMAIN', '`' . $config->db->prefix . 'domain`');
@@ -696,7 +699,6 @@ $config->objectTables['privmanager'] = TABLE_PRIVMANAGER;
$config->objectTables['privrelation'] = TABLE_PRIVRELATION;
$config->objectTables['scene'] = TABLE_SCENE;
$config->objectTables['pivot'] = TABLE_PIVOT;
$config->objectTables['account'] = TABLE_ACCOUNT;
$config->objectTables['serverroom'] = TABLE_SERVERROOM;
$config->objectTables['host'] = TABLE_ZAHOST;
$config->objectTables['instance'] = TABLE_INSTANCE;
@@ -720,6 +722,7 @@ $config->objectTables['marketresearch'] = TABLE_PROJECT;
$config->objectTables['researchstage'] = TABLE_PROJECT;
$config->objectTables['workflowgroup'] = TABLE_WORKFLOWGROUP;
$config->objectTables['productline'] = TABLE_MODULE;
$config->objectTables['repocommit'] = TABLE_REPOHISTORY;
$config->newFeatures = array('introduction', 'tutorial', 'youngBlueTheme', 'visions', 'aiPrompts', 'promptDesign', 'promptExec');
$config->disabledFeatures = '';
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -30,13 +30,13 @@ ALTER TABLE `zt_workflowui` ADD `group` mediumint(8) unsigned NOT NULL DEFAULT '
UPDATE `zt_workflow` SET `role` = 'custom' WHERE `buildin` = '0';
ALTER TABLE `zt_workflow` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflow`;
CREATE UNIQUE INDEX `unique` ON `zt_workflow`(`group`,`app`,`module`,`vision`);
ALTER TABLE `zt_workflowfield` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowfield`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowfield`(`group`,`module`,`field`);
ALTER TABLE `zt_workflowaction` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowaction`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowaction`(`group`,`module`,`action`,`vision`);
ALTER TABLE `zt_workflowlayout` DROP INDEX `unique`;
DROP INDEX `unique` ON `zt_workflowlayout`;
CREATE UNIQUE INDEX `unique` ON `zt_workflowlayout`(`group`,`module`,`action`,`ui`,`field`,`vision`);
ALTER TABLE `zt_product` ADD `workflowGroup` int(8) NOT NULL DEFAULT '0' AFTER `ticket`;
+51
View File
@@ -0,0 +1,51 @@
ALTER TABLE `zt_host` DROP COLUMN `serverModel`;
ALTER TABLE `zt_host` DROP COLUMN `hardwareType`;
ALTER TABLE `zt_host` DROP COLUMN `cpuBrand`;
ALTER TABLE `zt_host` DROP COLUMN `cpuModel`;
ALTER TABLE `zt_host` DROP COLUMN `provider`;
ALTER TABLE `zt_deploystep` DROP COLUMN `begin`;
ALTER TABLE `zt_deploystep` DROP COLUMN `end`;
ALTER TABLE `zt_deploystep` ADD `parent` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `deploy`;
DROP TABLE IF EXISTS `zt_deployscope`;
ALTER TABLE `zt_deploy` ADD `host` varchar(255) NOT NULL DEFAULT '' AFTER `name`;
ALTER TABLE `zt_deployproduct` DROP COLUMN `package`;
UPDATE `zt_deploy` SET `status` = 'fail' WHERE `status` = 'done' AND `result` = 'fail';
UPDATE `zt_deploy` SET `status` = 'success' WHERE `status` = 'done';
REPLACE INTO `zt_lang` (`lang`, `module`, `section`, `key`, `value`, `system`, `vision`) VALUES
('zh-cn', 'custom', 'relationList', '1', '{\"relation\":\"\\u76f8\\u5173\",\"relativeRelation\":\"\\u76f8\\u5173\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '2', '{\"relation\":\"\\u4f9d\\u8d56\",\"relativeRelation\":\"\\u88ab\\u4f9d\\u8d56\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '3', '{\"relation\":\"\\u91cd\\u590d\",\"relativeRelation\":\"\\u91cd\\u590d\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '4', '{\"relation\":\"\\u5f15\\u7528\",\"relativeRelation\":\"\\u88ab\\u5f15\\u7528\"}', '0', 'all'),
('en', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('en', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('en', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('en', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('de', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('de', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('de', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('de', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('fr', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('fr', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('fr', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('fr', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '1', '{\"relation\":\"\\u76f8\\u95dc\",\"relativeRelation\":\"\\u76f8\\u95dc\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '2', '{\"relation\":\"\\u4f9d\\u8cf4\",\"relativeRelation\":\"\\u88ab\\u4f9d\\u8cf4\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '3', '{\"relation\":\"\\u91cd\\u8907\",\"relativeRelation\":\"\\u91cd\\u8907\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '4', '{\"relation\":\"\\u5f15\\u7528\",\"relativeRelation\":\"\\u88ab\\u5f15\\u7528\"}', '0', 'all');
INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`) VALUES
('0', '2', '*', '*', '*', 'moduleName=upgrade&methodName=ajaxProcessObjectRelation', '更新关联关系', 'zentao', 1, 'normal');
UPDATE `zt_action` SET `action` = 'canceled' WHERE `objectType` = 'deploy' AND `action` = 'activated';
DROP TABLE IF EXISTS `zt_account`;
DELETE FROM `zt_lang` WHERE `module` = 'host' AND `section` = 'cpuBrandList';
DELETE FROM `zt_action` WHERE `objectType` = 'account';
DELETE FROM `zt_actionrecent` WHERE `objectType` = 'account';
DELETE FROM `zt_config` WHERE `module` ='common' AND `section` = 'zentaoWebsite';
+24
View File
@@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS `zt_system` (
`id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL DEFAULT '',
`product` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`integrated` ENUM('0','1') NOT NULL DEFAULT '0',
`latestRelease` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`latestDate` DATETIME NULL,
`children` VARCHAR(255) NOT NULL DEFAULT '',
`status` ENUM('active','inactive') NOT NULL DEFAULT 'active',
`desc` mediumtext NULL,
`createdBy` VARCHAR(30) NOT NULL DEFAULT '',
`createdDate` DATETIME NULL,
`editedBy` VARCHAR(30) NOT NULL DEFAULT '',
`editedDate` DATETIME NULL,
`deleted` ENUM('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX `idx_product` ON `zt_system`(`product`);
CREATE INDEX `idx_status` ON `zt_system`(`status`);
ALTER TABLE `zt_release` ADD `system` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0' AFTER `name`;
ALTER TABLE `zt_release` ADD `releases` VARCHAR(255) NOT NULL DEFAULT '' AFTER `system`;
CREATE INDEX `idx_system` ON `zt_release`(`system`);
+48 -42
View File
@@ -1630,6 +1630,8 @@ CREATE TABLE IF NOT EXISTS `zt_release` (
`shadow` mediumint(8) unsigned NOT NULL default '0',
`build` varchar(255) NOT NULL DEFAULT '',
`name` varchar(255) NOT NULL default '',
`system` mediumint(8) unsigned NOT NULL default '0',
`releases` varchar(255) NOT NULL default '',
`marker` enum('0','1') NOT NULL default '0',
`date` date NULL,
`releasedDate` date NULL,
@@ -1648,6 +1650,7 @@ CREATE TABLE IF NOT EXISTS `zt_release` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX `product` ON `zt_release` (`product`);
CREATE INDEX `build` ON `zt_release` (`build`);
CREATE INDEX `idx_system` ON `zt_release` (`system`);
-- DROP TABLE IF EXISTS `zt_repo`;
CREATE TABLE IF NOT EXISTS `zt_repo` (
@@ -2658,30 +2661,6 @@ CREATE TABLE IF NOT EXISTS `zt_serverroom` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_account`;
CREATE TABLE IF NOT EXISTS `zt_account` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '',
`type` varchar(255) NOT NULL DEFAULT '',
`provider` varchar(255) NOT NULL DEFAULT '',
`adminURI` varchar(255) NOT NULL DEFAULT '',
`account` varchar(255) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`mobile` varchar(255) NOT NULL DEFAULT '',
`extra` text NULL,
`createdBy` varchar(30) NOT NULL DEFAULT '',
`createdDate` datetime NULL,
`editedBy` varchar(30) NOT NULL DEFAULT '',
`editedDate` datetime NULL,
`status` varchar(30) NOT NULL DEFAULT '',
`deleted` enum('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX `name` ON `zt_account` (`name`);
CREATE INDEX `provider` ON `zt_account` (`provider`);
CREATE INDEX `status` ON `zt_account` (`status`);
-- DROP TABLE IF EXISTS `zt_host`;
CREATE TABLE `zt_host` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
@@ -2700,7 +2679,6 @@ CREATE TABLE `zt_host` (
`vsoft` varchar(30) NOT NULL DEFAULT '',
`heartbeat` datetime NULL,
`zap` varchar(10) NOT NULL DEFAULT '',
`provider` varchar(255) NOT NULL DEFAULT '',
`vnc` int(11) NOT NULL DEFAULT '0',
`ztf` int(11) NOT NULL DEFAULT '0',
`zd` int(11) NOT NULL DEFAULT '0',
@@ -2709,10 +2687,6 @@ CREATE TABLE `zt_host` (
`image` int(11) unsigned NOT NULL DEFAULT '0',
`admin` smallint(5) unsigned NOT NULL DEFAULT '0',
`serverRoom` mediumint(8) unsigned NOT NULL DEFAULT '0',
`serverModel` varchar(256) NOT NULL DEFAULT '',
`hardwareType` varchar(64) NOT NULL DEFAULT '',
`cpuBrand` varchar(128) NOT NULL DEFAULT '',
`cpuModel` varchar(128) NOT NULL DEFAULT '',
`cpuNumber` varchar(16) NOT NULL DEFAULT '',
`cpuCores` varchar(30) NOT NULL DEFAULT '',
`intranet` varchar(128) NOT NULL DEFAULT '',
@@ -2971,6 +2945,7 @@ CREATE TABLE IF NOT EXISTS `zt_deploy` (
`begin` datetime NULL,
`end` datetime NULL,
`name` varchar(255) NOT NULL DEFAULT '',
`host` varchar(255) NOT NULL DEFAULT '',
`desc` mediumtext NULL,
`status` varchar(20) NOT NULL DEFAULT '',
`owner` char(30) NOT NULL DEFAULT '',
@@ -2988,8 +2963,7 @@ CREATE TABLE IF NOT EXISTS `zt_deploy` (
CREATE TABLE IF NOT EXISTS `zt_deployproduct` (
`deploy` mediumint(8) unsigned NOT NULL DEFAULT '0',
`product` mediumint(8) unsigned NOT NULL DEFAULT '0',
`release` mediumint(8) unsigned NOT NULL DEFAULT '0',
`package` varchar(255) NOT NULL DEFAULT ''
`release` mediumint(8) unsigned NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE UNIQUE INDEX `deploy_product_release` ON `zt_deployproduct`(`deploy`,`product`,`release`);
@@ -2997,9 +2971,8 @@ CREATE UNIQUE INDEX `deploy_product_release` ON `zt_deployproduct`(`deploy`,`pro
CREATE TABLE IF NOT EXISTS `zt_deploystep` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`deploy` mediumint(8) unsigned NOT NULL DEFAULT '0',
`parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
`title` varchar(255) NOT NULL DEFAULT '',
`begin` datetime NULL,
`end` datetime NULL,
`stage` varchar(30) NOT NULL DEFAULT '',
`content` text NULL,
`status` varchar(30) NOT NULL DEFAULT '',
@@ -3013,15 +2986,6 @@ CREATE TABLE IF NOT EXISTS `zt_deploystep` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_deployscope`;
CREATE TABLE IF NOT EXISTS `zt_deployscope` (
`deploy` mediumint(8) unsigned NOT NULL DEFAULT '0',
`service` mediumint(8) unsigned NOT NULL DEFAULT '0',
`hosts` text NULL,
`remove` text NULL,
`add` text NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_traincourse`;
CREATE TABLE IF NOT EXISTS `zt_traincourse` (
`id` mediumint(8) NOT NULL AUTO_INCREMENT,
@@ -16117,3 +16081,45 @@ CREATE INDEX `idx_repo_deleted` ON `zt_job` (`repo`,`deleted`);
INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES
('*/5', '*', '*', '*', '*', 'moduleName=program&methodName=refreshStats', '刷新项目集统计数据', 'zentao', 1, 'normal', NULL),
('*/5', '*', '*', '*', '*', 'moduleName=product&methodName=refreshStats', '刷新产品统计数据', 'zentao', 1, 'normal', NULL);
REPLACE INTO `zt_lang` (`lang`, `module`, `section`, `key`, `value`, `system`, `vision`) VALUES
('zh-cn', 'custom', 'relationList', '1', '{\"relation\":\"\\u76f8\\u5173\",\"relativeRelation\":\"\\u76f8\\u5173\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '2', '{\"relation\":\"\\u4f9d\\u8d56\",\"relativeRelation\":\"\\u88ab\\u4f9d\\u8d56\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '3', '{\"relation\":\"\\u91cd\\u590d\",\"relativeRelation\":\"\\u91cd\\u590d\"}', '0', 'all'),
('zh-cn', 'custom', 'relationList', '4', '{\"relation\":\"\\u5f15\\u7528\",\"relativeRelation\":\"\\u88ab\\u5f15\\u7528\"}', '0', 'all'),
('en', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('en', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('en', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('en', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('de', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('de', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('de', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('de', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('fr', 'custom', 'relationList', '1', '{\"relation\":\"Relate\",\"relativeRelation\":\"Relate\"}', '0', 'all'),
('fr', 'custom', 'relationList', '2', '{\"relation\":\"Dependence\",\"relativeRelation\":\"Depended On\"}', '0', 'all'),
('fr', 'custom', 'relationList', '3', '{\"relation\":\"Repetition\",\"relativeRelation\":\"Repetition\"}', '0', 'all'),
('fr', 'custom', 'relationList', '4', '{\"relation\":\"Quote\",\"relativeRelation\":\"Quoted\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '1', '{\"relation\":\"\\u76f8\\u95dc\",\"relativeRelation\":\"\\u76f8\\u95dc\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '2', '{\"relation\":\"\\u4f9d\\u8cf4\",\"relativeRelation\":\"\\u88ab\\u4f9d\\u8cf4\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '3', '{\"relation\":\"\\u91cd\\u8907\",\"relativeRelation\":\"\\u91cd\\u8907\"}', '0', 'all'),
('zh-tw', 'custom', 'relationList', '4', '{\"relation\":\"\\u5f15\\u7528\",\"relativeRelation\":\"\\u88ab\\u5f15\\u7528\"}', '0', 'all');
CREATE TABLE IF NOT EXISTS `zt_system` (
`id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL DEFAULT '',
`product` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`integrated` ENUM('0','1') NOT NULL DEFAULT '0',
`latestRelease` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`latestDate` DATETIME NULL,
`children` VARCHAR(255) NOT NULL DEFAULT '',
`status` ENUM('active','inactive') NOT NULL DEFAULT 'active',
`desc` mediumtext NULL,
`createdBy` VARCHAR(30) NOT NULL DEFAULT '',
`createdDate` DATETIME NULL,
`editedBy` VARCHAR(30) NOT NULL DEFAULT '',
`editedDate` DATETIME NULL,
`deleted` ENUM('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX `idx_product` ON `zt_system`(`product`);
CREATE INDEX `idx_status` ON `zt_system`(`status`);
+79
View File
@@ -1,3 +1,82 @@
2024-11-15 21.1
完成的需求
开源版:
69184 在左侧目录树和下拉菜单中增加接口库的显示
68988 接口空间支持进入产品接口和项目接口列表页
68963 将接口空间中的版本管理和发布接口功能入口合并且移动到左侧树形导航中
68957 项目空间首页优化
68954 产品空间首页优化
68953 在接口空间的左侧树形导航中增加接口目录的分组名
68952 将接口空间中的数据结构功能移动到左侧树形导航中
68951 接口空间详情页对文档的操作功能入口调整
68940 接口空间支持进入独立接口列表页
68938 接口空间新增首页
68609 详情页面优化
68514 主机创建页面字段优化
68432 编辑页面优化
68347 优化主机列表
67973 接口空间下面包屑路径导航新增下拉菜单
企业版:
69158 上线申请的用例增加执行按钮
68946 删除账号、CPU品牌功能
68600 实现上线申请的完成功能
68589 上线步骤改为列表样式
68512 优化上线步骤的样式
68511 重新调整上线申请详情页面的排版
68504 简化上线申请的功能
68501 实现上线申请的取消上线功能
68429 打印部署的三级菜单
68422 实现上线申请的删除功能
68421 实现上线申请的上线功能
68417 实现编辑上线申请的功能
68416 实现上线申请列表的搜索功能
68412 实现上线申请列表的功能
68338 创建上线申请页面优化
修复的Bug
开源版:
51916 文档左侧树选中后刷新导致无法定位
53420 wiki中添加图片不在附件库中展示 (Feedback#5252)
53421 wiki中删除附件,附件库中没有同步删除 (Feedback#5251)
54306 在文档中创建空间、库和文档定位有问题
54307 在我的空间中我创建的列表里,点击面包屑导航定位有误
54817 通过链接打开文档,左侧目录未定位至文档所在位置
55664 评审为有待明确的需求,编辑保存重新发起评审后评审人置灰了 (Feedback#7227)
55828 表单数据标题颜色在地盘待处理列表不生效 (Feedback#7235)
55870 敏捷项目的执行列表中执行名称前面没有展开/收起操作
56362 编辑Bug调整所属产品时,产品和影响版本、解决版本不匹配 (Feedback#7385)
56423 文档面包屑导航过长时会挤压检索标签
56513 快捷访问文档详情页中,所在位置的跳转不对
56771 执行任务列表下,搜索后点击研发需求排序报错 (Feedback#7553)
56787 发布接口弹窗中描述提示信息显示为desc
56866 按模块分配权限保存报错 (Feedback#7595)
56890 需求列表阶段阶段字段没有根据规则更新 (Feedback#7541)
56897 联系方式是单数时,用户编辑页样式异常 (Feedback#7648)
56932 任务列表状态列进行中/已完成/已暂停不显示颜色了
56938 评审研发需求代码报错
57000 执行没关联产品进入执行-测试下有报错
企业版:
55879 字段的“分类设置”数据源无效 (Feedback#7280)
56576 任务列表上,父任务不展示 研发需求变更 的确认按钮 (Feedback#7374)
56749 导入用户,提示密码小于系统设定
56868 工作流中自定义附件字段设置为必填,不上传附件保存时页面代码报错 (Feedback#7633)
56876 审批流中存在“自动同意”时,提交审批报错
56884 文档模板类型的分组树没有英文语言项
56887 创建文档模板类型后跳转至文档模板首页
56970 需求在执行中直接创建用例后需求详情页有报错
56984 审批流的发起动作没法显示在列表上
56996 工作流时间/日期类型字段设置不必填,置空保存时代码报错
57009 登记工时,输入10小时会显示成1小时 (Feedback#7674)
旗舰版:
55822 20系列版本中项目甘特图中缺少高亮显示当天日期 (Feedback#7245)
56900 审批流绑定无法关联到流程中字段 (Feedback#7623)
57031 审批流中存在“指定人员”时,其他用户发起立项审批出现报错
IPD版:
56901 执行需求详情页有代码报错
56956 OR界面产品路标页面1.5级导航下拉菜单报错
56974 提交概念决策评审提示为空
56990 IPD项目评审点审批通过后,会出现一个多余的评审点
57006 设计图表页点击查询有报错 (Feedback#7670)
2024-11-01 21.0
完成的需求
开源版:
+1 -3
View File
@@ -572,9 +572,7 @@ $lang->task->methodOrder[125] = 'import';
$lang->resource->doc = new stdclass();
$lang->resource->doc->index = 'index';
$lang->resource->doc->mySpace = 'mySpace';
$lang->resource->doc->myView = 'myView';
$lang->resource->doc->myCollection = 'myCollection';
$lang->resource->doc->myCreation = 'myCreation';
$lang->resource->doc->quick = 'quick';
$lang->resource->doc->createSpace = 'createSpace';
$lang->resource->doc->createLib = 'createLibAction';
$lang->resource->doc->editLib = 'editLibAction';
+1 -1
View File
@@ -344,7 +344,7 @@ class baseEntry
{
$this->app->loadModuleConfig($moduleName, $appName);
$this->app->loadLang($moduleName, $appName);
$this->dao = new dao($this->dao);
$this->dao = new dao($this->app);
return false;
}
+15
View File
@@ -1093,6 +1093,21 @@ class baseHelper
{
throw EndResponseException::create($content);
}
/**
* 将科学计数法转化为两位小数。
* Convert scientific notation to two decimal places.
*
* @param int|float|string $hours
* @param string $characters
* @static
* @access public
* @return float
*/
public static function formatHours($hours, $characters = '.0'): float
{
return (float)rtrim(number_format((float)$hours, 2, '.', ''), $characters);
}
}
//------------------------------- 常用函数。Some tool functions.-------------------------------//
-15
View File
@@ -433,21 +433,6 @@ class helper extends baseHelper
$checkFunc = 'check' . $operator;
return validater::$checkFunc($value1, $value2);
}
/**
* 将科学计数法转化为两位小数。
* Convert scientific notation to two decimal places.
*
* @param int|float|string $hours
* @param string $characters
* @static
* @access public
* @return float
*/
public static function formatHours($hours, $characters = '.0'): float
{
return (float)rtrim(number_format((float)$hours, 2, '.', ''), $characters);
}
}
/**
+2 -1
View File
@@ -262,6 +262,7 @@ class dao extends baseDAO
*/
public function checkExtend($fields)
{
global $lang;
if(!$fields) return $this;
foreach($fields as $field)
{
@@ -276,7 +277,7 @@ class dao extends baseDAO
if($rule->type != 'system' || $rule->rule != 'notempty') continue;
$files = !empty($_FILES[$field->field]) ? $_FILES[$field->field] : '';
if(empty($files['size'][0])) dao::$errors[$field->field][] = sprintf($this->lang->error->notempty, $field->name);
if(empty($files['size'][0])) dao::$errors[$field->field][] = sprintf($lang->error->notempty, $field->name);
break;
}
continue;
+2 -6
View File
@@ -376,12 +376,8 @@ class dbh
{
$sql = trim($sql);
$sql = $this->formatFunction($sql);
if(defined('IN_UPGRADE'))
{
$sql = $this->processDmChangeColumn($sql);
$sql = $this->processDmTableIndex($sql);
}
$sql = $this->processDmChangeColumn($sql);
$sql = $this->processDmTableIndex($sql);
$actionPos = strpos($sql, ' ');
$action = strtoupper(substr($sql, 0, $actionPos));
+1 -1
View File
@@ -87,7 +87,7 @@ class trace
$profiling = $this->dao->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC);
foreach($profiling as $key => $profile) $profiling[$key]['Duration'] = number_format($profile['Duration'], 4);
foreach($profiling as $key => $profile) $profiling[$key]['Duration'] = round($profile['Duration'], 4);
$this->trace['profiles'] = $profiling;
}
+1
View File
@@ -243,6 +243,7 @@ function queryFilterModal(): queryFilterModal {return createWg('queryFilterModal
function pivotTable(): pivotTable {return createWg('pivotTable', func_get_args());}
function pivotConfig(): pivotConfig {return createWg('pivotConfig', func_get_args());}
function iconPicker(): iconPicker {return createWg('iconPicker', func_get_args());}
function relatedObjectList(): relatedObjectList {return createWg('relatedObjectList', func_get_args());}
if(is_dir(__DIR__ . DS . 'wg' . DS . 'schedule'))
{
+5 -3
View File
@@ -78,8 +78,8 @@ class backBtn extends btn
'ai' => 'ai-models',
'api' => 'api-index',
'demand' => 'demand-browse,demand-view,my-work,my-contribute,feedback-adminview,feedback-admin,my-index,demandpool-track,product-dynamic',
'issue' => 'issue-browse,issue-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'risk' => 'risk-browse,risk-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'issue' => 'issue-browse,issue-view,my-index,project-index,project-dynamic,execution-dynamic,project-view,execution-view',
'risk' => 'risk-browse,risk-view,my-index,project-index,project-dynamic,execution-dynamic,project-view,execution-view',
'meeting' => 'meeting-browse,meeting-view,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'todo' => 'my-todo,my-effort',
'feedback' => 'my-effort,feedback-admin,feedback-browse,my-work,my-contribute',
@@ -92,7 +92,9 @@ class backBtn extends btn
'review' => 'review-browse,my-index,product-dynamic,project-dynamic,project-view,programplan-browse,project-execution',
'charter' => 'charter-browse',
'roadmap' => 'charter-view',
'approvalflow' => 'approvalflow-browse'
'approvalflow' => 'approvalflow-browse',
'host' => 'host-browse,my-index',
'deploy' => 'deploy-browse',
);
$props = parent::getProps();
+20 -17
View File
@@ -20,7 +20,7 @@ class bugRelatedList extends relatedList
if(!$bug) $bug = data('bug');
if(!$bug) return;
global $lang;
global $lang, $config;
$canViewCase = common::hasPriv('testcase', 'view');
$canViewStory = common::hasPriv('story', 'view');
@@ -31,24 +31,27 @@ class bugRelatedList extends relatedList
$data = array();
/* Related bugs. */
$relatedBugList = isset($bug->relatedBugTitles) ? $bug->relatedBugTitles :array();
$relatedBugItems = array();
foreach($relatedBugList as $relatedBugID => $relatedBugTitle)
if($config->edition == 'open')
{
$relatedBugItem = new stdclass();
$relatedBugItem->id = $relatedBugID;
$relatedBugItem->title = $relatedBugTitle;
$relatedBugItems[] = $relatedBugItem;
}
$relatedBugList = isset($bug->relatedBugTitles) ? $bug->relatedBugTitles :array();
$relatedBugItems = array();
foreach($relatedBugList as $relatedBugID => $relatedBugTitle)
{
$relatedBugItem = new stdclass();
$relatedBugItem->id = $relatedBugID;
$relatedBugItem->title = $relatedBugTitle;
$relatedBugItems[] = $relatedBugItem;
}
$data['relatedBug'] = array
(
'title' => $lang->bug->relatedBug,
'items' => $relatedBugItems,
'url' => createLink('bug', 'view', 'bugID={id}'),
'data-toggle' => 'modal',
'data-size' => 'lg'
);
$data['relatedBug'] = array
(
'title' => $lang->bug->relatedBug,
'items' => $relatedBugItems,
'url' => createLink('bug', 'view', 'bugID={id}'),
'data-toggle' => 'modal',
'data-size' => 'lg'
);
}
/* To cases. */
$toCaseList = isset($bug->toCases) ? $bug->toCases :array();
+17 -14
View File
@@ -13,7 +13,7 @@ class caseRelatedList extends relatedList
protected function created()
{
global $app, $lang;
global $config, $lang;
$case = $this->prop('case', data('case'));
if(!$case) return array();
@@ -26,23 +26,26 @@ class caseRelatedList extends relatedList
'url' => hasPriv('bug', 'view') ? createLink('bug', 'view', 'bugID={id}') : false
);
$linkCases = array();
foreach($case->linkCaseTitles as $caseID => $linkCaseTitle)
if($config->edition == 'open')
{
$linkCase = new \stdclass();
$linkCase->id = $caseID;
$linkCase->title = $linkCaseTitle;
$linkCases = array();
foreach($case->linkCaseTitles as $caseID => $linkCaseTitle)
{
$linkCase = new \stdclass();
$linkCase->id = $caseID;
$linkCase->title = $linkCaseTitle;
$linkCases[] = $linkCase;
$linkCases[] = $linkCase;
}
$data['linkCases'] = array
(
'title' => $lang->testcase->linkCase,
'items' => $linkCases,
'url' => hasPriv('testcase', 'view') ? createLink('testcase', 'view', 'caseID={id}') : false
);
}
$data['linkCases'] = array
(
'title' => $lang->testcase->linkCase,
'items' => $linkCases,
'url' => hasPriv('testcase', 'view') ? createLink('testcase', 'view', 'caseID={id}') : false
);
$this->setProp('data', $data);
}
}
+30
View File
@@ -0,0 +1,30 @@
.doc-app-header > .nav {--nav-active-bg: var(--color-primary-100); --nav-active-color: var(--color-fore); --nav-item-height: 24px; --nav-divider-margin: 8px;}
.doc-space-card-libs {min-height: 118px;}
.icon-api {text-align: center; --api-color-rgb: var(--color-success-500-rgb); color: rgba(var(--api-color-rgb), 1); margin: 0 3px;}
.icon-api::before {content: ' '; display: inline-block; width: 8px; height: 8px; border-radius: 2px; background-color: currentColor; min-width: 0;}
.api-list-item.is-GET, .icon-api.is-GET {--api-color-rgb: var(--color-success-500-rgb)}
.api-list-item.is-POST, .icon-api.is-POST {--api-color-rgb: var(--color-important-500-rgb)}
.api-list-item.is-DELETE, .icon-api.is-DELETE {--api-color-rgb: var(--color-danger-500-rgb)}
.api-list-item.is-PUT, .icon-api.is-PUT {--api-color-rgb: var(--color-important-500-rgb)}
.api-list-item.is-PATCH, .icon-api.is-PATCH {--api-color-rgb: var(--color-special-500-rgb)}
.api-list-item.is-OPTIONS, .icon-api.is-OPTIONS {--api-color-rgb: var(--color-special-500-rgb)}
.api-list-item.is-HEAD, .icon-api.is-HEAD {--api-color-rgb: var(--color-gray-500-rgb)}
.api-list-item {background-color: rgba(var(--api-color-rgb), .1);}
.api-list-item .api-method {background-color: rgba(var(--api-color-rgb), 1); color: var(--color-canvas)}
.api-list-item .api-title {color: rgba(var(--api-color-rgb), 1)}
.doc-app-mode-edit[data-lib-type="api"], .doc-app-mode-create[data-lib-type="api"] {--doc-view-max-width: 1100px}
.doc-app-mode-edit[data-lib-type="api"] .doc-view-content,
.doc-app-mode-create[data-lib-type="api"] .doc-view-content {padding: 0 24px;}
.doc-app-mode-edit[data-lib-type="api"] form .form-actions,
.doc-app-mode-create[data-lib-type="api"] form .form-actions {display: none;}
.doc-tree .item-inner>.item-icon {margin-right: -4px;}
.doc-list > .dtable {--dtable-border-color: transparent}
.api-lib-menu + .doc-tree > .tree {padding-top: 0;}
.dropdown-menu .divider + .divider {display: none;}
.dropmenu-item .tree-item-inner[data-type="lib"] {padding-left: 8px;}
+464 -30
View File
@@ -31,7 +31,7 @@ function getDocApp()
* @param {string} key
* @param {array|string|object} args
*/
function getLang(key, args)
window.getLang = function(key, args)
{
if(!docLangData)
{
@@ -61,6 +61,27 @@ function hasPriv(priv, value)
return value !== undefined ? setting === value : !!setting;
}
function isApiLib(lib, docApp)
{
if(typeof lib === 'number') lib = getDocApp().getLib(lib);
if(!lib)
{
docApp = docApp || getDocApp();
if(!docApp) return false;
lib = docApp.lib;
}
return lib && lib.data.type === 'api';
}
function isApiDoc(doc)
{
const docApp = getDocApp();
if(typeof doc !== 'object' && docApp) doc = docApp.getDoc(doc);
if(doc && doc.api !== undefined) return doc.api;
const id = typeof doc === 'object' ? doc.id : doc;
return String(id).startsWith('api.');
}
/**
* 处理对话框的操作,执行文档应用实例上的方法。
* Process dialog actions, execute methods on the doc app instance.
@@ -232,9 +253,22 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData)
keywords : doc.keywords,
acl : 'private',
space : spaceType,
uid : doc.contentType === 'doc' ? '' : (doc.uid || `doc${doc.id}`),
uid : (doc.uid || `doc${doc.id}`),
};
if(formData) mergeDocFormData(docData, formData);
const getErrorMessage = (res) => {
if(typeof res.message === 'string') return res.message;
if(typeof res.message === 'object' && res.message && Object.values(res.message).length)
{
for(const x of Object.values(res.message))
{
if(typeof x === 'string') return x;
if(Array.isArray(x) && x.length) return x[0];
}
}
if(typeof data.error === 'string') return data.error;
return getLang('errorOccurred');
};
return new Promise((resolve, reject) =>
{
$.post(url, docData, (res) =>
@@ -244,9 +278,13 @@ function submitNewDoc(doc, spaceID, libID, moduleID, formData)
const data = JSON.parse(res);
if(typeof data !== 'object' || data.result === 'fail')
{
throw new Error(data.message || data.error || getLang('errorOccurred'));
throw new Error(getErrorMessage(data));
}
resolve($.extend(doc, {id: data.id}, docData, data.doc, {status: doc.status || data.status}));
const newDoc = $.extend(doc, {id: data.id}, docData, data.doc, {status: doc.status || data.status});
if (!newDoc.space.includes('.')) {
newDoc.space = `${spaceType}.${docData[spaceType]}`;
}
resolve(newDoc);
}
catch (error)
{
@@ -290,9 +328,12 @@ function handleSaveDoc(doc)
keywords : doc.keywords,
acl : doc.acl,
space : spaceType,
uid : doc.contentType === 'doc' ? '' : (doc.uid || `doc${doc.id}`),
uid : (doc.uid || `doc${doc.id}`),
};
const docAppData = docApp.doc.data;
if (doc.id === docAppData.id) docData.mailto = docAppData.mailto;
if(savingDocData[doc.id])
{
mergeDocFormData(docData, savingDocData[doc.id]);
@@ -330,7 +371,7 @@ function handleSaveDoc(doc)
*/
function canMoveDoc(doc)
{
return getDocApp().props.privs.moveDoc;
return !String(doc.id).startsWith('api.') && getDocApp().props.privs.moveDoc;
}
/**
@@ -455,7 +496,8 @@ const actionsMap =
const lang = getLang();
const doc = info.data;
const canModify = canModifySpace();
const canEditDoc = canModify && hasPriv('edit');
const isApi = isApiDoc(doc);
const canEditDoc = canModify && hasPriv(isApi ? 'editApi' : 'edit');
/* 侧边栏上的文档操作按钮。Doc actions in sidebar. */
if(info.ui === 'sidebar')
@@ -475,14 +517,16 @@ const actionsMap =
const moreItems = [];
if(canModify && canMoveDoc(doc)) moreItems.push({icon: 'folder-move', text: lang.moveDoc, command: `moveDoc/${doc.id}`});
if(canModify && hasPriv('delete')) moreItems.push({icon: 'trash', text: lang.delete, command: `deleteDoc/${doc.id}`});
if(canModify && hasPriv('effort')) moreItems.push({icon: 'time', text: lang.effort, command: `effortDoc/${doc.id}`});
if(hasPriv('exportDoc')) moreItems.push({icon: 'export', text: lang.export, command: 'exportDoc'});
if(!isApi && canModify && hasPriv('effort')) moreItems.push({icon: 'time', text: lang.effort, command: `effortDoc/${doc.id}`});
if(!isApi && hasPriv('exportDoc')) moreItems.push({icon: 'export', text: lang.export, command: 'exportDoc'});
return [
hasPriv('collect', 'no') ? null : {icon: doc.isCollector ? 'star text-warning' : 'star-empty', text: doc.collects || null, hint: doc.isCollector ? lang.cancelCollection : lang.collect, rounded: 'lg', textClass: 'text-gray', type: 'ghost', command: hasPriv('collect') ? `collectDoc/${doc.id}` : null},
const actions = [
isApi ? null : (hasPriv('collect', 'no') ? null : {icon: doc.isCollector ? 'star text-warning' : 'star-empty', text: doc.collects || null, hint: doc.isCollector ? lang.cancelCollection : lang.collect, rounded: 'lg', textClass: 'text-gray', type: 'ghost', command: hasPriv('collect') ? `collectDoc/${doc.id}` : null}),
canEditDoc ? {icon: 'edit', type: 'ghost text-primary', hint: lang.edit, rounded: 'lg', command: 'startEditDoc'} : null,
moreItems.length ? {icon: 'icon-ellipsis-v', type: 'dropdown', rounded: 'lg', placement: 'bottom-end', caret: false, items: moreItems} : null,
];
return actions;
},
/**
@@ -503,9 +547,9 @@ const actionsMap =
return [{text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`, icon: 'plus', type: 'primary-pale'}];
}
if(canAddModule && info.ui !== 'space-card' && info.ui !== 'sidebar' && info.ui !== 'sidebar-toolbar') items.push({text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`});
if(canAddModule && info.ui !== 'space-card' && info.ui !== 'sidebar') items.push({text: lang.actions.addModule, command: `addModule/${lib.id}/0/${lib.id}/child`});
if(canModify && hasPriv('editLib')) items.push({text: lang.actions.editLib, command: `editLib/${lib.id}`});
if(canModify && hasPriv('moveLib') && info.ui !== 'space-card' && info.ui !== 'sidebar-toolbar') items.push({text: lang.moveTo, command: `moveLib/${lib.id}`});
if(canModify && lib.type !== 'api' && hasPriv('moveLib') && info.ui !== 'space-card' && info.ui !== 'sidebar-toolbar') items.push({text: lang.moveTo, command: `moveLib/${lib.id}`});
if(canModify && hasPriv('deleteLib')) items.push({text: lang.actions.deleteLib, command: `deleteLib/${lib.id}`});
if(!items.length) return;
@@ -580,9 +624,36 @@ const actionsMap =
*/
'doc-list': function(info)
{
const lang = getLang();
const docApp = getDocApp();
const canModify = canModifySpace();
const docApp = this;
const lang = getLang();
const canModify = canModifySpace();
const libID = docApp.libID;
if(isApiLib(docApp.lib))
{
if(!canModify) return;
const canCreateApi = hasPriv('createApi');
if(info.ui === 'doc-list-empty')
{
return canCreateApi ? [{icon: 'plus', text: lang.createApi, btnType: 'primary', command: 'startCreateDoc'}] : null;
}
const canCreateLib = hasPriv('createLib');
const items =
[
canCreateApi ? {icon: 'plus', text: lang.createApi, command: 'startCreateDoc'} : null,
(canCreateApi && canCreateLib) ? {type: 'divider'} : null,
canCreateLib ? {icon: 'wiki-lib', text: lang.createLib, command: 'createLib'} : null,
].filter(Boolean);
const canExportApi = hasPriv('exportApi') && libID;
return [
canExportApi ? {type: 'divider', style: {margin: '6px 0'}} : null,
canExportApi ? {icon: 'export', text: lang.export, command: 'exportApi'} : null,
items.length ? {icon: 'plus', type: 'dropdown', btnType: 'primary', size: 'md', text: lang.create, items: items} : null,
];
}
const canCreateDoc = canModify && hasPriv('create');
const canCreateLib = canModify && hasPriv('createLib');
const canCreateSpace = canModify && hasPriv('createSpace');
@@ -608,8 +679,8 @@ const actionsMap =
canCreateLib ? {icon: 'wiki-lib', text: lang.createLib, command: 'createLib'} : null,
].filter(Boolean) : [];
return [
((canExportDoc && docApp.libID > 0 ) || canCreateDoc) ? {type: 'divider', style: {margin: '6px 0'}} : null,
canExportDoc && docApp.libID > 0 ? {icon: 'export', text: lang.export, command: 'exportDoc'} : null, // 不在库中,无法导出
((canExportDoc && libID > 0) || canCreateDoc) ? {type: 'divider', style: {margin: '6px 0'}} : null,
canExportDoc && libID > 0 ? {icon: 'export', text: lang.export, command: 'exportDoc'} : null, // 不在库中,无法导出
canUploadDoc ? {icon: 'import', text: lang.uploadDoc, command: 'uploadDoc'} : null,
items.length ? {icon: 'plus', type: 'dropdown', btnType: 'primary', size: 'md', text: lang.create, items: items} : null,
];
@@ -625,6 +696,15 @@ const actionsMap =
if(!doc) return;
const lang = getLang();
if(isApiLib())
{
return [
{text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
{text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelEditDoc'},
];
}
return [
doc.status === 'draft' ? {text: lang.saveDraft, size: 'md', className: 'btn-wide', type: 'secondary', command: 'saveDoc/draft'} : null,
{text: lang.release, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveDoc'},
@@ -640,6 +720,13 @@ const actionsMap =
'doc-create': function()
{
const lang = getLang();
if(isApiLib())
{
return [
{text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
{text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelCreateDoc'},
];
}
return [
{text: lang.saveDraft, size: 'md', className: 'btn-wide', type: 'secondary', command: 'saveNewDoc/draft'},
{text: lang.release, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveNewDoc'},
@@ -802,7 +889,7 @@ const commands =
libID: libID,
parentID: parentID,
objectID: objectID,
moduleType: 'doc',
moduleType: isApiLib(libID) ? 'api' : 'doc',
isUpdate: false,
createType: createType,
};
@@ -910,11 +997,11 @@ const commands =
{
const docApp = getDocApp();
const docID = args[0] || docApp.docID;
const isApi = isApiDoc(docID);
$.ajaxSubmit(
{
confirm: getLang('confirmDelete'),
url: $.createLink('doc', 'delete', `docID=${docID}`),
url: $.createLink(isApi ? 'api' : 'doc', 'delete', `${isApi ? 'apiID' : 'docID'}=${isApi ? getApiID(docID) : docID}`),
load: false,
onSuccess: function()
{
@@ -947,6 +1034,15 @@ const commands =
zui.Modal.open({url: url, size: 'sm'});
},
exportApi: function(_, args)
{
const docApp = getDocApp();
const libID = args[0] || docApp.libID;
const release = args[1] || docApp.signals.libReleaseMap.value[libID] || 0;
const moduleID = args[2] || docApp.moduleID;
const url = $.createLink('api', 'export', `libID=${libID}&version=0&release=${release}&moduleID=${moduleID}`);
zui.Modal.open({url: url, size: 'sm'});
},
showDocSettingModal: function(_, args)
{
const docApp = getDocApp();
@@ -975,6 +1071,323 @@ const commands =
$.post($.createLink('doc', 'edit', `docID=${docID}`), docData);
}
});
},
/**
* 保存 API 文档数据。
* Save the API doc data.
*/
saveApiDoc: function()
{
/* 触发 API 表单的提交事件。 */
$('#docApp .doc-view form').trigger('submit');
},
/**
* 加载指定的 API 文档。
* Load the specified API doc.
*/
loadApi: function(_, args)
{
const apiID = getApiID(args[0] || this.docID);
const version = args[1] || 0;
const release = args[2] || 0;
const select = !!args[3];
$.getJSON($.createLink('api', 'ajaxGetApi', `apiID=${apiID}&version=${version}&release=${release}`), function(result)
{
if(!result || typeof result !== 'object') return;
result.id = `api.${apiID}`;
const docApp = getDocApp();
docApp.update('doc', result);
if(select) docApp.selectDoc(result.id);
});
},
/**
* 选择 API 文档。
* Select the specified API doc.
*/
selectApi: function(_, args)
{
const apiID = args[0];
getDocApp().selectDoc(`api.${getApiID(apiID)}`);
},
/**
* 显示结构列表。
* Show the struct list.
*/
showStructs: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: 'structs',
});
},
/**
* 显示版本列表。
* Show the release list.
*/
showReleases: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: 'releases',
});
},
/**
* 显示API 目录。
* Show the api catalog.
*/
showModules: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: '',
});
},
/**
* 获取库的 API 列表。
* Get the API list of the library.
*/
loadLibApi: function(_, args)
{
const docApp = getDocApp();
const libReleaseMap = docApp.signals.libReleaseMap.value;
const libID = args[0] || docApp.libID;
if(!libID) return;
const releaseID = args[1] || libReleaseMap[libID] || 0;
const url = $.createLink('api', 'ajaxGetLibApiList', `libID=${libID}&releaseID=${releaseID}`);
$.getJSON(url, function(data)
{
const lib = docApp.getLib(libID);
if(lib && lib.docs && lib.docs.length) docApp.delete('doc', lib.docs.map(x => x.data.id));
data.forEach((x) => x.id = `api.${x.id}`);
docApp.update('doc', data);
});
},
/**
* 更改当前库的发布版本。
* Change the release of the current library.
*/
changeLibRelease: function(_, args)
{
const docApp = getDocApp();
const libID = args[0];
const release = args[1];
const libReleaseMap = docApp.signals.libReleaseMap.value;
if(libReleaseMap[libID] === release) return;
libReleaseMap[libID] = release;
docApp.signals.libReleaseMap.value = $.extend({}, libReleaseMap);
docApp.executeCommand('loadLibApi', [libID, release]);
const doc = docApp.doc;
if(doc && doc.data.lib === libID) docApp.executeCommand('loadApi', [doc.data.id, 0, release]);
},
/**
* 加载懒加载内容。
* Load the lazy content.
*/
loadLazyContent: function(_, args)
{
const selector = args[0];
if(!selector) return;
$(selector).closest('.lazy-content').trigger('loadContent');
},
/**
* 更新懒加载内容。
* Update the lazy content.
*/
updateLazyContent: function(context, args)
{
const event = context.event;
const $element = $(event.currentTarget);
const selector = args[0] || $element.data('lazyTarget');
const $lazy = (selector ? $(selector) : $element).closest('.lazy-content');
const url = $element.data('url') || $element.attr('href');
if(url) $lazy.trigger('loadContent', url);
event.preventDefault();
event.stopPropagation();
},
};
function getApiID(id)
{
if(typeof id === 'number') return id;
if(String(+id) === id) return +id;
return +id.replace('api.', '');
}
/**
* 定义页面上的自定义渲染器。
* Define the custom renderers on the page.
*/
const customRenders =
{
/**
* 定义 API 文档编辑器渲染,包括查看、编辑和创建。
* Define the API doc editor render, including view, edit and create.
*/
editor: function()
{
const mode = this.mode;
if(!isApiLib())
{
const docInfo = this.doc;
const doc = docInfo ? docInfo.data : null;
if(mode === 'view' && !hasPriv('view')) return {html: `<h1>${doc ? doc.title : ''}</h1><p>${getDocAppLang('accessDenied')}</p>`};
return;
}
if(mode === 'create')
{
const libID = this.libID;
const moduleID = this.moduleID;
return {fetcher: $.createLink('api', 'create', `libID=${libID}&moduleID=${moduleID}&space=api`), loadingText: getDocAppLang('loading')};
}
if(mode === 'edit')
{
const doc = this.doc.data;
return {fetcher: $.createLink('api', 'edit', `apiID=${getApiID(doc.id)}`), loadingText: getDocAppLang('loading')};
}
const doc = this.doc.data;
if(!hasPriv('viewApi')) return {html: `<h1>${doc ? doc.title : ''}</h1><p>${getDocAppLang('accessDenied')}</p>`};
const release = this.signals.libReleaseMap.value[doc.lib] || 0;
const version = this.signals.docVersion.value || 0;
return {fetcher: $.createLink('api', 'view', `libID=${doc.lib}&apiID=${getApiID(doc.id)}&moduleID=${doc.module}&version=${version}&release=${release}`), loadingText: getDocAppLang('loadingDocsData'), className: 'doc-editor-content'};
},
/**
* 定义 API 文档列表渲染,包括结构和版本。
* Define the API doc list render, including structs and releases.
*/
list: function()
{
if(!isApiLib()) return;
const listType = this.signals.listType.value;
const libID = getApiID(this.libID);
const releaseID = this.signals.libReleaseMap.value[libID] || 0;
if(listType === 'structs') return {fetcher: $.createLink('api', 'struct', `libID=${this.libID}&releaseID=${releaseID}`), loadingText: getDocAppLang('loading'), className: 'api-struct-list'};
if(listType === 'releases') return {fetcher: $.createLink('api', 'releases', `libID=${this.libID}`), loadingText: getDocAppLang('loading'), className: 'api-release-list'};
},
/**
* 定义 API 文档列表筛选菜单渲染,当展示结构和版本时不显示筛选菜单。
* Define the API doc list filter menu render, not show the filter menu when showing structs and releases.
*/
filters: function()
{
if(!isApiLib(null, this)) return;
const listType = this.signals.listType.value;
if(this.mode !== 'list' || !listType) return;
return null;
},
/**
* 定义 API 文档列表工具栏渲染。
* Define the API doc list toolbar render.
*/
toolbar: function()
{
if(!isApiLib()) return;
const listType = this.signals.listType.value;
if(this.mode === 'list' && listType)
{
const libID = getApiID(this.libID);
const items = [];
if(hasPriv('createStruct') && listType === 'structs') items.push({text: getDocAppLang('createStruct'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('api', 'createStruct', `libID=${libID}`)});
if(hasPriv('createRelease') && listType === 'releases') items.push({text: getDocAppLang('createRelease'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', url: $.createLink('api', 'createRelease', `libID=${libID}`)});
if(!items.length) return null;
return {component: 'toolbar', props: {items: items}};
}
},
/**
* 定义顶部面包屑导航渲染。
* Define the top breadcrumb render.
*/
'app-nav': function(items)
{
const lib = this.lib;
if(!lib || !isApiLib(lib)) return items;
const versions = lib.data.versions;
if(versions && versions.length)
{
const viewIndex = items.findIndex(item => item[0] === 'lib');
if(viewIndex >= 0)
{
const libView = items[viewIndex][1];
const libID = getApiID(lib.data.id);
const release = this.signals.libReleaseMap.value[libID] || 0;
const options = [{text: getDocAppLang('latestVersion'), value: 0, selected: !release, command: `changeLibRelease/${libID}/0`}];
versions.forEach(version => options.push({selected: release === version.id, text: `v${version.version}`, value: version.id, command: `changeLibRelease/${libID}/${version.id}`}));
const currentVersion = versions.find(x => x.id === release);
const versionPicker = zui.renderCustomContent(
{
content:
{
component: 'DropdownButton',
props:
{
text : currentVersion ? currentVersion.version : getDocAppLang('latestVersion'),
size : 'xs',
type : 'gray-pale',
rounded : 'full',
className: 'h-4 gap-1 mr-2',
items : options,
}
}
});
items[viewIndex] = ['lib', [libView, versionPicker]];
}
}
const listType = this.signals.listType.value;
if(this.mode !== 'list' || !listType) return items;
if(listType === 'structs') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('struct')})]);
if(listType === 'releases') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('releases')})]);
return items;
},
/**
* 定义侧边栏渲染,显示结构、版本和模块。
* Define the sidebar render, show structs, releases and modules.
*/
'sidebar-before': function()
{
if(!isApiLib()) return;
const canViewStructs = docAppHasPriv('struct');
const canViewReleases = docAppHasPriv('releases');
if(!canViewStructs && !canViewReleases) return;
const isListMode = this.mode === 'list';
const lang = getDocAppLang();
const listType = this.signals.listType.value;
const items = [];
if(canViewStructs) items.push({text: lang.struct, selected: listType === 'structs' && isListMode, icon: 'treemap muted', command: 'showStructs'}, {type: 'divider', className: 'my-1'});
if(canViewReleases) items.push({text: lang.releases, selected: listType === 'releases' && isListMode, icon: 'version muted', command: 'showReleases'}, {type: 'divider', className: 'my-1'});
items.push({text: lang.module, icon: 'list muted', command: 'showModules'});
return {
component: 'tree',
className: 'p-2 pb-0 api-lib-menu',
props: {items: items, itemProps: {className: 'state'}}
};
}
};
@@ -1000,9 +1413,11 @@ function getTableOptions(options, info)
{
if(!canModify) col.actions = [];
const actionHints = {edit: lang.editDoc, move: lang.moveDoc, delete: lang.deleteDoc};
const privMap = {edit: hasPriv('edit'), move: hasPriv('moveDoc'), delete: hasPriv('delete')};
$.each(col.actionsMap, (key, value) =>
{
if(typeof actionHints[key] === 'string') value.hint = actionHints[key];
value.disabled = !privMap[key];
});
}
});
@@ -1038,7 +1453,12 @@ function getFilterTypes(mode)
if(currentSpaceType === 'product') return getLang('productFilterTypes');
return getLang('spaceFilterTypes');
}
if(mode === 'list') return getLang('filterTypes');
if(mode === 'list')
{
const lib = this.lib;
if(lib && lib.data.type === 'api') return getLang('apifilterTypes');
return getLang('filterTypes');
}
if(mode === 'files') return getLang('fileFilterTypes');
}
@@ -1156,8 +1576,19 @@ function getSortableOptions(type)
*
* @param {object} doc
*/
function getDocViewSidebarTabs(doc, info)
window.getDocViewSidebarTabs = function(doc, info)
{
if(isApiLib())
{
const lang = getDocAppLang();
if(info.isNewDoc || info.mode === 'create') return [];
return [
{key: 'info', icon: 'info', title: lang.docInfo},
info.mode === 'edit' ? null : {key: 'outline', icon: 'list-box', title: lang.docOutline},
{key: 'history', icon: 'history', title: lang.history},
].filter(Boolean);
}
if(info.isNewDoc) return [{key: 'outline', icon: 'list-box', title: getLang('docOutline')}];
return [
{key: 'info', icon: 'info', title: getLang('docInfo')},
@@ -1218,9 +1649,11 @@ window.setDocAppOptions = function(_, options)
return $.extend(
{
defaultState : {listType: '', libReleaseMap: {}},
commands : commands,
onCreateDoc : userPrivs.create ? handleCreateDoc: null,
onSaveDoc : userPrivs.edit ? handleSaveDoc : null,
customRenders : customRenders,
onCreateDoc : userPrivs.create ? handleCreateDoc : null,
onSaveDoc : userPrivs.edit ? handleSaveDoc : null,
canMoveDoc : canMoveDoc,
onSwitchView : handleSwitchView,
getActions : getActions,
@@ -1233,8 +1666,9 @@ window.setDocAppOptions = function(_, options)
}, window.docAppOptions, options);
};
window.docAppActions = actionsMap;
window.docAppCommands = commands;
window.getDocApp = getDocApp;
window.getDocAppLang = getLang;
window.docAppHasPriv = hasPriv;
window.docAppActions = actionsMap;
window.docAppCommands = commands;
window.docAppCustomRenders = customRenders;
window.getDocApp = getDocApp;
window.getDocAppLang = getLang;
window.docAppHasPriv = hasPriv;
+25 -6
View File
@@ -52,6 +52,7 @@ class docApp extends wg
'search' => '?string', // 搜索关键字。
'filterType' => '?string', // 过滤类型。
'pager' => '?PagerInfo', // 分页信息。
'params' => '?string', // 额外参数信息。
'orderBy' => '?string', // 排序字段。
'langData' => '?array', // 语言数据。
'showLibFiles' => '?boolean|array|string',// 是否显示库文件。
@@ -78,6 +79,11 @@ class docApp extends wg
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
}
public static function getPageCSS(): ?string
{
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
}
protected function build()
{
global $app, $lang;
@@ -111,13 +117,26 @@ class docApp extends wg
* 设置前端语言数据。 在 js/app.ui.js 中使用 getLang('xxx') 来访问语言数据。
*/
$app->loadLang('file');
$app->loadLang('api');
$langData = $lang->doc->docLang;
$langData->filePreview = $lang->file->preview;
$langData->fileDownload = $lang->file->download;
$langData->fileDelete = $lang->file->delete;
$langData->fileRename = $lang->file->edit;
$langData->fileConfirmDelete = $lang->file->confirmDelete;
$langData->deleted = $lang->file->deleted;
$langData->filePreview = $lang->file->preview;
$langData->fileDownload = $lang->file->download;
$langData->fileDelete = $lang->file->delete;
$langData->fileRename = $lang->file->edit;
$langData->fileConfirmDelete = $lang->file->confirmDelete;
$langData->deleted = $lang->file->deleted;
$langData->createApi = $lang->api->createApi;
$langData->apifilterTypes = $lang->api->filterTypes;
$langData->module = $lang->api->module;
$langData->struct = $lang->api->struct;
$langData->releases = $lang->api->releases;
$langData->noApi = $lang->api->noApi;
$langData->version = $lang->api->version;
$langData->defaultVersion = $lang->api->defaultVersion;
$langData->createStruct = $lang->api->createStruct;
$langData->createRelease = $lang->api->createRelease;
$langData->libTypeList = $lang->api->libTypeList;
$langData->latestVersion = $lang->api->latestVersion;
/**
* 通过语言项定义文档表格列显示名称。
+21 -1
View File
@@ -38,7 +38,27 @@ class filter extends wg
set::items($items),
set::menu($menu),
set::multiple($multiple),
on::change("$onChange(e, '$name')")
on::change()->do(<<<JS
const hasUrl = () => \$this.zui().\$.props.items.hasOwnProperty('url');
if(hasUrl()) \$this.zui().\$.props.items.data.values = \$this.zui().\$.value;
$onChange(event, '$name');
JS),
on::inited()->do(<<<JS
const getItems = () => \$this.zui().\$.state.items;
const hasUrl = () => \$this.zui().\$.state.items.hasOwnProperty('url');
const waitItems = () => {
if(!hasUrl())
{
const picker = \$this.zui().\$;
const itemValues = picker.state.items.map(item => item.value);
const values = picker.valueList.filter(value => itemValues.includes(value));
picker.setValue(values);
return;
}
setTimeout(waitItems, 100);
}
if(hasUrl()) waitItems();
JS)
);
}
+5
View File
@@ -57,6 +57,10 @@ class formBatch extends formBase
'mode' => 'add'
);
protected static array $defineBlocks = array(
'formBefore' => array()
);
/**
* Handle building inner items.
*
@@ -135,6 +139,7 @@ class formBatch extends formBase
div
(
setClass('form-batch-container relative'),
$this->block('formBefore'),
h::table
(
setClass('table form-batch-table'),
-1
View File
@@ -647,7 +647,6 @@ function initGantt()
$(window).off('.gannt').on('resize.gannt', function(){resizeGanttView()});
gantt.init(ganttID);
gantt.clearAll();
gantt.parse(ganttData);
gantt.showDate(new Date());
+6 -6
View File
@@ -7,7 +7,7 @@ window.branchChange = function(e)
{
let $product = $(e.target).closest('.productBox').find("[name^='products']");
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
loadRoadmaps($product, $(e.target));
}
@@ -49,7 +49,7 @@ window.loadBranches = function(e)
{
let branch = $(e.target).closest('.productBox').find("[name^='branch']");
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
loadRoadmaps(e.target, branch.length > 0 ? branch[0] : branch);
}
@@ -63,7 +63,7 @@ window.loadBranches = function(e)
{
let branch = $(e.target).closest('.productBox').find("[name^='branch']");
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
loadRoadmaps(e.target, branch.length > 0 ? branch[0] : branch);
}
@@ -79,7 +79,7 @@ window.addNewLine = function(e)
{
const obj = e.target
const newLine = $(obj).closest('.productBox').clone();
const objectType = $('[name="charter"]').length && $('[name="charter"]').val() ? 'roadmap' : 'plan';
const objectType = $('[name="charter"]').length && $('[name="charter"]').val() > 0 ? 'roadmap' : 'plan';
let index = 0;
let chosenProducts = [];
@@ -128,7 +128,7 @@ window.addNewLine = function(e)
new zui.Picker(`#branch${index}`, {name: `branch[${index}]`, multiple: true, items: []});
new zui.Picker(`#${objectType}s${index}`, {items:[], multiple: true, name: `${objectType}s[${index}]`});
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
if(options.items.length == $('.productBox').length) $('.addLine').addClass('hidden');
}
@@ -151,7 +151,7 @@ window.removeLine = function(e)
$(obj).closest('.productBox').remove();
if(typeof toggleStageBy == 'function') toggleStageBy();
if($('[name="charter"]').length && $('[name="charter"]').val())
if($('[name="charter"]').length && $('[name="charter"]').val() > 0)
{
$('.addLine').removeClass('hidden');
}
+1 -1
View File
@@ -83,7 +83,7 @@ class queryBase extends wg
),
$formActionsBlock
),
set::submitBtnText($lang->pivot->query),
set::submitBtnText($lang->bi->query),
formGroup
(
setID('querySql'),
+6 -4
View File
@@ -88,13 +88,15 @@ class relatedList extends wg
$title = isset($lang->$moduleName->$langName) ? $lang->$moduleName->$langName : $type;
}
$groupItems = $this->getGroupItems($type, $group);
$groupItems = $this->getGroupItems((string)$type, $group);
$content = zget($group, 'content', '');
$items[] = array
(
'title' => $title,
'items' => $groupItems,
'content' => $showCount ? array('html' => '<span class="label gray-pale rounded-full size-sm">' . count($groupItems) . '</span>') : null
'title' => $title,
'titleAttrs' => array('title' => $title),
'items' => $groupItems,
'content' => $showCount ? array('html' => '<span class="label gray-pale rounded-full size-sm">' . count($groupItems) . '</span>' . $content) : null
);
}
+10 -2
View File
@@ -14,6 +14,13 @@ class relatedObjectList extends relatedList
'browseType' => '?string="byRelation"' //浏览类型 byRelation|byObject
);
public static function getPageCSS(): ?string
{
return <<<'CSS'
li.objectItem div.listitem span.is-empty {display: none !important;}
CSS;
}
public static function getPageJS(): ?string
{
global $lang;
@@ -59,7 +66,7 @@ class relatedObjectList extends relatedList
$item->title = "#$relatedObjectID $title";
$item->type = $itemType;
$item->url = !empty($relatedObjectInfo['url']) ? $relatedObjectInfo['url'] : null;
$item->titleAttrs = !empty($relatedObjectInfo['url']) && !in_array($relatedObjectType, array('repocommit', 'commit', 'mr')) ? array('data-toggle' => 'modal', 'data-size' => 'lg') : null;
$item->titleAttrs = !empty($relatedObjectInfo['url']) && !in_array($relatedObjectType, array('repocommit', 'commit', 'mr', 'release', 'build')) ? array('data-toggle' => 'modal', 'data-size' => 'lg') : null;
if(hasPriv('custom', 'removeObjects'))
{
@@ -92,8 +99,9 @@ class relatedObjectList extends relatedList
'title' => $title,
'hint' => $title,
'titleAttrs' => $item->titleAttrs,
'leading' => array('html' => wg(idLabel::create($item->type, array('class' => 'text-clip')))->render()),
'leading' => array('html' => wg(idLabel::create($item->type, array('class' => 'text-clip text-left', 'style' => array('max-width' => '45px'))))->render()),
'url' => $item->url,
'class' => 'objectItem',
'actions' => isset($item->actions) ? $item->actions : array()
);
return $info;
+7 -3
View File
@@ -39,6 +39,8 @@ class StepsEditor extends zui.Component
deleteStepTip: '',
dragNestedTip: '',
changeLevelByDrag: false,
expectDisabled: true,
postDataID: false
};
init()
@@ -262,6 +264,7 @@ class StepsEditor extends zui.Component
([
`<div class="steps-editor-row steps-editor-item" data-id="${item.id}">`,
'<div class="steps-editor-drag-ghost"></div>',
options.postDataID ? `<input type="hidden" class="steps-editor-post-data-id" value="${item.id}">` : '',
'<div class="steps-editor-col steps-editor-col-step form-control">',
'<div class="steps-editor-step-move"><i class="icon icon-move"></i></div>',
'<div class="steps-editor-step-name"></div>',
@@ -303,12 +306,13 @@ class StepsEditor extends zui.Component
$row.css('--name-indent', `${(item.level - 1) * 14}px`).find('.steps-editor-step-name').css('width', item.name.length * 12).text(item.name).css('paddingLeft', (item.level - 1) * 14);
$row.find('.steps-editor-step-text').attr('name', `${options.name}[${item.name}]`);
$row.find('.steps-editor-step-type').attr('name', `stepType[${item.name}]`).val(hasSub ? 'group' : (!!item.parent ? 'item' : 'step'));
if(options.postDataID) $row.find('.steps-editor-post-data-id').attr('name', `id[${item.name}]`);
const $expect = $row.find('.steps-editor-step-expect').attr(
{
name: `${options.expectsName}[${item.name}]`,
disabled: hasSub ? 'disabled' : null
}).toggleClass('disabled', hasSub);
$expect.parent().toggleClass('disabled', hasSub);
disabled: hasSub && options.expectDisabled ? 'disabled' : null
}).toggleClass('disabled', hasSub && options.expectDisabled);
$expect.parent().toggleClass('disabled', hasSub && options.expectDisabled);
$row.find('.steps-editor-col-delete .btn').toggleClass('disabled', hasSub || this._items.length == 1);
$row.find('.steps-editor-col-delete .btn.disabled').attr('title', options.deleteStepTip);
$row.find('.steps-editor-col-add .btn-action[data-action="sub"]').toggleClass('disabled', item.level >= 3);
+6 -4
View File
@@ -37,7 +37,9 @@ class stepsEditor extends wg
'subLevelText?: string', // 子级文本。
'expectDisabledTip?: string', // 预期输入框禁用提示。
'deleteStepTip?: string', // 有子层级禁用删除提示。
'dragNestedTip?: string' // 拖拽超出提示。
'dragNestedTip?: string', // 拖拽超出提示。
'expectDisabled: bool=true', // 是否禁用预期输入框。
'postDataID: bool=false' // 是否提交表单时附加 ID。
);
public static function getPageJS(): ?string
@@ -64,10 +66,10 @@ class stepsEditor extends wg
$subLevelText = $this->prop('subLevelText', data('lang.testcase.stepSubLevel'));
$id = $this->prop('id') ? $this->prop('id') : $this->gid;
$options = $this->props->pick(array('name', 'expectsName', 'data'));
$options = $this->props->pick(array('name', 'expectsName', 'data', 'postDataID', 'expectDisabled'));
$options['expectDisabledTip'] = $this->prop('expectDisabledTip', data('lang.testcase.expectDisabledTip'));
$options['deleteStepTip'] = $this->prop('deleteStepTip', data('lang.testcase.deleteStepTip'));
$options['dragNestedTip'] = $this->prop('dragNestedTip', data('lang.testcase.dragNestedTip'));
$options['deleteStepTip'] = $this->prop('deleteStepTip', data('lang.testcase.deleteStepTip'));
$options['dragNestedTip'] = $this->prop('dragNestedTip', data('lang.testcase.dragNestedTip'));
return div
(
+2 -2
View File
@@ -31,9 +31,9 @@ class storyLifeInfo extends wg
'children' => wg(div
(
setClass('row gap-2 flex-wrap'),
array_values(array_map(function($reviewer, $result) use($users, $lang)
array_values(array_map(function($reviewer, $result) use($users, $lang, $story)
{
return !empty($result) ? span(setClass('mr-2'), set::title($lang->story->reviewed), set::style(array('color' => '#cbd0db')), zget($users, $reviewer)) : span(setClass('mr-2'), set::title($lang->story->toBeReviewed), zget($users, $reviewer));
return !empty($result) ? span(setClass('mr-2'), set::title($lang->story->reviewed), $story->status != 'changing' && $story->status != 'draft' ? set::style(array('color' => '#cbd0db')) : null, zget($users, $reviewer)) : span(setClass('mr-2'), set::title($lang->story->toBeReviewed), zget($users, $reviewer));
}, array_keys($reviewers), array_values($reviewers)))
))
);
+2 -2
View File
@@ -99,7 +99,7 @@ class thinkMulticolumn extends thinkQuestion
if(empty($fields)) $fields = array($defalutField, $defalutField, $defalutField, $defalutField);
foreach($fields as $key => $field) $batchItems[] = $this->buildFormBatchItem($field, (int)$key, $isRun, $quotedQuestions, !empty($result));
if($isResult || $isRun) $result = $this->processResult($result);
if($isResult || $isRun || !empty($result)) $result = $this->processResult($result);
$detailWg[] = formBatch
(
setClass('think-form-batch'),
@@ -228,4 +228,4 @@ class thinkMulticolumn extends thinkQuestion
);
return $formItems;
}
}
}
+2 -2
View File
@@ -44,7 +44,7 @@ class thinkStepBase extends wg
if($mode != 'detail') return array();
$options = $step->options;
if($options)
if($step->type == 'questions')
{
$questionType = $options->questionType;
$tips = $lang->thinkstep->$questionType;
@@ -111,7 +111,7 @@ class thinkStepBase extends wg
if(!empty($step->options->fields)) $step->options->fields = is_string($step->options->fields) ? explode(', ', $step->options->fields) : array_values((array)$step->options->fields);
$questionType = !empty($step) && !empty($step->options) ? $step->options->questionType : '';
$questionType = !empty($step) && $step->type == 'questions' ? $step->options->questionType : '';
$isCheckBox = !empty($step) && $step->type == 'question' && in_array($questionType, $config->thinkstep->quoteQuestionType);
$isQuoteItem = $isCheckBox && !empty($step->options->setOption) && $step->options->setOption == 1;
$detailTip = array();
+24 -5
View File
@@ -14,6 +14,7 @@ class thinkTableInput extends thinkQuestion
'fields?: array', // 行标题
'supportAdd?: bool', // 是否支持用户添加行
'canAddRows: number=1', // 可添加行数
'inputType?: bool=flase', // 输入类型
);
public static function getPageJS(): string
@@ -31,7 +32,7 @@ class thinkTableInput extends thinkQuestion
{
global $lang;
$detailWg = parent::buildDetail();
list($step, $fields, $supportAdd, $canAddRows, $mode) = $this->prop(array('step', 'fields', 'supportAdd', 'canAddRows', 'mode'));
list($step, $fields, $supportAdd, $canAddRows, $mode, $inputType) = $this->prop(array('step', 'fields', 'supportAdd', 'canAddRows', 'mode', 'inputType'));
if($mode != 'detail') return array();
if($step)
@@ -42,6 +43,7 @@ class thinkTableInput extends thinkQuestion
$answer = $step->answer;
$result = isset($answer->result) && !empty($answer->result) ? (array) $answer->result : array();
$customFields = !empty($answer->customFields) ? get_object_vars($answer->customFields) : array();
$inputType = isset($step->options->inputType)? $step->options->inputType : false;
}
$tableInputItems = array();
@@ -62,14 +64,29 @@ class thinkTableInput extends thinkQuestion
div
(
set::title($value),
setClass('result-width', $index == 0 ? '' : 'mt-2'),
textarea
setClass(empty($inputType) ? 'result-width' : '', $index == 0 ? '' : 'mt-2'),
empty($inputType) ? textarea
(
set::rows('2'),
set::name('result[' . $index . ']'),
set::value($value),
set::placeholder($lang->thinkrun->pleaseInput)
)
) : inputControl
(
input(set(array(
'class' => 'w-72 h-10 dimension-weight',
'name' => 'result[' . $index . ']',
'type' => 'number',
'min' => 1,
'max' => 100,
'value' => $value,
'placeholder' => $lang->thinkrun->pleaseInput
)),
on::input('changeInput')
),
to::suffix($lang->thinkwizard->dimension->percentageSign),
set::suffixWidth(32)
),
),
div
(
@@ -200,7 +217,7 @@ class thinkTableInput extends thinkQuestion
$app->loadLang('thinkstep');
$formItems = parent::buildFormItem();
list($step, $required, $requiredRows, $supportAdd, $canAddRows, $fields) = $this->prop(array('step','required', 'requiredRows', 'supportAdd', 'canAddRows', 'fields'));
list($step, $required, $requiredRows, $supportAdd, $canAddRows, $fields, $inputType) = $this->prop(array('step','required', 'requiredRows', 'supportAdd', 'canAddRows', 'fields', 'inputType'));
if($step)
{
$required = $step->options->required;
@@ -209,10 +226,12 @@ class thinkTableInput extends thinkQuestion
$requiredRows = $step->options->requiredRows;
$supportAdd = $step->options->supportAdd;
$canAddRows = $step->options->canAddRows;
$inputType = isset($step->options->inputType) ? $step->options->inputType : false;
}
$formItems[] = array (
formHidden('options[questionType]', 'tableInput'),
formHidden('options[inputType]', $inputType),
formRow
(
setClass('mb-3'),
+1 -1
View File
@@ -224,7 +224,7 @@ pipeline {
script {
// 仅制作开源版时不上传收费版相关源码
if (params.publish_exts!="open") {
buildPkg.uploadSource("php8.1", ["php7.0", "php7.1", "php7.2_7.4"])
buildPkg.uploadSource("php8.1", ["php0.0", "php8.1", "php7.0", "php7.1", "php7.2_7.4"])
}
}
}
+7 -7
View File
@@ -1,5 +1,5 @@
library 'z-common@master'
library 'z-zentaopms@feat-240509'
library 'z-zentaopms'
pipeline {
agent {
@@ -52,11 +52,11 @@ pipeline {
checkout scm
script {
def triggeredBy = currentBuild.getBuildCauses()
println "Build triggered by: ${triggeredBy}"
println "Build triggered by: ${triggeredBy}"
env.GIT_URL = sh(returnStdout: true, script: 'git config --get remote.origin.url').trim()
env.GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
def j = job.newJob()
env.GIT_BRANCH = j.getBranchById(env.GIT_COMMIT)
@@ -73,7 +73,7 @@ pipeline {
[name: "downRepoExt", subDir: true, url: "https://${env.GITFOX_HOST}/git/${env.DOWNGRADE_EXT_GIT_REPO}.git", credentialsId: "gitfox-zcorp-cc-ci-robot", branchs: ["downgrade/blank"] ]
])
}
}
}
@@ -86,11 +86,11 @@ pipeline {
sh 'git config --global --add safe.directory $PWD/zentaoext'
sh 'git config --global --add safe.directory $PWD/downRepo'
sh 'git config --global --add safe.directory $PWD/downRepoExt'
sh 'echo ${GIT_BRANCH}'
sh 'mkdir -pv ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
sh 'make ciCommon'
withCredentials([gitUsernamePassword(credentialsId: 'gitfox-zcorp-cc-ci-robot', gitToolName: 'Default')]) {
sh '${ZENTAO_BUILD_PATH}/script/downgrade/fullDowngrade_v2.sh -c $PWD/misc -r $PWD/zentaopms -b ${GIT_BRANCH} -d downRepo -i -s -g $GIT_COMMIT -o "$ZENTAO_RELEASE_PATH" -p "$DOWNGRADE_VERSIONS" api extension framework lib module/*'
}
@@ -136,7 +136,7 @@ pipeline {
sh 'git config --global --add safe.directory $SRC_ZENTAOEXT_PATH/zentaomax'
sh 'git config --global --add safe.directory $SRC_ZENTAOEXT_PATH/zentaoipd'
sh 'cd $SRC_ZENTAOEXT_PATH;make build'
withCredentials([gitUsernamePassword(credentialsId: 'gitfox-zcorp-cc-ci-robot', gitToolName: 'Default')]) {
sh '${ZENTAO_BUILD_PATH}/script/downgrade/fullDowngrade_v2.sh -c $PWD/misc -r $ZENTAO_BUILD_PATH/zentaoext -b ${GIT_BRANCH} -d downRepoExt -i -s -g $GIT_COMMIT -o "$ZENTAO_RELEASE_PATH" -p "$DOWNGRADE_VERSIONS" bizext biz max ipd'
}
-10
View File
@@ -1,10 +0,0 @@
<?php
$config->account->create = new stdclass;
$config->account->create->requiredFields = 'name,account,provider';
$config->account->edit = new stdclass;
$config->account->edit->requiredFields = 'name,account,provider';
$config->account->actions = new stdclass();
$config->account->actions->view = array();
$config->account->actions->view['suffixActions'] = array('edit');
-69
View File
@@ -1,69 +0,0 @@
<?php
global $lang, $app;
$app->loadLang('serverroom');
$config->account->dtable = new stdclass();
$config->account->dtable->fieldList['id']['title'] = 'ID';
$config->account->dtable->fieldList['id']['name'] = 'id';
$config->account->dtable->fieldList['id']['type'] = 'id';
$config->account->dtable->fieldList['name']['title'] = $lang->account->name;
$config->account->dtable->fieldList['name']['name'] = 'name';
$config->account->dtable->fieldList['name']['type'] = 'desc';
$config->account->dtable->fieldList['name']['sortType'] = true;
$config->account->dtable->fieldList['name']['flex'] = 4;
$config->account->dtable->fieldList['name']['hint'] = true;
$config->account->dtable->fieldList['name']['link'] = array('module' => 'account', 'method' => 'view', 'params' => 'id={id}');
$config->account->dtable->fieldList['name']['data-toggle'] = 'modal';
$config->account->dtable->fieldList['provider']['title'] = $lang->account->provider;
$config->account->dtable->fieldList['provider']['name'] = 'provider';
$config->account->dtable->fieldList['provider']['type'] = 'text';
$config->account->dtable->fieldList['provider']['sortType'] = true;
$config->account->dtable->fieldList['provider']['width'] = 80;
$config->account->dtable->fieldList['provider']['map'] = $lang->serverroom->providerList;
$config->account->dtable->fieldList['account']['title'] = $lang->account->account;
$config->account->dtable->fieldList['account']['name'] = 'account';
$config->account->dtable->fieldList['account']['type'] = 'text';
$config->account->dtable->fieldList['account']['sortType'] = true;
$config->account->dtable->fieldList['email']['title'] = $lang->account->email;
$config->account->dtable->fieldList['email']['name'] = 'email';
$config->account->dtable->fieldList['email']['type'] = 'text';
$config->account->dtable->fieldList['email']['sortType'] = true;
$config->account->dtable->fieldList['mobile']['title'] = $lang->account->mobile;
$config->account->dtable->fieldList['mobile']['name'] = 'mobile';
$config->account->dtable->fieldList['mobile']['type'] = 'text';
$config->account->dtable->fieldList['mobile']['sortType'] = true;
$config->account->dtable->fieldList['createdBy']['title'] = $lang->account->createdBy;
$config->account->dtable->fieldList['createdBy']['name'] = 'createdBy';
$config->account->dtable->fieldList['createdBy']['type'] = 'user';
$config->account->dtable->fieldList['createdBy']['width'] = '100px';
$config->account->dtable->fieldList['createdBy']['sortType'] = true;
$config->account->actionList = array();
$config->account->actionList['edit']['icon'] = 'edit';
$config->account->actionList['edit']['text'] = $lang->edit;
$config->account->actionList['edit']['hint'] = $lang->edit;
$config->account->actionList['edit']['data-toggle'] = 'modal';
$config->account->actionList['edit']['data-size'] = 'sm';
$config->account->actionList['edit']['showText'] = true;
$config->account->actionList['edit']['url'] = array('module' => 'account', 'method' => 'edit', 'params' => 'id={id}');
$config->account->actionList['delete']['icon'] = 'trash';
$config->account->actionList['delete']['text'] = $lang->delete;
$config->account->actionList['delete']['hint'] = $lang->delete;
$config->account->actionList['delete']['ajaxSubmit'] = true;
$config->account->actionList['delete']['url'] = array('module' => 'account', 'method' => 'delete', 'params' => 'id={id}');
$config->account->dtable->fieldList['actions']['name'] = 'actions';
$config->account->dtable->fieldList['actions']['title'] = $lang->actions;
$config->account->dtable->fieldList['actions']['type'] = 'actions';
$config->account->dtable->fieldList['actions']['sortType'] = false;
$config->account->dtable->fieldList['actions']['fixed'] = 'right';
$config->account->dtable->fieldList['actions']['menu'] = array('edit', 'delete');
$config->account->dtable->fieldList['actions']['list'] = $config->account->actionList;
-27
View File
@@ -1,27 +0,0 @@
<?php
$config->account->form = new stdclass();
$config->account->form->create = array();
$config->account->form->create['name'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
$config->account->form->create['provider'] = array('type' => 'string', 'required' => true, 'default' => '');
$config->account->form->create['adminURI'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['account'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
$config->account->form->create['password'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['email'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['mobile'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['type'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['status'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['extra'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->create['createdDate'] = array('type' => 'string', 'required' => false, 'default' => helper::now());
$config->account->form->edit = array();
$config->account->form->edit['name'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['provider'] = array('type' => 'string', 'required' => true, 'default' => '');
$config->account->form->edit['adminURI'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['account'] = array('type' => 'string', 'required' => true, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['password'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['email'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['mobile'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['type'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['status'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['extra'] = array('type' => 'string', 'required' => false, 'default' => '', 'filter' => 'trim');
$config->account->form->edit['editedDate'] = array('type' => 'string', 'required' => false, 'default' => helper::now());
-25
View File
@@ -1,25 +0,0 @@
<?php
global $lang;
$config->account->search['module'] = 'account';
$config->account->search['fields']['name'] = $lang->account->name;
$config->account->search['fields']['id'] = 'ID';
$config->account->search['fields']['provider'] = $lang->account->provider;
$config->account->search['fields']['account'] = $lang->account->account;
$config->account->search['fields']['email'] = $lang->account->email;
$config->account->search['fields']['mobile'] = $lang->account->mobile;
$config->account->search['fields']['createdBy'] = $lang->account->createdBy;
$config->account->search['fields']['createdDate'] = $lang->account->createdDate;
$config->account->search['fields']['editedBy'] = $lang->account->editedBy;
$config->account->search['fields']['editedDate'] = $lang->account->editedDate;
$config->account->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['id'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['provider'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['account'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['email'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['mobile'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->account->search['params']['createdBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
$config->account->search['params']['createdDate'] = array('operator' => '=', 'control' => 'date', 'values' => '');
$config->account->search['params']['editedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
$config->account->search['params']['editedDate'] = array('operator' => '=', 'control' => 'date', 'values' => '');
-157
View File
@@ -1,157 +0,0 @@
<?php
declare(strict_types=1);
/**
* The control file of account of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv11.html)
* @author Xiying Guan <guanxiying@xirangit.com>
* @package account
* @link https://www.zentao.net
*/
class account extends control
{
/**
* 查看账号列表。
* Browse accouts page.
*
* @param string $browseType
* @param string $param
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function browse(string $browseType = 'all', string $param = '', string $orderBy = 'id_desc', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1)
{
$this->app->loadLang('serverroom');
$browseType = strtolower($browseType);
$this->session->set('accountList', $this->app->getURI(true));
$this->app->loadClass('pager', true);
$pager = pager::init($recTotal, $recPerPage, $pageID);
$accountList = $this->account->getList($browseType, $param, $orderBy, $pager);
/* Build the search form. */
$this->config->account->search['actionURL'] = $this->createLink('account', 'browse', "browseType=bySearch&queryID=myQueryID");
$this->config->account->search['queryID'] = $param;
$this->config->account->search['onMenuBar'] = 'no';
$this->loadModel('search')->setSearchParams($this->config->account->search);
$this->view->title = $this->lang->account->common;
$this->view->accountList = $accountList;
$this->view->users = $this->loadModel('user')->getPairs('noletter,noempty,noclosed');
$this->view->pager = $pager;
$this->view->param = $param;
$this->view->orderBy = $orderBy;
$this->view->browseType = $browseType;
$this->display();
}
/**
* 创建一个账号。
* Create account.
*
* @access public
* @return void
*/
public function create()
{
if($_POST)
{
$account = form::data($this->config->account->form->create)
->add('createdBy', $this->app->user->account)
->get();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->account->create($account);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if(isInModal()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => helper::createLink('account', 'browse')));
}
$this->app->loadLang('serverroom');
$this->view->title = $this->lang->account->create;
$this->display();
}
/**
* 编辑一个账号。
* Edit one account.
*
* @param int $accountID
* @access public
* @return void
*/
public function edit(int $accountID)
{
if($_POST)
{
$account = form::data($this->config->account->form->edit)
->add('editedBy', $this->app->user->account)
->get();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->account->update($accountID, $account);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'), 'closeModal' => true));
}
$this->view->title = $this->lang->account->edit;
$this->view->account = $this->account->fetchByID($accountID);
$this->view->rooms = $this->loadModel('serverroom')->getPairs();
$this->display();
}
/**
* 查看账号信息。
* View account.
*
* @param int $accountID
* @access public
* @return void
*/
public function view(int $accountID)
{
$this->view->title = $this->lang->account->view;
$this->view->account = $this->account->fetchByID($accountID);
$this->view->rooms = $this->loadModel('serverroom')->getPairs();
$this->view->actions = $this->loadModel('action')->getList('account', $accountID);
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->optionMenu = $this->loadModel('tree')->getOptionMenu(0, 'account');
$this->display();
}
/**
* 删除一个账号。
* Delete Account.
*
* @param int $accountID
* @access public
* @return void
*/
public function delete(int $accountID)
{
$this->account->delete(TABLE_ACCOUNT, $accountID);
if(dao::isError())
{
$response['result'] = 'fail';
$response['message'] = dao::getError(true);
}
else
{
$response['result'] = 'success';
$response['message'] = '';
$response['load'] = true;
}
return $this->send($response);
}
}
-1
View File
@@ -1 +0,0 @@
#accountCreateForm .form-row {max-width: 500px;}
-33
View File
@@ -1,33 +0,0 @@
<?php
$lang->account = new stdclass;
$lang->account->common = 'account';
$lang->account->all = 'All account';
$lang->account->browse = 'browse account';
$lang->account->create = 'create account';
$lang->account->edit = 'edit account';
$lang->account->view = 'view account';
$lang->account->editAction = 'edit account';
$lang->account->deleteAction = 'delete account';
$lang->account->id = 'id';
$lang->account->provider = 'provider';
$lang->account->type = 'type';
$lang->account->name = 'name';
$lang->account->adminURI = 'admin url';
$lang->account->account = 'account';
$lang->account->email = 'email';
$lang->account->mobile = 'mobile';
$lang->account->password = 'pasword';
$lang->account->createdBy = 'created by';
$lang->account->createdDate = 'created date';
$lang->account->editedBy = 'edited by';
$lang->account->editedDate = 'edited date';
$lang->account->confirmDelete = 'confirm delete';
$lang->account->byQuery = 'search';
$lang->account->empty = 'No Account';
$lang->account->typeList = array();
$lang->account->featureBar['browse']['all'] = $lang->account->all;
-33
View File
@@ -1,33 +0,0 @@
<?php
$lang->account = new stdclass;
$lang->account->common = 'account';
$lang->account->all = 'All account';
$lang->account->browse = 'browse account';
$lang->account->create = 'create account';
$lang->account->edit = 'edit account';
$lang->account->view = 'view account';
$lang->account->editAction = 'edit account';
$lang->account->deleteAction = 'delete account';
$lang->account->id = 'id';
$lang->account->provider = 'provider';
$lang->account->type = 'type';
$lang->account->name = 'name';
$lang->account->adminURI = 'admin url';
$lang->account->account = 'account';
$lang->account->email = 'email';
$lang->account->mobile = 'mobile';
$lang->account->password = 'pasword';
$lang->account->createdBy = 'created by';
$lang->account->createdDate = 'created date';
$lang->account->editedBy = 'edited by';
$lang->account->editedDate = 'edited date';
$lang->account->confirmDelete = 'confirm delete';
$lang->account->byQuery = 'search';
$lang->account->empty = 'No Account';
$lang->account->typeList = array();
$lang->account->featureBar['browse']['all'] = $lang->account->all;
-33
View File
@@ -1,33 +0,0 @@
<?php
$lang->account = new stdclass;
$lang->account->common = 'account';
$lang->account->all = 'All account';
$lang->account->browse = 'browse account';
$lang->account->create = 'create account';
$lang->account->edit = 'edit account';
$lang->account->view = 'view account';
$lang->account->editAction = 'edit account';
$lang->account->deleteAction = 'delete account';
$lang->account->id = 'id';
$lang->account->provider = 'provider';
$lang->account->type = 'type';
$lang->account->name = 'name';
$lang->account->adminURI = 'admin url';
$lang->account->account = 'account';
$lang->account->email = 'email';
$lang->account->mobile = 'mobile';
$lang->account->password = 'pasword';
$lang->account->createdBy = 'created by';
$lang->account->createdDate = 'created date';
$lang->account->editedBy = 'edited by';
$lang->account->editedDate = 'edited date';
$lang->account->confirmDelete = 'confirm delete';
$lang->account->byQuery = 'search';
$lang->account->empty = 'No Account';
$lang->account->typeList = array();
$lang->account->featureBar['browse']['all'] = $lang->account->all;
-33
View File
@@ -1,33 +0,0 @@
<?php
$lang->account = new stdclass;
$lang->account->common = '运维账号';
$lang->account->all = '全部账号';
$lang->account->browse = '账号列表';
$lang->account->create = '添加账号';
$lang->account->edit = '编辑账号';
$lang->account->view = '账号详情';
$lang->account->editAction = '编辑账号';
$lang->account->deleteAction = '删除账号';
$lang->account->id = 'id';
$lang->account->provider = '服务商';
$lang->account->type = '类型';
$lang->account->name = '名称';
$lang->account->adminURI = '登录地址';
$lang->account->account = '账户';
$lang->account->email = '邮件';
$lang->account->mobile = '手机';
$lang->account->password = '密码';
$lang->account->createdBy = '由谁添加';
$lang->account->createdDate = '添加时间';
$lang->account->editedBy = '由谁编辑';
$lang->account->editedDate = '编辑时间';
$lang->account->confirmDelete = '确定删除';
$lang->account->byQuery = '搜索';
$lang->account->empty = '暂时没有账号';
$lang->account->typeList = array();
$lang->account->featureBar['browse']['all'] = $lang->account->all;
-121
View File
@@ -1,121 +0,0 @@
<?php
declare(strict_types=1);
/**
* The model file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv11.html)
* @author Xiying Guan <guanxiying@xirangit.com>
* @package account
* @link https://www.zentao.net
*/
class accountModel extends model
{
/**
* 获取账号列表。
* Get account list.
*
* @param string $browseType
* @param int $param
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getList(string $browseType = 'all', string $param = '', string $orderBy = 'id_desc', object|null $pager = null): array
{
$query = '';
if($browseType == 'bysearch')
{
/* Concatenate the conditions for the query. */
if(!$this->session->accountQuery) $this->session->set('accountQuery', ' 1 = 1');
if($param)
{
$query = $this->loadModel('search')->getQuery((int)$param);
if($query)
{
$this->session->set('accountQuery', $query->sql);
$this->session->set('accountForm', $query->form);
}
}
$query = $this->session->accountQuery;
}
$accounts = $this->dao->select('*')->from(TABLE_ACCOUNT)
->where('deleted')->eq('0')
->beginIF($query)->andWhere($query)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll();
$users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noletter');
foreach($accounts as $account) $account->createdBy = zget($users, $account->createdBy, '');
return $accounts;
}
/**
* 获取账号的id:name键值对。
* Get account pairs.
*
* @access public
* @return array
*/
public function getPairs(): array
{
return $this->dao->select('id,name')->from(TABLE_ACCOUNT)->where('deleted')->eq('0')->fetchPairs();
}
/**
* 创建一个账号。
* Create account.
*
* @param object $account
* @access public
* @return int|false
*/
public function create(object $account): int|false
{
$this->dao->insert(TABLE_ACCOUNT)->data($account)
->batchCheck($this->config->account->create->requiredFields, 'notempty')
->checkIf($account->email, 'email', 'email')
->checkIf($account->mobile, 'mobile', 'mobile')
->exec();
if(dao::isError()) return false;
$accountID = $this->dao->lastInsertID();
$this->loadModel('action')->create('account', $accountID, 'created');
return $accountID;
}
/**
* 更新一个账号。
* Update one account.
*
* @param int $id
* @param object $account
* @access public
* @return bool
*/
public function update(int $id, object $account): bool
{
$oldAccount = $this->fetchByID($id);
$this->dao->update(TABLE_ACCOUNT)->data($account)->autoCheck()
->batchCheck($this->config->account->edit->requiredFields, 'notempty')
->checkIf($account->email, 'email', 'email')
->checkIf($account->mobile, 'mobile', 'mobile')
->where('id')->eq($id)
->exec();
if(!dao::isError())
{
$changes = common::createChanges($oldAccount, $account);
if(empty($changes)) return true;
$actionID = $this->loadModel('action')->create('account', $id, 'Edited');
$this->action->logHistory($actionID, $changes);
return true;
}
return false;
}
}
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env php
<?php
/**
title=accountModel->create();
cid=0
- 检查插入数据库的数据。
- 属性name @测试账号
- 属性provider @aliyun
- 属性account @admin
- 属性password @admin
- 必填项检查。第name条的0属性 @『名称』不能为空。
- 邮箱格式检查。第email条的0属性 @『邮件』应当为合法的EMAIL。
- 手机格式检查。第mobile条的0属性 @『手机』应当为合法的手机号码。
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
su('admin');
zenData('account')->gen(0);
$accountData = new stdclass();
$accountData->name = '测试账号';
$accountData->provider = 'aliyun';
$accountData->adminURI = '';
$accountData->account = 'admin';
$accountData->password = 'admin';
$accountData->email = 'admin@163.com';
$accountData->mobile = '';
$accountData->type = '';
$accountData->status = '';
$accountData->extra = '';
$accountData->createdBy = 'admin';
$accountData->createdDate = date('Y-m-d H:i:s');
global $tester;
$accountModel = $tester->loadModel('account');
$accountID = $accountModel->create($accountData);
$account = $accountModel->dao->select('*')->from(TABLE_ACCOUNT)->where('id')->eq($accountID)->fetch();
r($account) && p('name,provider,account,password') && e('测试账号,aliyun,admin,admin'); // 检查插入数据库的数据。
$accountData->name = '';
$accountModel->create($accountData);
r(dao::getError()) && p('name:0') && e('『名称』不能为空。'); //必填项检查。
$accountData->name = '测试账号';
$accountData->email = 'test';
$accountModel->create($accountData);
r(dao::getError()) && p('email:0') && e('『邮件』应当为合法的EMAIL。'); // 邮箱格式检查。
$accountData->email = 'admin@163.com';
$accountData->mobile = 'test';
$accountModel->create($accountData);
r(dao::getError()) && p('mobile:0') && e('『手机』应当为合法的手机号码。'); // 手机格式检查。
-34
View File
@@ -1,34 +0,0 @@
#!/usr/bin/env php
<?php
/**
title=accountModel->getList();
timeout=0
cid=0
- 获取所有数据。 @10
- 根据搜索条件搜索。 @10
- 根据第一个保存条件搜索。 @0
- 分页,每页5条,获取第一页。 @5
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
su('admin');
zenData('user')->gen(5);
zenData('account')->gen(10);
zenData('userquery')->gen(1);
global $tester;
$accountModel = $tester->loadModel('account');
$accountModel->app->moduleName = 'account';
$accountModel->app->methodName = 'browse';
r(count($accountModel->getList())) && p() && e('10'); // 获取所有数据。
r(count($accountModel->getList('bysearch', '0'))) && p() && e('10'); // 根据搜索条件搜索。
r(count($accountModel->getList('bysearch', '1'))) && p() && e('0'); // 根据第一个保存条件搜索。
$accountModel->app->loadClass('pager', $static = true);
$pager = new pager(0, 5, 1);
r(count($accountModel->getList('all', '', 'id_desc', $pager))) && p() && e('5'); // 分页,每页5条,获取第一页。
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env php
<?php
/**
title=accountModel->getPairs();
timeout=0
cid=0
- 检查获取数据数。 @10
- 检查部分数据。
- 属性1 @运维账号1
- 属性2 @运维账号2
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
su('admin');
zenData('account')->gen(10);
global $tester;
$accountModel = $tester->loadModel('account');
$pairs = $accountModel->getPairs();
r(count($pairs)) && p() && e('10'); // 检查获取数据数。
r($pairs) && p('1,2') && e('运维账号1,运维账号2'); // 检查部分数据。
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env php
<?php
/**
title=accountModel->update();
timeout=0
cid=0
- 检查插入数据库的数据。
- 属性id @1
- 属性name @运维账号1
- 属性provider @qingyun
- 检查插入数据库的数据。
- 属性id @1
- 属性name @测试账号
- 属性provider @aliyun
- 属性account @admin
- 属性password @admin
- 必填项检查。第name条的0属性 @『名称』不能为空。
- 邮箱格式检查。第email条的0属性 @『邮件』应当为合法的EMAIL。
- 手机格式检查。第mobile条的0属性 @『手机』应当为合法的手机号码。
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
su('admin');
zenData('account')->gen(5);
$account = new stdclass();
$account->name = '测试账号';
$account->provider = 'aliyun';
$account->adminURI = '';
$account->account = 'admin';
$account->password = 'admin';
$account->email = 'admin@163.com';
$account->mobile = '';
$account->type = '';
$account->status = '';
$account->extra = '';
$account->createdBy = 'admin';
$account->createdDate = date('Y-m-d H:i:s');
global $tester;
$accountID = 1;
$accountModel = $tester->loadModel('account');
$oldAccount = $accountModel->dao->select('*')->from(TABLE_ACCOUNT)->where('id')->eq($accountID)->fetch();
$accountModel->update($accountID, $account);
$newAccount = $accountModel->dao->select('*')->from(TABLE_ACCOUNT)->where('id')->eq($accountID)->fetch();
r($oldAccount) && p('id,name,provider') && e('1,运维账号1,qingyun'); // 检查插入数据库的数据。
r($newAccount) && p('id,name,provider,account,password') && e('1,测试账号,aliyun,admin,admin'); // 检查插入数据库的数据。
$account->name = '';
$accountModel->create($account);
r(dao::getError()) && p('name:0') && e('『名称』不能为空。'); //必填项检查。
$account->name = '测试账号';
$account->email = 'test';
$accountModel->create($account);
r(dao::getError()) && p('email:0') && e('『邮件』应当为合法的EMAIL。'); // 邮箱格式检查。
$account->email = 'admin@163.com';
$account->mobile = 'test';
$accountModel->create($account);
r(dao::getError()) && p('mobile:0') && e('『手机』应当为合法的手机号码。'); // 手机格式检查。
-44
View File
@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
/**
* The browse view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Ke Zhao<zhaoke@easycorp.ltd>
* @package account
* @link https://www.zentao.net
*/
namespace zin;
$queryMenuLink = createLink('account', 'browse', "browseType=bySearch&param={queryID}");
featureBar
(
set::queryMenuLinkCallback(fn($key) => str_replace('{queryID}', (string)$key, $queryMenuLink)),
li(searchToggle())
);
/* zin: Define the toolbar on main menu. */
$canCreate = hasPriv('account', 'create');
$createLink = $this->createLink('account', 'create');
$createItem = array('text' => $lang->account->create, 'url' => $createLink, 'class' => 'primary', 'icon' => 'plus', 'data-toggle' => 'modal', 'data-size' => 'sm');
$tableData = initTableData($accountList, $config->account->dtable->fieldList, $this->account);
toolbar
(
$canCreate ? item(set($createItem)) : null,
);
dtable
(
set::userMap($users),
set::cols(array_values($config->account->dtable->fieldList)),
set::data($tableData),
set::sortLink(createLink('account', 'browse', "browseType=$browseType&param=$param&orderBy={name}_{sortType}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}")),
set::orderBy($orderBy),
set::footPager(usePager())
);
render();
-78
View File
@@ -1,78 +0,0 @@
<?php
declare(strict_types=1);
/**
* The create view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Ke Zhao<zhaoke@easycorp.ltd>
* @package account
* @link https://www.zentao.net
*/
namespace zin;
formPanel
(
set::id('accountCreateForm'),
set::title($lang->account->create),
formRow
(
formGroup
(
set::name('name'),
set::label($lang->account->name)
)
),
formRow
(
formGroup
(
set::width('1/2'),
set::label($lang->account->provider),
set::control('picker'),
set::name('provider'),
set::items($lang->serverroom->providerList)
)
),
formRow
(
formGroup
(
set::name('adminURI'),
set::label($lang->account->adminURI)
)
),
formRow
(
formGroup
(
set::name('account'),
set::label($lang->account->account)
)
),
formRow
(
formGroup
(
set::name('password'),
set::label($lang->account->password)
)
),
formRow
(
formGroup
(
set::name('email'),
set::label($lang->account->email)
)
),
formRow
(
formGroup
(
set::name('mobile'),
set::label($lang->account->mobile)
)
)
);
-85
View File
@@ -1,85 +0,0 @@
<?php
declare(strict_types=1);
/**
* The edit view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Ke Zhao<zhaoke@easycorp.ltd>
* @package account
* @link https://www.zentao.net
*/
namespace zin;
formPanel
(
set::id('accountCreateForm'),
set::title($lang->account->edit),
formRow
(
formGroup
(
set::name('name'),
set::label($lang->account->name),
set::value($account->name)
)
),
formRow
(
formGroup
(
set::width('1/2'),
set::label($lang->account->provider),
set::control('picker'),
set::name('provider'),
set::items($lang->serverroom->providerList),
set::value($account->provider)
)
),
formRow
(
formGroup
(
set::name('adminURI'),
set::label($lang->account->adminURI),
set::value($account->adminURI)
)
),
formRow
(
formGroup
(
set::name('account'),
set::label($lang->account->account),
set::value($account->account)
)
),
formRow
(
formGroup
(
set::name('password'),
set::label($lang->account->password),
set::value($account->password)
)
),
formRow
(
formGroup
(
set::name('email'),
set::label($lang->account->email),
set::value($account->email)
)
),
formRow
(
formGroup
(
set::name('mobile'),
set::label($lang->account->mobile),
set::value($account->mobile)
)
)
);
-89
View File
@@ -1,89 +0,0 @@
<?php
declare(strict_types=1);
/**
* The detail view file of account module of ZenTaoPMS.
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Ke Zhao<zhaoke@easycorp.ltd>
* @package account
* @link https://www.zentao.net
*/
namespace zin;
global $lang;
if(isAjaxRequest('modal'))
{
$config->account->actionList['edit']['data-load'] = 'modal';
unset($config->account->actionList['edit']['data-toggle']);
}
$actions = $this->loadModel('common')->buildOperateMenu($account);
detailHeader
(
isAjaxRequest('modal') ? to::prefix() : '',
to::title(
entityLabel(
set(array('entityID' => $account->id, 'level' => 1, 'text' => $account->name))
)
)
);
detailBody
(
sectionList
(
section
(
tableData
(
item
(
set::name($lang->account->name),
$account->name
),
item
(
set::name($lang->account->provider),
\zget($lang->serverroom->providerList, $account->provider)
),
item
(
set::name($lang->account->adminURI),
$account->adminURI
),
item
(
set::name($lang->account->account),
$account->account
),
item
(
set::name($lang->account->password),
$account->password
),
item
(
set::name($lang->account->email),
$account->email
),
item
(
set::name($lang->account->mobile),
$account->mobile
)
)
)
),
history
(
set::commentUrl(createLink('action', 'comment', array('objectType' => 'account', 'objectID' => $account->id)))
),
floatToolbar
(
set::object($account),
isAjaxRequest('modal') ? null : to::prefix(backBtn(set::icon('back'), set::className('ghost text-white'), $lang->goback)),
set::suffix($actions['suffixActions'])
)
);
render();
-98
View File
@@ -1,98 +0,0 @@
<?php
/**
* The browse view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author pengjiangxiu <pengjiangxiu@cnezsoft.com>
* @package account
* @version $Id$
* @link https://www.zentao.net
*/
?>
<?php include $app->getModuleRoot() . 'common/view/header.html.php';?>
<?php js::set('confirmDelete', $lang->account->confirmDelete)?>
<?php js::set('browseType', $browseType)?>
<style>
.modal-header .modal-title {font-weight: bold !important;}
.modal-body #ajaxForm {margin-right: 20px;}
.c-actions .btn+.btn {margin-left: 0px !important;;}
</style>
<div id='mainMenu' class='clearfix'>
<div class='pull-left btn-toolbar'>
<?php echo html::a(inlink('browse'), "<span class='text'>{$lang->account->all}</span>", '', "class='btn btn-link btn-active-text' id='allTab'")?>
<a href='#' id='bysearchTab' class='btn btn-link querybox-toggle'><i class='icon-search icon'></i>&nbsp;<?php echo $lang->account->byQuery;?></a>
</div>
<?php if(common::hasPriv('account', 'create')):?>
<div class="btn-toolbar pull-right" id='createActionMenu'>
<?php
$misc = "class='btn btn-primary'";
echo html::a(inLink('create', '', '', true), "<i class='icon icon-plus'></i>" . $lang->account->create, '', "class='btn btn-primary' data-toggle='modal'");
?>
</div>
<?php endif;?>
</div>
<div id='queryBox' class='cell <?php if($browseType =='bysearch') echo 'show';?>' data-module='account'></div>
<div id='mainContent' class='main-row'>
<div class='main-col main-table' id='accountList'>
<?php if(empty($accountList)):?>
<div class="table-empty-tip">
<p>
<span class="text-muted"><?php echo $lang->account->empty;?></span>
<?php if(common::hasPriv('account', 'create')) echo html::a(inLink('create', '', '', true), "<i class='icon icon-plus'></i>" . $lang->account->create, '', "class='btn btn-info' data-toggle='modal'");?>
</p>
</div>
<?php else:?>
<table class='table has-sort-head table-fixed'>
<thead>
<tr>
<?php $vars = "browseType=$browseType&param=$param&orderBy=%s&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}";?>
<th class='w-60px'> <?php common::printOrderLink('id', $orderBy, $vars, $lang->idAB);?></th>
<th class='text-left'><?php common::printOrderLink('name', $orderBy, $vars, $lang->account->name);?></th>
<th class='w-100px'> <?php common::printOrderLink('provider', $orderBy, $vars, $lang->account->provider);?></th>
<th class='text-left'><?php common::printOrderLink('account', $orderBy, $vars, $lang->account->account);?></th>
<th class='w-110px'> <?php common::printOrderLink('email', $orderBy, $vars, $lang->account->email);?></th>
<th class='w-110px'> <?php common::printOrderLink('mobile', $orderBy, $vars, $lang->account->mobile);?></th>
<th class='w-100px'> <?php common::printOrderLink('createdBy', $orderBy, $vars, $lang->account->createdBy);?></th>
<th class='w-80px'> <?php echo $lang->actions?></th>
</tr>
</thead>
<tbody>
<?php foreach($accountList as $account):?>
<tr class='text-left'>
<td><?php printf('%03d', $account->id);?></td>
<td title='<?php echo $account->name?>'><?php echo html::a($this->inlink('view', "id=$account->id", 'html'), $account->name);?></td>
<td><?php echo zget($lang->serverroom->providerList, $account->provider);?></td>
<td><?php echo $account->account;?></td>
<td><?php echo $account->email;?></td>
<td><?php echo $account->mobile;?></td>
<td><?php echo $account->createdBy;?></td>
<td class='c-actions'>
<?php
common::printLink('account','edit', "id={$account->id}&from=self", "<i class='icon-common-edit icon-edit'> </i>", '', "data-toggle='modal' class='btn' title='{$lang->edit}'", '', true);
if(common::hasPriv('account', 'delete', $account))
{
$deleteURL = $this->createLink('account', 'delete', "id=$account->id");
echo html::a("javascript:ajaxDelete(\"$deleteURL\",\"accountList\",confirmDelete)", '<i class="icon-trash"></i>', '', "class='btn' title='{$lang->delete}'");
}
?>
</td>
</tr>
<?php endforeach;?>
</tbody>
</table>
<div class='table-footer'><?php $pager->show('right', 'pagerjs');?></div>
<?php endif;?>
</div>
</div>
<script>
$(function()
{
<?php if($browseType == 'bymodule'):?>
$('#module<?php echo $param?>').closest('li').addClass('active');
<?php endif;?>
if(browseType == 'bysearch') $.toggleQueryBox(true);
})
</script>
<?php include $app->getModuleRoot() . 'common/view/footer.html.php';?>
-52
View File
@@ -1,52 +0,0 @@
<?php
/**
* The create view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author guanxiying <guanxiying@easycorp.ltd>
* @package account
* @version $Id$
* @link https://www.zentao.net
*/
?>
<?php include '../../common/view/header.modal.html.php';?>
<form id='ajaxForm' method='post' action='<?php echo inlink('create');?>'>
<table class='table table-form'>
<tr>
<th class='w-100px'><?php echo $lang->account->name;?></th>
<td class='required'><?php echo html::input('name', '', "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->provider;?></th>
<td class='required'><?php echo html::select('provider', $lang->serverroom->providerList, '', "class='form-control chosen'");?></td>
</tr>
<tr>
<th><?php echo $lang->account->adminURI;?></th>
<td><?php echo html::input('adminURI', '', "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->account;?></th>
<td class='required'><?php echo html::input('account', '', "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->password;?></th>
<td><?php echo html::input('password', '', "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->email;?></th>
<td><?php echo html::input('email', '', "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->mobile;?></th>
<td><?php echo html::input('mobile', '', "class='form-control'")?></td>
</tr>
<tr>
<td colspan='2' class='text-center form-actions'>
<?php echo html::submitButton();?>
<?php if(!isonlybody()) echo html::backButton();?>
</td>
</tr>
</table>
</form>
<?php include '../../common/view/footer.modal.html.php';?>
-66
View File
@@ -1,66 +0,0 @@
<?php
/**
* The create view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author guanxiying <guanxiying@easycorp.ltd>
* @package account
* @version $Id$
* @link https://www.zentao.net
*/
?>
<?php include '../../common/view/header.modal.html.php';?>
<?php if(!helper::isAjaxRequest()):?>
<div>
<div class="modal-content">
<div class="modal-header">
<strong class="modal-title"><?php if(!empty($title)) echo $title; ?></strong>
<?php if(!empty($subtitle)) echo "<label class='text-important'>" . $subtitle . '</label>'; ?>
</div>
<div class="modal-body">
<?php endif;?>
<form id='ajaxForm' method='post' action="<?php echo inlink('edit', "id=$account->id&from=$from");?>">
<table class='table table-form'>
<tr>
<th class='w-100px'><?php echo $lang->account->name?></th>
<td class='required'><?php echo html::input('name', "$account->name", "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->provider;?></th>
<td class='required'><?php echo html::select('provider', $lang->serverroom->providerList, $account->provider, "class='form-control chosen'");?></td>
</tr>
<tr>
<th><?php echo $lang->account->adminURI?></th>
<td><?php echo html::input('adminURI', "$account->adminURI", "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->account;?></th>
<td class='required'><?php echo html::input('account', "$account->account", "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->password;?></th>
<td><?php echo html::input('password', "$account->password", "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->email;?></th>
<td><?php echo html::input('email', "$account->email", "class='form-control'")?></td>
</tr>
<tr>
<th><?php echo $lang->account->mobile;?></th>
<td><?php echo html::input('mobile', "$account->mobile", "class='form-control'")?></td>
</tr>
<tr>
<td colspan='2' class='text-center form-actions'>
<?php echo html::submitButton();?>
<?php echo html::backButton('', '', 'btn btn-wide');?>
</td>
</tr>
</table>
</form>
<?php if(!helper::isAjaxRequest()):?>
</div>
</div>
</div>
<?php endif;?>
<?php include '../../common/view/footer.modal.html.php';?>
-65
View File
@@ -1,65 +0,0 @@
<?php
/**
* The view view file of account module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Yidong Wang <yidong@cnezsoft.com>
* @package account
* @version $Id$
* @link https://www.zentao.net
*/
?>
<?php include $app->getModuleRoot() . 'common/view/header.html.php';?>
<?php js::set('confirmDelete', $lang->account->confirmDelete)?>
<div id='mainContent' class='main-content'>
<div class='main-header'>
<h2><?php echo $lang->account->view;?></h2>
</div>
<div class='main'>
<div class='detail'>
<table class='table table-form'>
<tr>
<th class='w-100px'><?php echo $lang->account->name?></th>
<td class='w-p25-f'><?php echo $account->name; ?></td>
<td></td>
</tr>
<tr>
<th><?php echo $lang->account->provider;?></th>
<td><?php echo zget($lang->serverroom->providerList, $account->provider);?></td>
</tr>
<tr>
<th><?php echo $lang->account->adminURI?></th>
<td><?php echo $account->adminURI; ?>
</tr>
<tr>
<th><?php echo $lang->account->account;?></th>
<td><?php echo $account->account; ?>
</tr>
<tr>
<th><?php echo $lang->account->password;?></th>
<td><?php echo $account->password; ?>
</tr>
<tr>
<th><?php echo $lang->account->email;?></th>
<td><?php echo $account->email; ?>
</tr>
<tr>
<th><?php echo $lang->account->mobile;?></th>
<td><?php echo $account->mobile; ?>
</tr>
</table>
</div>
<?php include $app->getModuleRoot() . 'common/view/action.html.php'?>
</div>
<div id='mainActions' class='main-actions'>
<nav class='container'></nav>
<div class='btn-toolbar'>
<?php
common::printLink('account', 'edit', "id=$account->id", "<i class='icon-edit'></i> " . $lang->edit, '', "class='btn iframe'", '', true, $account);
if(!isonlybody()) common::printLink('account', 'browse', "", "<i class='icon-goback icon-back'></i> " . $lang->goback, '', "class='btn'");
?>
</div>
</div>
</div>
<?php include $app->getModuleRoot() . 'common/view/footer.html.php';?>
+15 -2
View File
@@ -353,6 +353,14 @@ $lang->action->desc->updatecustom = '$date, updated by <strong>$actor</st
$lang->action->desc->delist = '$date, delist by <strong>$actor</strong>.' . "\n";
$lang->action->desc->publish = '$date, publish by <strong>$actor</strong>.' . "\n";
$lang->action->desc->manualbackup = '$date, Manually backed up by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autobackup = '$date, Automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->openautobackup = '$date, Enable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->closeautobackup = '$date, Disable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autodeletebackups = '$date, Automatically clean up backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->savebackupsettings = '$date, Save backup settings backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->deleteexpiredbackup = '$date, Clean up expired backups by <strong>$actor</strong>.' . "\n";
/* Used to display dynamic information. */
$lang->action->label = new stdclass();
$lang->action->label->install = 'install ';
@@ -553,8 +561,13 @@ $lang->action->label->deletebackup = 'Deleted system backup';
$lang->action->label->restorebackup = 'Restored system backup';
$lang->action->label->upgradesystem = 'Executed system upgrade';
$lang->action->label->system = '';
$lang->action->label->synced = 'synced';
$lang->action->label->imported = 'imported';
$lang->action->label->manualbackup = 'Manual backup';
$lang->action->label->autobackup = 'Automatic backup';
$lang->action->label->openautobackup = 'Enable automatic backup';
$lang->action->label->closeautobackup = 'Disable automatic backup';
$lang->action->label->autodeletebackups = 'Automatically clean up backups';
$lang->action->label->savebackupsettings = 'Save backup settings';
$lang->action->label->deleteexpiredbackup = 'Clean up expired backups';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
+15 -2
View File
@@ -353,6 +353,14 @@ $lang->action->desc->updatecustom = '$date, updated by <strong>$actor
$lang->action->desc->delist = '$date, delist by <strong>$actor</strong>.' . "\n";
$lang->action->desc->publish = '$date, publish by <strong>$actor</strong>.' . "\n";
$lang->action->desc->manualbackup = '$date, Manually backed up by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autobackup = '$date, Automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->openautobackup = '$date, Enable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->closeautobackup = '$date, Disable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autodeletebackups = '$date, Automatically clean up backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->savebackupsettings = '$date, Save backup settings backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->deleteexpiredbackup = '$date, Clean up expired backups by <strong>$actor</strong>.' . "\n";
/* Used to display dynamic information. */
$lang->action->label = new stdclass();
$lang->action->label->install = 'install ';
@@ -553,8 +561,13 @@ $lang->action->label->deletebackup = 'Deleted system backup';
$lang->action->label->restorebackup = 'Restored system backup';
$lang->action->label->upgradesystem = 'Executed system upgrade';
$lang->action->label->system = '';
$lang->action->label->synced = 'synced';
$lang->action->label->imported = 'imported';
$lang->action->label->manualbackup = 'Manual backup';
$lang->action->label->autobackup = 'Automatic backup';
$lang->action->label->openautobackup = 'Enable automatic backup';
$lang->action->label->closeautobackup = 'Disable automatic backup';
$lang->action->label->autodeletebackups = 'Automatically clean up backups';
$lang->action->label->savebackupsettings = 'Save backup settings';
$lang->action->label->deleteexpiredbackup = 'Clean up expired backups';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
+15 -2
View File
@@ -353,6 +353,14 @@ $lang->action->desc->updatecustom = '$date, updated by <strong>$actor
$lang->action->desc->delist = '$date, delist by <strong>$actor</strong>.' . "\n";
$lang->action->desc->publish = '$date, publish by <strong>$actor</strong>.' . "\n";
$lang->action->desc->manualbackup = '$date, Manually backed up by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autobackup = '$date, Automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->openautobackup = '$date, Enable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->closeautobackup = '$date, Disable automatic backup by <strong>$actor</strong>.' . "\n";
$lang->action->desc->autodeletebackups = '$date, Automatically clean up backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->savebackupsettings = '$date, Save backup settings backups by <strong>$actor</strong>.' . "\n";
$lang->action->desc->deleteexpiredbackup = '$date, Clean up expired backups by <strong>$actor</strong>.' . "\n";
/* Used to display dynamic information. */
$lang->action->label = new stdclass();
$lang->action->label->install = 'install ';
@@ -553,8 +561,13 @@ $lang->action->label->deletebackup = 'Deleted system backup';
$lang->action->label->restorebackup = 'Restored system backup';
$lang->action->label->upgradesystem = 'Executed system upgrade';
$lang->action->label->system = '';
$lang->action->label->synced = 'synced';
$lang->action->label->imported = 'imported';
$lang->action->label->manualbackup = 'Manual backup';
$lang->action->label->autobackup = 'Automatic backup';
$lang->action->label->openautobackup = 'Enable automatic backup';
$lang->action->label->closeautobackup = 'Disable automatic backup';
$lang->action->label->autodeletebackups = 'Automatically clean up backups';
$lang->action->label->savebackupsettings = 'Save backup settings';
$lang->action->label->deleteexpiredbackup = 'Clean up expired backups';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass();
+17 -2
View File
@@ -353,6 +353,14 @@ $lang->action->desc->updatecustom = '$date, 由 <strong>$actor</stron
$lang->action->desc->delist = '$date, 由 <strong>$actor</strong> 下架。' . "\n";
$lang->action->desc->publish = '$date, 由 <strong>$actor</strong> 发布。' . "\n";
$lang->action->desc->manualbackup = '$date, 由 <strong>$actor</strong> 手动备份。' . "\n";
$lang->action->desc->autobackup = '$date, 由 <strong>$actor</strong> 自动备份。' . "\n";
$lang->action->desc->openautobackup = '$date, 由 <strong>$actor</strong> 开启自动备份。' . "\n";
$lang->action->desc->closeautobackup = '$date, 由 <strong>$actor</strong> 关闭自动备份。' . "\n";
$lang->action->desc->autodeletebackups = '$date, 由 <strong>$actor</strong> 自动清理备份。' . "\n";
$lang->action->desc->savebackupsettings = '$date, 由 <strong>$actor</strong> 保存备份设置。' . "\n";
$lang->action->desc->deleteexpiredbackup = '$date, 由 <strong>$actor</strong> 清理过期备份。' . "\n";
/* 用来显示动态信息。*/
$lang->action->label = new stdclass();
$lang->action->label->install = '安装了';
@@ -553,8 +561,15 @@ $lang->action->label->deletebackup = '删除了系统备份';
$lang->action->label->restorebackup = '还原了系统备份';
$lang->action->label->upgradesystem = '执行了系统升级';
$lang->action->label->system = '';
$lang->action->label->synced = '同步了';
$lang->action->label->imported = '导入了';
$lang->action->label->manualbackup = '手动备份了';
$lang->action->label->autobackup = '自动备份了';
$lang->action->label->openautobackup = '开启自动备份';
$lang->action->label->closeautobackup = '关闭自动备份';
$lang->action->label->autodeletebackups = '自动清理备份';
$lang->action->label->savebackupsettings = '保存备份设置';
$lang->action->label->deleteexpiredbackup = '清理过期备份';
/* 动态信息按照对象分组 */
$lang->action->dynamicAction = new stdclass();
+21 -15
View File
@@ -271,7 +271,7 @@ class actionModel extends model
{
$history->oldValue = '';
$oldValues = explode(',', $history->old);
foreach($oldValues as $key => $value) $history->oldValue .= zget($fieldList, $value) . ',';
foreach($oldValues as $value) $history->oldValue .= zget($fieldList, $value) . ',';
$history->oldValue = trim($history->oldValue, ',');
}
@@ -279,7 +279,7 @@ class actionModel extends model
{
$history->newValue = '';
$newValues = explode(',', $history->new);
foreach($newValues as $key => $value) $history->newValue .= zget($fieldList, $value) . ',';
foreach($newValues as $value) $history->newValue .= zget($fieldList, $value) . ',';
$history->newValue = trim($history->newValue, ',');
}
}
@@ -294,7 +294,7 @@ class actionModel extends model
{
$history->oldValue = '';
$oldValues = explode(',', $history->old);
foreach($oldValues as $key => $value) $history->oldValue .= zget($users, $value) . ',';
foreach($oldValues as $value) $history->oldValue .= zget($users, $value) . ',';
$history->oldValue = trim($history->oldValue, ',');
}
@@ -302,7 +302,7 @@ class actionModel extends model
{
$history->newValue = '';
$newValues = explode(',', $history->new);
foreach($newValues as $key => $value) $history->newValue .= zget($users, $value) . ',';
foreach($newValues as $value) $history->newValue .= zget($users, $value) . ',';
$history->newValue = trim($history->newValue, ',');
}
}
@@ -777,13 +777,11 @@ class actionModel extends model
}
}
if($action->objectType == 'story' && $action->action == 'closed')
$isCloseStory = $action->objectType == 'story' && $action->action == 'closed';
if($isCloseStory && !empty($extra) && strpos($extra, '|') !== false)
{
if(!empty($extra) && strpos($extra, '|') !== false)
{
list($extra, $status) = explode('|', $extra);
if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
list($extra) = explode('|', $extra);
if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
if($action->objectType == 'module' && strpos(',created,moved,', $action->action) !== false)
@@ -957,18 +955,22 @@ class actionModel extends model
* 通过视野获取用户可访问的动态类型。
* Get the action types that the user can access through the vision.
*
* @param string $module
* @access public
* @return string
*/
public function getActionCondition(): string
public function getActionCondition($module = ''): string
{
if($this->app->user->admin) return '';
$actionCondition = '';
if(!empty($this->app->user->rights['acls']['actions']))
if(isset($this->app->user->rights['acls']['actions']))
{
if(empty($this->app->user->rights['acls']['actions'])) return '1 != 1';
if($module && !isset($this->app->user->rights['acls']['actions'][$module])) return '1 != 1';
foreach($this->app->user->rights['acls']['actions'] as $moduleName => $actions)
{
if($module && $module != $moduleName) continue;
if(isset($this->lang->mainNav->{$moduleName}) && !empty($this->app->user->rights['acls']['views']) && !isset($this->app->user->rights['acls']['views'][$moduleName])) continue;
$actionCondition .= "(`objectType` = '{$moduleName}' AND `action` " . helper::dbIN(array_keys($actions)) . ") OR ";
}
@@ -1454,8 +1456,9 @@ class actionModel extends model
$action = $this->getById($actionID);
if(!$action || $action->action != 'deleted') return false;
list($table, $orderby, $field) = $this->actionTao->getUndeleteParamsByObjectType($action->objectType);
$object = $this->actionTao->getObjectBaseInfo($table, array('id' => $action->objectID), $field, $orderby);
list($table, $orderby, $field, $queryKey) = $this->actionTao->getUndeleteParamsByObjectType($action->objectType);
if(empty($queryKey)) $queryKey = 'id';
$object = $this->actionTao->getObjectBaseInfo($table, array($queryKey => $action->objectID), $field, $orderby);
if(empty($object)) return false;
$result = $this->checkActionCanUndelete($action, $object);
@@ -1665,7 +1668,10 @@ class actionModel extends model
$actionType = strtolower($actionType);
if(!isset($this->config->search->fields->{$objectType})) return false;
if((strpos($this->config->search->buildAction, ",{$actionType},") === false && $actionType != 'commented' && empty($_POST['comment'])) || ($actionType == 'commented' && empty($_POST['actioncomment']))) return false;
$isCommentedAction = $actionType == 'commented';
if(strpos($this->config->search->buildAction, ",{$actionType},") === false && !$isCommentedAction && empty($_POST['comment'])) return false;
if($isCommentedAction && empty($_POST['actioncomment'])) return false;
if($actionType == 'deleted' || $actionType == 'erased') return $this->search->deleteIndex($objectType, $objectID);
$field = $this->config->search->fields->{$objectType};
+16 -9
View File
@@ -369,7 +369,12 @@ class actionTao extends actionModel
$name = $execution->name;
if($execution->type == 'kanban') $method = 'kanban';
if($name) $action->extra = !common::hasPriv('execution', $method) || ($method == 'kanban' && isonlybody()) || $this->config->vision == 'or' ? $name : html::a(helper::createLink('execution', $method, "executionID={$action->execution}"), $name, '', "data-app='execution'");
if($name)
{
$isModalKanban = $method == 'kanban' && isonlybody();
$canShowLink = common::hasPriv('execution', $method) && !$isModalKanban && $this->config->vision != 'or';
$action->extra = $canShowLink ? html::a(helper::createLink('execution', $method, "executionID={$action->execution}"), $name, '', "data-app='execution'") : $name;
}
}
elseif($type == 'project')
{
@@ -790,7 +795,7 @@ class actionTao extends actionModel
{
$action->objectName = $this->dao->select('name')->from(TABLE_AI_ASSISTANT)->where('id')->eq($action->objectID)->fetch('name');
}
if(empty($action->objectName) && (substr($objectType, 0, 6) == 'gitlab' || substr($objectType, 0, 5) == 'gitea' || substr($objectType, 0, 4) == 'gogs' || substr($objectType, 0, 2) == 'mr')) $action->objectName = $action->extra;
if (empty($action->objectName) && preg_match('/^(gitlab|gitea|gogs|mr)/', $objectType)) $action->objectName = $action->extra;
}
/**
@@ -983,9 +988,10 @@ class actionTao extends actionModel
*/
public function getUndeleteParamsByObjectType(string $objectType): array
{
$table = $this->config->objectTables[$objectType];
$orderby = '';
$field = '*';
$table = $this->config->objectTables[$objectType];
$orderby = '';
$field = '*';
$queryKey = 'id';
switch($objectType)
{
case 'product':
@@ -996,11 +1002,12 @@ class actionTao extends actionModel
$field = 'id, acl, name, hasProduct';
break;
case 'doc':
$table = TABLE_DOCCONTENT;
$orderby = 'version desc';
$table = TABLE_DOCCONTENT;
$orderby = 'version desc';
$queryKey = 'doc';
default:
break;
}
return array($table, $orderby, $field);
}
return array($table, $orderby, $field, $queryKey);
}
}
+2 -2
View File
@@ -63,7 +63,7 @@ $config->admin->plugins[194]->viewLink = 'https://www.zentao.net/extension-viewE
$config->admin->apiRoot = 'https://www.zentao.net';
$config->admin->cdnRoot = 'https://cdn.easycorp.cn/web';
$config->admin->classURL = 'https://www.zentao.net/publicclass.html';
$config->admin->classURL = 'https://www.zentao.net/zentao.html';
$config->admin->dynamicURL = 'https://www.zentao.net/dynamic.html';
$config->admin->downloadURL = 'https://www.zentao.net/download.html';
$config->admin->extensionURL = 'https://www.zentao.net/extension-browse.html';
@@ -73,7 +73,7 @@ $config->admin->extAPIURL = $config->admin->apiSite . 'extension-apiGetExte
$config->admin->patchAPIURL = $config->admin->apiSite . 'extension-apiGetExtensions-bymodule-MTIxOA==-0-100-1.json';
$config->admin->downloadAPIURL = $config->admin->apiSite . 'download.json';
$config->admin->dynamicAPIURL = $config->admin->apiSite . 'dynamic-activities-%s.json';
$config->admin->videoAPIURL = $config->admin->apiSite . 'publicclass.json';
$config->admin->videoAPIURL = $config->admin->apiSite . 'zentao.json';
$config->admin->liteMenuList = array('system', 'company', 'feature', 'message', 'extension', 'dev', 'ai');
$config->admin->helpURL['system'] = 'https://www.zentao.net/book/zentaopms/538.html';
+9 -15
View File
@@ -155,7 +155,7 @@ $lang->admin->menuList->feature['menuOrder']['15'] = 'execution';
$lang->admin->menuList->feature['menuOrder']['20'] = 'qa';
$lang->admin->menuList->feature['menuOrder']['25'] = 'kanban';
$lang->admin->menuList->feature['menuOrder']['30'] = 'doc';
$lang->admin->menuList->feature['menuOrder']['50'] = 'user';
$lang->admin->menuList->feature['menuOrder']['39'] = 'user';
$lang->admin->menuList->feature['dividerMenu'] = ',user,';
@@ -188,30 +188,24 @@ if(helper::hasFeature('devops'))
$lang->admin->menuList->platform['order'] = 55;
//$lang->admin->menuList->platform['subMenu']['environment'] = array('link' => "{$lang->devops->environment}|gitlab|browse|", 'subModule' => 'gitlab,jenkins,sonarqube,gitea,gogs', 'alias' => 'create,edit,import');
$lang->admin->menuList->platform['subMenu']['resource'] = array('link' => "{$lang->devops->resource}|host|browse|", 'subModule' => 'host,account,serverroom,ops,tree,domain,service');
$lang->admin->menuList->platform['subMenu']['resource'] = array('link' => "{$lang->devops->resource}|serverroom|browse|", 'subModule' => 'serverroom,ops,tree,domain,service');
$lang->admin->menuList->platform['subMenu']['setrules'] = array('link' => "{$lang->devops->rules}|repo|setrules|");
$lang->admin->menuList->platform['tabMenu']['resource']['host'] = array('link' => "{$lang->devops->host}|host|browse|", 'alias' => 'create,edit,browse,view,treemap,changestatus,group', 'subModule' => 'tree');
$lang->admin->menuList->platform['tabMenu']['resource']['serverroom'] = array('link' => "{$lang->devops->serverroom}|serverroom|browse|", 'alias' => 'create,edit,view,browse');
$lang->admin->menuList->platform['tabMenu']['resource']['service'] = array('link' => "{$lang->devops->service}|service|browse|", 'alias' => 'create,edit,view,browse,manage');
$lang->admin->menuList->platform['tabMenu']['resource']['account'] = array('link' => "{$lang->devops->account}|account|browse|", 'alias' => 'create,edit,view,browse');
$lang->admin->menuList->platform['tabMenu']['resource']['domain'] = array('link' => "{$lang->devops->domain}|domain|browse|", 'alias' => 'create,edit,view,browse');
$lang->admin->menuList->platform['tabMenu']['resource']['provider'] = array('link' => "{$lang->devops->provider}|ops|provider|", 'alias' => 'provider');
$lang->admin->menuList->platform['tabMenu']['resource']['city'] = array('link' => "{$lang->devops->city}|ops|city|", 'alias' => 'city');
$lang->admin->menuList->platform['tabMenu']['resource']['cpuBrand'] = array('link' => "{$lang->devops->cpuBrand}|ops|cpuBrand|", 'alias' => 'cpubrand');
$lang->admin->menuList->platform['tabMenu']['resource']['os'] = array('link' => "{$lang->devops->os}|ops|os|", 'alias' => 'os');
$lang->admin->menuList->platform['tabMenu']['resource']['stage'] = array('link' => "{$lang->devops->stage}|ops|stage|", 'alias' => 'stage');
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['10'] = 'host';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['15'] = 'serverroom';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['20'] = 'service';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['25'] = 'account';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['30'] = 'domain';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['35'] = 'provider';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['40'] = 'city';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['45'] = 'cpuBrand';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['50'] = 'os';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['55'] = 'stage';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['25'] = 'domain';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['30'] = 'provider';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['35'] = 'city';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['40'] = 'os';
$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['45'] = 'stage';
if($config->edition === 'open')
{
@@ -219,8 +213,8 @@ if(helper::hasFeature('devops'))
unset($lang->admin->menuList->platform['tabMenu']['resource']['service']);
unset($lang->admin->menuList->platform['tabMenu']['resource']['stage']);
unset($lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['20']);
unset($lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['30']);
unset($lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['55']);
unset($lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['25']);
unset($lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['45']);
}
//$lang->admin->menuList->platform['menuOrder']['15'] = 'environment';
+5 -3
View File
@@ -39,9 +39,11 @@ $config->api->dtable->struct->fieldList['actions']['fixed'] = 'right';
$config->api->dtable->struct->fieldList['actions']['name'] = 'actions';
$config->api->dtable->struct->fieldList['actions']['menu'] = array('editStruct', 'deleteStruct');
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['icon'] = 'edit';
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['hint'] = $lang->api->editStruct;
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['url'] = helper::createLink('api', 'editStruct', 'lib={lib}&id={id}');
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['icon'] = 'edit';
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['hint'] = $lang->api->editStruct;
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['data-toggle'] = 'modal';
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['data-size'] = 'lg';
$config->api->dtable->struct->fieldList['actions']['list']['editStruct']['url'] = helper::createLink('api', 'editStruct', 'lib={lib}&id={id}');
$config->api->dtable->struct->fieldList['actions']['list']['deleteStruct']['icon'] = 'trash';
$config->api->dtable->struct->fieldList['actions']['list']['deleteStruct']['hint'] = $lang->api->deleteStruct;
+303 -99
View File
@@ -36,26 +36,25 @@ class api extends control
* @param int $version
* @param int $release
* @param string $browseType
* @param int $param
* @param string $params
* @access public
* @return void
*/
public function index(int $libID = 0, int $moduleID = 0, int $apiID = 0, int $version = 0, int $release = 0, string $browseType = '', int $param = 0)
public function index(int $libID = 0, int $moduleID = 0, int $apiID = 0, int $version = 0, int $release = 0, string $browseType = 'nolink', string $params = '', string $orderBy = 'order_asc', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $mode = '', string $search = '')
{
/* Get an api doc. */
if($apiID > 0)
{
/* 有apiID的直接打印详情页面。 */
echo $this->fetch('api', 'view', "libID=$libID&apiID=$apiID&moduleID=$moduleID&version=$version&release=$release");
return;
}
/* 设置空间类型。 */
$this->session->set('spaceType', 'api', 'doc');
/* 详情页返回上一页用的链接。 */
$this->session->set('structList', inLink('index', "libID=$libID&moduleID=$moduleID"), 'doc');
setCookie("docSpaceParam", '', $this->config->cookieLife, $this->config->webRoot, '', false, true);
if(empty($mode))
{
if(!$libID) $mode = 'home';
elseif($apiID) $mode = 'view';
else $mode = 'list';
}
$this->setMenu($libID);
$objectType = $this->objectType;
$objectID = $this->objectID;
@@ -75,33 +74,268 @@ class api extends control
$objectType = !empty($lib->product) ? 'product' : (!empty($lib->project) ? 'project' : 'nolink');
$objectID = !empty($lib->product) ? $lib->product : (!empty($lib->project) ? $lib->project : 0);
/* Build the search form. */
if($lib)
{
$browseType = $release ? 'byrelease' : $browseType;
$param = $release ? $release : $param;
$queryID = $browseType == 'bySearch' ? (int)$param : 0;
$actionURL = $this->createLink('api', 'index', "libID=$libID&moduleID=0&apiID=0&version=0&release=0&browseType=bySearch&param=myQueryID");
$this->api->buildSearchForm($lib, $queryID, $actionURL, $libs);
}
$this->view->title = $this->lang->api->pageTitle;
$this->view->lib = $lib;
$this->view->libID = $libID;
$this->view->libs = $libs;
$this->view->release = $release;
$this->view->moduleID = $moduleID;
$this->view->apiID = (int)$apiID;
$this->view->release = (int)$release;
$this->view->apiVersion = (int)$version;
$this->view->mode = $mode;
$this->view->filterType = $browseType;
$this->view->search = $search;
$this->view->recTotal = $recTotal;
$this->view->recPerPage = $recPerPage;
$this->view->pageID = $pageID;
$this->view->orderBy = $orderBy;
$this->view->objectType = $objectType;
$this->view->objectID = $objectID;
$this->view->moduleID = $moduleID;
$this->view->version = $version;
$this->view->apiList = $browseType == 'bySearch' ? $this->api->getApiListBySearch($libID, $queryID, '', array_keys($libs)) : $this->api->getListByModuleID($libID, $moduleID, $release);
$this->view->libTree = $this->doc->getLibTree($libID, $libs, 'api', $moduleID, $objectID, $browseType, (int)$param);
$this->view->objectDropdown = isset($libs[$libID]) ? $this->apiZen->generateLibsDropMenu($libs[$libID], $release) : '';
$this->view->spaceType = 'api';
$this->view->linkParams = '%s';
$this->view->params = $params;
$this->view->users = $this->loadModel('user')->getPairs('noclosed,noletter');
$this->view->title = $this->lang->api->pageTitle;
$this->display();
}
/**
* 获取接口空间首页页面。
* Get the api home page.
*
* @param string $type
* @param string $params
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function ajaxGetHome(string $type = 'nolink', string $params = 'notempty_unclosed', int $recPerPage = 20, int $pageID = 1)
{
if(!in_array($type, array('product', 'project', 'nolink'))) $type = 'nolink';
$libs = $this->doc->getApiLibs(0, $type);
$flags = explode('_', $params);
$unclosed = in_array('unclosed', $flags);
$notempty = in_array('notempty', $flags);
$apis = $this->api->getApiListBySearch(0, 0);
$recTotal = 0;
$recPerPage = $recPerPage ? max(1, $recPerPage) : 20;
$productsCount = array();
$projectsCount = array();
foreach($apis as $api)
{
if(!isset($libs[$api->lib])) continue;
$lib = $libs[$api->lib];
if(isset($lib->apiCount)) $lib->apiCount++;
else $lib->apiCount = 1;
if($lib->product)
{
if(isset($productsCount[$lib->product])) $productsCount[$lib->product]++;
else $productsCount[$lib->product] = 1;
}
if($lib->project)
{
if(isset($projectsCount[$lib->project])) $projectsCount[$lib->project]++;
else $projectsCount[$lib->project] = 1;
}
}
if($type === 'product' || $type === 'project')
{
$this->loadModel('program');
$this->app->loadLang('project');
$this->app->loadLang('product');
$programPairs = $this->loadModel('program')->getTopPairs($unclosed ? 'noclosed' : '');
$programs[] = (object)array('id' => 0, 'name' => $this->lang->$type->emptyProgram);
foreach($programPairs as $id => $name)
{
$program = new stdclass();
$program->id = $id;
$program->name = $name;
$programs[$id] = $program;
}
if($type == 'product')
{
$products = $this->loadModel('product')->getList(0, $unclosed ? 'noclosed' : 'all', 0, 0);
foreach($products as $product)
{
if(!isset($programs[$product->program])) continue;
$program = $programs[$product->program];
$program->products[] = $product;
$product->apiCount = isset($productsCount[$product->id]) ? $productsCount[$product->id] : 0;
$program->apiCount = isset($program->apiCount) ? $program->apiCount + $product->apiCount : $product->apiCount;
}
}
else
{
$projects = $this->program->getProjectList(0, $unclosed ? 'unclosed' : 'all', 0, 'order_asc,id_desc');
foreach($projects as $project)
{
$topParent = $project->parent ? explode(',', trim($project->path, ','))[0] : 0;
if(!isset($programs[$topParent])) continue;
$program = $programs[$topParent];
$program->projects[] = $project;
$project->apiCount = isset($projectsCount[$project->id]) ? $projectsCount[$project->id] : 0;
$program->apiCount = isset($program->apiCount) ? $program->apiCount + $project->apiCount : $project->apiCount;
}
}
if($notempty) $programs = array_filter($programs, function($program) {return isset($program->apiCount) && $program->apiCount > 0;});
$recTotal = count($programs);
$pageID = max(1, min($pageID, ceil($recTotal / $recPerPage)));
$programs = array_slice($programs, ($pageID - 1) * $recPerPage, $recPerPage);
}
else
{
if($notempty) $libs = array_filter($libs, function($lib) {return isset($lib->apiCount) && $lib->apiCount > 0;});
$recTotal = count($libs);
$pageID = max(1, min($pageID, ceil($recTotal / $recPerPage)));
$libs = array_slice($libs, ($pageID - 1) * $recPerPage, $recPerPage);
}
$this->app->loadClass('pager', true);
$this->view->programs = isset($programs) ? $programs : array();
$this->view->unclosed = $unclosed;
$this->view->notempty = $notempty;
$this->view->libs = $libs;
$this->view->type = $type;
$this->view->pager = new pager($recTotal, $recPerPage, $pageID);
$this->display();
}
/**
* 获取接口界面数据。
* Get the api UI data.
*
* @param string $spaceID
* @param string $picks
* @access public
* @return void
*/
public function ajaxGetData(string $spaceID = 'nolink', string $picks = '')
{
$noPicks = empty($picks);
$picks = $noPicks ? '' : ",$picks,";
$pickLib = $noPicks || strpos($picks, ',lib,') !== false;
$pickModule = $noPicks || strpos($picks, ',module,') !== false;
$data = array('spaceID' => $spaceID);
$isNolink = $spaceID == 'nolink';
list($objectType, $objectID) = $isNolink ? array('', 0) : explode('.', $spaceID . '.');
if($noPicks || strpos($picks, ',space,') !== false)
{
$spaces['nolink'] = array('id' => 'nolink', 'name' => $this->lang->api->noLinked, 'type' => 'api');
list($normalObjects, $closedObjects) = $this->api->getOrderedObjects();
foreach($normalObjects as $type => $list)
{
foreach($list as $id => $name) $spaces["$type.{$id}"] = array('id' => "$type.{$id}", 'name' => $name, 'type' => 'api', 'objectType' => $type);
}
foreach($closedObjects as $type => $list)
{
foreach($list as $id => $name) $spaces["$type.{$id}"] = array('id' => "$type.{$id}", 'name' => $name, 'type' => 'api', 'objectType' => $type, 'closed' => true);
}
if(!$isNolink && !isset($spaces[$spaceID]))
{
$object = $this->loadModel($objectType)->getByID((int)$objectID);
$spaces[$spaceID] = array('id' => $spaceID, 'name' => $object->name, 'type' => 'api');
}
$data['spaces'] = array_values($spaces);
}
if($pickLib || $pickModule)
{
$libs = $isNolink ? $this->doc->getApiLibs(0, 'nolink') : $this->doc->getApiLibs(0, $objectType, (int)$objectID);
$libIds = array_keys($libs);
if($pickLib)
{
$releases = $this->api->getReleaseByQuery($libIds);
$releaseMap = array();
foreach($releases as $release) $releaseMap[$release->lib][] = $release;
foreach($libs as $lib)
{
$lib->versions = isset($releaseMap[$lib->id]) ? $releaseMap[$lib->id] : array();
if(!empty($lib->product)) $lib->space = "product.{$lib->product}";
elseif(!empty($lib->project)) $lib->space = "project.{$lib->project}";
else $lib->space = 'nolink';
}
$data['libs'] = array_values($libs);
}
}
if($pickModule) $data['modules'] = array_values($this->doc->getModulesOfLibs($libIds, 'api'));
if($noPicks || strpos($picks, ',doc,') !== false)
{
$apis = $this->api->getApiListBySearch(0, 0, $objectType);
$unsetProps = array('commonParams', 'params', 'paramsExample', 'response', 'responseExample');
foreach($apis as $api)
{
foreach($unsetProps as $prop) unset($api->$prop);
$api->originTitle = $api->title;
$api->icon = "api is-$api->method";
$api->api = true;
$api->title = "$api->method $api->path $api->title";
}
$data['docs'] = array_values($apis);
}
echo json_encode($data);
}
/**
* 获取指定库的 API 列表。
* Get the API list of the specified library.
*
* @param int $libID
* @param int $releaseID
* @access public
* @return void
*/
public function ajaxGetLibApiList(int $libID, int $releaseID = 0)
{
if($releaseID)
{
$release = $this->api->getRelease(0, 'byID', $releaseID);
$apis = $release ? $this->api->getApiListByRelease($release) : array();
}
else
{
$apis = $this->api->getApiListBySearch($libID, 0);
}
$unsetProps = array('commonParams', 'params', 'paramsExample', 'response', 'responseExample');
foreach($apis as $api)
{
foreach($unsetProps as $prop) unset($api->$prop);
$api->originTitle = $api->title;
$api->icon = "api is-$api->method";
$api->api = true;
$api->title = "$api->method $api->path $api->title";
}
echo json_encode($apis);
}
/**
* 获取接口数据。
*
* @param int $apiID
* @param int $version
* @param int $release
* @access public
* @return void
*/
public function ajaxGetApi(int $apiID, int $version = 0, int $release = 0)
{
$api = $this->api->getByID($apiID, $version, $release);
if($api)
{
$api->originTitle = $api->title;
$api->icon = "api is-$api->method";
$api->title = "$api->method $api->path $api->title";
}
$this->send($api);
}
/**
* API详情页面。
* View api.
@@ -116,43 +350,17 @@ class api extends control
*/
public function view(int $libID, int $apiID, int $moduleID = 0, int $version = 0, int $release = 0)
{
if(strpos($this->server->http_referer, 'space') === false && strpos($this->server->http_referer, 'api') === false)
if(helper::isAjaxRequest('fetch'))
{
setCookie("docSpaceParam", '', $this->config->cookieLife, $this->config->webRoot, '', false, true);
$api = $this->api->getByID($apiID, $version, $release);
$this->view->api = $api;
$this->view->typeList = $this->api->getTypeList($api->lib);
$this->display();
return;
}
/* 获取文档目录列表和当前选中的文档目录. */
$libs = $this->doc->getApiLibs($libID, $this->objectType, (int)$this->objectID);
$api = $this->api->getByID($apiID, $version, $release);
$libID = $api->lib;
$lib = zget($libs, $libID);
$api->desc = htmlspecialchars_decode($api->desc);
if($libID && !isset($libs[$libID])) return $this->send(array('result' => 'fail', 'load' => array('alert' => $this->lang->doc->accessDenied, 'locate' => $this->createLink('api', 'index'))));
/* 生成一些必要的参数。 */
$type = $lib->product ? 'product' : ($lib->project ? 'project' : 'nolink');
$objectID = $lib->product ? $lib->product : $lib->project;
$linkObject = zget($lib, $type, 0);
$spaceType = 'api';
$moduleID = $api->module;
$linkParams = "%s";
if($this->app->tab != 'doc' && $type != 'nolink') $linkParams = "objectID=$linkObject&$linkParams";
/* 解析cookie并获取左侧目录树。 */
$this->apiZen->parseDocSpaceParam($libs, $libID, $type, $objectID, $moduleID, $spaceType, $release);
$this->view->title = $this->lang->api->pageTitle;
$this->view->isRelease = $release > 0;
$this->view->release = $release;
$this->view->version = $version;
$this->view->apiID = $apiID;
$this->view->api = $api;
$this->view->linkParams = $linkParams;
$this->view->typeList = $this->api->getTypeList($api->lib);
$this->view->users = $this->user->getPairs('noclosed,noletter');
$this->view->actions = $apiID ? $this->action->getList('api', $apiID) : array();
$this->display();
echo $this->fetch('api', 'index', "libID=$libID&moduleID=$moduleID&apiID=$apiID&version=$version&release=$release");
}
/**
@@ -194,7 +402,7 @@ class api extends control
$this->api->deleteRelease($id);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('load' => true, 'closeModal' => true));
return $this->sendSuccess(array('load' => true, 'closeModal' => true, 'docApp' => array(array('executeCommand', 'loadLazyContent', array('.api-release-list')), array('load', null, null, null, array('noLoading' => true, 'picks' => 'lib')))));
}
/**
@@ -218,7 +426,7 @@ class api extends control
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('load' => true, 'closeModal' => true));
return $this->sendSuccess(array('load' => true, 'closeModal' => true, 'docApp' => array(array('executeCommand', 'loadLazyContent', array('.api-release-list')), array('load', null, null, null, array('noLoading' => true, 'picks' => 'lib')))));
}
$this->display();
@@ -286,7 +494,7 @@ class api extends control
$this->api->createStruct($formData);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('locate' => helper::createLink('api', 'struct', "libID=$libID")));
return $this->sendSuccess(array('locate' => helper::createLink('api', 'struct', "libID=$libID"), 'docApp' => array('executeCommand', 'loadLazyContent', array('.api-struct-list'))));
}
$options = array();
@@ -324,7 +532,7 @@ class api extends control
$this->api->updateStruct($formData);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('locate' => helper::createLink('api', 'struct', "libID={$struct->lib}")));
return $this->sendSuccess(array('locate' => helper::createLink('api', 'struct', "libID={$struct->lib}"), 'docApp' => array('executeCommand', 'loadLazyContent', array('.api-struct-list'))));
}
$options = array();
@@ -353,7 +561,7 @@ class api extends control
$this->api->delete(TABLE_APISTRUCT, $structID);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('load' => inlink('struct', "libID=$libID")));
return $this->sendSuccess(array('load' => inlink('struct', "libID=$libID"), 'docApp' => array('executeCommand', 'loadLazyContent', array('.api-struct-list'))));
}
/**
@@ -370,24 +578,26 @@ class api extends control
if(!empty($_POST))
{
/* 组装formData。 */
$fields = $this->config->api->form->createLib;
if($this->post->libType == 'project') $fields['project']['required'] = true;
if($this->post->libType == 'product') $fields['product']['required'] = true;
$fields = $this->config->api->form->createLib;
$libType = $this->post->libType;
if($libType == 'project') $fields['project']['required'] = true;
if($libType == 'product') $fields['product']['required'] = true;
$formData = form::data($fields)->add('addedBy', $this->app->user->account)->add('addedDate', helper::now())->get();
$formData->product = $formData->libType == 'product' && !empty($formData->product) ? $formData->product : 0;
$formData->project = $formData->libType == 'project' && !empty($formData->project) ? $formData->project : 0;
$formData->execution = $formData->libType == 'project' && !empty($formData->execution) ? $formData->execution : 0;
$this->doc->createApiLib($formData);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if(helper::isAjaxRequest('modal')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
$libID = $this->doc->createApiLib($formData);
$objectID = $libType == 'project' ? $this->post->project : $this->post->product;
if(helper::isAjaxRequest('modal')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true, 'docApp' => array("selectSpace", ($libType == 'product' || $libType == 'project') ? "$libType.$objectID" : 'nolink', $libID, true)));
/* Set locate object data. */
setCookie("objectType", $this->post->libType, $this->config->cookieLife, $this->config->webRoot);
setCookie("objectID", $this->post->libType == 'project' ? $this->post->project : $this->post->product, $this->config->cookieLife, $this->config->webRoot);
setCookie("objectType", $libType, $this->config->cookieLife, $this->config->webRoot);
setCookie("objectID", $objectID, $this->config->cookieLife, $this->config->webRoot);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('api', 'index', "libID=$libID"), 'closeModal' => true));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('api', 'index', "libID=$libID"), 'closeModal' => true, 'docApp' => array("selectSpace", ($libType == 'product' || $libType == 'project') ? "$libType.$objectID" : 'nolink', $libID, true)));
}
/* 设置默认访问控制的语言项。 */
@@ -426,7 +636,7 @@ class api extends control
$this->doc->updateApiLib($id, $formData);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
return $this->sendSuccess(array('message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true, 'docApp' => array('load', null, null, null, array('noLoading' => true, 'picks' => 'lib'))));
}
$type = $lib->product ? 'product' : ($lib->project ? 'project' : 'nolink');
@@ -489,7 +699,7 @@ class api extends control
if(dao::isError()) return $this->sendError(dao::getError());
if(isInModal()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
return $this->sendSuccess(array('locate' => helper::createLink('api', 'index', "libID={$formData->lib}&moduleID={$formData->module}&apiID={$apiID}")));
return $this->sendSuccess(array('locate' => helper::createLink('api', 'index', "libID={$formData->lib}&moduleID={$formData->module}&apiID={$apiID}"), 'docApp' => array('executeCommand', 'loadApi', array($apiID, 0, 0, true))));
}
$this->setMenu($libID, $space);
@@ -526,7 +736,7 @@ class api extends control
$this->api->update($formData);
if(dao::isError()) return $this->sendError(dao::getError());
return $this->sendSuccess(array('locate' => helper::createLink('api', 'index', "libID=$api->lib&moduleID=0&apiID=$apiID")));
return $this->sendSuccess(array('locate' => helper::createLink('api', 'index', "libID=$api->lib&moduleID=0&apiID=$apiID"), 'docApp' => array(array('executeCommand', 'selectApi', array($apiID)), array('executeCommand', 'loadApi'))));
}
$this->setMenu($api->lib);
@@ -818,29 +1028,23 @@ class api extends control
*
* @param string $objectType
* @param int $objectID
* @param string $module
* @param string $method
* @param int $libID
* @access public
* @return void
*/
public function ajaxGetDropMenu(string $objectType, int $objectID, string $module, string $method)
public function ajaxGetDropMenu(string $objectType, int $objectID, int $libID = 0)
{
list($normalObjects, $closedObjects) = $this->api->getOrderedObjects();
$objects = $this->api->getGroupedObjects();
$programs = $this->loadModel('program')->getList('all', 'order_asc', 'top');
$programs = array_filter($programs, function($program) {return $program->parent == '0';});
$titleList = array($this->lang->api->noLinked);
if(!empty($normalObjects['product'])) $titleList += array_values($normalObjects['product']);
if(!empty($normalObjects['project'])) $titleList += array_values($normalObjects['project']);
if(!empty($closedObjects['product'])) $titleList += array_values($closedObjects['product']);
if(!empty($closedObjects['project'])) $titleList += array_values($closedObjects['project']);
$this->view->objectType = $objectType;
$this->view->objectID = $objectID;
$this->view->module = $module;
$this->view->method = $method;
$this->view->normalObjects = $normalObjects;
$this->view->closedObjects = $closedObjects;
$this->view->nolinkLibs = $this->doc->getApiLibs(0, 'nolink');
$this->view->objectsPinYin = common::convert2Pinyin($titleList);
$this->view->objectType = $objectType;
$this->view->objectID = $objectID;
$this->view->libID = $libID;
$this->view->programs = $programs;
$this->view->products = $objects['product'];
$this->view->projects = $objects['project'];
$this->view->nolinkLibs = $objects['nolink'];
$this->display();
}
-23
View File
@@ -1,23 +0,0 @@
.base-url > p {margin: 0;}
.table-empty-tip > div {display: inline-block;}
.detail .list-group-item .heading.GET { background-color: #e7f0f7; }
.detail .list-group-item .heading.GET a { color: #0f6ab4; }
.detail .list-group { height: calc(100vh - 215px); overflow-y: auto; }
.detail .list-group-item .heading.OPTIONS { background-color: #e7f0f7; }
.detail .list-group-item .heading.OPTIONS a { color: #0f6ab4; }
.detail .list-group-item .heading.POST { background-color: #e7f6ec; }
.detail .list-group-item .heading.POST a { color: #10a54a; }
.detail .list-group-item .heading.PUT { background-color: #f9f2e9; }
.detail .list-group-item .heading.PUT a { color: #c5862b; }
.detail .list-group-item .heading.PATCH { background-color: #f9f2e9; }
.detail .list-group-item .heading.PATCH a { color: #c5862b; }
.detail .list-group-item .heading.DELETE { background-color: #f5e8e8; border: 1px solid #e8c6c7; }
.detail .list-group-item .heading.DELETE a { color: #a41e22; }
.detail .list-group-item a { display: flex; align-items: center; }
.detail .list-group-item .path, .detail .list-group-item .desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail .list-group-item .path { padding-left: 10px; font-size: 14px; color: black; flex: 1; }
.detail .list-group-item .desc { width: auto; font-size: 14px; line-height: 38px; float: right; padding-right: 10px; max-width: 200px; }
.detail .list-group-item {list-style: none; font-size: 14px; margin-bottom: 10px; line-height: 30px}
.detail .list-group-item span.method {line-height: 38px; width: 65px;background: #2e7fff; color: #fff; text-align: center;}
.detail .list-group-item .label+.label {margin-left: -4px;}
.detail .list-group-item .label {border-radius: 0;}
-1
View File
@@ -1 +0,0 @@
.releaseModal .modal-dialog .modal-body {overflow-y: hidden;}
-1
View File
@@ -1 +0,0 @@
#featureBar .divider {color: #D9D9D9;float: left; height: 20px; margin: 4px 15px 4px 8px; border-left: 1px solid rgba(0,0,0,.1);}
-12
View File
@@ -1,12 +0,0 @@
.panel-heading { justify-content: space-between; }
.panel-actions { margin-left: auto; }
.panel .path {font-size: 18px; font-weight: 600; margin: 0;}
.panel h2 {font-size: 17px; font-weight: 600; margin: 0;}
.panel h3 {font-size: 17px; margin-top: 20px; margin-bottom: 10px;}
pre { display: block; padding: 9.5px; margin: 0px 0px 10px; font-size: 12px; line-height: 1.38462; color: rgb(49, 60, 82); word-break: break-all; overflow-wrap: break-word; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-radius: 4px; }
.scrollbar-hover { overflow-y: scroll; }
#mainContent .panel+.panel {flex: none}
#history{position: relative; width: 400px}
#history .history-changes {max-width: 400px !important; word-wrap: break-word;}
#history > #closeBtn { position: absolute; right: 10px; top: 10px;}
+525
View File
@@ -0,0 +1,525 @@
/**
* 定义页面上的自定义渲染器。
* Define the custom renderers on the page.
*/
const customRenders =
{
/**
* 落地页渲染,根据 API 接口类型显示不同的内容。
* Home page render, show different content according to the API interface type.
*/
home: function()
{
const docApp = this;
const filterType = docApp.location.filterType || 'nolink';
const pager = $.extend({page: 1, recPerPage: 20}, docApp.signals.pager.value);
const params = docApp.signals.params.value || 'notempty_unclosed';
const homeViewUrl = $.createLink('api', 'ajaxgethome', `type=${filterType}&params=${params}&recPerPage=${pager.recPerPage}&pageID=${pager.page}`);
return {fetcher: homeViewUrl, clearBeforeLoad: false, className: 'doc-api-home h-full', class: 'h-full col',htmlRender: (element, props) => $(element).morphInner(`<div class="lazy-content doc-api-home h-full">${props.html}</div>`)};
},
/**
* 定义 API 文档编辑器渲染,包括查看、编辑和创建。
* Define the API doc editor render, including view, edit and create.
*/
editor: function()
{
const mode = this.mode;
if(mode === 'create')
{
const libID = this.libID;
const moduleID = this.moduleID;
return {fetcher: $.createLink('api', 'create', `libID=${libID}&moduleID=${moduleID}&space=api`), loadingText: getDocAppLang('loading')};
}
if(mode === 'edit')
{
const doc = this.doc.data;
return {fetcher: $.createLink('api', 'edit', `apiID=${doc.id}`), loadingText: getDocAppLang('loading')};
}
const doc = this.doc.data;
if(!docAppHasPriv('view')) return {html: `<h1>${doc ? doc.title : ''}</h1><p>${getDocAppLang('accessDenied')}</p>`};
const release = this.signals.libReleaseMap.value[doc.lib] || 0;
const version = this.signals.docVersion.value || 0;
return {fetcher: $.createLink('api', 'view', `libID=${doc.lib}&apiID=${doc.id}&moduleID=${doc.module}&version=${version}&release=${release}`), loadingText: getDocAppLang('loadingDocsData'), className: 'doc-editor-content'};
},
/**
* 定义 API 文档列表渲染,包括结构和版本。
* Define the API doc list render, including structs and releases.
*/
list: function()
{
const listType = this.signals.listType.value;
const libID = this.libID;
const releaseID = this.signals.libReleaseMap.value[libID] || 0;
if(listType === 'structs') return {fetcher: $.createLink('api', 'struct', `libID=${this.libID}&releaseID=${releaseID}`), loadingText: getDocAppLang('loading'), className: 'api-struct-list'};
if(listType === 'releases') return {fetcher: $.createLink('api', 'releases', `libID=${this.libID}`), loadingText: getDocAppLang('loading'), className: 'api-release-list'};
},
/**
* 定义 API 文档列表筛选菜单渲染,当展示结构和版本时不显示筛选菜单。
* Define the API doc list filter menu render, not show the filter menu when showing structs and releases.
*/
filters: function()
{
const listType = this.signals.listType.value;
if(this.mode !== 'list' || !listType) return;
return null;
},
/**
* 定义 API 文档列表工具栏渲染。
* Define the API doc list toolbar render.
*/
toolbar: function()
{
const listType = this.signals.listType.value;
if(this.mode === 'list' && listType)
{
const items = [];
if(docAppHasPriv('createStruct') && listType === 'structs') items.push({text: getDocAppLang('createStruct'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('api', 'createStruct', `libID=${this.libID}`)});
if(docAppHasPriv('createRelease') && listType === 'releases') items.push({text: getDocAppLang('createRelease'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', url: $.createLink('api', 'createRelease', `libID=${this.libID}`)});
if(!items.length) return null;
return {component: 'toolbar', props: {items: items}};
}
},
/**
* 定义顶部面包屑导航渲染。
* Define the top breadcrumb render.
*/
'app-nav': function(items)
{
const lib = this.lib;
if(!lib) return items;
const versions = lib.data.versions;
if(versions && versions.length)
{
const viewIndex = items.findIndex(item => item[0] === 'lib');
if(viewIndex >= 0)
{
const libView = items[viewIndex][1];
const libID = lib.data.id;
const release = this.signals.libReleaseMap.value[libID] || 0;
const options = [{text: getDocAppLang('latestVersion'), value: 0, selected: !release, command: `changeLibRelease/${libID}/0`}];
versions.forEach(version => options.push({selected: release === version.id, text: `v${version.version}`, value: version.id, command: `changeLibRelease/${libID}/${version.id}`}));
const currentVersion = versions.find(x => x.id === release);
const versionPicker = zui.renderCustomContent(
{
content:
{
component: 'DropdownButton',
props:
{
text : currentVersion ? currentVersion.version : getDocAppLang('latestVersion'),
size : 'xs',
type : 'gray-pale',
rounded : 'full',
className: 'h-4 gap-1 mr-2',
items : options,
}
}
});
items[viewIndex] = ['lib', [libView, versionPicker]];
}
}
const listType = this.signals.listType.value;
if(this.mode !== 'list' || !listType) return items;
if(listType === 'structs') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('struct')})]);
if(listType === 'releases') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('releases')})]);
return items;
},
/**
* 定义侧边栏渲染,显示结构、版本和模块。
* Define the sidebar render, show structs, releases and modules.
*/
'sidebar-before': function()
{
if(!this.libID) return;
const canViewStructs = docAppHasPriv('struct');
const canViewReleases = docAppHasPriv('releases');
if(!canViewStructs && !canViewReleases) return;
const isListMode = this.mode === 'list';
const lang = getDocAppLang();
const listType = this.signals.listType.value;
const items = [];
if(canViewStructs) items.push({text: lang.struct, selected: listType === 'structs' && isListMode, icon: 'treemap muted', command: 'showStructs'}, {type: 'divider', className: 'my-1'});
if(canViewReleases) items.push({text: lang.releases, selected: listType === 'releases' && isListMode, icon: 'version muted', command: 'showReleases'}, {type: 'divider', className: 'my-1'});
items.push({text: lang.module, icon: 'list muted', command: 'showModules'});
return {
component: 'tree',
className: 'p-2 pb-0 api-lib-menu',
props: {items: items, itemProps: {className: 'state'}}
};
}
};
/**
* 获取查看视图的 URL,用于更新浏览器地址栏。
* Get the view mode URL for updating the browser address bar.
*/
function getViewModeUrl(options)
{
const doc = this.doc;
options = $.extend({release: (doc ? this.signals.libReleaseMap.value[doc.lib] : 0) || 0}, options);
let url;
if(this.mode === 'view')
{
const params = zui.formatString('libID={libID}&apiID={docID}&moduleID={moduleID}&version={docVersion}&release={release}', options).replace('&version=0&release=0', '');
url = $.createLink('api', 'view', params);
}
else
{
const params = zui.formatString('libID={libID}&moduleID={moduleID}&apiID={docID}&version={docVersion}&release={release}&browseType={filterType}&params={params}&orderBy={orderBy}&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}&mode={mode}&search={search}', options).replace('libID=0&moduleID=0&apiID=0&version=0&release=0&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '').replace('&apiID=0&version=0&release=0&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '').replace('&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '');
url = $.createLink('api', 'index', params);
}
return url;
}
/**
* 获取文档详情侧边栏标签页定义。
* Get the doc view sidebar tabs.
*
* @param {object} doc
*/
function getDocViewSidebarTabs(doc, info)
{
const lang = getDocAppLang();
if(info.isNewDoc || info.mode === 'create') return [];
return [
{key: 'info', icon: 'info', title: lang.docInfo},
info.mode === 'edit' ? null : {key: 'outline', icon: 'list-box', title: lang.docOutline},
{key: 'history', icon: 'history', title: lang.history},
].filter(Boolean);
}
function getSpaceFetcher(spaceType, spaceID)
{
const parts = String(spaceID).split('.');
const objectType = parts[0] || 'nolink';
const objectID = parts[1] || 0;
const libID = getDocApp().libID;
return $.createLink('api', 'ajaxGetDropMenu', `objectType=${objectType}&objectID=${objectID}&libID=${libID}`);
}
function handleClickSpaceMenu(event, value)
{
event.preventDefault();
if($(event.target).closest('.dropmenu-nav').length) return;
const parts = String(value).split('.');
const type = parts[0];
const id = parts[1] || 0;
const docApp = getDocApp();
if(type === 'product' || type === 'project') docApp.selectSpace(value, true, true);
else docApp.selectSpace('nolink', +id, true);
}
function getSpaceMenuText(text, state)
{
const spaceID = this.spaceID;
if(spaceID === 'nolink') return text;
const libTypeList = getDocAppLang('libTypeList');
return `${libTypeList[spaceID.split('.')[0]]} / ${text}`;
}
function getSpaceMenuOptions(spaceType, spaceID)
{
const libID = this.libID;
return {
popWidth : 350,
onClickItem : handleClickSpaceMenu,
defaultValue: spaceID === 'nolink' ? `lib.${libID}`: spaceID,
display : getSpaceMenuText.bind(this)
};
}
/* 扩展文档应用操作按钮生成定义。 Extend the doc app action definition. */
$.extend(window.docAppActions,
{
/**
* 定义文档编辑时的操作按钮。
* Define the actions on toolbar of the doc editing page.
*/
'doc-edit': function(info)
{
const doc = info.data;
if(!doc) return;
const lang = getDocAppLang();
return [
{text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
{text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelEditDoc'},
];
},
/**
* 定义文档创建时的操作按钮。
* Define the actions on toolbar of the doc editing page.
*/
'doc-create': function(info)
{
const doc = info.data;
if(!doc) return;
const lang = getDocAppLang();
return [
{text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
{text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelCreateDoc'},
];
},
});
/* 扩展文档应用命令定义。 Extend the doc app command definition. */
$.extend(window.docAppCommands,
{
/**
* 保存 API 文档数据。
* Save the API doc data.
*/
saveApiDoc: function()
{
/* 触发 API 表单的提交事件。 */
$('#docApp .doc-view form').trigger('submit');
},
/**
* 取消编辑 API 文档库。
* Create api lib.
*/
createLib: function(_, args)
{
const docApp = getDocApp();
const spaceID = args[0] || docApp.spaceID;
const parts = String(spaceID).split('.');
const url = $.createLink('api', 'createLib', `type=${parts[0] || 'nolink'}&objectID=${parts[1] || 0}`);
zui.Modal.open({size: 'sm', url: url});
},
/**
* 加载指定的 API 文档。
* Load the specified API doc.
*/
loadApi: function(_, args)
{
const apiID = args[0] || this.docID;
const version = args[1] || 0;
const release = args[2] || 0;
const select = !!args[3];
$.getJSON($.createLink('api', 'ajaxGetApi', `apiID=${apiID}&version=${version}&release=${release}`), function(result)
{
if(!result || typeof result !== 'object') return;
const docApp = getDocApp();
docApp.update('doc', result);
if(select) docApp.selectDoc(apiID);
});
},
/**
* 选择 API 文档。
* Select the specified API doc.
*/
selectApi: function(_, args)
{
const apiID = args[0];
getDocApp().selectDoc(apiID);
},
/**
* 显示结构列表。
* Show the struct list.
*/
showStructs: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: 'structs',
});
},
/**
* 显示版本列表。
* Show the release list.
*/
showReleases: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: 'releases',
});
},
/**
* 显示API 目录。
* Show the api catalog.
*/
showModules: function()
{
getDocApp().changeState(
{
mode: 'list',
moduleID: 0,
docID: 0,
listType: '',
});
},
/**
* 获取库的 API 列表。
* Get the API list of the library.
*/
loadLibApi: function(_, args)
{
const docApp = getDocApp();
const libReleaseMap = docApp.signals.libReleaseMap.value;
const libID = args[0] || docApp.libID;
if(!libID) return;
const releaseID = args[1] || libReleaseMap[libID] || 0;
const url = $.createLink('api', 'ajaxGetLibApiList', `libID=${libID}&releaseID=${releaseID}`);
$.getJSON(url, function(data)
{
const lib = docApp.getLib(libID);
if(lib && lib.docs && lib.docs.length) docApp.delete('doc', lib.docs.map(x => x.data.id));
docApp.update('doc', data);
});
},
/**
* 更改当前库的发布版本。
* Change the release of the current library.
*/
changeLibRelease: function(_, args)
{
const docApp = getDocApp();
const libID = args[0];
const release = args[1];
const libReleaseMap = docApp.signals.libReleaseMap.value;
if(libReleaseMap[libID] === release) return;
libReleaseMap[libID] = release;
docApp.signals.libReleaseMap.value = $.extend({}, libReleaseMap);
docApp.executeCommand('loadLibApi', [libID, release]);
const doc = docApp.doc;
if(doc && doc.data.lib === libID) docApp.executeCommand('loadApi', [doc.data.id, 0, release]);
},
/**
* 加载懒加载内容。
* Load the lazy content.
*/
loadLazyContent: function(_, args)
{
const selector = args[0];
if(!selector) return;
$(selector).closest('.lazy-content').trigger('loadContent');
},
/**
* 更新懒加载内容。
* Update the lazy content.
*/
updateLazyContent: function(context, args)
{
const event = context.event;
const $element = $(event.currentTarget);
const selector = args[0] || $element.data('lazyTarget');
const $lazy = (selector ? $(selector) : $element).closest('.lazy-content');
const url = $element.data('url') || $element.attr('href');
if(url) $lazy.trigger('loadContent', url);
event.preventDefault();
event.stopPropagation();
},
/**
* 更新首页内容。
* Update the home content.
*/
loadHome: function(context, args)
{
const event = context.event;
const $element = $(event.currentTarget);
const data = $element.data();
const type = args[0] !== undefined ? args[0] : data.type;
const params = args[1] !== undefined ? args[1] : data.params;
const recPerPage = args[2] !== undefined ? args[2] : data.recPerPage;
const pageID = args[3] !== undefined ? args[3] : data.pageID;
window.loadHome(type, params, recPerPage, pageID);
},
/**
* 显示首页菜单。
* Show the home item menu.
*/
showHomeItemMenu: function(context, args)
{
const event = context.event;
const type = args[0];
const id = args[1];
const lang = getDocAppLang();
const items = [];
if(type === 'nolink')
{
if(docAppHasPriv('editLib')) items.push({text: lang.actions.editLib, command: `editLib/${id}`});
if(docAppHasPriv('deleteLib')) items.push({text: lang.actions.deleteLib, command: `deleteLib/${id}`});
}
else if(docAppHasPriv('createLib'))
{
items.push({text: lang.createLib, command: `createLib/${type}.${id}`});
}
if(items.length)
{
zui.ContextMenu.show({event: event, items: items, placement: 'bottom-end'});
}
event.preventDefault();
event.stopPropagation();
},
});
window.loadHome = function(type, params, recPerPage, pageID)
{
const location = {};
if(typeof type === 'object') $.extend(location, type);
else if(type !== undefined) location.filterType = type;
if(params !== undefined) location.params = params;
if(recPerPage !== undefined || pageID !== undefined)
{
location.pager = {};
if(recPerPage !== undefined) location.pager.recPerPage = recPerPage;
if(pageID !== undefined) location.pager.page = pageID;
}
if(Object.keys(location).length) getDocApp().switchView(location, 'home');
};
/**
* 重写文档应用的配置选项方法。
* Override the method to set the doc app options.
*/
window._setDocAppOptions = window.setDocAppOptions; // Save the original method.
window.setDocAppOptions = function(_, options) // Override the method.
{
options = window._setDocAppOptions(_, options);
const oldIsMatchFilter = options.isMatchFilter; // Save the original isMatchFilter method.
return $.extend(options,
{
defaultState : {libReleaseMap: {}, listType: ''},
spaceMenuOptions : getSpaceMenuOptions,
customRenders : customRenders,
viewModeUrl : getViewModeUrl,
getDocViewSidebarTabs: getDocViewSidebarTabs,
getSpaceFetcher : getSpaceFetcher,
isMatchFilter : function(type, filterType, item)
{
if(type === 'api') return (item.objectType || 'nolink').toLowerCase() === filterType.toLowerCase();
return oldIsMatchFilter.call(this, type, filterType, item);
}
});
};
-104
View File
@@ -1,104 +0,0 @@
/**
* Display the document in full screen.
*
* @access public
* @return void
*/
window.fullScreen = function()
{
var element = document.getElementById('content');
var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
if(requestMethod)
{
var afterEnterFullscreen = function()
{
$('#content').addClass('scrollbar-hover');
$('#content').css('background', '#fff');
$.cookie.set('isFullScreen', 1, {expires:config.cookieLife, path:config.webRoot});
};
var whenFailEnterFullscreen = function(error)
{
$.cookie.set('isFullScreen', 0, {expires:config.cookieLife, path:config.webRoot});
};
try
{
var result = requestMethod.call(element);
if(result && (typeof result.then === 'function' || result instanceof window.Promise))
{
result.then(afterEnterFullscreen).catch(whenFailEnterFullscreen);
}
else
{
afterEnterFullscreen();
}
}
catch (error)
{
whenFailEnterFullscreen(error);
}
}
}
/**
* Exit full screen.
*
* @access public
* @return void
*/
function exitFullScreen()
{
$('#content').removeClass('scrollbar-hover');
$.cookie.set('isFullScreen', 0, {expires:config.cookieLife, path:config.webRoot});
}
document.addEventListener('fullscreenchange', function (e)
{
if(!document.fullscreenElement) exitFullScreen();
});
document.addEventListener('webkitfullscreenchange', function (e)
{
if(!document.webkitFullscreenElement) exitFullScreen();
});
document.addEventListener('mozfullscreenchange', function (e)
{
if(!document.mozFullScreenElement) exitFullScreen();
});
document.addEventListener('msfullscreenChange', function (e)
{
if(!document.msfullscreenElement) exitFullScreen();
});
$(function()
{
if($.cookie.get('isFullScreen') == 1) fullScreen();
/* Update doc content silently on switch doc version, story #40503 */
$('.panel').on('click', '#closeBtn', function()
{
$('#history').addClass('hidden');
$('#hisTrigger').removeClass('text-primary');
});
$('#history').append('<a id="closeBtn" href="###" class="btn btn-link"><i class="icon icon-close"></i></a>');
});
window.showHistory = function()
{
var $history = $('#history');
var $icon = $('#hisTrigger');
if($history.hasClass('hidden'))
{
$history.removeClass('hidden');
$icon.addClass('text-primary');
}
else
{
$history.addClass('hidden');
$icon.removeClass('text-primary');
}
}
+14
View File
@@ -15,6 +15,7 @@ $lang->api->sql = 'SQL Query API';
$lang->api->manage = 'API management';
$lang->api->index = 'API Space';
$lang->api->view = 'API Detail';
$lang->api->editLib = 'Edit';
$lang->api->releases = 'Release';
$lang->api->deleteRelease = 'Delete Release';
@@ -63,6 +64,7 @@ $lang->api->zentaoAPI = "Zentao API v1";
$lang->api->search = "Search";
$lang->api->allLibs = "AllLibs";
$lang->api->noLinked = "No Linked {$lang->productCommon} and {$lang->projectCommon}";
$lang->api->apiCatalog = 'Api Catalogs';
$lang->api->addCatalog = 'Add Catalog';
$lang->api->editCatalog = 'Edit Catalog';
$lang->api->sortCatalog = 'Catalog Sorting';
@@ -160,6 +162,9 @@ $lang->api->format = 'Format';
$lang->api->libType = 'Api Library Type';
$lang->api->product = $lang->productCommon;
$lang->api->project = $lang->projectCommon;
$lang->api->apiTotalInfo = 'Total %d APIs';
$lang->api->showNotEmpty = 'Show not empty';
$lang->api->showClosed = 'Show with closed';
$lang->api->methodOptions = array(
'GET' => 'GET',
@@ -229,8 +234,17 @@ $lang->apistruct->name = 'Name';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = 'Version';
$lang->api_lib_release->desc = 'Description';
$lang->api->error = new stdclass();
$lang->api->error->onlySelect = 'Das SQL Interface erlaubt nur SELECT Abfragen.';
$lang->api->error->disabled = 'For security reasons, this feature is disabled. You can go to the config directory and modify the configuration item %s to open this function.';
$lang->api->error->notInput = 'Debugging is not supported temporarily due to field parameter type restrictions';
$lang->api->filterTypes[] = array('all', 'All');
$lang->api->filterTypes[] = array('createdByMe', 'Created by me');
$lang->api->filterTypes[] = array('editedByMe', 'Edited by me');
$lang->api->homeFilterTypes['nolink'] = $lang->api->libTypeList['nolink'];
$lang->api->homeFilterTypes['product'] = $lang->api->libTypeList['product'];
$lang->api->homeFilterTypes['project'] = $lang->api->libTypeList['project'];
+19 -4
View File
@@ -15,6 +15,7 @@ $lang->api->sql = 'SQL Query API';
$lang->api->manage = 'API management';
$lang->api->index = 'API Space';
$lang->api->view = 'API Detail';
$lang->api->editLib = 'Edit';
$lang->api->releases = 'Release';
$lang->api->deleteRelease = 'Delete Release';
@@ -59,10 +60,12 @@ $lang->api->confirmDeleteLib = "Do you want to delete this interface library?
$lang->api->confirmDeleteStruct = "Do you want to delete this data struct?";
$lang->api->filterStruct = "use struct";
$lang->api->defaultVersion = "Current Version";
$lang->api->latestVersion = 'Lastest Version';
$lang->api->zentaoAPI = "Zentao API v1";
$lang->api->search = "Search";
$lang->api->allLibs = "AllLibs";
$lang->api->noLinked = "No Linked {$lang->productCommon} and {$lang->projectCommon}";
$lang->api->noLinked = "Stand-alone APIs";
$lang->api->apiCatalog = 'Api Catalogs';
$lang->api->addCatalog = 'Add Catalog';
$lang->api->editCatalog = 'Edit Catalog';
$lang->api->sortCatalog = 'Catalog Sorting';
@@ -160,6 +163,9 @@ $lang->api->format = 'Format';
$lang->api->libType = 'Api Library Type';
$lang->api->product = $lang->productCommon;
$lang->api->project = $lang->projectCommon;
$lang->api->apiTotalInfo = 'Total %d APIs';
$lang->api->showNotEmpty = 'Show not empty';
$lang->api->showClosed = 'Show with closed';
$lang->api->methodOptions = array(
'GET' => 'GET',
@@ -183,9 +189,9 @@ $lang->api->requestTypeOptions['application/x-www-form-urlencoded'] = 'applicati
$lang->api->requestTypeOptions['multipart/form-data'] = 'multipart/form-data';
$lang->api->libTypeList = array();
$lang->api->libTypeList['product'] = $lang->productCommon . ' Api Library';
$lang->api->libTypeList['project'] = $lang->projectCommon . ' Api Library';
$lang->api->libTypeList['nolink'] = 'Independent Api Library';
$lang->api->libTypeList['product'] = $lang->productCommon . ' API';
$lang->api->libTypeList['project'] = $lang->projectCommon . ' API';
$lang->api->libTypeList['nolink'] = 'Independent API';
$lang->api->statusOptions = array(
'done' => 'Done',
@@ -229,8 +235,17 @@ $lang->apistruct->name = 'Name';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = 'Version';
$lang->api_lib_release->desc = 'Description';
$lang->api->error = new stdclass();
$lang->api->error->onlySelect = 'SQL API only allows SELECT query.';
$lang->api->error->disabled = 'For security reasons, this feature is disabled. Go to the config directory and modify the configuration item %s to enable it.';
$lang->api->error->notInput = 'Debugging is not supported temporarily due to field parameter type restrictions';
$lang->api->filterTypes[] = array('all', 'All');
$lang->api->filterTypes[] = array('createdByMe', 'Created by me');
$lang->api->filterTypes[] = array('editedByMe', 'Edited by me');
$lang->api->homeFilterTypes['nolink'] = $lang->api->libTypeList['nolink'];
$lang->api->homeFilterTypes['product'] = $lang->api->libTypeList['product'];
$lang->api->homeFilterTypes['project'] = $lang->api->libTypeList['project'];
+14
View File
@@ -15,6 +15,7 @@ $lang->api->sql = 'SQL Query API';
$lang->api->manage = 'API management';
$lang->api->index = 'API Space';
$lang->api->view = 'API Detail';
$lang->api->editLib = 'Edit';
$lang->api->releases = 'Release';
$lang->api->deleteRelease = 'Delete Release';
@@ -63,6 +64,7 @@ $lang->api->zentaoAPI = "Zentao API v1";
$lang->api->search = "Search";
$lang->api->allLibs = "AllLibs";
$lang->api->noLinked = "No Linked {$lang->productCommon} and {$lang->projectCommon}";
$lang->api->apiCatalog = 'Api Catalogs';
$lang->api->addCatalog = 'Add Catalog';
$lang->api->editCatalog = 'Edit Catalog';
$lang->api->sortCatalog = 'Catalog Sorting';
@@ -160,6 +162,9 @@ $lang->api->format = 'Format';
$lang->api->libType = 'Api Library Type';
$lang->api->product = $lang->productCommon;
$lang->api->project = $lang->projectCommon;
$lang->api->apiTotalInfo = 'Total %d APIs';
$lang->api->showNotEmpty = 'Show not empty';
$lang->api->showClosed = 'Show with closed';
$lang->api->methodOptions = array(
'GET' => 'GET',
@@ -229,8 +234,17 @@ $lang->apistruct->name = 'Name';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = 'Version';
$lang->api_lib_release->desc = 'Description';
$lang->api->error = new stdclass();
$lang->api->error->onlySelect = 'Cette interface SQL ne supporte que les requêtes SELECT.';
$lang->api->error->disabled = 'For security reasons, this feature is disabled. You can go to the config directory and modify the configuration item %s to open this function.';
$lang->api->error->notInput = 'Debugging is not supported temporarily due to field parameter type restrictions';
$lang->api->filterTypes[] = array('all', 'All');
$lang->api->filterTypes[] = array('createdByMe', 'Created by me');
$lang->api->filterTypes[] = array('editedByMe', 'Edited by me');
$lang->api->homeFilterTypes['nolink'] = $lang->api->libTypeList['nolink'];
$lang->api->homeFilterTypes['product'] = $lang->api->libTypeList['product'];
$lang->api->homeFilterTypes['project'] = $lang->api->libTypeList['project'];
+19 -4
View File
@@ -15,6 +15,7 @@ $lang->api->sql = 'SQL查询接口';
$lang->api->manage = '接口管理';
$lang->api->index = '接口空间';
$lang->api->view = '接口详情';
$lang->api->editLib = '编辑库';
$lang->api->releases = '版本管理';
$lang->api->deleteRelease = '删除版本';
@@ -59,10 +60,12 @@ $lang->api->confirmDeleteLib = "您确定删除该库吗?";
$lang->api->confirmDeleteStruct = "您确定删除该数据结构吗?";
$lang->api->filterStruct = "使用数据结构填充";
$lang->api->defaultVersion = "当前版本";
$lang->api->latestVersion = '最新版本';
$lang->api->zentaoAPI = "禅道API文档v1";
$lang->api->search = "搜索";
$lang->api->allLibs = "全部库";
$lang->api->noLinked = "未关联{$lang->productCommon}和{$lang->projectCommon}";
$lang->api->noLinked = "独立接口";
$lang->api->apiCatalog = '接口目录';
$lang->api->addCatalog = '添加目录';
$lang->api->editCatalog = '编辑目录';
$lang->api->sortCatalog = '目录排序';
@@ -160,6 +163,9 @@ $lang->api->format = '格式化';
$lang->api->libType = '接口库类型';
$lang->api->product = '所属' . $lang->productCommon;
$lang->api->project = '所属' . $lang->projectCommon;
$lang->api->apiTotalInfo = '共 %d 个 API';
$lang->api->showNotEmpty = '仅显示有接口的';
$lang->api->showClosed = '显示包含已关闭的';
$lang->api->methodOptions = array(
'GET' => 'GET',
@@ -183,9 +189,9 @@ $lang->api->requestTypeOptions['application/x-www-form-urlencoded'] = 'applicati
$lang->api->requestTypeOptions['multipart/form-data'] = 'multipart/form-data';
$lang->api->libTypeList = array();
$lang->api->libTypeList['product'] = $lang->productCommon . '接口库';
$lang->api->libTypeList['project'] = $lang->projectCommon . '接口库';
$lang->api->libTypeList['nolink'] = '独立接口库';
$lang->api->libTypeList['product'] = $lang->productCommon . '接口';
$lang->api->libTypeList['project'] = $lang->projectCommon . '接口';
$lang->api->libTypeList['nolink'] = '独立接口';
$lang->api->statusOptions = array(
'done' => '开发完成',
@@ -229,8 +235,17 @@ $lang->apistruct->name = '结构名';
$lang->api_lib_release = new stdClass();
$lang->api_lib_release->version = '版本号';
$lang->api_lib_release->desc = '描述';
$lang->api->error = new stdclass();
$lang->api->error->onlySelect = 'SQL查询接口只允许SELECT查询';
$lang->api->error->disabled = '因为安全原因,该功能被禁用。可以到config目录,修改配置项 %s,打开此功能。';
$lang->api->error->notInput = '因字段参数类型限制,暂不支持调试';
$lang->api->filterTypes[] = array('all', '全部');
$lang->api->filterTypes[] = array('createdByMe', '我创建的');
$lang->api->filterTypes[] = array('editedByMe', '我编辑的');
$lang->api->homeFilterTypes['nolink'] = $lang->api->libTypeList['nolink'];
$lang->api->homeFilterTypes['product'] = $lang->api->libTypeList['product'];
$lang->api->homeFilterTypes['project'] = $lang->api->libTypeList['project'];
+44
View File
@@ -903,6 +903,50 @@ class apiModel extends model
return array($normalObjects, $closedObjects);
}
/**
* 获取分组后的对象列表。
*
* @access public
* @return array
*/
public function getGroupedObjects(): array
{
$libs = $this->loadModel('doc')->getApiLibs();
$libIDs = array_keys($libs);
/* 获取接口库所属产品的产品列表。 */
$products = $this->dao->select('t1.id, t1.order, t1.name, t1.status, t1.program')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_DOCLIB)->alias('t2')->on('t2.product=t1.id')
->where('t2.id')->gt(0)
->andWhere('t1.vision')->eq($this->config->vision)
->andWhere('t1.deleted')->eq(0)
->andWhere('t2.id')->in($libIDs)
->orderBy('order_asc')
->fetchAll('id');
/* 获取接口库所属项目的项目列表。 */
$projects = $this->dao->select('t1.id, t1.order, t1.name, t1.status, t1.parent')->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_DOCLIB)->alias('t2')->on('t2.project=t1.id')
->where('t2.id')->gt(0)
->andWhere('t1.vision')->eq($this->config->vision)
->andWhere('t1.deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->projects)->fi()
->andWhere('t2.id')->in($libIDs)
->orderBy('t1.hasProduct_asc, order_asc')
->fetchAll('id');
$nolinks = array();
foreach($libs as $lib)
{
if($lib->product != 0 && isset($products[$lib->product]) && !isset($products[$lib->product]->firstLib)) $products[$lib->product]->firstLib = $lib->id;
if($lib->project != 0 && isset($projects[$lib->project]) && !isset($projects[$lib->project]->firstLib)) $projects[$lib->project]->firstLib = $lib->id;
if($lib->product != 0 || $lib->project != 0 || $lib->execution != 0) continue;
$nolinks[$lib->id] = $lib;
}
return array('product' => $products, 'project' => $projects, 'nolink' => $nolinks);
}
/**
* 获取有权限的接口文档列表。
* Get priv Apis.
+45 -38
View File
@@ -10,46 +10,46 @@ declare(strict_types=1);
*/
namespace zin;
$data = array('normal' => array(), 'closed' => array());
$data['normal'][] = array('id' => 0, 'text' => $lang->api->noLinked, 'active' => $objectID == 0, 'type' => 'nolink');
$data = array('nolink' => array(), 'product' => array(), 'project' => array());
/* 处理分组数据。Process grouped data. */
foreach(array('product', 'project') as $moduleType)
/* 处理独立接口库。Process independent libs. */
foreach($nolinkLibs as $lib) $data['nolink'][] = array('id' => "lib.$lib->id", 'text' => $lib->name, 'active' => $lib->id == $libID, 'type' => 'item', 'url' => createLink('api', 'index', "libID=$lib->id"));
/* 处理产品和项目数据。Process product and project data. */
foreach($products as $product)
{
if(isset($normalObjects[$moduleType]))
$programID = $product->program;
$item = array('type' => 'product', 'id' => "product.$product->id", 'text' => $product->name, 'active' => $objectType == 'product' && $product->id == $objectID, 'url' => createLink('api', 'index', 'libID=' . (isset($product->firstLib) ? $product->firstLib : 0)));
if(isset($programs[$programID]))
{
foreach($normalObjects[$moduleType] as $normalObjectID => $normalObjectName)
{
$item = array();
$item['id'] = $normalObjectID;
$item['text'] = $normalObjectName;
$item['active'] = $normalObjectID == $objectID;
$item['type'] = $moduleType;
$item['keys'] = zget(common::convert2Pinyin(array($normalObjectName)), $normalObjectName, '');
$data['normal'][] = $item;
}
$programKey = "program.$programID";
$program = $programs[$programID];
if(!isset($data['product'][$programKey])) $data['product'][$programKey] = array('id' => $programKey, 'text' => $program->name, 'type' => 'program', 'items' => array());
$data['product'][$programKey]['items'][] = $item;
}
if(isset($closedObjects[$moduleType]))
else
{
foreach($closedObjects[$moduleType] as $closedObjectID => $closedObjectName)
{
$item = array();
$item['id'] = $closedObjectID;
$item['text'] = $closedObjectName;
$item['active'] = $closedObjectID == $objectID;
$item['type'] = $moduleType;
$item['keys'] = zget(common::convert2Pinyin(array($closedObjectName)), $closedObjectName, '');
$data['closed'][] = $item;
}
$data['product'][$product->id] = $item;
}
}
$tabs = array();
$tabs[] = array('name' => 'normal', 'text' => '');
$tabs[] = array('name' => 'closed', 'text' => $lang->doc->closed);
foreach($projects as $project)
{
$programID = $project->parent;
$item = array('type' => 'project', 'id' => "project.$project->id", 'text' => $project->name, 'active' => $objectType == 'project' && $project->id == $objectID, 'url' => createLink('api', 'index', 'libID=' . (isset($project->firstLib) ? $project->firstLib : 0)));
if(isset($programs[$programID]))
{
$programKey = "program.$programID";
$program = $programs[$programID];
if(!isset($data['project'][$programKey])) $data['project'][$programKey] = array('id' => $programKey, 'text' => $program->name, 'type' => 'program', 'items' => array());
$data['project'][$programKey]['items'][] = $item;
}
else
{
$data['project'][$project->id] = $item;
}
}
$data['product'] = array_values($data['product']);
$data['project'] = array_values($data['project']);
/**
* 定义最终的 JSON 数据。
@@ -57,13 +57,20 @@ $tabs[] = array('name' => 'closed', 'text' => $lang->doc->closed);
*/
$link = $this->createLink('api', 'ajaxGetList', "objectID={id}&objectType={type}");
$tabs = array();
$tabs[] = array('name' => 'nolink', 'active' => $libID, 'text' => $lang->api->libTypeList['nolink']);
$tabs[] = array('name' => 'product', 'active' => $objectType == 'product', 'text' => $lang->api->libTypeList['product']);
$tabs[] = array('name' => 'project', 'active' => $objectType == 'project', 'text' => $lang->api->libTypeList['project']);
$json = array();
$json['data'] = $data;
$json['tabs'] = $tabs;
$json['searchHint'] = $lang->searchAB;
$json['link'] = $link;
$json['itemType'] = 'api';
$json['expandName'] = 'closed';
$json['data'] = $data;
$json['tabs'] = $tabs;
$json['searchHint'] = $lang->searchAB;
$json['labelMap'] = array('program' => $lang->program->common);
$json['itemType'] = 'lib';
$json['typeIconMap'] = array('lib' => 'interface-lib');
$json['debug'] = array('libID' => $libID, 'objectType' => $objectType, 'objectID' => $objectID);
/**
* 渲染 JSON 字符串并发送到客户端。
+191
View File
@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
/**
* The api home file of api module of ZenTaoPMS.
* @copyright Copyright 2024 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Sun Hao<sunhao@easycorp.ltd>
* @package api
* @link https://www.zentao.net
*/
namespace zin;
$libColors = array('var(--color-secondary-500)', 'var(--color-primary-500)', 'var(--color-warning-500)', 'var(--color-success-500)', 'var(--color-special-500)');
$isNolink = $type == 'nolink';
$isEmpty = $isNolink ? empty($libs) : empty($programs);
$canCreateLib = hasPriv('api', 'createLib');
$canEditLib = hasPriv('api', 'editLib');
$canDeleteLib = hasPriv('api', 'deleteLib');
$showLibAction = $isNolink ? ($canEditLib || $canDeleteLib) : $canCreateLib;
/* Create load home command statement. */
$createCommand = function(array $args) use($pager, $type, $unclosed, $notempty)
{
$args = array_merge(array('type' => $type, 'unclosed' => $unclosed, 'notempty' => $notempty, 'recPerPage' => $pager->recPerPage, 'pageID' => $pager->pageID), $args);
$parts = array('loadHome');
$parts[] = $args['type'];
$params = array();
if($args['notempty']) $params[] = 'notempty';
if($args['unclosed']) $params[] = 'unclosed';
$parts[] = implode('_', $params);
$parts[] = $args['recPerPage'];
$parts[] = $args['pageID'];
return implode('/', $parts);
};
$buildLibItem = function(int $id, string $name, int $count = 0) use ($libColors, $type, $isNolink, $showLibAction, $lang)
{
return div
(
setClass('doc-space-card-lib p-1.5 w-1/5 group'),
setKey($id),
div
(
setClass('canvas border rounded py-2 px-3 col gap-1 hover:shadow-lg hover:border-primary relative cursor-pointer'),
set('zui-command', $isNolink ? "selectSpace/nolink/$id" : "selectSpace/$type.$id/_first"),
icon($isNolink ? 'interface-lib' : $type, setClass('text-2xl'), $isNolink ? setStyle('color', $libColors[$id % count($libColors)]) : setClass('text-gray')),
div(setClass('font-bold text-clip'), set::title($name), $name),
div(setClass('text-gray text-sm'), $count ? sprintf($lang->api->apiTotalInfo, $count) : $lang->api->noApi),
$showLibAction ? div
(
setClass('toolbar absolute top-1 right-1 opacity-0 group-hover:opacity-100'),
btn
(
setClass('doc-space-lib-btn'),
set('zui-command', "showHomeItemMenu/$type/$id"),
set::icon('ellipsis-v'),
set::size('sm'),
set::type('ghost')
)
) : null
)
);
};
$buildProgramItem = function($program) use ($type, $buildLibItem, $lang)
{
$items = array();
if($type === 'product' && !empty($program->products))
{
foreach($program->products as $product) $items[] = $buildLibItem($product->id, $product->name, isset($product->apiCount) ? $product->apiCount : 0);
}
if($type === 'project' && !empty($program->projects))
{
foreach($program->projects as $project) $items[] = $buildLibItem($project->id, $project->name, isset($project->apiCount) ? $project->apiCount : 0);
}
return div
(
setClass('doc-space-card ring rounded surface-light'),
setKey($program->id),
div
(
setClass('row items-center justify-between gap-2 px-2.5 py-1 border-b'),
div
(
setClass('row items-center gap-2 flex-none'),
icon('program', setClass('text-gray flex-none')),
div(setClass('min-w-0 flex-auto'), strong($program->name)),
)
),
div
(
setClass('doc-space-card-libs p-1.5'),
empty($items) ? div(setClass('h-24 center text-gray'), $lang->noData) : div(setClass('row flex-wrap'), $items)
)
);
};
$filterItems = array();
foreach($lang->api->homeFilterTypes as $key => $text)
{
$item = array('text' => $text, 'zui-command' => $createCommand(array('type' => $key)));
if($key === $type)
{
$count = $isNolink ? count($libs) : count($programs);
$item['active'] = true;
$item['badge'] = wg(label(setClass('size-sm canvas ring-0 rounded-md'), $count));
}
$filterItems[] = $item;
}
div
(
setKey('header'),
setClass('doc-app-header flex-none surface-light row items-center border-b py-1 pl-1 h-10 pr-2 gap-3 flex-none'),
nav
(
setClass('ml-2'),
set::compact(),
set::items($filterItems)
),
checkbox(set::name('notempty'), set::text($lang->api->showNotEmpty), set::checked($notempty)),
$type !== 'nolink' ? checkbox(set::name('showclosed'), set::text($lang->api->showClosed), set::checked(!$unclosed)) : null,
div(setClass('flex-auto')),
(!$isEmpty && $canCreateLib) ? btn
(
set::icon('plus'),
set::type('primary'),
set::size('md'),
set::text($lang->api->createLib),
set('zui-command', "createLib/$type")
) : null,
on::change('[type=checkbox]')
->const('notempty', jsRaw('$this.closest(".doc-app-header").find("[name=notempty]:checked").length'))
->const('showclosed', jsRaw('$this.closest(".doc-app-header").find("[name=showclosed]:checked").length'))
->call('loadHome', $type, jsRaw('[notempty ? "notempty" : "", showclosed ? "" : "unclosed"].join("_")'))
);
$views = array();
if($isEmpty)
{
$views = div
(
setKey('empty'),
setClass('doc-app-empty h-full center gap-4'),
div(setClass('text-gray'), $lang->api->noLib),
$canCreateLib ? btn
(
set::icon('plus'),
set::type('primary'),
set::text($lang->api->createLib),
set('zui-command', "createLib/$type")
) : null
);
}
elseif($type === 'nolink')
{
foreach($libs as $lib) $views[] = $buildLibItem($lib->id, $lib->name, isset($lib->apiCount) ? $lib->apiCount : 0);
$views = div
(
setClass('doc-space-card-libs'),
setStyle('margin', '-8px'),
div(setClass('row flex-wrap'), $views)
);
}
else
{
foreach($programs as $program) $views[] = $buildProgramItem($program);
}
div
(
setKey('body'),
setClass('doc-app-body flex-auto min-h-0 col gap-4 p-4 items-stretch overflow-auto scrollbar-hover no-morph'),
$views
);
if(!$isEmpty)
{
div
(
setKey('footer'),
setClass('doc-app-footer surface-light row items-center border-t py-1.5 px-2'),
div(setClass('flex-auto')),
pager
(
set(usePager()),
set::useState(),
set::onChangePageInfo(jsRaw('(info, event) => {event.preventDefault(); window.loadHome({pager: {recPerPage: info.recPerPage, page: info.page}});}'))
)
);
}
+40 -153
View File
@@ -1,165 +1,52 @@
<?php
declare(strict_types=1);
/**
* The index view file of api module of ZenTaoPMS.
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* The api index file of api module of ZenTaoPMS.
* @copyright Copyright 2024 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Shujie Tian<tianshujie@easycorp.ltd>
* @author Sun Hao<sunhao@easycorp.ltd>
* @package api
* @link https://www.zentao.net
*/
namespace zin;
if(empty($libTree))
{
featureBar();
toolbar
(
btn
(
setClass('btn secondary ml-2'),
set::icon('plus'),
set::url(createLink('api', 'createLib', 'type=nolink&objectID=0')),
setData('toggle', 'modal'),
$lang->api->createLib
)
);
div
(
setClass('canvas text-center py-8'),
p
(
setClass('py-8 my-8'),
span
(
setClass('text-gray'),
$lang->api->noLib,
),
a
(
setClass('btn primary-pale bd-primary ml-0.5'),
set::href(createLink('api', 'createLib', 'type=nolink&objectID=0')),
setData('toggle', 'modal'),
icon('plus'),
$lang->api->createLib,
)
)
);
renderPage();
return;
}
$privs = array();
$privs['collect'] = 'no';
$privs['createApi'] = hasPriv('api', 'create');
$privs['exportApi'] = $this->config->edition != 'open' && hasPriv('api', 'export');
$privs['edit'] = hasPriv('api', 'edit');
$privs['editApi'] = hasPriv('api', 'edit');
$privs['view'] = hasPriv('api', 'view');
$privs['delete'] = hasPriv('api', 'delete');
$privs['createLib'] = hasPriv('api', 'createLib');
$privs['editLib'] = hasPriv('api', 'editLib');
$privs['moveLib'] = hasPriv('api', 'moveLib');
$privs['sortDoclib'] = hasPriv('doc', 'sortDoclib');
$privs['deleteLib'] = hasPriv('api', 'deleteLib');
$privs['createStruct'] = hasPriv('api', 'createStruct');
$privs['createRelease']= hasPriv('api', 'createRelease');
$privs['addModule'] = hasPriv('doc', 'addCatalog');
$privs['deleteModule'] = hasPriv('doc', 'deleteCatalog');
$privs['editModule'] = hasPriv('doc', 'editCatalog');
$privs['sortModule'] = hasPriv('doc', 'sortCatalog');
$privs['releases'] = hasPriv('api', 'releases');
$privs['struct'] = hasPriv('api', 'struct');
/* zin: Define the set::module('api') feature bar on main menu. */
if($app->rawModule == 'api')
{
featureBar
(
li(searchToggle(set::module('api')))
);
toolbar
(
$libID && common::hasPriv('api', 'struct') ? item(set(array
(
'icon' => 'treemap',
'class' => 'ghost',
'text' => $lang->api->struct,
'url' => createLink('api', 'struct', "libID={$libID}")
))) : null,
$libID && common::hasPriv('api', 'releases') ? item(set(array
(
'icon' => 'version',
'class' => 'ghost',
'text' => $lang->api->releases,
'url' => createLink('api', 'releases', "libID={$libID}"),
'data-toggle' => 'modal',
'data-class-name' => 'releaseModal'
))) : null,
$libID && common::hasPriv('api', 'createRelease') ? item(set(array
(
'icon' => 'publish',
'class' => 'ghost',
'text' => $lang->api->createRelease,
'url' => createLink('api', 'createRelease', "libID={$libID}"),
'data-toggle' => 'modal'
))) : null,
$libID && common::hasPriv('api', 'export') && $config->edition != 'open' ? item(set(array
(
'icon' => 'export',
'class' => 'ghost export',
'text' => $lang->export,
'url' => createLink('api', 'export', "libID={$libID}&version={$version}&release={$release}&moduleID={$moduleID}"),
'data-size' => 'sm',
'data-toggle' => 'modal'
))) : null,
common::hasPriv('api', 'createLib') ? item(set(array
(
'icon' => 'plus',
'class' => 'btn secondary',
'text' => $lang->api->createLib,
'url' => createLink('api', 'createLib', "type=" . ($objectType ? $objectType : 'nolink') . "&objectID=$objectID"),
'data-toggle' => 'modal'
))) : null,
$libID && common::hasPriv('api', 'create') ? item(set(array
(
'icon' => 'plus',
'class' => 'btn primary',
'text' => $lang->api->createApi,
'url' => createLink('api', 'create', "libID={$libID}&moduleID={$moduleID}")
))) : null,
);
include '../../doc/ui/lefttree.html.php';
}
$list = array();
foreach($apiList as $api)
{
$list[] = h::li
(
setClass('list-group-item'),
div
(
setClass("heading {$api->method}"),
a
(
set::href(createLink('api', 'index', "libID={$api->lib}&moduleID=0&apiID={$api->id}&version={$api->version}")),
span
(
setClass('method'),
$api->method
),
span
(
setClass('path'),
$api->path
),
span
(
setClass('desc'),
$api->title
)
)
)
);
}
$delimiter = strpos($app->clientLang, 'zh') === 0 ? ':' : ': ';
$docContent = panel
docApp
(
$lib ? div
(
setClass('detail base-url'),
$lang->api->baseUrl . $delimiter . $lib->baseUrl
) : null,
$lib ? h::hr(setClass('mb-4')) : null,
div
(
setClass('detail'),
h::ul
(
setClass('list-group'),
$list
)
)
set::spaceType('api'),
set::spaceID($objectType == 'nolink' ? 'nolink' : "$objectType.$objectID"),
set::mode($mode),
set::pager(array('recTotal' => $recTotal, 'recPerPage' => $recPerPage, 'page' => $pageID)),
set::privs($privs),
set::docID($apiID),
set::libIcon('interface-lib'),
set::fetcher(createLink('api', 'ajaxGetData', 'spaceID={spaceID}&picks={picks}')),
set::docFetcher(null),
set::libSummariesFetcher(null),
set::maxHomeLibsOfSpace(0),
set::params($params),
set::autoSelectLib(),
set::fetchOnChangeSpace(),
set('$options', jsRaw('window.setDocAppOptions'))
);
+15 -11
View File
@@ -12,17 +12,21 @@ namespace zin;
set::title($lang->api->releases);
$releases = initTableData($releases, $config->api->dtable->release->fieldList, $this->api);
$releases = initTableData($releases, $config->api->dtable->release->fieldList, $this->api);
$footerInfo = empty($releases) ? $lang->pager->noRecord : str_replace('{recTotal}', '' . count($releases), $lang->pager->totalCountAB);
dtable
div
(
setStyle('overflow-y', 'hidden'),
set::extraHeight('+144'),
set::cols(array_values($config->api->dtable->release->fieldList)),
set::data(array_values($releases)),
set::userMap($users),
set::orderBy($orderBy),
set::sortLink(inlink('releases', "libID={$libID}&orderBy={name}_{sortType}"))
dtable
(
set::_className(''),
set::cols(array_values($config->api->dtable->release->fieldList)),
set::data(array_values($releases)),
set::userMap($users),
set::orderBy($orderBy),
set::footer($footerInfo),
set::loadPartial(),
set::sortLink(inlink('releases', "libID={$libID}&orderBy={name}_{sortType}")),
set::loadOptions(array('zui-command' => 'updateLazyContent'))
),
);
render();
+12 -27
View File
@@ -10,35 +10,20 @@ declare(strict_types=1);
*/
namespace zin;
featureBar
(
li(backBtn(setClass('ghost'), set::icon('back'), $lang->goback)),
div(setClass('divider')),
li
(
set::className('nav-item'),
$lang->struct->list
)
);
toolbar
(
hasPriv('api', 'createStruct') ? item(set(array
(
'icon' => 'plus',
'text' => $lang->api->createStruct,
'class' => 'primary',
'url' => createLink('api', 'createStruct', "libID=$libID"),
))) : null
);
$structs = initTableData($structs, $config->api->dtable->struct->fieldList, $this->api);
$cols = array_values($config->api->dtable->struct->fieldList);
$data = array_values($structs);
dtable
div
(
set::cols($cols),
set::data($data),
set::userMap($users),
set::footPager(usePager())
dtable
(
set::_className(''),
set::cols($cols),
set::data($data),
set::userMap($users),
set::footPager(usePager()),
set::loadPartial(),
set::loadOptions(array('zui-command' => 'updateLazyContent', 'data-lazy-target' => '#table-api-struct'))
),
);
+16 -160
View File
@@ -10,18 +10,6 @@ declare(strict_types=1);
*/
namespace zin;
include '../../doc/ui/lefttree.html.php';
$versionList = array();
for($itemVersion = $api->version; $itemVersion > 0; $itemVersion--)
{
$link = createLink('api', 'index', "libID={$libID}&moduleID=$moduleID&apiID={$apiID}&version={$itemVersion}");
$versionList[] = array(
'text' => "V$itemVersion",
'onClick' => jsRaw("() => {loadPage('$link', '#docContent')}")
);
}
$apiHeader = $apiQuery = $apiParams = $apiResponse = array();
$parseTree = function($data, $typeList, $level = 0) use(&$parseTree)
{
@@ -146,157 +134,25 @@ if($api->response)
);
}
featureBar
(
li(backBtn(setClass('ghost'), set::icon('back'), $lang->goback))
);
toolbar
(
$libID && common::hasPriv('api', 'struct') ? item(set(array
(
'icon' => 'treemap',
'class' => 'ghost',
'text' => $lang->api->struct,
'url' => createLink('api', 'struct', "libID={$libID}")
))) : null,
$libID && common::hasPriv('api', 'releases') ? item(set(array
(
'icon' => 'version',
'class' => 'ghost',
'text' => $lang->api->releases,
'url' => createLink('api', 'releases', "libID={$libID}"),
'data-toggle' => 'modal'
))) : null,
$libID && common::hasPriv('api', 'createRelease') ? item(set(array
(
'icon' => 'publish',
'class' => 'ghost',
'text' => $lang->api->createRelease,
'url' => createLink('api', 'createRelease', "libID={$libID}"),
'data-toggle' => 'modal'
))) : null,
$libID && common::hasPriv('api', 'export') && $config->edition != 'open' ? item(set(array
(
'icon' => 'export',
'class' => 'ghost export',
'text' => $lang->export,
'url' => createLink('api', 'export', "libID={$libID}&version={$version}&release={$release}&moduleID={$moduleID}"),
'data-size' => 'sm',
'data-toggle' => 'modal'
))) : null,
common::hasPriv('api', 'createLib') ? item(set(array
(
'icon' => 'plus',
'class' => 'btn secondary',
'text' => $lang->api->createLib,
'url' => createLink('api', 'createLib', "type=" . ($objectType ? $objectType : 'nolink') . "&objectID=$objectID"),
'data-toggle' => 'modal'
))) : null,
$libID && common::hasPriv('api', 'create') ? item(set(array
(
'icon' => 'plus',
'class' => 'btn primary ml-2',
'text' => $lang->api->createApi,
'url' => createLink('api', 'create', "libID={$libID}&moduleID={$moduleID}"),
'data-size' => 'lg',
'data-toggle' => $spaceType != 'api' ? 'modal' : ''
))) : null
);
div
(
setID('docContent'),
setClass('flex flex-wrap content-start'),
setID('api-content'),
setClass('article'),
div
(
setClass('doc-content flex-initial w-full flex'),
panel
(
setClass('flex-auto'),
div
(
setClass('panel-heading'),
div
(
setClass('http-method label'),
$api->method
),
div
(
setClass('path'),
$api->path
),
dropdown
(
btn
(
setClass('ghost btn square btn-default'),
'V' . ($version ? $version : $api->version)
),
set::items($versionList)
),
div
(
setClass('panel-actions'),
div
(
setClass('toolbar'),
btn
(
set::url('javascript:fullScreen()'),
setClass('btn ghost'),
icon('fullscreen')
),
(!$isRelease && common::hasPriv('api', 'edit')) ? btn
(
set::url(createLink('api', 'edit', "apiID=$api->id")),
setClass('btn ghost'),
icon('edit')
) : null,
(!$isRelease && common::hasPriv('api', 'delete')) ? btn
(
set::url(createLink('api', 'delete', "apiID=$api->id")),
setClass('btn ghost ajax-submit'),
setData(array('confirm' => $lang->api->confirmDelete)),
icon('trash')
) : null,
btn
(
setID('hisTrigger'),
setClass('btn ghost'),
icon('clock'),
set('data-on', 'click'),
set('data-call', 'showHistory')
)
)
)
),
div
(
setClass('panel-body'),
setID('content'),
h2($api->title),
div(setClass('desc'), html($api->desc)),
$apiHeader,
$apiQuery,
$apiParams,
$api->paramsExample ? h3($lang->api->paramsExample) : null,
$api->paramsExample ? html("<pre><code>" . $api->paramsExample . "</code></pre>") : null,
$apiResponse,
$api->responseExample ? h3($lang->api->responseExample) : null,
$api->responseExample ? html("<pre><code>" . $api->responseExample . "</code></pre>") : null
)
),
panel
(
setID('history'),
setClass('hidden'),
history
(
set::objectID($api->id),
set::objectType('api')
)
)
)
setClass("api-list-item row items-center mb-1 gap-2 flex-auto is-$api->method rounded"),
div(setClass('font-mono w-14 text-center api-method py-1 rounded rounded-r-none'), $api->method),
div(setClass('font-mono font-bold text-md api-path'), $api->path)
),
h2($api->title),
div(setClass('desc'), html($api->desc)),
$apiHeader,
$apiQuery,
$apiParams,
$api->paramsExample ? h3($lang->api->paramsExample) : null,
$api->paramsExample ? html("<pre><code>" . $api->paramsExample . "</code></pre>") : null,
$apiResponse,
$api->responseExample ? h3($lang->api->responseExample) : null,
$api->responseExample ? html("<pre><code>" . $api->responseExample . "</code></pre>") : null
);
-1
View File
@@ -1,4 +1,3 @@
<?php
$config->backup = new stdclass();
$config->backup->holdDays = 14;
$config->backup->setting = '';
+35 -7
View File
@@ -2705,7 +2705,7 @@ $config->bi->builtin->metrics[] = array
$config->bi->builtin->metrics[] = array
(
'name' => '按产品统计的月度解决Bug数',
'name' => '按产品统计的月度修复Bug数',
'alias' => '解决Bug数',
'code' => 'count_of_monthly_fixed_bug_in_product',
'purpose' => 'scale',
@@ -2713,8 +2713,8 @@ $config->bi->builtin->metrics[] = array
'object' => 'bug',
'unit' => 'count',
'dateType' => 'month',
'desc' => '按产品统计的月度解决Bug数是指每天在产品开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "产品中Bug个数求和\n解决日期为某年某月\n过滤已删除的Bug\n过滤已删除的产品"
'desc' => '按产品统计的月度修复Bug数是指每天在产品开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "产品中Bug的个数求和\n关闭时间为某年某月\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的产品\n",
);
$config->bi->builtin->metrics[] = array
@@ -4665,14 +4665,42 @@ $config->bi->builtin->metrics[] = array
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的月度解决Bug数',
'alias' => '解决Bug数',
'name' => '按项目统计的月度修复Bug数',
'alias' => '修复Bug数',
'code' => 'count_of_monthly_fixed_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'month',
'desc' => '按项目统计的月度解决Bug数是指每天在项目开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "项目中创建时间在某年某月的Bug个数求和\n过滤已删除的Bug\n过滤已删除的项目\n",
'desc' => '按项目统计的月度修复Bug数是指每月在项目开发过程中被解决并关闭的Bug的数量。该度量项可以帮助我们了解开发团队解决Bug的速度和效率。',
'definition' => "项目中Bug的个数求和\n关闭时间为某年某月\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的项目\n",
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的年度修复Bug数',
'alias' => '修复Bug数',
'code' => 'count_of_annual_fixed_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'year',
'desc' => '按项目统计的年度修复Bug数是指在某年度解决并关闭的Bug数量。这个度量项反映了项目在某年度解决的问题数量。年度修复Bug数越多可能说明开发团队在Bug解决方面的工作效率较高。',
'definition' => "项目中Bug的个数求和\n关闭时间为某年\n解决方案为已解决\n过滤已删除的Bug\n过滤已删除的项目\n",
);
$config->bi->builtin->metrics[] = array
(
'name' => '按项目统计的年度新增有效Bug数',
'alias' => '新增有效Bug数',
'code' => 'count_of_annual_created_effective_bug_in_project',
'purpose' => 'scale',
'scope' => 'project',
'object' => 'bug',
'unit' => 'count',
'dateType' => 'year',
'desc' => '按项目统计的年度新增有效Bug数是指项目在某年度新发现的真正具有影响和价值的Bug数量。有效Bug通常是指导致项目不正常运行或影响用户体验的Bug。统计有效Bug数可以帮助评估项目的稳定性和质量也可以评估测试人员之前的协作或对项目的了解程度。',
'definition' => "项目中Bug个数求和\n创建时间为某年\n解决方案为已解决和延期处理或者状态为激活\n过滤已删除的Bug\n过滤已删除的项目\n",
);
+1
View File
@@ -8,6 +8,7 @@ $lang->bi->driver = 'Driver';
$lang->bi->driverList = array();
$lang->bi->driverList['mysql'] = 'MySQL';
$lang->bi->query = 'Query';
$lang->bi->sqlQuery = 'SQL statements query';
$lang->bi->sqlBuilder = 'SQL builder';
$lang->bi->dictionary = 'Data Dictionary';
+1
View File
@@ -8,6 +8,7 @@ $lang->bi->driver = 'Driver';
$lang->bi->driverList = array();
$lang->bi->driverList['mysql'] = 'MySQL';
$lang->bi->query = 'Query';
$lang->bi->sqlQuery = 'SQL statements query';
$lang->bi->sqlBuilder = 'SQL builder';
$lang->bi->dictionary = 'Data Dictionary';
+1
View File
@@ -8,6 +8,7 @@ $lang->bi->driver = 'Driver';
$lang->bi->driverList = array();
$lang->bi->driverList['mysql'] = 'MySQL';
$lang->bi->query = 'Query';
$lang->bi->sqlQuery = 'SQL statements query';
$lang->bi->sqlBuilder = 'SQL builder';
$lang->bi->dictionary = 'Data Dictionary';
+1
View File
@@ -8,6 +8,7 @@ $lang->bi->driver = '数据库类型';
$lang->bi->driverList = array();
$lang->bi->driverList['mysql'] = 'MySQL';
$lang->bi->query = '查询';
$lang->bi->sqlQuery = 'SQL语句查询';
$lang->bi->sqlBuilder = 'SQL构建器';
$lang->bi->dictionary = '数据字典';
+2
View File
@@ -59,6 +59,8 @@ window.renderCell = function(result, info)
result[0] = {html: '<a href="' + $.createLink(info.row.data.storyType, 'view', `id=${info.row.data.id}`) + '" data-app="' + openTab + '">' + info.row.data.title + '</a>'};
}
if(info.col.name == 'title' && info.row.data.module == 'attend') result[0] = {html: '<span>' + result[0].props.children + '</span>'};
return result;
}
+118 -48
View File
@@ -1587,8 +1587,17 @@ class blockZen extends block
$count = isset($block->params->count) ? (int)$block->params->count : 0;
/* 测试统计是按产品分组统计的。 */
$products = $this->loadModel('product')->getOrderedProducts($status, $count, 0, 'all');
$products = $this->loadModel('product')->getOrderedProducts($status, $count, 0, 'all');
$shadowProductList = array();
foreach($products as $productID => $product) if($product->shadow) $shadowProductList[] = $productID;
$productProject = $this->dao->select('t1.product, t1.project')->from(TABLE_PROJECTPRODUCT)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
->where('t1.product')->in(array_values($shadowProductList))
->andWhere('t2.type')->eq('project')
->fetchPairs();
$productIdList = array_keys($products);
$projectIdList = array_values($productProject);
/* 计算昨日和今日可能包含的日期情况。 */
$years = array();
@@ -1600,18 +1609,35 @@ class blockZen extends block
}
$this->loadModel('metric');
$createdBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_created_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日新增的Bug数。
$resolvedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_resolved_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日解决的Bug数。
$closedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_closed_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日关闭的Bug数。
$bugFixRate = $this->metric->getResultByCodeWithArray('rate_of_fixed_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug修复率。
$effectiveBug = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取有效Bug总数。
$fixedBug = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug修复数。
$activatedBug = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug激活数。
$createdBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_created_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日新增的Bug数。
$shadowCreatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_created_bug_in_project', array('project' => join(',', $projectIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日新增的Bug数。
if(!empty($bugFixRate)) $bugFixRate = array_column($bugFixRate, null, 'product');
if(!empty($effectiveBug)) $effectiveBug = array_column($effectiveBug, null, 'product');
if(!empty($fixedBug)) $fixedBug = array_column($fixedBug, null, 'product');
if(!empty($activatedBug)) $activatedBug = array_column($activatedBug, null, 'product');
$resolvedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_resolved_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日解决的Bug数。
$shadowResolvedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_resolved_bug_in_project', array('project' => join(',', $projectIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取影子产品昨日和今日解决的Bug数。
$closedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_closed_bug_in_product', array('product' => join(',', $productIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取昨日和今日关闭的Bug数。
$shadowClosedBugGroup = $this->metric->getResultByCodeWithArray('count_of_daily_closed_bug_in_project', array('project' => join(',', $projectIdList), 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 获取影子产品昨日和今日关闭的Bug数。
$bugFixRate = $this->metric->getResultByCodeWithArray('rate_of_fixed_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug修复率。
$shadowBugFixRate = $this->metric->getResultByCodeWithArray('rate_of_fixed_bug_in_project', array('project' => join(',', $projectIdList)), 'cron'); // 获取影子产品Bug修复率。
$effectiveBug = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取有效Bug总数。
$shadowEffectiveBug = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_project', array('project' => join(',', $projectIdList)), 'cron'); // 获取影子产品有效Bug总数。
$fixedBug = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug修复数。
$shadowFixedBug = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_project', array('project' => join(',', $projectIdList)), 'cron'); // 获取影子产品Bug修复数。
$activatedBug = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_product', array('product' => join(',', $productIdList)), 'cron'); // 获取Bug激活数。
$shadowActivatedBug = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_project', array('project' => join(',', $projectIdList)), 'cron'); // 获取影子产品Bug激活数。
if(!empty($bugFixRate)) $bugFixRate = array_column($bugFixRate, null, 'product');
if(!empty($shadowBugFixRate)) $shadowBugFixRate = array_column($shadowBugFixRate, null, 'project');
if(!empty($effectiveBug)) $effectiveBug = array_column($effectiveBug, null, 'product');
if(!empty($shadowEffectiveBug)) $shadowEffectiveBug = array_column($shadowEffectiveBug, null, 'project');
if(!empty($fixedBug)) $fixedBug = array_column($fixedBug, null, 'product');
if(!empty($shadowFixedBug)) $shadowFixedBug = array_column($shadowFixedBug, null, 'project');
if(!empty($activatedBug)) $activatedBug = array_column($activatedBug, null, 'product');
if(!empty($shadowActivatedBug)) $shadowActivatedBug = array_column($shadowActivatedBug, null, 'project');
$productIdList = array_keys($products);
$unclosedTesttasks = $this->dao->select('product,id,name')->from(TABLE_TESTTASK)
@@ -1625,47 +1651,65 @@ class blockZen extends block
/* 将获取出的度量项数据塞入产品列表数据中。 */
foreach($products as $productID => $product)
{
$isShadow = $product->shadow;
$useObjectID = !$isShadow ? $productID : (isset($productProject[$productID]) ? $productProject[$productID] : $productID); // 如果是影子产品,用项目查询
$useDataKey = !$isShadow ? 'product' : 'project';
$product->addToday = 0;
$product->addYesterday = 0;
$product->resolvedToday = 0;
$product->resolvedYesterday = 0;
$product->closedToday = 0;
$product->closedYesterday = 0;
if(!empty($createdBugGroup))
$useCreatedBug = !$isShadow ? 'createdBugGroup' : 'shadowCreatedBugGroup';
if(!empty($$useCreatedBug))
{
foreach($createdBugGroup as $data)
foreach($$useCreatedBug as $data)
{
$objectEQ = $useObjectID == $data[$useDataKey];
$currentDay = "{$data['year']}-{$data['month']}-{$data['day']}";
if($currentDay == date('Y-m-d') && $productID == $data['product']) $product->addToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $productID == $data['product']) $product->addYesterday = $data['value'];
if($currentDay == date('Y-m-d') && $objectEQ) $product->addToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $objectEQ) $product->addYesterday = $data['value'];
}
}
if(!empty($resolvedBugGroup))
$useResolvedBug = !$isShadow ? 'resolvedBugGroup' : 'shadowResolvedBugGroup';
if(!empty($$useResolvedBug))
{
foreach($resolvedBugGroup as $data)
foreach($$useResolvedBug as $data)
{
$objectEQ = $useObjectID == $data[$useDataKey];
$currentDay = "{$data['year']}-{$data['month']}-{$data['day']}";
if($currentDay == date('Y-m-d') && $productID == $data['product']) $product->resolvedToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $productID == $data['product']) $product->resolvedYesterday = $data['value'];
if($currentDay == date('Y-m-d') && $objectEQ) $product->resolvedToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $objectEQ) $product->resolvedYesterday = $data['value'];
}
}
if(!empty($closedBugGroup))
$useClosedBug = !$isShadow ? 'closedBugGroup' : 'shadowClosedBugGroup';
if(!empty($$useClosedBug))
{
foreach($closedBugGroup as $data)
foreach($$useClosedBug as $data)
{
$objectEQ = $useObjectID == $data[$useDataKey];
$currentDay = "{$data['year']}-{$data['month']}-{$data['day']}";
if($currentDay == date('Y-m-d') && $productID == $data['product']) $product->closedToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $productID == $data['product']) $product->closedYesterday = $data['value'];
if($currentDay == date('Y-m-d') && $objectEQ) $product->closedToday = $data['value'];
if($currentDay == date('Y-m-d', strtotime("-1 day")) && $objectEQ) $product->closedYesterday = $data['value'];
}
}
$product->fixedBugRate = isset($bugFixRate[$productID]['value']) ? $bugFixRate[$productID]['value'] * 100 : 0;
$product->totalBug = isset($effectiveBug[$productID]['value']) ? $effectiveBug[$productID]['value'] : 0;
$product->fixedBug = isset($fixedBug[$productID]['value']) ? $fixedBug[$productID]['value'] : 0;
$product->activatedBug = isset($activatedBug[$productID]['value']) ? $activatedBug[$productID]['value'] : 0;
$product->unclosedTesttasks = isset($unclosedTesttasks[$productID]) ? $unclosedTesttasks[$productID] : '';
$useBugFixRate = !$isShadow ? 'bugFixRate' : 'shadowBugFixRate';
$useEffectiveBug = !$isShadow ? 'effectiveBug' : 'shadowEffectiveBug';
$useFixedBug = !$isShadow ? 'fixedBug' : 'shadowFixedBug';
$useActivatedBug = !$isShadow ? 'activatedBug' : 'shadowActivatedBug';
$product->fixedBugRate = isset($$useBugFixRate[$useObjectID]['value']) ? $$useBugFixRate[$useObjectID]['value'] * 100 : 0;
$product->totalBug = isset($$useEffectiveBug[$useObjectID]['value']) ? $$useEffectiveBug[$useObjectID]['value'] : 0;
$product->fixedBug = isset($$useFixedBug[$useObjectID]['value']) ? $$useFixedBug[$useObjectID]['value'] : 0;
$product->activatedBug = isset($$useActivatedBug[$useObjectID]['value']) ? $$useActivatedBug[$useObjectID]['value'] : 0;
$product->unclosedTesttasks = isset($unclosedTesttasks[$productID]) ? $unclosedTesttasks[$productID] : '';
}
$this->view->products = $products;
@@ -2696,16 +2740,32 @@ class blockZen extends block
$productID = !empty($params['active']) ? $params['active'] : key($products);
if(empty($productID)) $productID = 0;
$isShadow = $productID ? (bool)$products[$productID]->shadow : false;
$projectID = $isShadow ? $this->dao->select('project')->from(TABLE_PROJECTPRODUCT)->where('product')->eq($productID)->fetch('project') : 0;
$useObjectID = $isShadow ? $projectID : $productID;
$useDataKey = $isShadow ? 'project' : 'product';
$this->loadModel('metric');
$bugFixedRate = $this->metric->getResultByCodeWithArray('rate_of_fixed_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的Bug修复率。
$effectiveBugGroup = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的有效Bug数。
$fixedBugGroup = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的Bug修复数。
$activatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的Bug激活数。
$shadowBugFixRate = $this->metric->getResultByCodeWithArray('rate_of_fixed_bug_in_project', array('project' => $projectID), 'cron'); // 获取影子产品Bug修复率。
if(!empty($bugFixedRate)) $bugFixedRate = array_column($bugFixedRate, null, 'product');
if(!empty($effectiveBugGroup)) $effectiveBugGroup = array_column($effectiveBugGroup, null, 'product');
if(!empty($fixedBugGroup)) $fixedBugGroup = array_column($fixedBugGroup, null, 'product');
if(!empty($activatedBugGroup)) $activatedBugGroup = array_column($activatedBugGroup, null, 'product');
$effectiveBugGroup = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的有效Bug数。
$shadowEffectiveBug = $this->metric->getResultByCodeWithArray('count_of_effective_bug_in_project', array('project' => $projectID), 'cron'); // 获取影子产品有效Bug总数。
$fixedBugGroup = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的Bug修复数。
$shadowFixedBugGroup = $this->metric->getResultByCodeWithArray('count_of_fixed_bug_in_project', array('project' => $projectID), 'cron'); // 获取影子产品Bug修复数。
$activatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_product', array('product' => $productID), 'cron'); // 从度量项获取各个产品的Bug激活数。
$shadowActivatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_activated_bug_in_project', array('project' => $projectID), 'cron'); // 获取影子产品Bug激活数。
if(!empty($bugFixedRate)) $bugFixedRate = array_column($bugFixedRate, null, 'product');
if(!empty($shadowBugFixRate)) $shadowBugFixRate = array_column($shadowBugFixRate, null, 'project');
if(!empty($effectiveBugGroup)) $effectiveBugGroup = array_column($effectiveBugGroup, null, 'product');
if(!empty($shadowEffectiveBug)) $shadowEffectiveBug = array_column($shadowEffectiveBug, null, 'project');
if(!empty($fixedBugGroup)) $fixedBugGroup = array_column($fixedBugGroup, null, 'product');
if(!empty($shadowFixedBugGroup)) $shadowFixedBugGroup = array_column($shadowFixedBugGroup, null, 'project');
if(!empty($activatedBugGroup)) $activatedBugGroup = array_column($activatedBugGroup, null, 'product');
if(!empty($shadowActivatedBugGroup)) $shadowActivatedBugGroup = array_column($shadowActivatedBugGroup, null, 'project');
/* 按照产品和日期分组获取产品每月新增和完成的需求数度量项。 */
$years = array();
@@ -2718,33 +2778,43 @@ class blockZen extends block
$dates[] = date('Y-m', strtotime("first day of -{$i} month"));
}
$monthCreatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_created_bug_in_product', array('product' => $productID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的激活Bug数。
$monthClosedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_closed_bug_in_product', array('product' => $productID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的关闭Bug数。
$monthCreatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_created_bug_in_product', array('product' => $productID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的激活Bug数。
$shadowMonthCreatedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_created_bug_in_project', array('project' => $projectID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的激活Bug数。
$monthClosedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_closed_bug_in_product', array('product' => $productID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的关闭Bug数。
$shadowMonthClosedBugGroup = $this->metric->getResultByCodeWithArray('count_of_monthly_closed_bug_in_project', array('project' => $projectID, 'year' => join(',', $years), 'month' => join(',', $months)), 'cron'); // 从度量项获取每月的关闭Bug数。
/* 组装页面所需的度量项数组。 */
$closedBugs = isset($fixedBugGroup[$productID]['value']) ? $fixedBugGroup[$productID]['value'] : 0;
$unresovledBugs = isset($activatedBugGroup[$productID]['value']) ? $activatedBugGroup[$productID]['value'] : 0;
$totalBugs = isset($effectiveBugGroup[$productID]['value']) ? $effectiveBugGroup[$productID]['value'] : 0;
$resolvedRate = isset($bugFixedRate[$productID]['value']) ? $bugFixedRate[$productID]['value'] * 100 : 0;
$useClosedBug = $isShadow ? 'shadowFixedBugGroup' : 'fixedBugGroup';
$useUnresovledBugs = $isShadow ? 'shadowActivatedBugGroup' : 'activatedBugGroup';
$useTotalBugs = $isShadow ? 'shadowEffectiveBug' : 'effectiveBugGroup';
$useResolvedRate = $isShadow ? 'shadowBugFixRate' : 'bugFixedRate';
$closedBugs = isset($$useClosedBug[$useObjectID]['value']) ? $$useClosedBug[$useObjectID]['value'] : 0;
$unresovledBugs = isset($$useUnresovledBugs[$useObjectID]['value']) ? $$useUnresovledBugs[$useObjectID]['value'] : 0;
$totalBugs = isset($$useTotalBugs[$useObjectID]['value']) ? $$useTotalBugs[$useObjectID]['value'] : 0;
$resolvedRate = isset($$useResolvedRate[$useObjectID]['value']) ? $$useResolvedRate[$useObjectID]['value'] * 100 : 0;
$useMonthCreate = $isShadow ? 'shadowMonthCreatedBugGroup' : 'monthCreatedBugGroup';
$useMonthClose = $isShadow ? 'shadowMonthClosedBugGroup' : 'monthClosedBugGroup';
foreach($dates as $date)
{
$activateBugs[$date] = 0;
$resolveBugs[$date] = 0;
$closeBugs[$date] = 0;
if(!empty($monthCreatedBugGroup))
if(!empty($$useMonthCreate))
{
foreach($monthCreatedBugGroup as $data)
foreach($$useMonthCreate as $data)
{
if($date == "{$data['year']}-{$data['month']}" && $productID == $data['product']) $activateBugs[$date] = $data['value'];
if($date == "{$data['year']}-{$data['month']}" && $useObjectID == $data[$useDataKey]) $activateBugs[$date] = $data['value'];
}
}
if(!empty($monthClosedBugGroup))
if(!empty($$useMonthClose))
{
foreach($monthClosedBugGroup as $data)
foreach($$useMonthClose as $data)
{
if($date == "{$data['year']}-{$data['month']}" && $productID == $data['product']) $closeBugs[$date] = $data['value'];
if($date == "{$data['year']}-{$data['month']}" && $useObjectID == $data[$useDataKey]) $closeBugs[$date] = $data['value'];
}
}
}

Some files were not shown because too many files have changed in this diff Show More