TeamToy企业协同工具

时间:14-01-23 栏目:网络&技术 作者:wukong 评论:0 点击: 1,132 次

TeamToy是为创新团队量身定制的效率工具。和以人为核心的社交网络不同,它是以事为核心的交流环境。我们期望TeamToy能提升团队的开发效率,而不是相反。这也是为什么默认的页面是我的TODO。

TeamToy致力于让用户体验到新兴技术带来的美好,所以我们会使用一些还没有大规模普及的技术,并只兼容Chrome、Firefox和Safari等现代浏览器。

产品结构

在产品层面,TeamToy分为核心和插件体系(稍后会详细讲)。

核心只专注于TODO和沟通两方面。在TeamToy里边我们为大家准备了非常多的沟通层次,有完全私人的WebIM、一对多的广播、带有事件上下文的TODO评论。这将保证TeamToy的结构清晰,对小团队不会出现大量用不上的功能。

插件体系则为大家在个性化、垂直应用方向留出了足够的空间。不管是项目管理、Bug追踪、客户服务,还是和EverNote、新浪微博、新浪微盘的整合,都可以很方便的通过TeamToy强大的插件体系实现。我们还为大家提供了内置的插件管理页面,方便大家可以在线安装插件。

技术架构

在架构上,TeamToy实现了完全的接口化。Web版的所有功能都是通过OpenAPI来实现的。这意味着你可以开发自己的全功能客户端,同时也意味着它可以很方便的和其他系统进行整合。上周我的同事通过TeamToy的team_members接口查询到了所有同事的最新邮件地址,并为他们发送了测试代金券。科技正是应该在这些烦人的小事上帮助我们。

秉承敏捷开发的理念,TeamToy建立了几乎实时的测试-发布-分发体系。我们一般2~3天就会有一个较大的版本更新,而小版本更可能几个小时一次迭代,这也是为什么TeamToy直接使用Reversion来标识版本。借助TeamToy的一键升级功能,一个新版本从测试、打包、发布到用户升级只需要5分钟。

开源授权

在授权上,TeamToy对非商业用户(只要你不直接用它卖钱,我们都认为是非商业用户)采用GPL2协议,这保证你的数据、你开发的插件、你贡献的代码随时都可以自由的使用,不会出现因为软件厂商消失,一个好不容易建立起来的良好工作氛围无法继续的问题。

之前我们也在开放平台上试运营过,但最终还是选择了开源平台。开源平台和开放平台最大的不同在于,如果你在开放平台上开发了应用,只要他们哪天心情不好disable掉你的key,辛辛苦苦几个月开发的应用就报废了;而开源平台却可以自行搭建,只要能找到服务器,自己开发的插件就可以正常运行,我们创造的美好生活就会继续下去。这也是我们将TeamToy以开源方式运作的最大原因——客户能Hold住自己的一切。

不同于其他的开源项目,为了保持产品的质量和简洁性,TeamToy的核心不接受新功能的push,只接收已有功能的bugfix和优化。我们希望贡献者将新功能以插件的形式贡献,我们将推送给感兴趣的用户进行试用,达到一定的成熟度后进行预装或者融入核心产品。

安装

虽然TeamToy的主要用户中技术类团队居多,但这并不意味着它只适合技术团队,实际上非技术团队更能体会到TeamToy的神奇。通过和新浪云商店的合作,我们为非技术客户提供了一键安装功能,它可以在30秒内架设起一个可以立即使用的TeamToy,并为你提供一个二级域名以便使用。云商店的托管由TeamToy提供代码、新浪提供云环境、用户完全控制数据,“三权分立”,比起由软件商提供的托管服务信息更加安全。云商店空间入门级空间的费用是6元每月,足够二十人左右的团队使用了,对于没有或者不想自己架设服务器的同学来说是个不错的选择。

如果你希望自己架设TeamToy,请准备一个具备下边条件的空间:

  • PHP5.3及以上、支持GD、CURL、JSON模块,打开ini中的URL File-Access权限。
  • MySQL4及以上,关闭Strict Mode,否则可能出现“字段缺少默认值”的错误。
  • 为支持在线升级,请将放置TeamToy的目录设置为可写,从TeamToy目录可以访问到外部网络。

下载TeamToy的安装包,解压后修改数据库配置文件:进入config目录,复制db.config.sample.php,改名为db.config.php。

修改上图中选中的部分。TeamToy不会自动创建数据库,所以请预先创建好。由于在线升级会覆盖db.config.sample.php,请一定不要直接修改它。

我们推荐将TeamToy安装到域名根目录,如果你希望安装到子目录的话,需要修改config目录下的app.config.php文件。如果文件不存在,请复制app.config.sample.php改名为app.config.php。

 

请在上图选中的site_url行末加上对应的目录。

很多同学搞不清楚 app.config.sample.php 和 app.config.php 的关系,其实在实现上,TeamToy先载入 app.config.sample.php,再载入app.config.php。就是说在app.config.php中的设置会覆盖app.config.sample.php中的设置。而每次升级都会覆盖掉app.config.sample.php,所以自定义配置要写到的app.config.php中。db.config.sample.php 和 db.config.php 也是类似的关系。

然后将代码上传到空间根目录,通过浏览器访问初始化脚本。如果你的网站地址为 http://cool.teamtoy.net,那么请访问 http://cool.teamtoy.net/?c=install。

初始化成功后,会提供默认创建的用户名和密码,这个时候就可以正常使用了。

最佳实践

添加成员

admin账号是为了方便安装而创建的,不建议直接使用,一般我们会保留它用于和其他程序的接口调用,当然你也可以在创建好其他管理员后,将其关闭。

使用admin登入TeamToy,点击顶部导航的团队成员图标  ,进入成员列表。

点击右侧”搜索成员”后的“添加”链接,在滑出的表单中添加同事。TeamToy中姓名是不可以修改的,请填写同事的真实姓名。如果有同事同名,可选其中一个使用英文名。

当鼠标移动到成员名片上时,如果你是管理员,会出现灰色的操作提示,可将该成员提升为管理员或者关闭。

 

名片中的邮箱、电话都是可以点击的,如果你装有skype,点击电话号码就可以直接拨打;在TeamToy移动客户端中,点击电话会呼叫出手机的拨号界面。使用TeamToy,你就拥有了一个实时更新的团队通讯录,再也不用每隔一段时间就让助理一个人一个人的收集最新通讯录了。

目前TeamToy在成员导入时还比较繁琐,我们以后将支持通过手机直接导入联系人,让架设人能方便的将账号发送给大家。

更新头像和个人资料

接下来,请给自己换个头像。点击顶部右侧的用户菜单,可以看到“更换头像”选项。

 

如果你使用的浏览器支持FileReader功能,你还可以对头像图片进行在线裁剪,再也不用遍地找PhotoShop啦。

在用户菜单中还包含了“更新个人资料”选项,随时保持自己的资料最新是一个好习惯,这样团队中对你有好感的同学才能在需要的时候速度联系上你:P

开启桌面通知

接下来我们要设置一个TeamToy中将用得最多的功能——桌面消息推送。Desktop notification是HTML5的功能,只要你在浏览器中打开了TeamToy,不管你现在在浏览微博、用PhotoShop修图、还是在和PPT里边码字,右上方(或者右下方)都会升起一个小提示窗口,告诉你TeamToy有你要处理的消息。

遗憾的是目前Firefox还不支持这个HTML5特性,所以我们强烈推荐你使用Chrome浏览器,当然Safari也是支持的。

点击顶部导航中的收件箱按钮  ,如果你的浏览器支持,那么会出现桌面通知的设置图标。

 

点击图标,浏览器会提示,是否允许网站进行桌面消息推送,点是。然后你在电脑上就可以随时看到TeamToy中的新消息了。

TODO

然后是我们的重头戏,TODO。我们建议你将你想到要做的事情,事无巨细全部记录在这里,为了方便你能在会议室或者地铁上添加TODO,我们特意开发了移动版,让你只要能上网就能看到自己的TODO:)

我们总是希望在一个地方统一管理自己要做的事情,所以TeamToy也支持私人的TODO。发布的时候选择私有就可以,现实时标记为红色的就是私有TODO。在添加私有TODO的时候要在发布前就选好,发布成公共TODO再修改的话,TODO的标题还是会进入团队动态中的。

在添加TODO时,也可以直接把TODO添加给同事,点击输入框右下方的“添加给我”链接就可以选择其他人的。选择完成后在不刷新页面的情况下,转让人会一直有效,就是说如果连着给某位同学添加TODO,只要选择一次就OK啦。

TeamToy最大的特点就是TODO本身除了可以转让外,还可以评论,点击TODO的标题就可以在右侧展开TODO详情页,在评论中还可以用@符号对同事进行点名。这样我们在工作遇到问题时,可以很方便的把负责的同事加入到讨论中。看起来很简单的功能,在实际使用中能发挥意想不到的重要作用。

 

查看完后,可以点击右上方的减号将详情页收起,这样TODO的添加图层才会重新出来。对TODO标题的修改操作隐藏了起来,其实直接点击详情页里的TODO标题就可以了。

TeamToy2中的TODO没有了“进行中”的状态,取而代之的是“星标”功能。我们一般会对当天要完成的和特别重要的TODO添加星标,这样在TODO列表中会在最上方显示。直接点最右边的小书签图标就可以,点灰色的添加星标,点彩色的去掉星标。

我们建议大家把每天要完成的工作加星,然后在下班前进行检查和核对。遇到的问题和用到的资料可以在TODO的评论中发布,这样其他人查看时就能了解到具体的情况。

点击顶部导航中的引号图标 可以进入团队动态页面,这里你可以看到整个团队的工作进展。TeamToy2采用了in-place的设计,不管是在团队动态页面,还是在收件箱,点击列表时都会直接在右侧显示对应的内容。默认页面右侧是广播功能,广播是从原来的微博功能进化而来的,因为我们发现在工作环境中,没有发布个人微博的需求,所以它不再支持静默发布,而是以通知消息为目标;用它发布消息时必须进行点名,如果不点名则认为是要通知所有人,这也是为什么它被称为广播的原因。

如果你有悄悄话和某人聊,请使用页面右下角的WebIM,这东西使用起来比较简单,就不详细介绍了。

以上就是TeamToy的核心功能,可以看出它是围绕事和沟通来组织的。初次使用时可能会有少许的不习惯,一旦熟悉后你将发现TeamToy能极大地提升团队的效率,尤其是当团队人数在10人以上的时候。每天将一堆TODO划掉的感觉会让团队成员非常有成就感,能真实的感受到项目的一步步前进,是一个非常容易上瘾的好习惯。

和那些“管理”软件不同,TeamToy不改变团队的现有流程,只是让现有的流程更便捷。你可以在开会的同时就给对应的同事添加好的TODO,然后在他完成TODO时得到通知;你也可以在中午吃午饭时广播一句“谁跟我吃肉去”,不用担心办公区太大同事没听到;需要传给同事的账号和网址不用再去问人家的QQ,直接用私信搞定。

移动客户端

TeamToy提供了使用PhoneGap技术开发的移动客户端,目前支持Android和iOS两个平台,可以从TeamToy官方网站下载到。由于目前还处于开发期,我们还没有上架官方市场,iOS的版本需要越狱,且暂不支持推送。Android版本在Nexus4和小米2上运行良好,但在某些2.x的老机器上会有卡顿,我们希望稍后能推出原生版本的Android客户端(有兴趣的同学请赶紧和我联系,Easychen@qq.com)。

移动版独有的特色功能包括二维码登入、推送通知、联系人导出和短信群发。

二维码登入

为了省却在手机上输入各种字串的痛苦,TeamToy可以扫码登入。登入网页版后,在顶部导航右侧的用户菜单里点击“通过二维码登入”选项,即可获得登入用二维码,这时候用手机客户端直接扫描就能登入,方便又快捷。

 

需要注意的是,部分在自己电脑上测试TeamToy的同学,请确保你的手机可以通过网络访问到你的电脑,像127.0.0.1这样的网址是不行哦!

推送通知

 

登入移动版后,点击最右侧的小齿轮进入设置界面,点击接收推送消息即可打开。推送消息会消耗一定量的流量,在Wifi环境下(Wifi下检测频率会比3G下高)为每天2M左右,3G下为1M左右,流量包月的同学可以放心使用,其他同学请密切留意流量,以免产生不必要的费用。

联系人导出和短信群发

 

在移动版的团队成员页面顶部,我们提供了联系人导出和短信群发功能。联系人导出可以将TeamToy中的联系人以 [T]name 的形式写入手机通讯录,这样当其他同事给你打电话的时候,你就不用回到TeamToy查看是谁了;定期将TeamToy的联系人导出到手机通讯录可以保持联系人数据最新。短信群发功能是为紧急事件准备的,它会调用当前手机的短信发送功能无差别的向TeamToy中填写了手机号的同学发送短信。在50个成员的TeamToy中每群发一次都会花费4~5元(和手工发送一样的,由你的移动运营商收费),请谨慎使用。

和其他系统的对接

前边已经提到过了,TeamToy提供完全的API接口,这让它和其他系统对接变得非常简单。

TeamToy的标准API采用token认证,只需要先用用户名和密码换取token,之后请求其他接口的时候带上这个token就可以了。API接口地址为domain.com/?c=api。

下边这几行实例代码演示了如何从TeamToy中读取团队通讯录。
<?php
$api = 'http://ttsite.sinaapp.com/index.php?c=api';

// 获取Token
$info = json_decode( file_get_contents( $api .’&a=user_get_token&email=’ . urlencode(‘we@teamtoy.net’) . ’&password=’. urlencode(‘think_different’)  ) , true ) ;
$token = $info['data']['token'];

// 取得团队成员名单
print_r( json_decode(file_get_contents($api.’&a=team_members&token=’.$token), true  ) );

是不是很简单?还有更简单的。从版本776开始自带的SimpleToken插件让上边的代码变得只需要一行。

首先请在顶部导航右侧的用户菜单中选择“通过Token访问数据”选项,点击“启用Token”按钮。这样我们就为当前账号创建了一个永久有效的Token。通过OpenApi获取的token是会过期的,而通过这个插件创建的token则永不过期,除非我们手工停用或者重置它。

 

OK,一旦我们创建完这个SimpleToken,我们就可以直接通过URL来取得json数据。URL的格式和API文档中描述的一致,但为了区别,我们把参数中的token改为了stoken。比如要获取我们demo站上的成员联系人信息,只需要访问这样一个链接:

http://ttsite.sinaapp.com/?c=api&a=team_members&stoken=[stoken]

够方便吧。更棒的是所有TeamToy的功能都可以通过这种方式实现。要和TeamToy进行通信,你只需要会拼接URL。

如果你不会PHP只会Javascript,那也没有关系,SimpleToken还支持JsonP。只要在上边的链接最后加上&jsonp=callbackfunction , 你就可以在其他网站上通过JsonP跨域引用TeamToy的数据了,不过记住stoken不要暴露给别人,要让用户填写并暂存下来。

API的详细文档请点击右侧  APIDOC

 扩展机制和插件开发

很多同学表示要给TeamToy开发新功能,但是TeamToy升级的时候会覆盖原来的代码,修改都丢失了。卡卡,这个时候就需要扩展机制出马了。从版本776开始,TeamToy引入了一套强大的扩展机制,它允许你把代码写到一个地方,却可以修改整个TeamToy的各项功能。

说得那么玄乎,其实我们只是借鉴了WordPress采用的hook机制而已,这种机制将要运行的代码挂接到特定的hook上,当hook触发的时候就会运行我们的代码。通过hook的挂接,我们就可以把一个文件中的代码,分散到系统的各个地方,

首先,在TeamToy中hook分两种,带有返回值的叫做Filter,主要对数据做过滤;不带返回值的叫Action,一般处理完数据后直接输出。Filter和Action的底层实现是一样的,只是一种逻辑上的区分。注册Filter用add_filter函数,调用Filter时用apply_filer函数;注册Action时用add_action函数,调用Action时用do_action函数;虽然名字不一样,其实它们背后都是add_hook和apply_hook函数。

我们在TeamToy中提供三大类hook,分别是API hook,Controller hook和UI hook。

API hook允许你对已有API的输入参数和返回数据进行过滤;通过PHP的魔术方法__call,我们也允许你直接在插件中创建新的API。

创建自己的API时,只需要调用add_action( ‘API_*’ , 回调函数名 );就可以了。在回调函数中,可以用apiController::send_result和apiController::send_error两个静态方法来输出API的返回值。以下是一个返回自定义CSS内容分的API接口实例。

<?php
add_action( "API_MYCSS" , "api_mycss");
function api_mycss()
{
$sql = "SELECT `css` FROM `css` WHERE `uid` ='" . intval(uid()) . "' LIMIT 1";
$data = get_var( $sql ) ;
if( db_errno() != 0  ) apiController::send_error( LR_API_DB_ERROR , "DATABASE ERROR" . db_error() );
return apiController::send_result( $data );
}

修改现有的API时,只需要挂上 API_接口名_INPUT_FILTER 和 API_接口名_OUTPUT_FILTER 两个hook就可以了。
下边的代码拦截user_profile接口调用,并向结果中追加了一个def_css字段。

<?php
add_filter('API_USER_PROFILE_OUTPUT_FILTER' ,'user_profile_css' );
function user_profile_css( $data )
{
$uid = intval($data['id']);
$data['def_css'] = get_var("SELECT `css` FROM `css` WHERE `uid` = '" . intval($uid) . "' LIMIT 1");
return $data;
}

TeamToy采用LazyPHP3开发,Controller是其核心逻辑组件,Controller hook主要用于修改TeamToy现有的功能,包含一个Controller的Input filter,允许你可以对输入参数进行过滤;同时在负责渲染页面的Render函数中包含了一个Render filter。

<?php

//对dashboardContrlloer的upgrade方法进行输入过滤
add_filter(‘CTRL_DASHBOARD_UPGRADE_INPUT_FILTER’ ,’clean_upgrade_args’ );
function clean_upgrade_args( $data )
{
$_REQUEST["version"] = 1;
}

//对dashboardContrlloer的upgrade方法用于页面渲染的数据进行过滤
add_filter(‘CTRL_DASHBOARD_UPGRADE_RENDER_FILTER’ ,’clean_upgrade_data’ );
function clean_upgrade_data( $data )
{
$data["verison"] = 1024;
return $data; // 务必return $data
}

UI hook 主要应用于模板中,通过它你可以在合适的地方插入菜单、显示数据。典型的UI hook如:

  • UI_HEAD – 用于在页面head区域追加输出
  • UI_NAVLIST_LAST – 用于在顶部栏目导航最后添加新菜单
  • UI_USERMENU_BOTTOM - 用于在顶部右侧用户菜单尾部追加新菜单

而在一些常用区块前和后都有UI hook,配合ob_start和ob_get_contents 使用,可以对整个区块输出的html做修改。例如在页脚部分,我们有两个hook:

  • UI_FOOTER_BEFORE
  • UI_FOOTER_AFTER

在 UI_FOOTER_BEFORE 中调用ob_start,在 UI_FOOTER_AFTER 中调用ob_get_contents 就可以拦截掉footer的默认输出,通过str_replace可以对内容进行修改。
UI hook非常的多,这里就不一一列举了,最方便的办法是直接打开你需要修改部分的模板,查看里边的 add_action 函数就一目了然了。关于模板文件的存放位置和组织方式,建议阅读下LazyPHP3的说明文档。

了解了hook机制后,我们来说说插件开发。以自定义CSS这个插件为例,我们一步步讲。
自定义CSS插件为每个用户提供一个可自行填写CSS的区域,用于TeamToy的个性化。借助这个插件,用户可以更换自己的背景图、修改TODO的显示颜色等。每个用户填写的CSS只对自己生效。

下边我们来创建插件。在TeamToy根目录下,有一个plugin目录,在里边创建我们的插件目录,就叫mycss吧。然后再mycss目录下创建一个app.php。这是我们的插件入口文件,TeamToy就调用它。

为了能让插件出现在TeamToy的插件管理面板中,我们需要在app.php最上方添加一个插件元信息注释块。注释块的开头和结束必须用三个*,不然会识别不到。下边是一个例子,写插件时复制过去然后修改成自己对应的就好了。folder_name是非常重要的信息,没有folder_name将无法通过插件管理器管理。

/***
TeamToy extenstion info block
##name 在插件管理器中显示的名字 如 SimpleToken
##folder_name 插件目录的名字,需要遵守文件命名规范,如 simple_token
##author 作者 如 Easy
##email 联系邮箱 如 Easychen@qq.com
##reversion 版本,建议直接使用版本管理中的reversion 如 1
##desp 简要描述,让用户知道这个插件可以干什么 如 SimpleToken让用户可以创建一个当前账户的永不过期的Token,并在调用API的时候使用。SimpleToekn支持json和jsonp两种输出。
##update_url 最新升级包url,升级时会抓取它。如  http://tt2net.sinaapp.com/?c=plugin&a=update_package&name=stoken
##reverison_url 最新版本号url,直接输出数字即可。 http://tt2net.sinaapp.com/?c=plugin&a=latest_reversion&name=stoken
***/

然后在config目录下,修改app.config.php中的plugins设置,这是一个数组,把我们的mycss添加上。
添加完成后看起来像这样。
$GLOBALS['config']['plugins'] = array( ‘css_modifier’ , ‘simple_token’ );
PS:TeamToy 1055版本后也可以使用插件管理面板管理。这个版本将在1月28日发布。
OK,现在这个插件已经可以用了。TeamToy会在启动时载入app.php文件,不过我们还没做什么事情呢,继续修改app.php吧。
首先我们先创建一个存放css的表。

<?php
// 创建数据表
if( !mysql_query("SHOW COLUMNS FROM `css`",db()) )
{
// table not exists
// create it
run_sql("CREATE TABLE  `css` (
`uid` INT NOT NULL ,
`css` TEXT NOT NULL ,
PRIMARY KEY (  `uid` )
) ENGINE = MYISAM ");
}

接下来我们在app.php中添加一些hook,用来实现我们的逻辑。


// 添加顶部导航按钮

add_action( 'UI_NAVLIST_LAST' , 'mycss_nav_list');
function mycss_nav_list()
{
?><li <?php if( g(‘c’) == ’plugin’ && g(‘a’) == ’mycss’ ): ?>class=”active”<?php endif; ?>><a href=”?c=plugin&a=mycss” title=”自定义CSS” >
<div><img src=”plugin/css_modifier/cssicon.png”/></div></a>
</li><?php
}

// 添加显示页面的逻辑
add_action( ’PLUGIN_MYCSS’ , ’plugin_mycss’);
function  plugin_mycss()
{
$data['top'] = $data['top_title'] = ’自定义样式表’;
$data['css'] = get_var( ”SELECT `css` FROM `css` WHERE `uid` = ’” . intval(uid()) . ”‘ LIMIT 1″ );
render( $data , ’web’ , ’plugin’ , ’mycss’ );
}

// 添加保存css的函数
add_action( ’PLUGIN_MYCSS_SAVE’ , ’plugin_mycss_save’);
function plugin_mycss_save()
{
$css = z(t(v(‘css’)));
$sql = ”REPLACE INTO `css` ( `uid` , `css` ) VALUES ( ’” . intval(uid()) . ”‘ , ’” . s( $css ) . ”‘ )”;
run_sql( $sql );
$location = ’?c=plugin&a=mycss’;
if( db_errno() != 0 )
return info_page(‘数据保存失败,请稍后重试。<a href=”‘ . $location . ’”>点击返回</a>’);
else
header(“Location:” . $location);

}

// 在head标签中输出自定义的CSS
add_action( 'UI_HEAD' ,'mycss_ui_head');
function mycss_ui_head()
{
echo '<script type="text/javascript" src="plugin/css_modifier/tabindent.js"></script>'."rn";
if( $css = get_var( "SELECT `css` FROM `css` WHERE `uid` = '" . intval(uid()) . "' LIMIT 1" ) )
{
?><style type="text/css"><?=$css?></style><?php
}
}

 

上边的代码通过UI_HEAD hook在页面头部输出用户自定义的css,实现了插件的功能;但里边出现了两个我们没有提到过的hook – PLUGIN_*。

其实PLUGIN_* 只是一个内置的Controller hook,如果我们add_action(‘PLUGIN_ABC’); 那么TeamToy就会在用户访问?c=plugin&a=abc的时候调用我们注册的函数,在这个被调用的函数中按平时LazyPHP的action去写就好了。

需要注意的是,plugin_mycss 中的render函数采用了名字为plugin的sharp,并传递了插件目录mycss。这是因为LazyPHP默认的模板需要放到view目录,而通过这种方式,我们可以直接把插件的模板放到插件目录下的view子目录中,这样插件所有的代码都能放置到一起了。

完整的插件代码可以点击右边下载 css_modifier

短短几十行代码,全部集中在业务逻辑上,在不需要的时候,只要将config中的plugin配置去掉,所有的功能就消失了。这种将功能模块化的方式,既维持了核心产品的简洁,又保证了长尾功能的覆盖,在TeamToy升级的时候,插件的功能也不会因为代码更新而受影响,我们推荐所有希望扩展TeamToy的同学都按这个方式来操作。


声明: 本文由( wukong )原创编译,转载请保留链接: TeamToy企业协同工具

------====== 本站公告 ======------
大家有任何疑问和建议,请到这里留言:点击留言板

读者排行