Merge branch 'pro' into okr

# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Models/User.php
#	docker-compose.yml
#	docker/nginx/default.conf
#	language/original-web.txt
#	language/translate.json
#	public/docs/assets/main.bundle.js
#	public/docs/index.html
#	resources/assets/js/pages/manage.vue
#	resources/assets/js/pages/manage/components/ChatInput/index.vue
#	resources/assets/sass/pages/_.scss
This commit is contained in:
weifashi 2023-08-17 09:28:38 +08:00
commit 146fb3321a
929 changed files with 16122 additions and 8316 deletions

View File

@ -1,3 +1,5 @@
TIMEZONE=PRC
APP_NAME=DooTask
APP_ENV=local
APP_KEY=

View File

@ -1,3 +1,5 @@
TIMEZONE=PRC
APP_NAME=Laravel
APP_ENV=local
APP_KEY=

View File

@ -2,12 +2,167 @@
All notable changes to this project will be documented in this file.
## [0.30.13]
### Bug Fixes
- 消息列表过大导致无法查看图片
- 桌面端drawio版本错误
### Performance
- 优化菜单颜色选择
## [0.30.6]
### Bug Fixes
- 无法在任务新窗口打开引用的任务
- 在任务新窗口使用显示文件窗口错误的情况
- 部分iOS系统按录音时页面闪烁的情况
- 修改cookie协议页面的标题及点击cookie协议链接跳转新页面
- Claude 机器人返回内容错误的情况
- 在文件页面编辑文本时选择已传图片缩列图不显示的情况
- 桌面客户端提示request错误
- 客户端无法保存网络文件的情况
- 可以发送空白md消息的情况
- 桌面客户端缺失文件
- 打开工作流设置后无法关闭桌面客户端的问题
- 打不开已归档任务的情况
### Performance
- 升级客户端框架
- 优化工作汇报提交表单
- 优化确认框按钮样式
- 优化时间冲突提示框
- 文件页面弹出菜单时误操作优化
- 优化任务描述编辑器
- 优化表情快捷提示框
- 优化移动端编辑任务详情
- 优化桌面端邮件图片菜单
- 优化表情关键词匹配
- 工作流支持关联任务列表自动移动
- 支持手动打卡
- 优化数据流推送消息页面滚动
- 优化再次点击消息定位到未读、待办、灰色未读
- 优化复制链接
- 优化可见消息列表
- 优化动画样式
- 优化菜单显示、选择复制
- Ai聊天小概率出现重复推流的情况
- 适配arm64
## [0.29.21]
### Performance
- 会话消息没有接收人时已读进度优化
- 优化拖拽文件夹上传提示
- 深色模式硬件加速
- 优化Android弹出键盘后聊天内容被覆盖的问题
## [0.29.11]
### Bug Fixes
- 修复用户列表默认排序
- 1. 审批通知模版 - 按钮白色修复 2. 审批详情样式相等 3. 审批评论 - 0分钟换成刚刚 4. 没有加入部门也能发起 审批申请 5. 审批流程设置页 - 样式调整
### Performance
- 管理员可以修改系统机器人基本资料
- 优化深色模式
- 添加ChatGPT、Claude智能机器人
- 机器人群聊消息被@到时发送到webhook
## [0.28.91]
### Bug Fixes
- 修复无法从任务消息对话中打开任务详情的情况
- 按照dootask启动原始尺寸截取使用说明的图
- 修改边栏目录滚动效果
- 修改边栏目录滚动效果
- 官网使用说明的图重新截取更换
- 优化官网布局与样式
- 修复下载英文页面跳转
### Features
- 增加获取更新日志接口,更改前端页面默认请求地址
- 使用说明提交
### Performance
- 优化审批机器人模板消息样式
- 优化添加任务样式
- 优化任务默认时间
- 优化深色模式
- 任务详情发送文件时防止按esc关闭发送窗口
- 深色模式下无法扫描登录二维码的情况
- 优化iOS深色模式
- Safari支持暗黑模式
- 优化任务时间冲突提示
- IOS部分点击事件存在阻塞的情况
## [0.28.36]
### Bug Fixes
- 修改英文页面
- 导航按钮英文修改
- 修复导航按钮
- 前端取消会议屏幕常亮
### Features
- 修改英文的下载单次手写字母大写
- 链接调整和价格页面调整
- 立即体验按钮、价格页面等样式调整
### Performance
- 整理官网页面
- 任务详情页可见性选项默认不显示
- 避免删除后不关闭任务窗口
- 添加任务支持自定义协助人
## [0.28.6]
### Bug Fixes
- 全员群禁言仅管理员可发言无效的问题
- 发送消息失败再次编辑格式丢失的问题
- 1.修复审核导出缺少 2.修复审核导出小时计算误差
- 请假表格导出sheeft里名称显示人名
- 修正初始化可见性人员异常问题
- 修复审批通过人员姓名显示不正确
### Features
- 官网页面首版提交
- 优化子任务可见性
- 负责人、协助人更改可见性推送收回
- 新增任务可见性操作模块、任务详情子任务样式优化
### Performance
- 审批中心图片压缩优化
- 修改可见性推送优化
- 代码优化
- 冗余代码去除
- 调整
## [0.27.46]
### Bug Fixes
- 会议窗口恢复不显示的情况
- 修复已知bug
- 打开会话面板报错
- 子任务通知无法打开
- 修复审批的图片无法查看
### Performance
@ -15,17 +170,6 @@ All notable changes to this project will be documented in this file.
- 聊天输入框iOS输入第一个字母出现抖动的情况
- 优化iOS出现连续加载消息列表的情况
- 移动端键盘发送
## [0.27.35]
### Bug Fixes
- 打开会话面板报错
- 子任务通知无法打开
- 修复审批的图片无法查看
### Performance
- 优化会员选择器
- 优化图片压缩
- 回复图片显示图片搜略图

View File

@ -11,7 +11,7 @@ Group No.: `546574618`
## Setup
- `Docker` & `Docker Compose v2.0+` must be installed
- `Docker v20.10+` & `Docker Compose v2.0+` must be installed
- System: `Centos/Debian/Ubuntu/macOS`
- Hardware suggestion: 2 cores and above 4G memory

View File

@ -11,7 +11,7 @@
## 安装程序
- 必须安装:`Docker` 和 `Docker Compose v2.0+`
- 必须安装:`Docker v20.10+` 和 `Docker Compose v2.0+`
- 支持环境:`Centos/Debian/Ubuntu/macOS`
- 硬件建议2核4G以上

View File

@ -29,7 +29,7 @@ class ApproveController extends AbstractController
private $flow_url = '';
public function __construct()
{
$this->flow_url = env('FLOW_URL') ?: 'http://dootask-approve-'.env('APP_ID');
$this->flow_url = env('FLOW_URL') ?: 'http://approve';
}
/**
@ -74,6 +74,7 @@ class ApproveController extends AbstractController
$ret = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/procdef/findAll', json_encode($data));
$procdef = json_decode($ret['ret'] == 1 ? $ret['data'] : '{}', true);
if (!$procdef || $procdef['status'] != 200 || $ret['ret'] == 0) {
// info($ret);
return Base::retError($procdef['message'] ?? '查询失败');
}
return Base::retSuccess('success', Base::arrayKeyToUnderline($procdef['data']));
@ -190,12 +191,25 @@ class ApproveController extends AbstractController
$data['userid'] = (string)$user->userid;
$data['content'] = Request::input('content'); //内容+图片
$processInst = $this->getProcessById($data['proc_inst_id']);
$ret = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/process/addGlobalComment', json_encode(Base::arrayKeyToCamel($data)));
$process = json_decode($ret['ret'] == 1 ? $ret['data'] : '{}', true);
if (!$process || $process['status'] != 200) {
return Base::retError($process['message'] ?? '添加失败');
}
//
// 推送通知
$botUser = User::botGetOrCreate('approval-alert');
foreach ( $processInst['userids'] as $id) {
if($id != $user->userid){
$dialog = WebSocketDialog::checkUserDialog($botUser, $id);
$processInst['comment_user_id'] = $user->userid;
$processInst['comment_content'] = json_decode($data['content'],true)['content'];
$this->approveMsg('approve_comment_notifier', $dialog, $botUser, $processInst, $processInst);
}
}
$res = Base::arrayKeyToUnderline($process['data']);
return Base::retSuccess('success', $res);
}
@ -206,7 +220,7 @@ class ApproveController extends AbstractController
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup approve
* @apiName task__complete
* @apiName task__complete
*
* @apiQuery {Number} task_id 流程ID
* @apiQuery {String} pass 标题 [true-通过false-拒绝]
@ -958,6 +972,8 @@ class ApproveController extends AbstractController
'type' => $process['var']['type'],
'start_time' => $process['var']['start_time'],
'end_time' => $process['var']['end_time'],
'comment_nickname' => $process['comment_user_id'] ? User::userid2nickname($process['comment_user_id']) : '',
'comment_content' => $process['comment_content'] ?? ''
];
$text = view('push.bot', ['type' => $type, 'action' => $action, 'is_finished' => $process['is_finished'], 'data' => (object)$data])->render();
$text = preg_replace("/^\x20+/", "", $text);
@ -1011,6 +1027,7 @@ class ApproveController extends AbstractController
}
//
$res = Base::arrayKeyToUnderline($process['data']);
$res['userids'] = [];
foreach ($res['node_infos'] as &$val) {
if (isset($val['node_user_list'])) {
$node = $val['node_user_list'];
@ -1020,10 +1037,12 @@ class ApproveController extends AbstractController
continue;
}
$val['node_user_list'][$k]['userimg'] = User::getAvatar($info->userid, $info->userimg, $info->email, $info->nickname);
$res['userids'][] = $item['target_id'];
}
}else if($val['aprover_id']){
$info = User::whereUserid($val['aprover_id'])->first();
$val['userimg'] = $info ? User::getAvatar($info->userid, $info->userimg, $info->email, $info->nickname) : '';
$res['userids'][] = $val['aprover_id'];
}
}
// 全局评论
@ -1039,6 +1058,10 @@ class ApproveController extends AbstractController
}
$info = User::whereUserid($res['start_user_id'])->first();
$res['userimg'] = $info ? User::getAvatar($info->userid, $info->userimg, $info->email, $info->nickname) : '';
//
$res['userids'][] = $info->userid;
$res['userids'] = array_unique($res['userids']);
//
return $res;
}

View File

@ -2,7 +2,9 @@
namespace App\Http\Controllers\Api;
use App\Tasks\PushTask;
use DB;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Request;
use Redirect;
use Carbon\Carbon;
@ -78,7 +80,7 @@ class DialogController extends AbstractController
return Base::retError('请输入搜索关键词');
}
// 搜索会话
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$key}%")
->where('u.userid', $user->userid)
@ -115,7 +117,7 @@ class DialogController extends AbstractController
}
// 搜索消息会话
if (count($list) < 20) {
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at', 'm.id as search_msg_id'])
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at', 'm.id as search_msg_id'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->join('web_socket_dialog_msgs as m', 'web_socket_dialogs.id', '=', 'm.dialog_id')
->where('u.userid', $user->userid)
@ -152,7 +154,7 @@ class DialogController extends AbstractController
//
$dialog_id = intval(Request::input('dialog_id'));
//
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.id', $dialog_id)
->where('u.userid', $user->userid)
@ -403,6 +405,8 @@ class DialogController extends AbstractController
if ($msg_type) {
if ($msg_type === 'tag') {
$builder->where('tag', '>', 0);
} elseif ($msg_type === 'todo') {
$builder->where('todo', '>', 0);
} elseif ($msg_type === 'link') {
$builder->whereLink(1);
} elseif (in_array($msg_type, ['text', 'image', 'file', 'record', 'meeting'])) {
@ -635,7 +639,46 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendtext 14. 发送消息
* @api {post} api/dialog/msg/stream 14. 通知成员监听消息
*
* @apiDescription 通知指定会员EventSource监听流动消息
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__stream
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} userid 通知会员ID
* @apiParam {String} stream_url 流动消息地址
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__stream()
{
// $dialog_id = intval(Request::input('dialog_id'));
$userid = intval(Request::input('userid'));
$stream_url = trim(Request::input('stream_url'));
//
if ($userid <= 0) {
return Base::retError('参数错误');
}
//
$params = [
'userid' => $userid,
'msg' => [
'type' => 'msgStream',
'stream_url' => $stream_url,
]
];
$task = new PushTask($params, false);
Task::deliver($task);
//
return Base::retSuccess('success');
}
/**
* @api {post} api/dialog/msg/sendtext 15. 发送消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -648,6 +691,9 @@ class DialogController extends AbstractController
* - html: HTML默认
* - md: MARKDOWN
* @apiParam {Number} [update_id] 更新消息ID优先大于 reply_id
* @apiParam {String} [update_mark] 是否更新标记
* - no: 不标记(仅机器人支持)
* - yes: 标记(默认)
* @apiParam {Number} [reply_id] 回复ID
* @apiParam {String} [silence] 是否静默发送
* - no: 正常发送(默认)
@ -674,69 +720,77 @@ class DialogController extends AbstractController
}
//
$dialog_id = intval(Request::input('dialog_id'));
$dialog_ids = trim(Request::input('dialog_ids'));
$update_id = intval(Request::input('update_id'));
$update_mark = !($user->bot && in_array(strtolower(trim(Request::input('update_mark'))), ['no', 'false', '0']));
$reply_id = intval(Request::input('reply_id'));
$text = trim(Request::input('text'));
$text_type = strtolower(trim(Request::input('text_type')));
$silence = in_array(strtolower(trim(Request::input('silence'))), ['yes', 'true', '1']);
$markdown = in_array($text_type, ['md', 'markdown']);
//
WebSocketDialog::checkDialog($dialog_id);
//
if ($update_id > 0) {
$action = "update-$update_id";
} elseif ($reply_id > 0) {
$action = "reply-$reply_id";
} else {
$action = "";
}
//
if (!$markdown) {
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
}
$strlen = mb_strlen($text);
$noimglen = mb_strlen(preg_replace("/<img[^>]*?>/i", "", $text));
if ($strlen < 1) {
return Base::retError('消息内容不能为空');
}
if ($noimglen > 200000) {
return Base::retError('消息内容最大不能超过200000字');
}
if ($noimglen > 5000) {
// 内容过长转成文件发送
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($path));
$path = $path . md5($text) . ".htm";
$file = public_path($path);
file_put_contents($file, $text);
$size = filesize(public_path($path));
if (empty($size)) {
return Base::retError('消息发送保存失败');
//
$result = [];
$dialogIds = $dialog_ids ? explode(',', $dialog_ids) : [$dialog_id ?: 0];
foreach($dialogIds as $dialog_id) {
//
WebSocketDialog::checkDialog($dialog_id);
//
if ($update_id > 0) {
$action = $update_mark ? "update-$update_id" : "change-$update_id";
} elseif ($reply_id > 0) {
$action = "reply-$reply_id";
} else {
$action = "";
}
$ext = $markdown ? 'md' : 'htm';
$fileData = [
'name' => "LongText-{$strlen}.{$ext}",
'size' => $size,
'file' => $file,
'path' => $path,
'url' => Base::fillUrl($path),
'thumb' => '',
'width' => -1,
'height' => -1,
'ext' => $ext,
];
return WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence);
//
if (!$markdown) {
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
}
$strlen = mb_strlen($text);
$noimglen = mb_strlen(preg_replace("/<img[^>]*?>/i", "", $text));
if ($strlen < 1) {
return Base::retError('消息内容不能为空');
}
if ($noimglen > 200000) {
return Base::retError('消息内容最大不能超过200000字');
}
if ($noimglen > 5000) {
// 内容过长转成文件发送
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($path));
$path = $path . md5($text) . ".htm";
$file = public_path($path);
file_put_contents($file, $text);
$size = filesize(public_path($path));
if (empty($size)) {
return Base::retError('消息发送保存失败');
}
$ext = $markdown ? 'md' : 'htm';
$fileData = [
'name' => "LongText-{$strlen}.{$ext}",
'size' => $size,
'file' => $file,
'path' => $path,
'url' => Base::fillUrl($path),
'thumb' => '',
'width' => -1,
'height' => -1,
'ext' => $ext,
];
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence);
}
//
$msgData = ['text' => $text];
if ($markdown) {
$msgData['type'] = 'md';
}
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence);
}
//
$msgData = ['text' => $text];
if ($markdown) {
$msgData['type'] = 'md';
}
return WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence);
return $result;
}
/**
* @api {post} api/dialog/msg/sendrecord 15. 发送语音
* @api {post} api/dialog/msg/sendrecord 16. 发送语音
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -783,7 +837,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendfile 16. 文件上传
* @api {post} api/dialog/msg/sendfile 17. 文件上传
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -815,6 +869,7 @@ class DialogController extends AbstractController
/**
* @api {post} api/dialog/msg/sendfiles 17. 群发文件上传
* @api {post} api/dialog/msg/sendfiles 18. 群发文件上传
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -869,7 +924,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/sendfileid 18. 通过文件ID发送文件
* @api {get} api/dialog/msg/sendfileid 19. 通过文件ID发送文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -939,7 +994,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendanon 19. 发送匿名消息
* @api {post} api/dialog/msg/sendanon 20. 发送匿名消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -992,7 +1047,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/readlist 20. 获取消息阅读情况
* @api {get} api/dialog/msg/readlist 21. 获取消息阅读情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1021,7 +1076,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/detail 21. 消息详情
* @api {get} api/dialog/msg/detail 22. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1069,7 +1124,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/download 22. 文件下载
* @api {get} api/dialog/msg/download 23. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1112,7 +1167,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/withdraw 23. 聊天消息撤回
* @api {get} api/dialog/msg/withdraw 24. 聊天消息撤回
*
* @apiDescription 消息撤回限制24小时内需要token身份
* @apiVersion 1.0.0
@ -1138,7 +1193,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/mark 24. 消息标记操作
* @api {get} api/dialog/msg/mark 25. 消息标记操作
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1205,7 +1260,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/silence 25. 消息免打扰
* @api {get} api/dialog/msg/silence 26. 消息免打扰
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1268,7 +1323,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/forward 26. 转发消息给
* @api {get} api/dialog/msg/forward 27. 转发消息给
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1305,7 +1360,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/emoji 27. emoji回复
* @api {get} api/dialog/msg/emoji 28. emoji回复
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1340,7 +1395,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/tag 28. 标注/取消标注
* @api {get} api/dialog/msg/tag 29. 标注/取消标注
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1369,7 +1424,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/todo 29. 设待办/取消待办
* @api {get} api/dialog/msg/todo 30. 设待办/取消待办
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1412,7 +1467,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/todolist 30. 获取消息待办情况
* @api {get} api/dialog/msg/todolist 31. 获取消息待办情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1442,7 +1497,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/done 31. 完成待办
* @api {get} api/dialog/msg/done 32. 完成待办
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1489,7 +1544,48 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/add 32. 新增群组
* @api {get} api/dialog/msg/color 30. 设置颜色
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__color
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {String} color 颜色
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__color()
{
$user = User::auth();
$dialogId = intval(Request::input('dialog_id'));
$color = Request::input('color','');
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
if (!$dialogUser) {
return Base::retError("会话不存在");
}
//
$dialogData = WebSocketDialog::find($dialogId);
if (empty($dialogData)) {
return Base::retError("会话不存在");
}
//
$dialogUser->color = $color;
$dialogUser->save();
//
$data = [
'id' => $dialogId,
'color' => $color
];
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/dialog/group/add 33. 新增群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1551,7 +1647,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/edit 33. 修改群组
* @api {get} api/dialog/group/edit 34. 修改群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1612,7 +1708,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/adduser 34. 添加群成员
* @api {get} api/dialog/group/adduser 35. 添加群成员
*
* @apiDescription 需要token身份
* - 有群主时:只有群主可以邀请
@ -1648,7 +1744,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/deluser 35. 移出(退出)群成员
* @api {get} api/dialog/group/deluser 36. 移出(退出)群成员
*
* @apiDescription 需要token身份
* - 只有群主、邀请人可以踢人
@ -1692,7 +1788,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/transfer 36. 转让群组
* @api {get} api/dialog/group/transfer 37. 转让群组
*
* @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散
@ -1736,7 +1832,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/disband 37. 解散群组
* @api {get} api/dialog/group/disband 38. 解散群组
*
* @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散
@ -1764,7 +1860,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/searchuser 38. 搜索个人群(仅限管理员)
* @api {get} api/dialog/group/searchuser 39. 搜索个人群(仅限管理员)
*
* @apiDescription 需要token身份用于创建部门搜索个人群组
* @apiVersion 1.0.0

View File

@ -979,10 +979,14 @@ class ProjectController extends AbstractController
$builder->leftJoin('project_users', function ($query) {
$query->on('project_tasks.project_id', '=', 'project_users.project_id')->where('project_users.owner', 1);
});
$builder->leftJoin('project_task_users as project_p_task_users', function ($query) {
$query->on('project_p_task_users.task_pid', '=', 'project_tasks.parent_id');
});
$builder->where(function ($query) use ($userid) {
$query->where("project_tasks.is_all_visible", 1);
$query->orWhere("project_users.userid", $userid);
$query->orWhere("project_task_users.userid", $userid);
$query->orWhere("project_p_task_users.userid", $userid);
});
// 优化子查询汇总
$builder->leftJoinSub(function ($query) {
@ -991,10 +995,10 @@ class ProjectController extends AbstractController
->groupBy('task_id');
}, 'task_files', 'task_files.task_id', '=', 'project_tasks.id');
$builder->leftJoinSub(function ($query) {
$query->select('id', DB::raw('count(*) as msg_num'))
->from('web_socket_dialogs')
->groupBy('id');
}, 'socket_dialogs', 'socket_dialogs.id', '=', 'project_tasks.id');
$query->select('dialog_id', DB::raw('count(*) as msg_num'))
->from('web_socket_dialog_msgs')
->groupBy('dialog_id');
}, 'socket_dialog_msgs', 'socket_dialog_msgs.dialog_id', '=', 'project_tasks.dialog_id');
$builder->leftJoinSub(function ($query) {
$query->select('parent_id', DB::raw('count(*) as sub_num, sum(CASE WHEN complete_at IS NOT NULL THEN 1 ELSE 0 END) sub_complete') )
->from('project_tasks')
@ -1003,7 +1007,7 @@ class ProjectController extends AbstractController
// 给前缀“_”是为了不触发获取器
$prefix = DB::getTablePrefix();
$builder->selectRaw("{$prefix}task_files.file_num as _file_num");
$builder->selectRaw("{$prefix}socket_dialogs.msg_num as _msg_num");
$builder->selectRaw("{$prefix}socket_dialog_msgs.msg_num as _msg_num");
$builder->selectRaw("{$prefix}sub_task.sub_num as _sub_num");
$builder->selectRaw("{$prefix}sub_task.sub_complete as _sub_complete");
$builder->selectRaw("
@ -1044,7 +1048,63 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/export 19. 导出任务(限管理员)
* @api {get} api/project/task/easylists 19. 任务列表-简单的
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__easylists
* @apiParam {String} [taskid] 排除的任务ID
* @apiParam {String} [userid] 用户ID1,2
* @apiParam {String} [timerange] 时间范围2022-03-01 12:12:12,2022-05-01 12:12:12
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__easylists()
{
User::auth();
//
$taskid = trim(Request::input('taskid'));
$userid = Request::input('userid');
$timerange = Request::input('timerange');
//
$list = ProjectTask::with(['taskUser'])
->select('projects.name as project_name', 'project_tasks.id', 'project_tasks.name', 'project_tasks.start_at', 'project_tasks.end_at')
->join('projects','project_tasks.project_id','=','projects.id')
->leftJoin('project_task_users', function ($query) {
$query->on('project_tasks.id', '=', 'project_task_users.task_id')->where('project_task_users.owner', '=', 1);
})
->whereIn('project_task_users.userid', is_array($userid) ? $userid : explode(',', $userid) )
->when(!empty($timerange), function ($query) use ($timerange) {
if (!is_array($timerange)) {
$timerange = explode(',', $timerange);
}
if (Base::isDateOrTime($timerange[0]) && Base::isDateOrTime($timerange[1])) {
$query->where('project_tasks.start_at', '<=', Carbon::parse($timerange[1])->endOfDay());
$query->where('project_tasks.end_at', '>=', Carbon::parse($timerange[0])->startOfDay());
}
})
->when(!empty($taskid), function ($query) use ($taskid) {
$query->where('project_tasks.id', "!=", $taskid);
})
->whereNull('complete_at')
->distinct()
->orderByDesc('project_tasks.id')
->paginate(Base::getPaginate(200, 100));
//
$list->transform(function ($customer) {
$customer->setAppends([]);
return $customer;
});
//
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/project/task/export 20. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
@ -1247,7 +1307,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/exportoverdue 20. 导出超期任务(限管理员)
* @api {get} api/project/task/exportoverdue 21. 导出超期任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
@ -1356,7 +1416,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/down 21. 下载导出的任务
* @api {get} api/project/task/down 22. 下载导出的任务
*
* @apiVersion 1.0.0
* @apiGroup project
@ -1382,7 +1442,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/one 22. 获取单个任务信息
* @api {get} api/project/task/one 23. 获取单个任务信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1426,7 +1486,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/content 23. 获取任务详细描述
* @api {get} api/project/task/content 24. 获取任务详细描述
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1454,7 +1514,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/files 24. 获取任务文件列表
* @api {get} api/project/task/files 25. 获取任务文件列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1479,7 +1539,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedelete 25. 删除任务文件
* @api {get} api/project/task/filedelete 26. 删除任务文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1512,7 +1572,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedetail 26. 获取任务文件详情
* @api {get} api/project/task/filedetail 27. 获取任务文件详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1556,7 +1616,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedown 27. 下载任务文件
* @api {get} api/project/task/filedown 28. 下载任务文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1605,7 +1665,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/add 28. 添加任务
* @api {post} api/project/task/add 29. 添加任务
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1689,7 +1749,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/addsub 29. 添加子任务
* @api {get} api/project/task/addsub 30. 添加子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1722,7 +1782,7 @@ class ProjectController extends AbstractController
'column_id' => $task->column_id,
'times' => [$task->start_at, $task->end_at],
'owner' => [User::userid()],
'is_all_visible' => 2,
'is_all_visible' => $task->is_all_visible,
]);
$data = ProjectTask::oneTask($task->id);
$pushUserIds = ProjectTaskUser::whereTaskId($task->id)->pluck('userid')->toArray();
@ -1734,7 +1794,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/update 30. 修改任务、子任务
* @api {post} api/project/task/update 31. 修改任务、子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1833,7 +1893,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/dialog 31. 创建/获取聊天室
* @api {get} api/project/task/dialog 32. 创建/获取聊天室
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1882,7 +1942,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/archived 32. 归档任务
* @api {get} api/project/task/archived 33. 归档任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1924,7 +1984,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/remove 33. 删除任务
* @api {get} api/project/task/remove 34. 删除任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1958,7 +2018,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/resetfromlog 34. 根据日志重置任务
* @api {get} api/project/task/resetfromlog 35. 根据日志重置任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -2017,7 +2077,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/flow 35. 任务工作流信息
* @api {get} api/project/task/flow 36. 任务工作流信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -2099,7 +2159,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/flow/list 36. 工作流列表
* @api {get} api/project/flow/list 37. 工作流列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -2125,7 +2185,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/flow/save 37. 保存工作流
* @api {post} api/project/flow/save 38. 保存工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@ -2159,7 +2219,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/flow/delete 38. 删除工作流
* @api {get} api/project/flow/delete 39. 删除工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@ -2191,7 +2251,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/log/lists 39. 获取项目、任务日志
* @api {get} api/project/log/lists 40. 获取项目、任务日志
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -2244,7 +2304,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/top 40. 项目置顶
* @api {get} api/project/top 41. 项目置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Models\UserBot;
use App\Models\UserCheckinMac;
use App\Models\UserCheckinRecord;
use App\Models\WebSocketDialog;
@ -89,90 +90,14 @@ class PublicController extends AbstractController
if ($setting['open'] !== 'open') {
return 'function off';
}
if (!in_array('auto', $setting['modes'])) {
return 'mode off';
}
if ($key != $setting['key']) {
return 'key error';
}
$times = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
$advance = (intval($setting['advance']) ?: 120) * 60;
$delay = (intval($setting['delay']) ?: 120) * 60;
//
$nowDate = date("Y-m-d");
$nowTime = date("H:i:s");
//
$timeStart = strtotime("{$nowDate} {$times[0]}");
$timeEnd = strtotime("{$nowDate} {$times[1]}");
$timeAdvance = max($timeStart - $advance, strtotime($nowDate));
$timeDelay = min($timeEnd + $delay, strtotime("{$nowDate} 23:59:59"));
if (Base::time() < $timeAdvance || $timeDelay < Base::time()) {
return "not in valid time, valid time is " . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
}
//
$macs = explode(",", $mac);
$checkins = [];
foreach ($macs as $mac) {
$mac = strtoupper($mac);
if (Base::isMac($mac) && $UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
$checkins[] = $UserCheckinMac;
$array = [
'userid' => $UserCheckinMac->userid,
'mac' => $UserCheckinMac->mac,
'date' => $nowDate,
];
$record = UserCheckinRecord::where($array)->first();
if (empty($record)) {
$record = UserCheckinRecord::createInstance($array);
}
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
$record->report_time = $time;
$record->save();
}
}
//
if ($checkins && $botUser = User::botGetOrCreate('check-in')) {
$getJokeSoup = function($type) {
$pre = $type == "up" ? "每日开心:" : "心灵鸡汤:";
$key = $type == "up" ? "JokeSoupTask:jokes" : "JokeSoupTask:soups";
$array = Base::json2array(Cache::get($key));
if ($array) {
$item = $array[array_rand($array)];
if ($item) {
return $pre . $item;
}
}
return null;
};
$sendMsg = function($type, UserCheckinMac $checkin) use ($getJokeSoup, $botUser, $nowDate) {
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin->userid;
if (Cache::get($cacheKey) === "yes") {
return;
}
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
//
$dialog = WebSocketDialog::checkUserDialog($botUser, $checkin->userid);
if ($dialog) {
$hi = date("H:i");
$pre = $type == "up" ? "上班" : "下班";
$remark = $checkin->remark ? " ({$checkin->remark})": "";
$text = "<p>{$pre}打卡成功,打卡时间: {$hi}{$remark}</p>";
$suff = $getJokeSoup($type);
if ($suff) {
$text = "{$text}<p>----------</p><p>{$suff}</p>";
}
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
}
};
if ($timeAdvance <= Base::time() && Base::time() < $timeEnd) {
// 上班打卡通知(从最早打卡时间 到 下班打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('up', $checkin);
}
}
if ($timeEnd <= Base::time() && Base::time() <= $timeDelay) {
// 下班打卡通知(下班打卡时间 到 最晚打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('down', $checkin);
}
}
if ($error = UserBot::checkinBotCheckin($mac, $time)) {
return $error;
}
return 'success';
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use Request;
use Session;
use Response;
@ -38,7 +40,7 @@ class SystemController extends AbstractController
* @apiParam {String} type
* - get: 获取(默认)
* - all: 获取所有(需要管理员权限)
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'image_compress', 'image_save_local', 'start_home', 'home_footer']
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'all_group_mute', 'all_group_autoin', 'image_compress', 'image_save_local', 'start_home']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -65,12 +67,13 @@ class SystemController extends AbstractController
'anon_message',
'auto_archived',
'archived_day',
'task_visible',
'task_default_time',
'all_group_mute',
'all_group_autoin',
'image_compress',
'image_save_local',
'start_home',
'home_footer'
])) {
unset($all[$key]);
}
@ -104,6 +107,8 @@ class SystemController extends AbstractController
$setting['anon_message'] = $setting['anon_message'] ?: 'open';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
$setting['task_visible'] = $setting['task_visible'] ?: 'close';
$setting['task_default_time'] = $setting['task_default_time'] ? Base::json2array($setting['task_default_time']) : ['09:00', '18:00'];
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
$setting['image_compress'] = $setting['image_compress'] ?: 'open';
@ -226,7 +231,92 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/setting/checkin 04. 获取签到设置、保存签到设置(限管理员)
* @api {get} api/system/setting/aibot 04. 获取会议设置、保存AI机器人设置限管理员
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__aibot
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['openai_key', 'openai_agency', 'claude_token', 'claude_agency']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__aibot()
{
$user = User::auth('admin');
//
$type = trim(Request::input('type'));
$setting = Base::setting('aibotSetting');
$keys = [
'openai_key',
'openai_agency',
'claude_token',
'claude_agency',
'wenxin_key',
'wenxin_secret',
'wenxin_model',
'qianwen_key',
'qianwen_model'
];
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, $keys)) {
unset($all[$key]);
}
}
$backup = $setting;
$setting = Base::setting('aibotSetting', Base::newTrim($all));
//
if ($backup['openai_key'] != $setting['openai_key']) {
$botUser = User::botGetOrCreate('ai-openai');
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
}
}
if ($backup['claude_token'] != $setting['claude_token']) {
$botUser = User::botGetOrCreate('ai-claude');
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
}
}
if ($backup['wenxin_key'] != $setting['wenxin_key']) {
$botUser = User::botGetOrCreate('ai-wenxin');
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
}
}
if ($backup['qianwen_key'] != $setting['qianwen_key']) {
$botUser = User::botGetOrCreate('ai-qianwen');
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
}
}
}
//
$setting['wenxin_model'] = $setting['wenxin_model'] ?: 'ERNIE-Bot-turbo';
$setting['qianwen_model'] = $setting['qianwen_model'] ?: 'qwen-v1';
if (env("SYSTEM_SETTING") == 'disabled') {
foreach ($keys as $item) {
if (strlen($setting[$item]) > 12) {
$setting[$item] = substr($setting[$item], 0, 4) . str_repeat('*', strlen($setting[$item]) - 8) . substr($setting[$item], -4);
}
}
}
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/checkin 05. 获取签到设置、保存签到设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
@ -234,7 +324,7 @@ class SystemController extends AbstractController
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['open', 'time', 'advance', 'delay', 'remindin', 'remindexceed', 'edit', 'key']
* - save: 保存设置(参数:['open', 'time', 'advance', 'delay', 'remindin', 'remindexceed', 'edit', 'modes', 'key']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
@ -258,6 +348,7 @@ class SystemController extends AbstractController
'remindin',
'remindexceed',
'edit',
'modes',
'key',
])) {
unset($all[$key]);
@ -266,6 +357,7 @@ class SystemController extends AbstractController
if ($all['open'] === 'close') {
$all['key'] = md5(Base::generatePassword(32));
}
$all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'location']);
$setting = Base::setting('checkinSetting', Base::newTrim($all));
} else {
$setting = Base::setting('checkinSetting');
@ -283,13 +375,14 @@ class SystemController extends AbstractController
$setting['remindin'] = intval($setting['remindin']) ?: 5;
$setting['remindexceed'] = intval($setting['remindexceed']) ?: 10;
$setting['edit'] = $setting['edit'] ?: 'close';
$setting['modes'] = is_array($setting['modes']) ? $setting['modes'] : [];
$setting['cmd'] = "curl -sSL '" . Base::fillUrl("api/public/checkin/install?key={$setting['key']}") . "' | sh";
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/apppush 05. 获取APP推送设置、保存APP推送设置限管理员
* @api {get} api/system/setting/apppush 06. 获取APP推送设置、保存APP推送设置限管理员
*
* @apiVersion 1.0.0
* @apiGroup system
@ -334,7 +427,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/setting/thirdaccess 06. 第三方帐号(限管理员)
* @api {get} api/system/setting/thirdaccess 07. 第三方帐号(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
@ -404,7 +497,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/demo 07. 获取演示帐号
* @api {get} api/system/demo 08. 获取演示帐号
*
* @apiVersion 1.0.0
* @apiGroup system
@ -428,7 +521,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/priority 08. 任务优先级
* @api {post} api/system/priority 09. 任务优先级
*
* @apiDescription 获取任务优先级、保存任务优先级
* @apiVersion 1.0.0
@ -477,7 +570,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/column/template 09. 创建项目模板
* @api {post} api/system/column/template 10. 创建项目模板
*
* @apiDescription 获取创建项目模板、保存创建项目模板
* @apiVersion 1.0.0
@ -524,7 +617,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/license 10. License
* @api {post} api/system/license 11. License
*
* @apiDescription 获取License信息、保存License限管理员
* @apiVersion 1.0.0
@ -587,7 +680,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 11. 获取终端详细信息
* @api {get} api/system/get/info 12. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -616,7 +709,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 12. 获取IP地址
* @api {get} api/system/get/ip 13. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -631,7 +724,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 13. 是否中国IP地址
* @api {get} api/system/get/cnip 14. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -648,7 +741,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 14. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 15. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@ -665,7 +758,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 15. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 16. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -682,7 +775,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/imgupload 16. 上传图片
* @api {post} api/system/imgupload 17. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -742,7 +835,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 17. 浏览图片空间
* @api {get} api/system/get/imgview 18. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -839,7 +932,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 18. 上传文件
* @api {post} api/system/fileupload 19. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -881,18 +974,18 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/showitem 19. 首页显示ITEM
* @api {get} api/system/get/updatelog 20. 获取更新日志
*
* @apiDescription 用于判断首页是否显示pro、github、更新日志...
* @apiDescription 获取更新日志
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__showitem
* @apiName get__updatelog
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function get__showitem()
public function get__updatelog()
{
$logPath = base_path('CHANGELOG.md');
$logContent = "";
@ -905,30 +998,7 @@ class SystemController extends AbstractController
}
}
return Base::retSuccess('success', [
'pro' => str_contains(Request::getHost(), "dootask.com") || str_contains(Request::getHost(), "127.0.0.1"),
'github' => env('GITHUB_URL') ?: false,
'updateLog' => $logContent ?: false,
'updateVer' => $logVersion,
]);
}
/**
* @api {get} api/system/get/starthome 20. 启动首页设置信息
*
* @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__starthome
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function get__starthome()
{
return Base::retSuccess('success', [
'need_start' => Base::settingFind('system', 'start_home') == 'open',
'home_footer' => Base::settingFind('system', 'home_footer')
]);
}

View File

@ -759,8 +759,7 @@ class UsersController extends AbstractController
$builder->whereNull('disable_at');
$builder->where('bot', 0);
}
$builder = $keys['department'] == '0' ? $builder->orderByDesc('userid') : $builder;
$list = $builder->paginate(Base::getPaginate(50, 20));
$list = $builder->orderByDesc('userid')->paginate(Base::getPaginate(50, 20));
//
if ($getCheckinMac) {
$list->transform(function (User $user) use ($getCheckinMac) {
@ -1733,21 +1732,36 @@ class UsersController extends AbstractController
}
$userBot = UserBot::whereBotId($botUser->userid)->whereUserid($user->userid)->first();
if (empty($userBot)) {
return Base::retError('不是你的机器人');
if (UserBot::systemBotName($botUser->email)) {
// 系统机器人(仅限管理员)
if (!$user->isAdmin()) {
return Base::retError('权限不足');
}
} else {
// 其他用户的机器人(仅限主人)
return Base::retError('不是你的机器人');
}
}
return Base::retSuccess('success', [
//
$data = [
'id' => $botUser->userid,
'name' => $botUser->nickname,
'avatar' => $botUser->userimg,
'clear_day' => $userBot->clear_day,
'webhook_url' => $userBot->webhook_url,
]);
'clear_day' => 0,
'webhook_url' => '',
'system_name' => UserBot::systemBotName($botUser->email),
];
if ($userBot) {
$data['clear_day'] = $userBot->clear_day;
$data['webhook_url'] = $userBot->webhook_url;
}
return Base::retSuccess('success', $data);
}
/**
* @api {post} api/users/bot/edit 30. 编辑机器人
*
* @apiDescription 需要token身份编辑我的机器人信息
* @apiDescription 需要token身份编辑 我的机器人 管理员修改系统机器人 信息
* @apiVersion 1.0.0
* @apiGroup users
* @apiName bot__edit
@ -1755,8 +1769,8 @@ class UsersController extends AbstractController
* @apiParam {Number} id 机器人ID
* @apiParam {String} [name] 机器人名称
* @apiParam {String} [avatar] 机器人头像
* @apiParam {Number} [clear_day] 清理天数
* @apiParam {String} [webhook_url] Webhook地址
* @apiParam {Number} [clear_day] 清理天数(仅 我的机器人)
* @apiParam {String} [webhook_url] Webhook地址(仅 我的机器人)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -1773,7 +1787,15 @@ class UsersController extends AbstractController
}
$userBot = UserBot::whereBotId($botUser->userid)->whereUserid($user->userid)->first();
if (empty($userBot)) {
return Base::retError('不是你的机器人');
if (UserBot::systemBotName($botUser->email)) {
// 系统机器人(仅限管理员)
if (!$user->isAdmin()) {
return Base::retError('权限不足');
}
} else {
// 其他用户的机器人(仅限主人)
return Base::retError('不是你的机器人');
}
}
//
$data = Request::input();
@ -1802,18 +1824,24 @@ class UsersController extends AbstractController
$botUser->updateInstance($upUser);
$botUser->save();
}
if ($upBot) {
if ($upBot && $userBot) {
$userBot->updateInstance($upBot);
$userBot->save();
}
//
return Base::retSuccess('修改成功', [
$data = [
'id' => $botUser->userid,
'name' => $botUser->nickname,
'avatar' => $botUser->userimg,
'clear_day' => $userBot->clear_day,
'webhook_url' => $userBot->webhook_url,
]);
'clear_day' => 0,
'webhook_url' => '',
'system_name' => UserBot::systemBotName($botUser->email),
];
if ($userBot) {
$data['clear_day'] = $userBot->clear_day;
$data['webhook_url'] = $userBot->webhook_url;
}
return Base::retSuccess('修改成功', $data);
}
/**
@ -1823,6 +1851,10 @@ class UsersController extends AbstractController
* @apiGroup users
* @apiName share__list
*
* @apiParam {String} [type] 分享类型file-文件text-列表 默认file
* @apiParam {Number} [pid] 父级文件id用于获取子目录和上传到指定目录的id
* @apiParam {Number} [upload_file_id] 上传文件id
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
@ -1830,6 +1862,7 @@ class UsersController extends AbstractController
public function share__list()
{
$user = User::auth();
$type = Request::input('type', 'file');
$pid = intval(Request::input('pid', -1));
$uploadFileId = intval(Request::input('upload_file_id', -1));
// 上传文件
@ -1841,7 +1874,7 @@ class UsersController extends AbstractController
}
// 获取数据
$lists = [];
if ($pid !== -1) {
if ($type == 'file' && $pid !== -1) {
$fileList = (new File)->getFileList($user, $pid, 'dir', false);
foreach ($fileList as $file) {
if ($file['id'] != $pid) {
@ -1854,15 +1887,16 @@ class UsersController extends AbstractController
];
}
}
} else {
$lists[] = [
'type' => 'children',
'url' => Base::fillUrl("api/users/share/list") . "?pid=0",
'icon' => url("images/file/light/folder.png"),
'extend' => ['upload_file_id' => 0],
'name' => Doo::translate('文件'),
];
if($type == 'file'){
$lists[] = [
'type' => 'children',
'url' => Base::fillUrl("api/users/share/list") . "?pid=0",
'icon' => url("images/file/light/folder.png"),
'extend' => ['upload_file_id' => 0],
'name' => Doo::translate('文件'),
];
}
$dialogList = (new WebSocketDialog)->getDialogList($user->userid);
foreach ($dialogList['data'] as $dialog) {
if ($dialog['avatar']) {
@ -1881,8 +1915,13 @@ class UsersController extends AbstractController
'type' => 'item',
'name' => $dialog['name'],
'icon' => $avatar,
'url' => Base::fillUrl("api/dialog/msg/sendfiles"),
'extend' => ['dialog_ids' => $dialog['id']]
'url' => $type == "file" ? Base::fillUrl("api/dialog/msg/sendfiles") : Base::fillUrl("api/dialog/msg/sendtext"),
'extend' => [
'dialog_ids' => $dialog['id'],
'text_type' => 'text',
'reply_id' => 0,
'silence' => 'no'
]
];
}
}

View File

@ -390,6 +390,7 @@ class Project extends AbstractModel
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
$usertype = trim($item['usertype']);
$userlimit = intval($item['userlimit']);
$columnid = intval($item['columnid']);
if ($usertype == 'replace' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
}
@ -411,6 +412,7 @@ class Project extends AbstractModel
'userids' => $userids,
'usertype' => trim($item['usertype']),
'userlimit' => $userlimit,
'columnid' => $columnid,
], [], $isInsert);
if ($flow) {
$ids[] = $flow->id;
@ -545,7 +547,7 @@ class Project extends AbstractModel
$project->save();
//
if ($flow == 'open') {
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0}]'));
}
});
//

View File

@ -16,6 +16,7 @@ use App\Module\Base;
* @property array $userids 状态负责人ID
* @property string|null $usertype 流转模式
* @property int|null $userlimit 限制负责人
* @property int|null $columnid 对应的项目列表
* @property int|null $sort 排序
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
@ -23,6 +24,7 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereColumnid($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereFlowId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereId($value)

View File

@ -363,15 +363,16 @@ class ProjectTask extends AbstractModel
$content = $data['content'];
$times = $data['times'];
$owner = $data['owner'];
$add_assist = intval($data['add_assist']);
$add_assist = intval($data['add_assist']); // 将自己添加到参与者
$assist = $data['assist']; // 参与者,此项设置时 add_assist 无效
$subtasks = $data['subtasks'];
$p_level = intval($data['p_level']);
$p_name = $data['p_name'];
$p_color = $data['p_color'];
$top = intval($data['top']);
$userid = User::userid();
$is_all_visible = $data['visibility_appoint'];
$visibility_userids = $data['visibility_appointor'];
$is_all_visible = isset($data['visibility_appoint']) ? $data['visibility_appoint'] : $data['is_all_visible'];
$visibility_userids = $data['visibility_appointor'] ?: [];
//
if (ProjectTask::whereProjectId($project_id)
->whereNull('project_tasks.complete_at')
@ -400,7 +401,7 @@ class ProjectTask extends AbstractModel
'p_level' => $p_level,
'p_name' => $p_name,
'p_color' => $p_color,
'is_all_visible' => $is_all_visible
'is_all_visible' => $is_all_visible ?: 1
]);
if ($content) {
$task->desc = Base::getHtml($content, 100);
@ -438,9 +439,12 @@ class ProjectTask extends AbstractModel
}
$owner = $tmpArray;
// 协助人员
$assist = [];
if (!in_array($userid, $owner) && $add_assist) {
$assist = [$userid];
$assist = is_array($assist) ? $assist : [];
if (empty($assist)) {
// 添加自己
if (!in_array($userid, $owner) && $add_assist) {
$assist = [$userid];
}
}
// 创建人
$task->userid = $userid;
@ -614,6 +618,9 @@ class ProjectTask extends AbstractModel
$data['assist'] = array_values(array_unique(array_diff($data['assist'], $data['owner'])));
}
}
if ($newFlowItem->columnid && ProjectColumn::whereProjectId($this->project_id)->whereId($newFlowItem->columnid)->exists()) {
$data['column_id'] = $newFlowItem->columnid;
}
$this->flow_item_id = $newFlowItem->id;
$this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name;
$this->addLog("修改{任务}状态", [
@ -718,6 +725,7 @@ class ProjectTask extends AbstractModel
if (Arr::exists($data, 'is_all_visible') || Arr::exists($data, 'visibility_appointor')) {
if (Arr::exists($data, 'is_all_visible')) {
ProjectTask::whereId($data['task_id'])->update(['is_all_visible' => $data["is_all_visible"]]);
ProjectTask::whereParentId($data['task_id'])->update(['is_all_visible' => $data["is_all_visible"]]);
}
ProjectTaskUser::whereTaskId($data['task_id'])->whereOwner(2)->delete();
if (Arr::exists($data, 'visibility_appointor')) {

View File

@ -167,7 +167,7 @@ class User extends AbstractModel
if (!$this->bot) {
return 0;
}
$key = "getBotOwner::" . $this->userid;
$key = "userBotOwner::" . $this->userid;
return Cache::remember($key, now()->addMonth(), function() {
return intval(UserBot::whereBotId($this->userid)->value('userid')) ?: $this->userid;
});
@ -535,6 +535,10 @@ class User extends AbstractModel
return url("images/avatar/default_approval.png");
case 'okr-alert@bot.system':
return url("images/avatar/default_task.png");
case 'ai-openai@bot.system':
return url("images/avatar/default_openai.png");
case 'ai-claude@bot.system':
return url("images/avatar/default_claude.png");
case 'bot-manager@bot.system':
return url("images/avatar/default_bot.png");
}
@ -628,6 +632,7 @@ class User extends AbstractModel
$update['nickname'] = '机器人管理';
break;
}
$update['nickname'] = UserBot::systemBotName($email);
}
if ($update) {
$botUser->updateInstance($update);

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Module\Base;
use App\Module\Doo;
use App\Module\Extranet;
use Cache;
@ -36,6 +37,31 @@ use Carbon\Carbon;
class UserBot extends AbstractModel
{
/**
* 系统机器人名称
* @param $name string 邮箱 邮箱前缀
* @return string
*/
public static function systemBotName($name)
{
if (str_contains($name, "@")) {
$name = explode("@", $name)[0];
}
return match ($name) {
'system-msg' => '系统消息',
'task-alert' => '任务提醒',
'check-in' => '签到打卡',
'anon-msg' => '匿名消息',
'approval-alert' => '审批',
'ai-openai' => 'ChatGPT',
'ai-claude' => 'Claude',
'ai-wenxin' => '文心一言',
'ai-qianwen' => '通义千问',
'bot-manager' => '机器人管理',
default => '', // 不是系统机器人时返回空(也可以拿来判断是否是系统机器人)
};
}
/**
* 机器人菜单
* @param $email
@ -47,7 +73,7 @@ class UserBot extends AbstractModel
'check-in@bot.system' => [
[
'key' => 'checkin',
'label' => Doo::translate('我要签到')
'label' => Doo::translate('我要打卡')
], [
'key' => 'it',
'label' => Doo::translate('IT资讯')
@ -104,11 +130,139 @@ class UserBot extends AbstractModel
}
Cache::put("UserBot::checkinBotQuickMsg:{$userid}", "yes", Carbon::now()->addSecond());
//
$text = match ($command) {
"checkin" => "暂未开放手动签到。",
default => Extranet::checkinBotQuickMsg($command),
};
return $text ?: '维护中...';
if ($command === 'checkin') {
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return '暂未开启签到功能。';
}
if (!in_array('manual', $setting['modes'])) {
return '暂未开放手动签到。';
}
if ($error = UserBot::checkinBotCheckin($userid, Base::time(), true)) {
return $error;
}
return null;
} else {
return Extranet::checkinBotQuickMsg($command);
}
}
/**
* 签到机器人签到
* @param $mac
* @param $time
* @param bool $alreadyTip 签到过是否提示
* @return string|null 返回string表示错误信息返回null表示签到成功
*/
public static function checkinBotCheckin($mac, $time, $alreadyTip = false)
{
$setting = Base::setting('checkinSetting');
$times = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
$advance = (intval($setting['advance']) ?: 120) * 60;
$delay = (intval($setting['delay']) ?: 120) * 60;
//
$nowDate = date("Y-m-d");
$nowTime = date("H:i:s");
//
$timeStart = strtotime("{$nowDate} {$times[0]}");
$timeEnd = strtotime("{$nowDate} {$times[1]}");
$timeAdvance = max($timeStart - $advance, strtotime($nowDate));
$timeDelay = min($timeEnd + $delay, strtotime("{$nowDate} 23:59:59"));
if (Base::time() < $timeAdvance || $timeDelay < Base::time()) {
return "不在有效时间内,有效时间为:" . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
}
//
$macs = explode(",", $mac);
$checkins = [];
foreach ($macs as $mac) {
$mac = strtoupper($mac);
$array = [];
if (Base::isMac($mac)) {
if ($UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
$array = [
'userid' => $UserCheckinMac->userid,
'mac' => $UserCheckinMac->mac,
'date' => $nowDate,
];
$checkins[] = [
'userid' => $UserCheckinMac->userid,
'remark' => $UserCheckinMac->remark,
];
}
} elseif (Base::isNumber($mac)) {
if ($UserInfo = User::whereUserid($mac)->whereBot(0)->first()) {
$array = [
'userid' => $UserInfo->userid,
'mac' => '00:00:00:00:00:00',
'date' => $nowDate,
];
$checkins[] = [
'userid' => $UserInfo->userid,
'remark' => '手动签到',
];
}
}
if ($array) {
$record = UserCheckinRecord::where($array)->first();
if (empty($record)) {
$record = UserCheckinRecord::createInstance($array);
}
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
$record->report_time = $time;
$record->save();
}
}
//
if ($checkins && $botUser = User::botGetOrCreate('check-in')) {
$getJokeSoup = function($type) {
$pre = $type == "up" ? "每日开心:" : "心灵鸡汤:";
$key = $type == "up" ? "JokeSoupTask:jokes" : "JokeSoupTask:soups";
$array = Base::json2array(Cache::get($key));
if ($array) {
$item = $array[array_rand($array)];
if ($item) {
return $pre . $item;
}
}
return null;
};
$sendMsg = function($type, $checkin) use ($alreadyTip, $getJokeSoup, $botUser, $nowDate) {
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin['userid'];
$typeDesc = $type == "up" ? "上班" : "下班";
if (Cache::get($cacheKey) === "yes") {
if ($alreadyTip && $dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
$text = "<p>今日已{$typeDesc}打卡,无需重复打卡。</p>";
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
}
return;
}
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
//
if ($dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
$hi = date("H:i");
$remark = $checkin['remark'] ? " ({$checkin['remark']})": "";
$text = "<p>{$typeDesc}打卡成功,打卡时间: {$hi}{$remark}</p>";
$suff = $getJokeSoup($type);
if ($suff) {
$text = "{$text}<p>----------</p><p>{$suff}</p>";
}
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
}
};
if ($timeAdvance <= Base::time() && Base::time() < $timeEnd) {
// 上班打卡通知(从最早打卡时间 到 下班打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('up', $checkin);
}
}
if ($timeEnd <= Base::time() && Base::time() <= $timeDelay) {
// 下班打卡通知(下班打卡时间 到 最晚打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('down', $checkin);
}
}
}
return null;
}
/**

View File

@ -76,7 +76,7 @@ class WebSocketDialog extends AbstractModel
*/
public function getDialogList($userid, $updated = "", $deleted = "")
{
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('u.userid', $userid);
if ($updated) {
@ -193,6 +193,7 @@ class WebSocketDialog extends AbstractModel
$this->has_image = $msgBuilder->clone()->whereMtype('image')->exists();
$this->has_file = $msgBuilder->clone()->whereMtype('file')->exists();
$this->has_link = $msgBuilder->clone()->whereLink(1)->exists();
$this->has_todo = $msgBuilder->clone()->where('todo', '>', 0)->exists();
}
return $this;
}
@ -389,7 +390,7 @@ class WebSocketDialog extends AbstractModel
case 'all':
throw new ApiException('当前会话全员禁言');
case 'user':
if (!User::find($userid)?->checkAdmin()) {
if (!User::find($userid)?->isAdmin()) {
throw new ApiException('当前会话禁言');
}
}
@ -555,7 +556,7 @@ class WebSocketDialog extends AbstractModel
return $dialogUser;
}
if ($receiver > 0 && $user->isTemp()) {
throw new ApiException('无法发起会话');
throw new ApiException('无法发起会话,请联系管理员。');
}
return AbstractModel::transaction(function () use ($receiver, $user) {
$dialog = self::createInstance([

View File

@ -641,7 +641,7 @@ class WebSocketDialogMsg extends AbstractModel
}
// 其他网络图片
$imageSaveLocal = Base::settingFind("system", "image_save_local");
preg_match_all("/<img[^>]*?src=([\"'])(.*?\.(png|jpg|jpeg|webp|gif))\\1[^>]*?>/is", $text, $matchs);
preg_match_all("/<img[^>]*?src=([\"'])(.*?(png|jpg|jpeg|webp|gif).*?)\\1[^>]*?>/is", $text, $matchs);
foreach ($matchs[2] as $key => $str) {
if ($imageSaveLocal === 'close') {
$imageSize = getimagesize($str);
@ -770,18 +770,19 @@ class WebSocketDialogMsg extends AbstractModel
* 发送消息、修改消息
* @param string $action 动作
* - reply-98回复消息ID=98
* - update-99更新消息ID=99
* - update-99更新消息ID=99(标记修改)
* - change-99更新消息ID=99(不标记修改)
* @param int $dialog_id 会话ID 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统
* @param int|null $sender 发送的会员ID默认自己0为系统
* @param bool $push_self 推送-是否推给自己
* @param bool $push_retry 推送-失败后重试1次有时候在事务里执行数据还没生成时会出现找不到消息的情况
* @param bool|null $push_silence 推送-静默
* - type = [text|file|record|meeting] 默认为false
* @return array
*/
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = 0, $push_self = false, $push_retry = false, $push_silence = null)
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null)
{
$link = 0;
$mtype = $type;
@ -809,15 +810,23 @@ class WebSocketDialogMsg extends AbstractModel
}
//
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
$change_id = preg_match("/^change-(\d+)$/", $action, $match) ? $match[1] : 0;
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
$sender = $sender ?: User::userid();
$sender = $sender === null ? User::userid() : $sender;
//
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
throw new ApiException('获取会话失败');
}
$dialog->checkMute($sender);
if ($sender > 0) {
$dialog->checkMute($sender);
}
//
$modify = 1;
if ($change_id) {
$modify = 0;
$update_id = $change_id;
}
if ($update_id) {
// 修改
$dialogMsg = self::whereId($update_id)->whereDialogId($dialog_id)->first();
@ -835,12 +844,14 @@ class WebSocketDialogMsg extends AbstractModel
'mtype' => $mtype,
'link' => $link,
'msg' => $msg,
'modify' => 1,
'modify' => $modify,
];
$dialogMsg->updateInstance($updateData);
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
//
$dialogMsg->msgJoinGroup($dialog);
//
$dialog->pushMsg('update', array_merge($updateData, [
'id' => $dialogMsg->id
]));
@ -887,4 +898,42 @@ class WebSocketDialogMsg extends AbstractModel
return Base::retSuccess('发送成功', $dialogMsg);
}
}
/**
* 将被@的人加入群
* @param WebSocketDialog $dialog 对话
* @return array
*/
public function msgJoinGroup(WebSocketDialog $dialog)
{
$updateds = [];
$silences = [];
foreach ($dialog->dialogUser as $dialogUser) {
$updateds[$dialogUser->userid] = $dialogUser->updated_at;
$silences[$dialogUser->userid] = $dialogUser->silence;
}
$userids = array_keys($silences);
// 提及会员
$mentions = [];
if ($this->type === 'text') {
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $this->msg['text'], $matchs);
if ($matchs) {
$mentions = array_values(array_filter(array_unique($matchs[1])));
}
}
// 将会话以外的成员加入会话内
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
if ($dialog->type === 'group' && in_array($dialog->owner_id, [0, $this->userid])) {
$dialog->joinGroup($diffids, $this->userid);
$dialog->pushMsg("groupJoin", null, $diffids);
$userids = array_values(array_unique(array_merge($mentions, $userids)));
}
}
return compact('updateds', 'silences', 'userids', 'mentions');
}
}

View File

@ -125,9 +125,9 @@ class Base
* @param $min
* @return bool
*/
public static function judgeClientVersion($min)
public static function judgeClientVersion($min, $clientVersion = null)
{
return !version_compare(Base::getClientVersion(), $min, '<');
return !version_compare($clientVersion ?: Base::getClientVersion(), $min, '<');
}
/**

View File

@ -36,6 +36,7 @@ class AutoArchivedTask extends AbstractTask
->whereNull('archived_at')
->take(100)
->get();
/** @var ProjectTask $task */
foreach ($taskLists AS $task) {
$task->archivedTask(Carbon::now(), true);
}

View File

@ -23,12 +23,16 @@ class BotReceiveMsgTask extends AbstractTask
{
protected $userid;
protected $msgId;
protected $mention;
protected $client = [];
public function __construct($userid, $msgId)
public function __construct($userid, $msgId, $mention, $client = [])
{
parent::__construct(...func_get_args());
$this->userid = $userid;
$this->msgId = $msgId;
$this->mention = $mention;
$this->client = is_array($client) ? $client : [];
}
public function start()
@ -42,14 +46,6 @@ class BotReceiveMsgTask extends AbstractTask
return;
}
$msg->readSuccess($botUser->userid);
//
$dialog = WebSocketDialog::find($msg->dialog_id);
if (empty($dialog)) {
return;
}
if ($dialog->type !== 'user') {
return;
}
$this->botManagerReceive($msg, $botUser);
}
@ -70,11 +66,28 @@ class BotReceiveMsgTask extends AbstractTask
return;
}
$original = $msg->msg['text'];
if ($this->mention) {
$original = preg_replace("/<span class=\"mention user\" data-id=\"(\d+)\">(.*?)<\/span>/", "", $original);
}
if (preg_match("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
$command = $match[2];
} else {
$command = trim(strip_tags($original));
}
//
$dialog = WebSocketDialog::find($msg->dialog_id);
if (empty($dialog)) {
return;
}
// 推送Webhook
if ($command
&& !str_starts_with($command, '/')
&& ($dialog->type === 'user' || $this->mention)) {
$this->botManagerWebhook($command, $msg, $botUser, $dialog);
}
if ($dialog->type !== 'user') {
return;
}
// 签到机器人
if ($botUser->email === 'check-in@bot.system') {
$text = UserBot::checkinBotQuickMsg($command, $msg->userid);
@ -300,7 +313,7 @@ class BotReceiveMsgTask extends AbstractTask
$nameKey = $isManager ? $array[2] : $array[1];
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$nameKey}%")
->where('u.userid', $data->userid)
@ -337,24 +350,127 @@ class BotReceiveMsgTask extends AbstractTask
$text = preg_replace("/^\x20+/", "", $text);
$text = preg_replace("/\n\x20+/", "\n", $text);
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
}
}
/**
* 机器人处理 Webhook
* @param string $command
* @param WebSocketDialogMsg $msg
* @param User $botUser
* @param WebSocketDialog $dialog
* @return void
*/
private function botManagerWebhook(string $command, WebSocketDialogMsg $msg, User $botUser, WebSocketDialog $dialog)
{
$serverUrl = 'http://' . env('APP_IPPR') . '.3';
$userBot = null;
$extras = [];
$error = null;
switch ($botUser->email) {
// ChatGPT 机器人
case 'ai-openai@bot.system':
$setting = Base::setting('aibotSetting');
$webhookUrl = "{$serverUrl}/ai/openai/send";
$extras = [
'openai_key' => $setting['openai_key'],
'openai_agency' => $setting['openai_agency'],
'server_url' => $serverUrl,
];
if (empty($extras['openai_key'])) {
$error = 'Robot disabled.';
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
$error = 'The client version is low (required version ≥ v0.29.11).';
}
break;
// Claude 机器人
case 'ai-claude@bot.system':
$setting = Base::setting('aibotSetting');
$webhookUrl = "{$serverUrl}/ai/claude/send";
$extras = [
'claude_token' => $setting['claude_token'],
'claude_agency' => $setting['claude_agency'],
'server_url' => $serverUrl,
];
if (empty($extras['claude_token'])) {
$error = 'Robot disabled.';
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
$error = 'The client version is low (required version ≥ v0.29.11).';
}
break;
// Wenxin 机器人
case 'ai-wenxin@bot.system':
$setting = Base::setting('aibotSetting');
$webhookUrl = "{$serverUrl}/ai/wenxin/send";
$extras = [
'wenxin_key' => $setting['wenxin_key'],
'wenxin_secret' => $setting['wenxin_secret'],
'wenxin_model' => $setting['wenxin_model'],
'server_url' => $serverUrl,
];
if (empty($extras['wenxin_key'])) {
$error = 'Robot disabled.';
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
$error = 'The client version is low (required version ≥ v0.29.12).';
}
break;
// QianWen 机器人
case 'ai-qianwen@bot.system':
$setting = Base::setting('aibotSetting');
$webhookUrl = "{$serverUrl}/ai/qianwen/send";
$extras = [
'qianwen_key' => $setting['qianwen_key'],
'qianwen_model' => $setting['qianwen_model'],
'server_url' => $serverUrl,
];
if (empty($extras['qianwen_key'])) {
$error = 'Robot disabled.';
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
$error = 'The client version is low (required version ≥ v0.29.12).';
}
break;
// 其他机器人
default:
$userBot = UserBot::whereBotId($botUser->userid)->first();
$webhookUrl = $userBot?->webhook_url;
break;
}
if ($error) {
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $error], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
}
// 推送Webhook
if ($command) {
$userBot = UserBot::whereBotId($botUser->userid)->first();
if ($userBot && preg_match("/^https*:\/\//", $userBot->webhook_url)) {
Ihttp::ihttp_post($userBot->webhook_url, [
'text' => $command,
'token' => User::generateToken($botUser),
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'msg_uid' => $msg->userid,
'bot_uid' => $botUser->userid,
'version' => Base::getVersion(),
], 10);
if (!preg_match("/^https*:\/\//", $webhookUrl)) {
return;
}
//
try {
$res = Ihttp::ihttp_post($webhookUrl, [
'text' => $command,
'token' => User::generateToken($botUser),
'dialog_id' => $dialog->id,
'dialog_type' => $dialog->type,
'msg_id' => $msg->id,
'msg_uid' => $msg->userid,
'mention' => $this->mention ? 1 : 0,
'bot_uid' => $botUser->userid,
'version' => Base::getVersion(),
'extras' => Base::array2json($extras)
], 10);
if ($userBot) {
$userBot->webhook_num++;
$userBot->save();
}
if($res['data'] && $data = json_decode($res['data'])){
if($data['code'] != 200 && $data['message']){
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $res['data']['message']], $botUser->userid, false, false, true);
}
}
} catch (\Throwable $th) {
//throw $th;
}
}

View File

@ -25,6 +25,7 @@ class WebSocketDialogMsgTask extends AbstractTask
protected $ignoreFd;
protected $msgNotExistRetry = false; // 推送失败后重试
protected $silence = false; // 静默推送前端不通知、App不推送如果会话设置了免打扰则强制静默
protected $client = []; // 客户端信息(版本、语言、平台)
protected $endPush = [];
protected $endArray = [];
@ -38,6 +39,11 @@ class WebSocketDialogMsgTask extends AbstractTask
parent::__construct(...func_get_args());
$this->id = $id;
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
$this->client = [
'version' => Base::headerOrInput('version'),
'language' => Base::headerOrInput('language'),
'platform' => Base::headerOrInput('platform'),
];
}
/**
@ -85,33 +91,13 @@ class WebSocketDialogMsgTask extends AbstractTask
if (empty($dialog)) {
return;
}
$updateds = [];
$silences = [];
foreach ($dialog->dialogUser as $dialogUser) {
$updateds[$dialogUser->userid] = $dialogUser->updated_at;
$silences[$dialogUser->userid] = $dialogUser->silence;
}
$userids = array_keys($silences);
// 提及会员
$mentions = [];
if ($msg->type === 'text') {
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $msg->msg['text'], $matchs);
if ($matchs) {
$mentions = array_values(array_filter(array_unique($matchs[1])));
}
}
// 将会话以外的成员加入会话内
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
if ($dialog->type === 'group' && in_array($dialog->owner_id, [0, $msg->userid])) {
$dialog->joinGroup($diffids, $msg->userid);
$dialog->pushMsg("groupJoin", null, $diffids);
$userids = array_values(array_unique(array_merge($mentions, $userids)));
}
}
$msgJoinGroupResult = $msg->msgJoinGroup($dialog);
$updateds = $msgJoinGroupResult['updateds'];
$silences = $msgJoinGroupResult['silences'];
$userids = $msgJoinGroupResult['userids'];
$mentions = $msgJoinGroupResult['mentions'];
// 推送目标①:会话成员/群成员
$array = [];
@ -144,13 +130,28 @@ class WebSocketDialogMsgTask extends AbstractTask
// 机器人收到消处理
$botUser = User::whereUserid($userid)->whereBot(1)->first();
if ($botUser) {
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id);
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id, $mention, $this->client);
}
}
}
// 更新已发送数量
$msg->send = WebSocketDialogMsgRead::whereMsgId($msg->id)->count();
$msg->save();
// 没有接收人时通知发送人已读
if ($msg->send === 0) {
PushTask::push([
'userid' => $msg->userid,
'msg' => [
'type' => 'dialog',
'mode' => 'readed',
'data' => [
'id' => $msg->id,
'read' => $msg->read,
'percentage' => $msg->percentage,
],
]
]);
}
// 开始推送消息
$umengUserid = [];
foreach ($array as $item) {

View File

@ -67,7 +67,7 @@ return [
|
*/
'timezone' => 'PRC',
'timezone' => env('TIMEZONE', 'PRC'),
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,32 @@
<?php
use App\Models\User;
use App\Models\WebSocketDialog;
use Carbon\Carbon;
use Illuminate\Database\Migrations\Migration;
class CreateDefaultRobot extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (WebSocketDialog::count() > 0) {
User::botGetOrCreate('ai-openai');
User::botGetOrCreate('ai-claude');
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -0,0 +1,29 @@
<?php
use App\Module\Base;
use Illuminate\Database\Migrations\Migration;
class SettingCheckinModesValue extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$setting = Base::setting('checkinSetting');
$setting['modes'] = ['auto'];
Base::setting('checkinSetting', $setting);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectFlowItemsAddColumnid extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_flow_items', function (Blueprint $table) {
if (!Schema::hasColumn('project_flow_items', 'columnid')) {
$table->bigInteger('columnid')->nullable()->default(0)->after('userlimit')->comment('对应的项目列表');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_flow_items', function (Blueprint $table) {
$table->dropColumn("columnid");
});
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogUsersColor extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_users', 'color')) {
$table->string('color', 20)->nullable()->default('')->after('important')->comment('颜色');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("color");
});
}
}

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
class repairProjectTasksIsAllVisible extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// 修复子任务可见性字段为null的数据
if (Schema::hasTable('project_tasks')) {
$prefix = DB::getConfig('prefix');
DB::update("
UPDATE {$prefix}project_tasks
SET is_all_visible = 1
WHERE is_all_visible is null AND parent_id = 0;
");
DB::update("
UPDATE {$prefix}project_tasks t1
JOIN {$prefix}project_tasks t2 ON t1.parent_id = t2.id
SET t1.is_all_visible = t2.is_all_visible
WHERE t1.is_all_visible is null AND t1.parent_id > 0;
");
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -232,6 +232,8 @@ class WebSocketDialogsTableSeeder extends Seeder
$dialog->save();
}
}
User::botGetOrCreate('ai-openai');
User::botGetOrCreate('ai-claude');
$userids = User::whereBot(0)->whereNull('disable_at')->pluck('userid')->toArray();
WebSocketDialog::createGroup("全体成员 All members", $userids, 'all');

View File

@ -3,7 +3,7 @@ version: '3'
services:
php:
container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.0.rc10"
image: "kuaifan/php:swoole-8.0.rc14"
shm_size: "2gb"
ulimits:
core:
@ -49,6 +49,7 @@ services:
- drawio-webapp
- drawio-export
- minder
- ai
restart: unless-stopped
redis:
@ -101,8 +102,7 @@ services:
fileview:
container_name: "dootask-fileview-${APP_ID}"
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC20"
platform: linux/amd64
image: "kuaifan/fileview:4.2.0-SNAPSHOT-RC22"
environment:
KK_CONTEXT_PATH: "/fileview"
KK_OFFICE_PREVIEW_SWITCH_DISABLED: true
@ -131,8 +131,7 @@ services:
drawio-export:
container_name: "dootask-drawio-export-${APP_ID}"
image: "kuaifan/export-server:latest"
platform: linux/amd64
image: "kuaifan/export-server:0.0.1"
networks:
extnetwork:
ipv4_address: "${APP_IPPR}.9"
@ -150,9 +149,9 @@ services:
approve:
container_name: "dootask-approve-${APP_ID}"
image: "hitosea2020/go-approve:latest"
image: "hitosea2020/go-approve:0.1.4"
environment:
TZ: "Asia/Shanghai"
TZ: "${TIMEZONE:-PRC}"
MYSQL_HOST: "${DB_HOST}"
MYSQL_PORT: "${DB_PORT}"
MYSQL_DBNAME: "${DB_DATABASE}"
@ -185,6 +184,13 @@ services:
- mariadb
restart: unless-stopped
ai:
container_name: "dootask-ai-${APP_ID}"
image: "kuaifan/dooai:0.0.4"
networks:
extnetwork:
ipv4_address: "${APP_IPPR}.12"
restart: unless-stopped
networks:
extnetwork:

View File

@ -189,6 +189,14 @@ server {
proxy_pass http://okr:5566/apps/okr/;
}
# AI
location /ai/ {
proxy_http_version 1.1;
proxy_set_header Scheme $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://ai:8881/;
}
}
include /etc/nginx/conf.d/conf.d/*.conf;

168
electron/electron-menu.js vendored Normal file
View File

@ -0,0 +1,168 @@
const {
clipboard,
nativeImage,
Menu,
MenuItem,
dialog,
shell,
} = require('electron')
const fs = require('fs')
const url = require('url')
const request = require("request");
const MAILTO_PREFIX = "mailto:";
const PERMITTED_URL_SCHEMES = ["http:", "https:", MAILTO_PREFIX];
const electronMenu = {
language: {
openInBrowser: "在浏览器中打开",
saveImageAs: "图片存储为...",
copyImage: "复制图片",
copyEmailAddress: "复制电子邮件地址",
copyLinkAddress: "复制链接地址",
copyImageAddress: "复制图片地址",
failedToSaveImage: "图片保存失败",
theImageFailedToSave: "图片无法保存",
},
setLanguage(language) {
this.language = Object.assign(this.language, language);
},
safeOpenURL(target) {
const parsedUrl = url.parse(target);
if (PERMITTED_URL_SCHEMES.includes(parsedUrl.protocol)) {
const newTarget = url.format(parsedUrl);
shell.openExternal(newTarget).then(r => {
});
}
},
isBlob(url) {
return url.startsWith("blob:");
},
isDataUrl(url) {
return url.startsWith("data:");
},
isBlobOrDataUrl(url) {
return electronMenu.isBlob(url) || electronMenu.isDataUrl(url);
},
async saveImageAs(url, params) {
const targetFileName = params.suggestedFilename || params.altText || "image.png";
const {filePath} = await dialog.showSaveDialog({
defaultPath: targetFileName,
});
if (!filePath) return; // user cancelled dialog
try {
if (electronMenu.isBlobOrDataUrl(url)) {
await electronMenu.writeNativeImage(filePath, nativeImage.createFromDataURL(url));
} else {
const writeStream = fs.createWriteStream(filePath)
const readStream = request(url)
readStream.pipe(writeStream);
readStream.on('end', function (response) {
writeStream.end();
});
}
} catch (err) {
await dialog.showMessageBox({
type: "error",
title: electronMenu.language.failedToSaveImage,
message: electronMenu.language.theImageFailedToSave,
});
}
},
writeNativeImage(filePath, img) {
switch (filePath.split(".").pop()?.toLowerCase()) {
case "jpg":
case "jpeg":
return fs.promises.writeFile(filePath, img.toJPEG(100));
case "bmp":
return fs.promises.writeFile(filePath, img.toBitmap());
case "png":
default:
return fs.promises.writeFile(filePath, img.toPNG());
}
},
webContentsMenu(webContents) {
webContents.on("context-menu", function (e, params) {
if (params.linkURL || params.srcURL) {
const url = params.linkURL || params.srcURL;
const popupMenu = new Menu();
if (!electronMenu.isBlobOrDataUrl(url)) {
popupMenu.append(
new MenuItem({
label: electronMenu.language.openInBrowser,
accelerator: "o",
click() {
electronMenu.safeOpenURL(url);
},
}),
);
}
if (params.hasImageContents) {
if (!electronMenu.isBlob(url)) {
popupMenu.append(
new MenuItem({
label: electronMenu.language.saveImageAs,
accelerator: "s",
click: async function () {
await electronMenu.saveImageAs(url, params);
},
}),
);
}
popupMenu.append(
new MenuItem({
label: electronMenu.language.copyImage,
accelerator: "c",
click() {
webContents.copyImageAt(params.x, params.y);
},
}),
);
}
if (!electronMenu.isBlobOrDataUrl(url)) {
if (url.startsWith(MAILTO_PREFIX)) {
popupMenu.append(
new MenuItem({
label: electronMenu.language.copyEmailAddress,
accelerator: "a",
click() {
clipboard.writeText(url.substring(MAILTO_PREFIX.length));
},
}),
);
} else {
popupMenu.append(
new MenuItem({
label: params.hasImageContents ? electronMenu.language.copyImageAddress : electronMenu.language.copyLinkAddress,
accelerator: "a",
click() {
clipboard.writeText(url);
},
}),
);
}
}
if (popupMenu.items.length > 0) {
popupMenu.popup({});
e.preventDefault();
}
}
})
}
}
module.exports = electronMenu;

93
electron/electron.js vendored
View File

@ -11,6 +11,7 @@ const crc = require('crc');
const zlib = require('zlib');
const utils = require('./utils');
const config = require('./package.json');
const electronMenu = require("./electron-menu");
const spawn = require("child_process").spawn;
const isMac = process.platform === 'darwin'
@ -58,6 +59,7 @@ function createMainWindow() {
openExternal(url)
return {action: 'deny'}
})
electronMenu.webContentsMenu(mainWindow.webContents)
if (devloadUrl) {
mainWindow.loadURL(devloadUrl).then(_ => {
@ -77,7 +79,7 @@ function createMainWindow() {
mainWindow.on('close', event => {
if (!willQuitApp) {
utils.onBeforeUnload(event).then(() => {
utils.onBeforeUnload(event, mainWindow).then(() => {
if (process.platform === 'win32') {
mainWindow.hide()
} else if (process.platform === 'darwin') {
@ -137,8 +139,8 @@ function createSubWindow(args) {
browser.on('close', event => {
if (!willQuitApp) {
utils.onBeforeUnload(event).then(() => {
event.sender.destroy()
utils.onBeforeUnload(event, browser).then(() => {
browser.destroy()
})
}
})
@ -158,20 +160,58 @@ function createSubWindow(args) {
openExternal(url)
return {action: 'deny'}
})
electronMenu.webContentsMenu(browser.webContents)
const hash = args.hash || args.path;
if (devloadUrl) {
browser.loadURL(devloadUrl + '#' + (args.hash || args.path)).then(_ => {
browser.loadURL(devloadUrl + '#' + hash).then(_ => {
})
} else {
browser.loadFile('./public/index.html', {
hash: args.hash || args.path
hash
}).then(_ => {
})
}
}
/**
* 更新子窗口
* @param browser
* @param args
*/
function updateSubWindow(browser, args) {
if (!args) {
return;
}
if (!utils.isJson(args)) {
args = {path: args, name: null}
}
const hash = args.hash || args.path;
if (hash) {
if (devloadUrl) {
browser.loadURL(devloadUrl + '#' + hash).then(_ => {
})
} else {
browser.loadFile('./public/index.html', {
hash
}).then(_ => {
})
}
}
if (args.name) {
const er = subWindow.find(item => item.browser == browser);
if (er) {
er.name = args.name;
}
}
}
const getTheLock = app.requestSingleInstanceLock()
if (!getTheLock) {
app.quit()
@ -257,6 +297,17 @@ app.on('browser-window-focus', () => {
}
})
/**
* 设置菜单语言包
* @param args {path}
*/
ipcMain.on('setMenuLanguage', (event, args) => {
if (utils.isJson(args)) {
electronMenu.setLanguage(args)
}
event.returnValue = "ok"
})
/**
* 打开文件
* @param args {path}
@ -283,6 +334,16 @@ ipcMain.on('windowRouter', (event, args) => {
event.returnValue = "ok"
})
/**
* 更新路由窗口
* @param args {?name, ?path} // name: 不是要更改的窗口名,是要把窗口名改成什么, path: 地址
*/
ipcMain.on('updateRouter', (event, args) => {
const browser = BrowserWindow.fromWebContents(event.sender);
updateSubWindow(browser, args)
event.returnValue = "ok"
})
/**
* 隐藏窗口macwin隐藏其他关闭
*/
@ -475,6 +536,28 @@ ipcMain.on('copyBase64Image', (event, args) => {
event.returnValue = "ok"
})
/**
* 复制图片根据坐标
* @param args
*/
ipcMain.on('copyImageAt', (event, args) => {
try {
event.sender.copyImageAt(args.x, args.y);
} catch (e) {
// log.error(e)
}
event.returnValue = "ok"
})
/**
* 保存图片
* @param args
*/
ipcMain.on('saveImageAt', async (event, args) => {
await electronMenu.saveImageAs(args.url, args.params)
event.returnValue = "ok"
})
/**
* 绑定截图快捷键
* @param args

View File

@ -26,14 +26,14 @@
"url": "https://github.com/kuaifan/dootask.git"
},
"devDependencies": {
"@electron-forge/cli": "^6.1.1",
"@electron-forge/maker-deb": "^6.1.1",
"@electron-forge/maker-rpm": "^6.1.1",
"@electron-forge/maker-squirrel": "^6.1.1",
"@electron-forge/maker-zip": "^6.1.1",
"@electron-forge/cli": "^6.3.0",
"@electron-forge/maker-deb": "^6.3.0",
"@electron-forge/maker-rpm": "^6.3.0",
"@electron-forge/maker-squirrel": "^6.3.0",
"@electron-forge/maker-zip": "^6.3.0",
"dotenv": "^16.0.3",
"electron": "^24.5.0",
"electron-builder": "^23.6.0",
"electron": "^25.5.0",
"electron-builder": "^24.6.3",
"electron-notarize": "^1.2.2",
"form-data": "^4.0.0",
"ora": "^4.1.1"
@ -44,9 +44,10 @@
"electron-log": "^4.4.8",
"electron-screenshots-tool": "^1.0.4",
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "^5.3.0",
"electron-updater": "^6.1.1",
"fs-extra": "^10.1.0",
"pdf-lib": "^1.17.1"
"pdf-lib": "^1.17.1",
"request": "^2.88.2"
},
"trayIcon": {
"dev": {
@ -67,6 +68,7 @@
},
"files": [
"public/**/*",
"electron-menu.js",
"electron-preload.js",
"electron.js",
"utils.js"

5
electron/utils.js vendored
View File

@ -291,12 +291,11 @@ module.exports = {
*/
onBeforeUnload(event, app) {
return new Promise(resolve => {
const sender = event.sender
const contents = sender.webContents
const contents = app.webContents
if (contents != null) {
contents.executeJavaScript('if(typeof window.__onBeforeUnload === \'function\'){window.__onBeforeUnload()}', true).then(options => {
if (this.isJson(options)) {
let choice = dialog.showMessageBoxSync(sender, options)
let choice = dialog.showMessageBoxSync(app, options)
if (choice === 1) {
contents.executeJavaScript('if(typeof window.__removeBeforeUnload === \'function\'){window.__removeBeforeUnload()}', true).catch(() => {});
resolve()

View File

@ -391,7 +391,7 @@ error
个人项目
注册时系统自动创建项目,你可以自由删除。
无法发起会话
无法发起会话,请联系管理员。
无法创建群组
无法查看联系电话
@ -410,6 +410,7 @@ error
匿名消息
系统管理员
我要签到
我要打卡
关键词不能为空
@ -470,3 +471,4 @@ Api接口文档
找不到符合条件的子节点
文件
未完成

View File

@ -95,7 +95,7 @@
仅限项目负责人操作
任务描述,回车创建
你好,扫码确认登录
你确定要登出系统?
你确定要登出系统
你确认领取任务吗?
列表名称,回车创建
同步修改子任务时间
@ -1251,4 +1251,121 @@ Markdown 格式发送
会议组件加载失败!
OKR管理
OKR结果分析
OKR结果分析
计划时间冲突提示
忽略并继续
你确定要清除缓存吗?
可见性选项
保持
保持:任务详情页可见性选项保持显示。
自动:默认值情况下显示在合并项目,设置时保持显示。
修改子任务时间
请输入修改备注
任务默认时间
请选择默认时间
添加任务计划时间默认时分。
导出审批数据
审批类型
导出类型
未完成
AI机器人
任务相关
请填写名称!
访问OpenAI网站查看
使用代理
支持 http 或 socks 代理
例如http://proxy.com 或 socks5://proxy.com
查看 Cookie 中的 sessionKey 便是
暂不支持拖拽文件夹。
暂不支持粘贴文件夹。
暂不支持拖拽文件夹,请手动上传文件夹。
暂不支持粘贴文件夹,请手动上传文件夹。
最近
暂无结果
新项目负责人
恢复帐号(已离职)
你确定恢复已离职帐号【(*)】吗?(注:此操作仅恢复帐号状态,无法恢复操作离职时移交的数据)
流转到【(*)】时改变任务负责人为状态负责人(并保留操作状态的人员),原本的任务负责人移至协助人员。
删除文件夹
你确定要删除【(*)】文件夹吗?
请输入新的密码
请输入新的邮箱((*)
截图快捷键
新项目负责人
暂无结果
最近
加班
离职申请
录用申请
修改的内容尚未保存,确定要放弃修改吗?
获取方式
查看说明
正在修改系统机器人:(*)
复制选择
复制全部
返回首页
刷新链接
确认
还原归档
签到方式
手动签到
定位签到
详情看下文安装说明
通过在签到打卡机器人发送指令签到
通过在签到打卡机器人发送位置签到
状态设置
关联列表
选择关联列表
取消关联
流转到【(*)】时自动将任务移动至关联列表。
状态[(*)]设置错误,设置剔除模式时必须填写状态负责人
键盘设置
在浏览器中打开
图片存储为...
复制图片
复制电子邮件地址
复制链接地址
复制图片地址
图片保存失败
图片无法保存
事项
编辑描述
没有任何修改!
项目类型
团队项目
个人项目
全部项目
打开链接
汇报人
提交时间
应用
机器人设置
去聊天
返回
会议设置
我是一个人工智能助手,为用户提供问题解答和指导。我没有具体的身份,只是一个程序。您有什么问题可以问我哦?
我是Claude,一个由Anthropic公司创造出来的AI助手机器人。我的工作是帮助人类,与人对话并给出解答。
我是文心一言英文名是ERNIE Bot。我能够与人对话互动回答问题协助创作高效便捷地帮助人们获取信息、知识和灵感。
我是达摩院自主研发的超大规模语言模型,能够回答问题、创作文字,还能表达观点、撰写代码。
机器人暂未开启
创建一个全新的会议视频会议,与会者可以在实时中进行面对面的视听交流。通过视频会议平台,参与者可以分享屏幕、共享文档,并与其他与会人员进行讨论和协。
加入视频会议,参与已经创建的会议,在会议过程中与其他参会人员进行远程实时视听交流和协作。
新会议
新建会议
LDAP设置
邮件管理
APP推送

View File

@ -12004,11 +12004,11 @@
"key": "待办",
"zh": "",
"zh-CHT": "待辦",
"en": "To be done",
"en": "Todo",
"ko": "할 일",
"ja": "待办",
"de": "Unbesiegbar",
"fr": "Le pense-bête",
"de": "Todo",
"fr": "Todo",
"id": "Harus"
},
{
@ -12018,7 +12018,7 @@
"en": "Required",
"ko": "필수",
"ja": "必ず埋め",
"de": "Sie müssen sie füllen.",
"de": "Required",
"fr": "Quil remplit",
"id": "Akan mengisi"
},
@ -16905,5 +16905,907 @@
"de": "Wenn die ergebnisse umgekehrt sind",
"fr": "Analyse des résultats OkR",
"id": "Analisis hasil OkR"
},
{
"key": "以下人员已存在任务",
"zh": "",
"zh-CHT": "以下人員已存在任務",
"en": "The following personnel already have tasks",
"ko": "다음 구성원에 대한 작업이 이미 있습니다",
"ja": "次の者には任務があります",
"de": "Die folgenden personen haben den auftrag erledigt",
"fr": "Les personnes suivantes ont déjà une mission",
"id": "Yang berikut ini ada tugas"
},
{
"key": "你确定要登出系统吗?",
"zh": "",
"zh-CHT": "你確定要登出系統嗎?",
"en": "You sure you want to log out of the system?",
"ko": "시스템에서 로그아웃하시겠습니까?",
"ja": "システムをログアウトするのは確かですか?",
"de": "Willst du dich sicher ins system einloggen?",
"fr": "Êtes-vous sûr de vouloir vous déconnecter du système?",
"id": "Kau yakin ingin keluar dari sistem?"
},
{
"key": "计划时间冲突提示",
"zh": "",
"zh-CHT": "計劃時間衝突提示",
"en": "Schedule time conflict prompt",
"ko": "예약된 시간 알림 알림",
"ja": "予定時間の衝突のヒントです",
"de": "Geplante zeitkollisionen sind unvermeidlich",
"fr": "Conseils de conflit de temps de planification",
"id": "Merencanakan benturan waktu"
},
{
"key": "忽略并继续",
"zh": "",
"zh-CHT": "忽略並繼續",
"en": "Ignore and continue",
"ko": "무시하고 계속 진행",
"ja": "無視し続けます",
"de": "Ignorieren und weitermachen.",
"fr": "Ignorer et continuer",
"id": "Abaikan dan lanjutkan"
},
{
"key": "你确定要清除缓存吗?",
"zh": "",
"zh-CHT": "你確定要清除緩存嗎?",
"en": "Are you sure you want to clear the cache?",
"ko": "정말로 캐시를 지우시겠습니까?",
"ja": "確かにキャッシュを消去しますか?",
"de": "Bist du dir sicher, dass du es löschen willst?",
"fr": "Êtes-vous sûr de vouloir effacer votre cache?",
"id": "Apakah anda yakin ingin menghapus cache?"
},
{
"key": "可见性选项",
"zh": "",
"zh-CHT": "可見性選項",
"en": "Visibility option",
"ko": "표시 옵션",
"ja": "可視的選択肢です",
"de": "Sichtbaren optionen.",
"fr": "Options de visibilité",
"id": "Pilihan yang terlihat"
},
{
"key": "保持",
"zh": "",
"zh-CHT": "保持",
"en": "Hold",
"ko": "유지",
"ja": "保持します。",
"de": "Halten! Und halten!",
"fr": "Maintenir",
"id": "Tetap"
},
{
"key": "保持:任务详情页可见性选项保持显示。",
"zh": "",
"zh-CHT": "保持:任務詳情頁可見性選項保持顯示。",
"en": "Hold: The task details page visibility option remains displayed.",
"ko": "유지:작업 정보 페이지 표시 옵션은 계속 표시됩니다.",
"ja": "維持:タスク詳細ページの可視性オプションを表示したままにします。",
"de": "Beibehaltung: die option sollte im detail beschrieben werden. Die folgenden optionen werden angezeigt",
"fr": "Garder: loption de visibilité de la page de détails de la tâche reste affichée.",
"id": "Menjaga: detail misi halaman visibilitas pilihan tetap ditampilkan."
},
{
"key": "自动:默认值情况下显示在合并项目,设置时保持显示。",
"zh": "",
"zh-CHT": "自動:默認值情況下顯示在合併項目,設置時保持顯示。",
"en": "Automatic: The default value is displayed when merging items, and the setting remains displayed.",
"ko": "자동:합칠 때 기본값으로 표시되며, 설정하는 동안 표시되지 않습니다.",
"ja": "自働:デフォルトの場合はマージ項目を表示し、設定時は表示のままにします。",
"de": "Automatische anzeige (standard) zeigt die einstellungen im verlauf einer fusion an",
"fr": "Automatique: par défaut, les éléments fusionnés sont affichés.",
"id": "Otomatis: standar ditampilkan pada item penggabungan ketika anda mengatur."
},
{
"key": "请输入修改备注",
"zh": "",
"zh-CHT": "請輸入修改備註",
"en": "Please enter modification remarks",
"ko": "수정 비고를 입력하십시오",
"ja": "修正注記をお願いします。",
"de": "Bitte geben sie die anmerkung ein",
"fr": "Veuillez entrer une remarque modifiée",
"id": "Masukkan komentar revisi"
},
{
"key": "任务默认时间",
"zh": "",
"zh-CHT": "任務默認時間",
"en": "Task default time",
"ko": "작업 기본 시간",
"ja": "タスクデフォルト時間です",
"de": "Standard-zeit für mission:",
"fr": "Tâche temps par défaut",
"id": "Waktu standar tugas"
},
{
"key": "请选择默认时间",
"zh": "",
"zh-CHT": "請選擇默認時間",
"en": "Please select the default time",
"ko": "기본 시간을 선택하십시오",
"ja": "デフォルト時間をお願いします。",
"de": "Bitte wählen sie die standard-zeit",
"fr": "Veuillez sélectionner lheure par défaut",
"id": "Silahkan pilih waktu standar"
},
{
"key": "添加任务计划时间默认时分。",
"zh": "",
"zh-CHT": "添加任務計劃時間默認時分。",
"en": "Add task schedule Time The default time.",
"ko": "기본 작업 스케줄 시간을 추가합니다.",
"ja": "タスクスケジュールのデフォルト時間を追加します。",
"de": "Festlegung der zielzeit standard-zeit hinzufügen",
"fr": "Ajouter le temps de planification des tâches par défaut.",
"id": "Tambahkan waktu perencanaan tugas ke default."
},
{
"key": "无法发起会话,请联系管理员。",
"zh": "",
"zh-CHT": "無法發起會話,請聯繫管理員。",
"en": "Unable to initiate a session. Please contact the administrator.",
"ko": "세션을 시작할 수 없습니다. 관리자에게 문의하십시오.",
"ja": "セッションが開始できませんので、管理人に連絡してください。",
"de": "Die sitzung kann nicht gestartet werden. Bitte sprechen sie mit dem systemadministrator",
"fr": "Impossible dinitier la session. Veuillez contacter ladministrateur.",
"id": "Tak dapat memulai sesi, hubungi administrator."
},
{
"key": "未完成",
"zh": "",
"zh-CHT": "未完成",
"en": "Uncompleted",
"ko": "완료하지 않음",
"ja": "未完成です",
"de": "Noch nicht ganz perfekt.",
"fr": "Non terminé non terminé",
"id": "Tidak lengkap"
},
{
"key": "导出审批数据",
"zh": "",
"zh-CHT": "導出審批數據",
"en": "Export approval data",
"ko": "승인 데이터 내보내기",
"ja": "決裁データを導き出します",
"de": "Exportiert die lizenzdaten",
"fr": "Exportation des données dapprobation",
"id": "Mengirimkan data persetujuan"
},
{
"key": "审批类型",
"zh": "",
"zh-CHT": "審批類型",
"en": "Approval type",
"ko": "승인 형식",
"ja": "承認タイプです",
"de": "Art des LKW?",
"fr": "Type dagrément",
"id": "Tipe persetujuan"
},
{
"key": "导出类型",
"zh": "",
"zh-CHT": "導出類型",
"en": "Derived type",
"ko": "형식 내보내기",
"ja": "導出型です",
"de": "Art des exportieren",
"fr": "Types dexport",
"id": "Tipe ekspor"
},
{
"key": "AI机器人",
"zh": "",
"zh-CHT": "AI機器人",
"en": "AI robot",
"ko": "Ai 로봇",
"ja": "AIロボットです",
"de": "AI roboter",
"fr": "Le robot AI",
"id": "Robot al"
},
{
"key": "任务相关",
"zh": "",
"zh-CHT": "任務相關",
"en": "Task correlation",
"ko": "작업 관련",
"ja": "任務関連です",
"de": "Es ist eine relevante aufgabe.",
"fr": "Lié à la tâche",
"id": "Tugas terkait"
},
{
"key": "请填写名称!",
"zh": "",
"zh-CHT": "請填寫名稱!",
"en": "Please fill in the name!",
"ko": "이름을 기입해 주세요!",
"ja": "名称をお願いします!",
"de": "Bitte geben sie einen namen ein!",
"fr": "Veuillez remplir le nom!",
"id": "Nama, silakan!"
},
{
"key": "访问OpenAI网站查看",
"zh": "",
"zh-CHT": "訪問OpenAI網站查看",
"en": "Visit the OpenAI website to see:",
"ko": "Openai 사이트를 방문하십시오:",
"ja": "OpenAIのウェブサイトをご覧ください。",
"de": "Besuch der website OpenAI:",
"fr": "Visitez le site OpenAI pour voir:",
"id": "Kunjungi tampilan situs OpenAI:"
},
{
"key": "使用代理",
"zh": "",
"zh-CHT": "使用代理",
"en": "Use agent",
"ko": "에이전트 사용하기",
"ja": "エージェントを使います",
"de": "Verwendung eines agenten",
"fr": "Utiliser un proxy",
"id": "Gunakan agen"
},
{
"key": "支持 http 或 socks 代理",
"zh": "",
"zh-CHT": "支持 http 或 socks 代理",
"en": "Supports http or socks proxy",
"ko": "Http 또는 socks 프록시를 지원한다",
"ja": "Httpやsocksエージェントをサポートしています",
"de": "Kauft die vertreter vom typ HTTP Oder würmchen",
"fr": "Support du proxy HTTP ou socks",
"id": "Yang mendukung HTTP atau agen socks"
},
{
"key": "例如http:\/\/proxy.com 或 socks5:\/\/proxy.com",
"zh": "",
"zh-CHT": "例如http:\/\/proxy.com 或 socks5:\/\/proxy.com",
"en": "For example, http:\/\/proxy.com or socks5:\/\/proxy.com",
"ko": "례를 들면 http:\/\/proxy.com 혹은 socks5\/\/proxy.com이다",
"ja": "<s:1>またはsocks5:\/\/proxy.comです",
"de": "Ein beispiel: http:\/\/proxyproxy.com, Oder socks5.com proxy.com",
"fr": "Par exemple: http:\/\/proxy.com ou socks5:\/\/proxy.com",
"id": "Sebagai contoh: http:\/\/proxycom. Atau socks5:\/proxycom"
},
{
"key": "查看 Cookie 中的 sessionKey 便是",
"zh": "",
"zh-CHT": "查看 Cookie 中的 sessionKey 便是",
"en": "Look at the sessionKey in the Cookie",
"ko": "쿠키의 sessionkey를 볼 수 있습니다",
"ja": "クッキーのsessionKeyを見るのです",
"de": "Ja, guck dir ein date in cookies arsch an",
"fr": "Voir la sessionKey dans les cookies est",
"id": "Periksa kunci sesi pada Cookie"
},
{
"key": "暂不支持拖拽文件夹。",
"zh": "",
"zh-CHT": "暫不支持拖拽文件夾。",
"en": "Drag-and-drop folders are not supported.",
"ko": "폴더 끌기는 지원되지 않습니다.",
"ja": "フォルダのドラッグ・ドロップには対応していません。",
"de": "Es wird niemandem helfen, den ordner zu ziehen",
"fr": "Les dossiers de glisser-déposer ne sont pas pris en charge.",
"id": "Tak akan menarik folder."
},
{
"key": "暂不支持粘贴文件夹。",
"zh": "",
"zh-CHT": "暫不支持粘貼文件夾。",
"en": "It does not support pasting folders.",
"ko": "폴더 붙여넣기는 지원되지 않습니다.",
"ja": "フォルダの貼り付けには対応していません。",
"de": "Tragt den ordner nicht ein",
"fr": "Les dossiers coller ne sont pas supportés pour le moment.",
"id": "Tak akan meminta penundaan folder."
},
{
"key": "暂不支持拖拽文件夹,请手动上传文件夹。",
"zh": "",
"zh-CHT": "暫不支持拖拽文件夾,請手動上傳文件夾。",
"en": "Dragging and dropping folders is not supported, please upload folders manually.",
"ko": "폴더를 끌어 올리는 것은 지원되지 않습니다. 폴더를 업로드하십시오.",
"ja": "フォルダをドラッグ&ドロップに対応していませんので、手動でフォルダをアップロードしてください。",
"de": "Ordner abschleppen unterstützt nicht; bitte diesen ordner manuell hochladen",
"fr": "Les dossiers de glisser-déposer ne sont pas pris en charge pour le moment. Veuillez télécharger les dossiers manuellement.",
"id": "Tak ada dukungan pada folder penarik, upload secara manual."
},
{
"key": "暂不支持粘贴文件夹,请手动上传文件夹。",
"zh": "",
"zh-CHT": "暫不支持粘貼文件夾,請手動上傳文件夾。",
"en": "It does not support pasting folders. Please upload folders manually.",
"ko": "폴더 붙여넣기는 지원되지 않습니다. 폴더 업로드하십시오.",
"ja": "フォルダの貼り付けには対応していませんので、手動でフォルダをアップロードしてください。",
"de": "Den ordner zum einfügen nicht unterstützen; bitte diesen manuell hochladen",
"fr": "Le dossier coller nest pas pris en charge pour le moment. Veuillez télécharger le dossier manuellement.",
"id": "Tak ada folder tempel yang didukung, unggah secara manual."
},
{
"key": "最近",
"zh": "",
"zh-CHT": "最近",
"en": "Lately",
"ko": "최근",
"ja": "最近です",
"de": "Erst kürzlich",
"fr": "La récente",
"id": "Baru-baru ini"
},
{
"key": "暂无结果",
"zh": "",
"zh-CHT": "暫無結果",
"en": "No result yet",
"ko": "아직 결과가 없다.",
"ja": "まだ結果はありません",
"de": "Vom tisch.",
"fr": "Pas encore de résultats",
"id": "Tidak ada hasil."
},
{
"key": "新项目负责人",
"zh": "",
"zh-CHT": "新項目負責人",
"en": "New project leader",
"ko": "새 프로젝트 책임자",
"ja": "新しいプロジェクトリーダーです",
"de": "Neuer projektleiter.",
"fr": "Nouveau chef de projet",
"id": "Direktur proyek baru"
},
{
"key": "恢复帐号(已离职)",
"zh": "",
"zh-CHT": "恢復帳號(已離職)",
"en": "Restore Account (Retired)",
"ko": "계정 복원 (이미 종료됨)",
"ja": "アカウント復旧(退職しました)",
"de": "Reaktivieren sie den account wieder",
"fr": "Récupération de compte (quittée)",
"id": "Kembalikan akun (tidak aktif)"
},
{
"key": "你确定恢复已离职帐号【(*)】吗?(注:此操作仅恢复帐号状态,无法恢复操作离职时移交的数据)",
"zh": "",
"zh-CHT": "你確定恢復已離職帳號【(*)】嗎?(注:此操作僅恢復帳號狀態,無法恢復操作離職時移交的數據)",
"en": "Are you sure to restore the departed account [(*)]? (Note: This operation only restores the account status and cannot restore the data transferred when the operation is terminated.)",
"ko": "이직한 계정 [(*)]을 복원하시겠습니까?(주:이 작업은 계정 상태만 복구할 수 있습니다. 작업이 중단되었을 때 전송된 데이터는 복구할 수 없습니다)",
"ja": "退職したアカウント【(*)】の復旧は確実ですか?(注:この作業はアカウントの復旧のみであり、作業終了時に移管したデータの復旧はできません)",
"de": "Sind sie sicher, dass sie alles wieder eingestellt haben? Dies stellt nur die rufnummer wieder ein; keine wiederherstellung der im gang befindlichen daten",
"fr": "Êtes-vous sûr de récupérer un compte sortant [(*)]? (remarque: cette opération ne permet que de restaurer létat du compte, les données transférées lors du départ de lopération ne peuvent pas être récupérées)",
"id": "Apakah anda yakin telah kembali akun yang tidak aktif ((*)? (catatan: operasi ini hanya mengembalikan status akun dan tidak dapat mengembalikan data yang diserahkan saat operasi meninggalkan)"
},
{
"key": "流转到【(*)】时改变任务负责人为状态负责人(并保留操作状态的人员),原本的任务负责人移至协助人员。",
"zh": "",
"zh-CHT": "流轉到【(*)】時改變任務負責人爲狀態負責人(並保留操作狀態的人員),原本的任務負責人移至協助人員。",
"en": "When transferring to [(*)], the task leader is changed to the status leader (and the operational status person is retained), and the original task leader is moved to the assistance person.",
"ko": "【(*)】로 이전할 때 임무책임자는 상태책임자 (동시에 조작상태를 보류한 인원)로 변경되며 원래의 임무책임자는 협조인원으로 이전된다.",
"ja": "【(*)】に流れた時点でタスクの責任者を状態責任者(作業状態を保持している人)に変更し、元のタスクの責任者をサポートスタッフに移します。",
"de": "Nach der umleitung zu [*] ändert sich der dienstleiter zum dienstleiter (der auch diesen bereitschaftsstatus behalten hat), so dass der ursprüngliche controller zu einem helfer umgeleitet wird.",
"fr": "Lorsque le flux passe à [(*)], le chef de mission devient le chef détat (et la personne qui conserve le statut opérationnel), le chef de mission dorigine passe à lassistant.",
"id": "Ubah direktur misi menjadi pemimpin negara (dan tetap memegang status operasional) ketika petugas yang bertugas dipindahkan ke asisten."
},
{
"key": "删除文件夹",
"zh": "",
"zh-CHT": "刪除文件夾",
"en": "Delete folder",
"ko": "폴더 지우기",
"ja": "フォルダを削除します",
"de": "Ordner löschen",
"fr": "Supprimer un dossier",
"id": "Hapus folder"
},
{
"key": "你确定要删除【(*)】文件夹吗?",
"zh": "",
"zh-CHT": "你確定要刪除【(*)】文件夾嗎?",
"en": "Are you sure you want to delete the [(*)] folder?",
"ko": "[(*)] 폴더를 정말로 삭제하시겠습니까?",
"ja": "【(*)】フォルダを削除しますか?",
"de": "Möchten sie wirklich (*) den ordner löschen?",
"fr": "Êtes-vous sûr de vouloir supprimer le dossier [(*)]?",
"id": "Apakah anda yakin ingin menghapus folder [*]?"
},
{
"key": "请输入新的密码",
"zh": "",
"zh-CHT": "請輸入新的密碼",
"en": "Please enter a new password",
"ko": "새 비밀번호를 입력하십시오",
"ja": "新しいパスワードをお願いします。",
"de": "Bitte geben sie ein neues passwort ein",
"fr": "Veuillez entrer un nouveau mot de passe",
"id": "Password baru, silakan"
},
{
"key": "请输入新的邮箱((*)",
"zh": "",
"zh-CHT": "請輸入新的郵箱((*)",
"en": "Please enter a new email ((*))",
"ko": "새 편지함을 입력하십시오 (*)",
"ja": "新しいメールアドレス((*))をお願いします。",
"de": "Bitte geben sie einen neuen briefkasten ein",
"fr": "Veuillez entrer un nouvel email ((*))",
"id": "Silahkan masukkan email baru (*)"
},
{
"key": "截图快捷键",
"zh": "",
"zh-CHT": "截圖快捷鍵",
"en": "Screenshot shortcut",
"ko": "스크린샷 단축키",
"ja": "スクリーンショットショートカットキーです",
"de": "Fahre den kurzbefehl ab",
"fr": "Raccourcis pour captures décran",
"id": "Shortcut screenshot"
},
{
"key": "加班",
"zh": "",
"zh-CHT": "加班",
"en": "Overtime",
"ko": "초과 근무",
"ja": "残業",
"de": "Überstunden.",
"fr": "Les heures supplémentaires",
"id": "Kerja lembur."
},
{
"key": "离职申请",
"zh": "",
"zh-CHT": "離職申請",
"en": "Resignation application",
"ko": "이직 신청",
"ja": "退職願いです",
"de": "Eine freistellung beantragen.",
"fr": "Demande de départ",
"id": "Tinggalkan aplikasi"
},
{
"key": "录用申请",
"zh": "",
"zh-CHT": "錄用申請",
"en": "Application for employment",
"ko": "채용 지원서",
"ja": "採用申し込みです",
"de": "Wir nehmen die bewerbung an.",
"fr": "Demande doffre",
"id": "Aplikasi untuk diterima"
},
{
"key": "我要打卡",
"zh": "",
"zh-CHT": "我要打卡",
"en": "I need to punch in",
"ko": "카드를 찍을래요",
"ja": "タイムカードを押します",
"de": "Ich will mit karte spielen",
"fr": "Je veux pointer",
"id": "Aku mau hitung mundur."
},
{
"key": "修改的内容尚未保存,确定要放弃修改吗?",
"zh": "",
"zh-CHT": "修改的內容尚未保存,確定要放棄修改嗎?",
"en": "The modified content has not been saved. Are you sure you want to abandon the modification?",
"ko": "수정된 내용이 저장되지 않았습니다. 수정을 취소하시겠습니까?",
"ja": "修正内容が保存されていない場合、修正を断念することは確実でしょうか?",
"de": "Die änderungen sind noch nicht gespeichert. Sollten sie auf sie verzichten?",
"fr": "Le contenu des modifications na pas été sauvegardé. Êtes-vous sûr de vouloir abandonner les modifications?",
"id": "Isi revisi belum tersimpan. Apakah anda yakin ingin meninggalkan perubahan?"
},
{
"key": "获取方式",
"zh": "",
"zh-CHT": "獲取方式",
"en": "Acquisition mode",
"ko": "가져오는 방법",
"ja": "入手方法です",
"de": "Mach den zugriff.",
"fr": "Modalités daccès",
"id": "Mendapatkan mode"
},
{
"key": "查看说明",
"zh": "",
"zh-CHT": "查看說明",
"en": "View instructions",
"ko": "설명 보기",
"ja": "説明を見ます",
"de": "Prüfe die anweisungen.",
"fr": "Voir les instructions",
"id": "Tampilkan deskripsi"
},
{
"key": "正在修改系统机器人:(*)",
"zh": "",
"zh-CHT": "正在修改系統機器人:(*)",
"en": "System robot being modified: (*)",
"ko": "시스템 로봇 수정하는 중:*)",
"ja": "システムを修正していますロボット:(*)",
"de": "System-roboter komplett umbauen (*)",
"fr": "Modification du robot système :(*)",
"id": "Sedang memodifikasi robot sistem :(*)"
},
{
"key": "复制选择",
"zh": "",
"zh-CHT": "複製選擇",
"en": "Copy selection",
"ko": "복사 선택",
"ja": "複製選択です",
"de": "Wahl zum kopieren",
"fr": "Copier la sélection",
"id": "Salin pilihan"
},
{
"key": "复制全部",
"zh": "",
"zh-CHT": "複製全部",
"en": "Copy all",
"ko": "모두 복사",
"ja": "全てを複製します",
"de": "Sie kopierten alles.",
"fr": "Copier tout",
"id": "Salin semua"
},
{
"key": "返回首页",
"zh": "",
"zh-CHT": "返回首頁",
"en": "Back to home page",
"ko": "홈 페이지로 돌아가기",
"ja": "トップページに戻ります",
"de": "Mach die titelseite auf.",
"fr": "Retour à laccueil",
"id": "Kembali ke rumah"
},
{
"key": "刷新链接",
"zh": "",
"zh-CHT": "刷新鏈接",
"en": "Refresh link",
"ko": "링크 새로 고침",
"ja": "リンクを更新します",
"de": "Aktualisieren.",
"fr": "Rafraîchir le lien",
"id": "Segarkan tautan"
},
{
"key": "确认",
"zh": "",
"zh-CHT": "確認",
"en": "Verify",
"ko": "확인",
"ja": "確認します",
"de": "Abschuss bestätigen.",
"fr": "La confirmation",
"id": "Konfirmasi"
},
{
"key": "还原归档",
"zh": "",
"zh-CHT": "還原歸檔",
"en": "Restore archive",
"ko": "압축 파일 복원",
"ja": "復元アーカイブです",
"de": "Archive zurückstellen",
"fr": "Restaurer les archives",
"id": "Pengurangan file"
},
{
"key": "签到方式",
"zh": "",
"zh-CHT": "簽到方式",
"en": "Sign-in method",
"ko": "체크인 방법",
"ja": "サインイン方式",
"de": "Mach die aufzeichnungen.",
"fr": "Mode de connexion",
"id": "Cara masuk"
},
{
"key": "手动签到",
"zh": "",
"zh-CHT": "手動簽到",
"en": "Manual check-in",
"ko": "수동 체크",
"ja": "手動でサインインする",
"de": "Manuelle eingabe",
"fr": "Se connecter manuellement",
"id": "Tanda tangan"
},
{
"key": "定位签到",
"zh": "",
"zh-CHT": "定位簽到",
"en": "Location check-in",
"ko": "위치 체크",
"ja": "ポジショニングサインイン",
"de": "Gps-koordinaten eingeben.",
"fr": "Localisez votre connexion",
"id": "Letakkan tanda tangan"
},
{
"key": "详情看下文安装说明",
"zh": "",
"zh-CHT": "詳情看下文安裝說明",
"en": "See installation instructions below for details",
"ko": "자세한 내용은 아래의 설치 지침을 참조하십시오",
"ja": "詳細はインストールの説明をご覧いただきます",
"de": "Details zur installation finden sie hier",
"fr": "Voir les instructions dinstallation ci-dessous pour plus de détails",
"id": "Untuk informasi lebih lanjut tentang pemasangan di bawah ini"
},
{
"key": "通过在签到打卡机器人发送指令签到",
"zh": "",
"zh-CHT": "通過在簽到打卡機器人發送指令簽到",
"en": "Sign in by sending instructions at the check-in check-in robot",
"ko": "체크인 로봇으로 명령을 보냅니다",
"ja": "タイムカードを押すロボットに指示を送ります",
"de": "Der keildroide erstellt die aufträge",
"fr": "Se connecter en envoyant des instructions au robot de pointage à lenregistrement",
"id": "Dengan mengirimkan perintah masuk ke abbot"
},
{
"key": "通过在签到打卡机器人发送位置签到",
"zh": "",
"zh-CHT": "通過在簽到打卡機器人發送位置簽到",
"en": "Send location check-in by punching in the check-in robot",
"ko": "체크인 로봇에 체크인 위치를 보냅니다",
"ja": "タイムカードを押すロボットでロケーションチェックをします",
"de": "Der kupfer-roboter registriert den ort, an dem er sich einloggt",
"fr": "Enregistrement en envoyant lemplacement par le robot de pointage à lenregistrement",
"id": "Dengan mengirimkan tempat masuk ke mesin masukan"
},
{
"key": "状态设置",
"zh": "",
"zh-CHT": "狀態設置",
"en": "Status setting",
"ko": "상태 설정",
"ja": "ステータス設定です",
"de": "Du schlägst zu!",
"fr": "Paramètres de statut",
"id": "Pengaturan negara"
},
{
"key": "关联列表",
"zh": "",
"zh-CHT": "關聯列表",
"en": "Association list",
"ko": "연결 목록",
"ja": "関連リストです",
"de": "Eine liste Von dingen.",
"fr": "Liste des associations",
"id": "Daftar yang terkait"
},
{
"key": "选择关联列表",
"zh": "",
"zh-CHT": "選擇關聯列表",
"en": "Select association list",
"ko": "연결된 목록 선택",
"ja": "関連リストを選択します",
"de": "Wählen sie eine liste Von zufälligen beziehungen",
"fr": "Sélectionnez la liste associée",
"id": "Pilih daftar yang berkaitan"
},
{
"key": "取消关联",
"zh": "",
"zh-CHT": "取消關聯",
"en": "Disassociate",
"ko": "연결 해제",
"ja": "関連をキャンセルします",
"de": "Streichen sie die verbindung.",
"fr": "Annuler une association",
"id": "Batal diasosiasikan"
},
{
"key": "流转到【(*)】时自动将任务移动至关联列表。",
"zh": "",
"zh-CHT": "流轉到【(*)】時自動將任務移動至關聯列表。",
"en": "Automatically moves the task to the associated list when flow to [(*)].",
"ko": "로 이동하면 자동으로 연결된 목록으로 작업이 이동됩니다.",
"ja": "【(*)】にフローすると自動的にタスクが関連リストに移動します。",
"de": "Bei einem wechsel zu [*] wird die aufgabe automatisch zu einer liste Von zufälligen verbindungen verschoben.",
"fr": "Déplacez automatiquement la tâche à la liste associée lorsque le flux va à [(*)].",
"id": "Pindahkan tugas ke senarai terkait ketika ((*)."
},
{
"key": "在浏览器中打开",
"zh": "",
"zh-CHT": "在瀏覽器中打開",
"en": "Open in your browser",
"ko": "브라우저에서 열기",
"ja": "ブラウザで開きます",
"de": "Und öffnet sich im browser",
"fr": "Ouvrir dans votre navigateur",
"id": "Buka di browser anda"
},
{
"key": "图片存储为...",
"zh": "",
"zh-CHT": "圖片存儲爲...",
"en": "Image stored as...",
"ko": "그림 저장...",
"ja": "画像は…です",
"de": "Die bilder sind als",
"fr": "Images stockées pour...",
"id": "Simpan gambar sebagai …"
},
{
"key": "复制电子邮件地址",
"zh": "",
"zh-CHT": "複製電子郵件地址",
"en": "Copy the email address",
"ko": "전자 우편 주소를 복사합니다",
"ja": "メールアドレスをコピーします",
"de": "Die e-mail-adresse wird kopiert",
"fr": "Copiez ladresse e-mail",
"id": "Salin alamat email"
},
{
"key": "复制链接地址",
"zh": "",
"zh-CHT": "複製鏈接地址",
"en": "Copy link address",
"ko": "링크 주소 복사",
"ja": "リンク先をコピーします",
"de": "Die adresse des link kopieren",
"fr": "Copier ladresse du lien",
"id": "Salin alamat tautan"
},
{
"key": "复制图片地址",
"zh": "",
"zh-CHT": "複製圖片地址",
"en": "Copy image address",
"ko": "그림 주소 복사",
"ja": "コピー画像アドレスです",
"de": "Kopien der adresse.",
"fr": "Copiez ladresse de limage",
"id": "Salin alamat gambar"
},
{
"key": "图片无法保存",
"zh": "",
"zh-CHT": "圖片無法保存",
"en": "Image cannot be saved",
"ko": "그림을 저장할 수 없음",
"ja": "画像は保存できません。",
"de": "Bild kann nicht mehr gerettet werden",
"fr": "Limage ne peut pas être sauvegardée",
"id": "Gambar tidak bisa disimpan"
},
{
"key": "键盘设置",
"zh": "",
"zh-CHT": "鍵盤設置",
"en": "Keyboard Settings",
"ko": "키보드 설정",
"ja": "キーボードの設定です",
"de": "Die tastatureinstellung",
"fr": "Réglage du clavier",
"id": "Pengaturan papan ketik"
},
{
"key": "事项",
"zh": "",
"zh-CHT": "事項",
"en": "Todo",
"ko": "사항",
"ja": "事項です",
"de": "Todo",
"fr": "Todo",
"id": "Todo"
},
{
"key": "编辑描述",
"zh": "",
"zh-CHT": "編輯描述",
"en": "Edit description",
"ko": "설명 편집",
"ja": "記述を編集します。",
"de": "Bearbeiten sie die beschreibung",
"fr": "Editer la description",
"id": "Deskripsi pengeditan"
},
{
"key": "没有任何修改!",
"zh": "",
"zh-CHT": "沒有任何修改!",
"en": "No modifications!",
"ko": "변경된 것은 없습니다!",
"ja": "何の修正もありません!",
"de": "Es gibt keine änderungen!",
"fr": "Sans aucune modification!",
"id": "Tak ada yang perlu diubah!"
},
{
"key": "项目类型",
"zh": "",
"zh-CHT": "項目類型",
"en": "Item type",
"ko": "항목 형식",
"ja": "プロジェクトタイプです",
"de": "Typ eins?",
"fr": "Type de projet",
"id": "Jenis item"
},
{
"key": "团队项目",
"zh": "",
"zh-CHT": "團隊項目",
"en": "Team project",
"ko": "팀 프로젝트",
"ja": "チームプロジェクトです",
"de": "Ein teamprojekt.",
"fr": "Projets par équipe",
"id": "Proyek tim"
},
{
"key": "全部项目",
"zh": "",
"zh-CHT": "全部項目",
"en": "All items",
"ko": "모든 항목",
"ja": "全項目です",
"de": "Gesamte projekte.",
"fr": "Tous les projets",
"id": "Semua item"
},
{
"key": "打开链接",
"zh": "",
"zh-CHT": "打開鏈接",
"en": "Open the link",
"ko": "링크 열기",
"ja": "リンクを開きます",
"de": "Link öffnen.",
"fr": "Ouvrir le lien",
"id": "Buka tautan"
},
{
"key": "汇报人",
"zh": "",
"zh-CHT": "彙報人",
"en": "Informant",
"ko": "접수인",
"ja": "発信者です",
"de": "Das telegramm.",
"fr": "Consignataire",
"id": "Pembawa berita"
},
{
"key": "提交时间",
"zh": "",
"zh-CHT": "提交時間",
"en": "Submission time",
"ko": "제출 날짜",
"ja": "提出時期です",
"de": "Zeit bis zur abgabe",
"fr": "Heure de soumission",
"id": "Waktu pengiriman"
}
]

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.27.46",
"version": "0.30.13",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",
@ -26,7 +26,7 @@
"css-loader": "^6.7.2",
"dexie": "^3.2.3",
"echarts": "^5.2.2",
"element-sea": "^2.15.10-3",
"element-sea": "^2.15.10-6",
"file-loader": "^6.2.0",
"highlight.js": "^11.7.0",
"inquirer": "^8.2.0",
@ -55,7 +55,7 @@
"stylus-loader": "^7.1.0",
"tinymce": "^5.10.3",
"tui-calendar-hi": "^1.15.1-5",
"view-design-hi": "^4.7.0-47",
"view-design-hi": "^4.7.0-48",
"vite": "^2.9.15",
"vite-plugin-file-copy": "^1.0.0",
"vite-plugin-require": "^1.1.10",
@ -68,7 +68,7 @@
"vue-resize-observer": "^2.0.16",
"vue-router": "^3.6.5",
"vue-template-compiler": "~2.6.14",
"vue-virtual-scroll-list-hi": "^2.3.4-6",
"vue-virtual-scroll-list-hi": "^2.3.5-3",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},

0
public/audio/call.mp3 Executable file → Normal file
View File

0
public/audio/message.mp3 Executable file → Normal file
View File

0
public/css/fonts/ionicons.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 542 KiB

After

Width:  |  Height:  |  Size: 542 KiB

0
public/css/fonts/ionicons.ttf Executable file → Normal file
View File

0
public/css/fonts/ionicons.woff Executable file → Normal file
View File

View File

@ -53,6 +53,11 @@ input[type="date"] {
url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
}
/* Hide vertical scrollbar on off canvas animation ("left" positioning) */
html {
overflow-x: hidden;
}
body {
font-family: "Source Sans Pro", sans-serif;
}
@ -63,7 +68,6 @@ a:focus {
#content {
margin-top: 10px;
margin-left: 20%;
padding-left: 10px;
}
@ -193,16 +197,30 @@ td.code {
/* ------------------------------------------------------------------------------------------
* Sidenav
* ------------------------------------------------------------------------------------------ */
.sidenav {
color: var(--white);
width: 20%;
#scrollingNav {
position: fixed;
top: 50px;
top: 0;
left: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
z-index: 10;
background-color: var(--dark-gray);
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%);
}
.sidenav {
color: var(--white);
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: hidden;
}
.sidenav:hover {
overflow-x: auto;
overflow-y: auto;
}
.sidenav > li > a {
@ -250,14 +268,80 @@ td.code {
}
*/
/*
* Off Canvas
* --------------------------------------------------
*/
@media screen and (max-width: 767px) {
#content {
margin-top: 58px;
}
.row-offcanvas {
position: relative;
-webkit-transition: all .25s ease-out;
-o-transition: all .25s ease-out;
transition: all .25s ease-out;
left: 0;
}
.row-offcanvas,
.row-offcanvas * {
transition: all 0.5s ease-out;
}
.row-offcanvas .sidebar-offcanvas {
position: absolute;
top: 0;
left: -200px !important; /* 6 columns */
width: 100%; /* 6 columns */
max-width: 200px;
}
.nav-toggle {
position: fixed;
left: 0;
background: var(--dark-gray);
width: 100%;
}
.nav-toggle .btn {
margin: 10px 14px;
}
.nav-toggle .icon-bar {
display: block;
width: 22px;
height: 2px;
border-radius: 1px;
background-color: var(--white);
}
.nav-toggle .icon-bar + .icon-bar {
margin-top: 4px;
}
.row-offcanvas.active .sidebar-offcanvas {
left: 0 !important; /* 6 columns */
}
.row-offcanvas.active, .row-offcanvas.active .nav-toggle {
left: 200px;
}
/* Styling the three lines to make it an X */
.row-offcanvas.active .nav-toggle .btn > .icon-bar {
transform: rotate(45deg) translate(-4px, -4px);
}
.row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(2) {
display: none;
}
.row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(3) {
transform: rotate(-45deg);
}
}
/* ------------------------------------------------------------------------------------------
* Side nav search
* ------------------------------------------------------------------------------------------ */
.sidenav-search {
width: 20%;
left: 0px;
position: fixed;
padding: 16px 20px 10px 20px;
padding: 16px 10px 10px;
background-color: var(--dark-gray);
}
@ -273,10 +357,28 @@ td.code {
height: 20px;
text-align: center;
right: 28px;
top: 17px;
top: 18px;
background-color: #fff;
}
/* ------------------------------------------------------------------------------------------
* Prism - Toolbar
* ------------------------------------------------------------------------------------------ */
div.code-toolbar.code-toolbar > .toolbar {
top: .4rem;
right: .4rem;
}
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:hover,
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:focus {
color: var(--white);
}
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button {
color: var(--light-gray);
padding: .5em;
background: var(--hover-gray);
box-shadow: 0 2px 1px 1px rgba(0,0,0,.5);
}
/* ------------------------------------------------------------------------------------------
* Compare
* ------------------------------------------------------------------------------------------ */

View File

@ -0,0 +1,13 @@
pre.diff-highlight > code .token.deleted:not(.prefix),
pre > code.diff-highlight .token.deleted:not(.prefix) {
background-color: rgba(255, 0, 0, .1);
color: inherit;
display: block;
}
pre.diff-highlight > code .token.inserted:not(.prefix),
pre > code.diff-highlight .token.inserted:not(.prefix) {
background-color: rgba(0, 255, 128, .1);
color: inherit;
display: block;
}

65
public/docs/assets/prism-toolbar.css vendored Normal file
View File

@ -0,0 +1,65 @@
div.code-toolbar {
position: relative;
}
div.code-toolbar > .toolbar {
position: absolute;
z-index: 10;
top: .3em;
right: .2em;
transition: opacity 0.3s ease-in-out;
opacity: 0;
}
div.code-toolbar:hover > .toolbar {
opacity: 1;
}
/* Separate line b/c rules are thrown out if selector is invalid.
IE11 and old Edge versions don't support :focus-within. */
div.code-toolbar:focus-within > .toolbar {
opacity: 1;
}
div.code-toolbar > .toolbar > .toolbar-item {
display: inline-block;
}
div.code-toolbar > .toolbar > .toolbar-item > a {
cursor: pointer;
}
div.code-toolbar > .toolbar > .toolbar-item > button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none; /* for button */
-moz-user-select: none;
-ms-user-select: none;
}
div.code-toolbar > .toolbar > .toolbar-item > a,
div.code-toolbar > .toolbar > .toolbar-item > button,
div.code-toolbar > .toolbar > .toolbar-item > span {
color: #bbb;
font-size: .8em;
padding: 0 .5em;
background: #f5f2f0;
background: rgba(224, 224, 224, 0.2);
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
border-radius: .5em;
}
div.code-toolbar > .toolbar > .toolbar-item > a:hover,
div.code-toolbar > .toolbar > .toolbar-item > a:focus,
div.code-toolbar > .toolbar > .toolbar-item > button:hover,
div.code-toolbar > .toolbar > .toolbar-item > button:focus,
div.code-toolbar > .toolbar > .toolbar-item > span:hover,
div.code-toolbar > .toolbar > .toolbar-item > span:focus {
color: inherit;
text-decoration: none;
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#87D068;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<path class="st1" d="M35.7,20.1c0-0.8-0.4-1.5-1.1-1.9l-8.9-5.1c-1-0.6-2.2-0.6-3.1,0l-9.1,5.2c-0.6,0.4-1,1-1,1.8s0.4,1.4,1,1.8
l9.1,5.2c0.5,0.3,1,0.4,1.6,0.4c0.5,0,1.1-0.1,1.6-0.4l8.9-5.1C35.3,21.6,35.7,20.9,35.7,20.1z"/>
<path class="st1" d="M24,29.7c-0.4,0-0.8-0.1-1.2-0.3l-9-5.1c-0.4-0.2-0.9-0.1-1.2,0.3c-0.2,0.4-0.1,0.9,0.3,1.2l9,5.1
c0.6,0.4,1.3,0.6,2,0.6c0,0,0,0,0,0c0.5,0,0.9-0.4,0.9-0.9C24.9,30.1,24.5,29.7,24,29.7z"/>
<path class="st1" d="M24,33.5c-0.6,0-1.3-0.2-1.8-0.5l-8.4-4.8c-0.4-0.2-0.9-0.1-1.2,0.3c-0.2,0.4-0.1,0.9,0.3,1.2l8.4,4.8
c0.8,0.5,1.7,0.7,2.6,0.7c0,0,0,0,0,0c0.5,0,0.9-0.4,0.9-0.9C24.9,33.9,24.5,33.5,24,33.5z"/>
<path class="st1" d="M34.8,30h-2.6v-2.6c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9V30h-2.6c-0.5,0-0.9,0.4-0.9,0.9
s0.4,0.9,0.9,0.9h2.6v2.6c0,0.5,0.4,0.9,0.9,0.9c0.5,0,0.9-0.4,0.9-0.9v-2.6h2.6c0.5,0,0.9-0.4,0.9-0.9S35.3,30,34.8,30z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#87D068;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<circle class="st1" cx="19.7" cy="26.6" r="0.9"/>
<g>
<path class="st1" d="M29,26.9c0.5,0,0.9,0.4,0.9,0.9l0,2.6l2.6,0c0.5,0,0.9,0.4,0.9,0.9c0,0.5-0.4,0.9-0.9,0.9l-2.6,0l0,2.6
c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9l0-2.6l-2.6,0c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9l2.6,0l0-2.6
C28.1,27.3,28.5,26.9,29,26.9"/>
</g>
<g>
<path class="st1" d="M26.6,34.8l0-1.2l-1.2,0c0,0,0,0,0,0c-0.6,0-1.2-0.2-1.6-0.7c-0.4-0.4-0.7-1-0.7-1.6c0-0.6,0.2-1.2,0.7-1.6
c0.4-0.4,1-0.7,1.6-0.7l1.2,0l0-1.2c0-1.3,1-2.3,2.3-2.3c0,0,0,0,0,0c1.3,0,2.3,1,2.3,2.3l0,1.2l1.2,0c0,0,0,0,0,0
c0.3,0,0.6,0.1,0.9,0.2V14.7c0-1.3-1.1-2.3-2.3-2.3H17c-1.3,0-2.3,1.1-2.3,2.3v18.7c0,1.3,1,2.3,2.3,2.3h9.8
C26.7,35.4,26.6,35.1,26.6,34.8z M19.7,29.3c-1.4,0-2.6-1.2-2.6-2.6s1.2-2.6,2.6-2.6s2.6,1.2,2.6,2.6S21.1,29.3,19.7,29.3z
M22.6,17.9l-2.9,2.9c-0.2,0.2-0.4,0.3-0.6,0.3s-0.4-0.1-0.6-0.3l-1.2-1.2c-0.3-0.3-0.3-0.9,0-1.2s0.9-0.3,1.2,0l0.5,0.5l2.3-2.3
c0.3-0.3,0.9-0.3,1.2,0S23,17.6,22.6,17.9z M24.9,17.6h4.7c0.5,0,0.9,0.4,0.9,0.9s-0.4,0.9-0.9,0.9h-4.7c-0.5,0-0.9-0.4-0.9-0.9
S24.4,17.6,24.9,17.6z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F57775;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<path class="st1" d="M21.5,24.5c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6C27.6,21.8,24.9,24.5,21.5,24.5z"/>
</g>
<g>
<path class="st1" d="M28.8,35.5H14.3c-1.1,0-2-0.9-2-2v-4.1c0-1.7,1.4-3.1,3.1-3.1h12.2c1.7,0,3.1,1.4,3.1,3.1v4.1
C30.7,34.6,29.9,35.5,28.8,35.5z"/>
</g>
<g>
<path class="st1" d="M29.6,22.5c-0.3,0-0.5-0.1-0.7-0.3c-0.2-0.3-0.2-0.7,0-1c0.6-0.8,0.9-1.7,0.9-2.7s-0.3-1.9-0.9-2.7
c-0.2-0.3-0.2-0.7,0-1s0.6-0.4,0.9-0.3c1.7,0.6,2.9,2.2,2.9,4s-1.2,3.4-2.9,4C29.8,22.5,29.7,22.5,29.6,22.5z"/>
</g>
<g>
<path class="st1" d="M34,34.7h-0.7c-0.3,0-0.5-0.1-0.7-0.3s-0.2-0.5-0.1-0.7c0-0.1,0-0.1,0-0.1v-4c0-0.4-0.2-0.7-0.3-0.9
c-0.2-0.2-0.2-0.6-0.1-0.9c0.1-0.3,0.4-0.5,0.7-0.5H33c1.4,0,2.6,1.2,2.6,2.6V33C35.7,34,34.9,34.7,34,34.7z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25679"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#9D95E5" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25679)"><g><path d="M35.6226,12.187076C35.9089,12.366451,36.0526,12.70563,35.9824,13.03611L32.5574,33.5856C32.4912,34.000299999999996,32.1282,34.3018,31.7084,34.290800000000004C31.599,34.289100000000005,31.4912,34.2646,31.3918,34.2188L25.333399999999997,31.7437L22.095599999999997,35.6866C21.9377,35.8896,21.69045,36.0025,21.43364,35.9888C21.33001,35.990700000000004,21.22712,35.9711,21.131439999999998,35.9313C20.96605,35.8728,20.82448,35.7615,20.72851,35.6147C20.62727,35.4715,20.57206,35.300799999999995,20.57021,35.1254L20.57021,30.4629L32.125699999999995,16.28835L17.821640000000002,28.6641L12.540347,26.4912C12.225567,26.3831,12.0117595,26.09,12.0050232,25.7573C11.9677587,25.428800000000003,12.140421,25.112299999999998,12.436736,24.9658L34.7016,12.115123C34.833200000000005,12.040216,34.981899999999996,12.000561138,35.1333,12C35.3072,12.0294109,35.4735,12.092995,35.6226,12.187076Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25613"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#F57775" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25613)"><g><g><path d="M32.753299999999996,26.9593L30.357,26.9593C29.1345,26.957700000000003,28.1438,25.967100000000002,28.142200000000003,24.744500000000002C28.139499999999998,24.1851,28.3448,23.6446,28.7182,23.228099999999998C31.2206,20.597839999999998,31.1498,16.44632,28.5592,13.9029C25.9685,11.359486,21.816380000000002,11.365081,19.23258,13.91547C16.648789999999998,16.46586,16.58916,20.61755,19.09864,23.2411C19.45887,23.6577,19.65585,24.1909,19.65307,24.7416C19.65997,25.8988,18.7744,26.8659,17.62114,26.9608L15.40058,26.9608C14.07758,26.9608,13.00396661,28.0311,13,29.3541L13,31.5142C13,32.024,13.413276,32.4373,13.923077,32.4373L34.227900000000005,32.4373C34.737700000000004,32.4373,35.150999999999996,32.024,35.150999999999996,31.5142L35.150999999999996,29.3541C35.145399999999995,28.0327,34.0747,26.9633,32.753299999999996,26.9593Z" fill="#FFFFFF" fill-opacity="1"/></g><g><path d="M34.2308,35.999994833374025L13.923077,35.999994833374025C13.413276,35.999994833374025,13.000000054161,35.58672483337402,13.000000054161,35.07692183337402C13.000000054161,34.56712083337403,13.413276,34.15384483337402,13.923077,34.15384483337402L34.227900000000005,34.15384483337402C34.737700000000004,34.15384483337402,35.150999999999996,34.56712083337403,35.150999999999996,35.07692183337402C35.150999999999996,35.58672483337402,34.737700000000004,35.999994833374025,34.227900000000005,35.999994833374025L34.2308,35.999994833374025Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFAA64;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<path class="st1" d="M32.5,14.5h-4.3v-1c0-0.4-0.3-0.8-0.8-0.8s-0.8,0.3-0.8,0.8v1h-5.3v-1c0-0.4-0.3-0.8-0.8-0.8s-0.8,0.3-0.8,0.8
v1h-4.3c-1.5,0-2.8,1.2-2.8,2.8v15.3c0,1.5,1.2,2.8,2.8,2.8h17c1.5,0,2.8-1.2,2.8-2.8V17.2C35.2,15.7,34,14.5,32.5,14.5z M15.5,16
h4.3v1c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8v-1h5.3v1c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8v-1h4.3c0.7,0,1.2,0.6,1.2,1.2v2.9
H14.2v-2.9C14.2,16.5,14.8,16,15.5,16z M18.3,28.1L18.3,28.1c0.6,0,1.1,0.5,1.1,1.1c0,0.6-0.5,1-1,1c-0.6,0-1.1-0.5-1.1-1
C17.3,28.5,17.8,28.1,18.3,28.1z M17.3,25.1c0-0.6,0.5-1,1-1h0c0.6,0,1,0.5,1,1s-0.5,1-1,1C17.8,26.2,17.3,25.7,17.3,25.1z M24,28.1
L24,28.1c0.6,0,1.1,0.5,1.1,1.1c0,0.6-0.5,1-1,1c-0.6,0-1.1-0.5-1.1-1C23,28.5,23.4,28.1,24,28.1z M23,25.1c0-0.6,0.5-1,1-1h0
c0.6,0,1,0.5,1,1s-0.5,1-1,1C23.4,26.2,23,25.7,23,25.1z M29.7,28.1L29.7,28.1c0.6,0,1.1,0.5,1.1,1.1c0,0.6-0.5,1-1,1
c-0.6,0-1.1-0.5-1.1-1C28.6,28.5,29.1,28.1,29.7,28.1z M28.6,25.1c0-0.6,0.5-1,1-1h0c0.6,0,1,0.5,1,1s-0.5,1-1,1
C29.1,26.2,28.6,25.7,28.6,25.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25658"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#72A1F7" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25658)"><g><path d="M33.1767,19.46395L30.5723,21.34133L30.5723,19.01332C30.5723,17.34516,29.217,15.99442094,27.5489,16L15.01332,16C13.34911,15.999999801611,12,17.34911,12,19.01332L12,29.5822C12,31.2464,13.34911,32.595600000000005,15.01332,32.595600000000005L27.5489,32.595600000000005C29.217,32.6011,30.5723,31.2504,30.5723,29.5822L30.5723,27.2542L33.182500000000005,29.1259C33.7237,29.5146,34.436800000000005,29.5683,35.0302,29.2651C35.623599999999996,28.9619,35.9979,28.3525,36,27.6861L36,20.907980000000002C36.0011,20.23879,35.626599999999996,19.62559,35.0308,19.32086C34.4351,19.01614,33.7187,19.07142,33.1767,19.46395Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFAA64;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<path class="st1" d="M28.7,18.4h3.8c0.5,0,0.8-0.6,0.4-1l-4.7-4.7c-0.4-0.4-1-0.1-1,0.4V17C27.3,17.8,27.9,18.4,28.7,18.4z"/>
<path class="st1" d="M28.7,20.2c-1.8,0-3.2-1.4-3.2-3.2v-4c0-0.3-0.3-0.6-0.6-0.6H17c-1.3,0-2.3,1.1-2.3,2.3v18.7
c0,1.3,1,2.3,2.3,2.3h14c1.3,0,2.3-1,2.3-2.3V20.7c0-0.3-0.3-0.6-0.6-0.6H28.7z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 890 B

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#53CBAE;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<path class="st1" d="M35.8,18.3l-2.6,2.2c-0.3,0.3-0.5,0.6-0.5,1v4.8c0,0.4,0.2,0.8,0.5,1l2.6,2.2c0.9,0.8,2.2,0.1,2.2-1v-9.3
C38,18.2,36.6,17.6,35.8,18.3z"/>
<path class="st1" d="M28.8,14.6H12c-1.1,0-2,0.9-2,2v14.8c0,1.1,0.9,2,2,2h16.8c1.1,0,2-0.9,2-2V16.6
C30.8,15.5,29.9,14.6,28.8,14.6z M23.4,24.7h-2.2V27c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7v-2.2h-2.2
c-0.4,0-0.7-0.3-0.7-0.7s0.3-0.7,0.7-0.7h2.2V21c0-0.4,0.3-0.7,0.7-0.7c0.4,0,0.7,0.3,0.7,0.7v2.2h2.2c0.4,0,0.7,0.3,0.7,0.7
S23.8,24.7,23.4,24.7z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25667"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#FFC835" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25667)"><g><path d="M33.400099999999995,12C34.0625,12,34.599599999999995,12.537033,34.599599999999995,13.1995L34.599599999999995,34.799099999999996C34.6004,35.4621,34.0631,36,33.400099999999995,36L16.59993,36C14.61174,36,13,34.3883,13,32.400099999999995L13,15.59993C13,13.61174,14.61174,12,16.59993,12L33.400099999999995,12ZM32.1991,31.1991L16.59993,31.1991C15.953240000000001,31.2215,15.440570000000001,31.7523,15.440570000000001,32.3994C15.440570000000001,33.0464,15.953240000000001,33.577200000000005,16.59993,33.599599999999995L32.1991,33.599599999999995L32.1991,31.1991ZM23.799799999999998,22.799799999999998C21.8116,22.799799999999998,20.19986,24.4115,20.19986,26.3997L27.3997,26.3997C27.3997,24.4115,25.788,22.799799999999998,23.799799999999998,22.799799999999998ZM23.799799999999998,16.799419999999998C22.47406,16.799419999999998,21.39935,17.87413,21.39935,19.19986C21.39935,20.525579999999998,22.47406,21.60029,23.799799999999998,21.60029C25.125500000000002,21.60029,26.200200000000002,20.525579999999998,26.200200000000002,19.19986C26.1994,17.87446,25.1252,16.80022,23.799799999999998,16.799419999999998Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25401"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#F57775" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25401)"><g><path d="M36,19.35553L36,31.6471C36,32.8166,35.0519,33.764700000000005,33.882400000000004,33.764700000000005L14.11765,33.764700000000005C12.948108,33.764700000000005,12.00000549316,32.8166,12.00000549316,31.6471L12.00000549316,19.35553L20.804470000000002,26.8202C22.6481,28.3834,25.3519,28.3834,27.1955,26.8202L36,19.35553ZM33.882400000000004,14.000000646254C34.1404,14.000286291,34.3963,14.0471559,34.6376,14.138353C34.984700000000004,14.270521,35.2905,14.492136,35.5242,14.780706C35.6509,14.935896,35.7549,15.10818,35.8334,15.29247C35.925200000000004,15.50988,35.9816,15.74565,35.9958,15.99341L36,16.11765L36,16.823529999999998L25.3744,25.8871C24.6211,26.5295,23.5233,26.5644,22.730800000000002,25.9711L22.625700000000002,25.886400000000002L12.00000517004,16.823529999999998L12.00000517004,16.11765C11.9982451,15.318200000000001,12.448325,14.586376,13.16259,14.227294C13.45869,14.0776132,13.78587,13.999747961,14.11765,14.000000646254L33.882400000000004,14.000000646254Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25658"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#72A1F7" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25658)"><g><path d="M33.1767,19.46395L30.5723,21.34133L30.5723,19.01332C30.5723,17.34516,29.217,15.99442094,27.5489,16L15.01332,16C13.34911,15.999999801611,12,17.34911,12,19.01332L12,29.5822C12,31.2464,13.34911,32.595600000000005,15.01332,32.595600000000005L27.5489,32.595600000000005C29.217,32.6011,30.5723,31.2504,30.5723,29.5822L30.5723,27.2542L33.182500000000005,29.1259C33.7237,29.5146,34.436800000000005,29.5683,35.0302,29.2651C35.623599999999996,28.9619,35.9979,28.3525,36,27.6861L36,20.907980000000002C36.0011,20.23879,35.626599999999996,19.62559,35.0308,19.32086C34.4351,19.01614,33.7187,19.07142,33.1767,19.46395Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25545"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#9D95E5" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25545)"><g><g><path d="M27.3699,31.3292C26.4274,32.2528,24.9169,32.2452,23.983800000000002,31.3121C23.0507,30.379,23.043100000000003,28.8686,23.9667,27.926099999999998L32.3201,19.578400000000002L32.3201,15.27301C32.3201,13.46482,30.8539,11.999210349,29.0457,12L15.27301,12C13.46538,11.999999784513,12,13.46538,12,15.27301L12,32.6318C11.999209911,34.44,13.46482,35.9063,15.27301,35.9063L29.0457,35.9063C30.8541,35.9063,32.3201,34.4403,32.3201,32.6318L32.3201,26.3789L27.3699,31.3292ZM17.74958,20.06886C17.25443,20.06886,16.85304,19.66746,16.85304,19.17232C16.85304,18.67717,17.25443,18.27578,17.74958,18.27578L23.1288,18.27578C23.624000000000002,18.27578,24.025399999999998,18.67717,24.025399999999998,19.17232C24.025399999999998,19.66746,23.624000000000002,20.06886,23.1288,20.06886L17.74958,20.06886Z" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(0.7071067690849304,0.7071067690849304,-0.7071067690849304,0.7071067690849304,23.826460884525744,-18.843463462634503)"><rect x="34.65930366516113" y="19.33935022354126" width="1.8960309028625488" height="14.49763011932373" rx="0.9480154514312744" fill="#FFFFFF" fill-opacity="1"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25405"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#FFC835" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25405)"><g><g><rect x="12" y="26.31494140625" width="1.8119423389434814" height="7.173644542694092" rx="0.9059711694717407" fill="#FFFFFF" fill-opacity="1"/></g><g><rect x="34.18804931640625" y="26.31494140625" width="1.8119423389434814" height="7.173644542694092" rx="0.9059711694717407" fill="#FFFFFF" fill-opacity="1"/></g><g><path d="M29.53465,20.54139L31.35485,17.38422C31.41525,17.38422,31.47695,17.39383,31.53875,17.39383C32.931349999999995,17.39648,33.97485,16.118949999999998,33.69415,14.75489C33.41345,13.390835,31.95045,12.629057,30.67195,13.181321C29.393549999999998,13.733585,28.94525,15.32101,29.74605,16.4604L27.76795,19.89211C25.287950000000002,19.2077,22.669629999999998,19.20201,20.18666,19.87564L18.21274,16.46315C19.0135,15.32376,18.56525,13.73633,17.28679,13.184066C16.00834,12.631803,14.545278,13.393581,14.2646052,14.75764C13.983932,16.1217,15.02738,17.39923,16.42001,17.39657C16.48179,17.39657,16.542180000000002,17.39657,16.60258,17.386960000000002L18.417270000000002,20.529040000000002C16.05625,21.62718,14.483158,23.3979,14.36648,25.4172L14.356871,25.4172L14.356871,33.289500000000004C14.356871,34.7254,15.52087,35.889399999999995,16.95673,35.889399999999995L30.96905,35.889399999999995C32.40495,35.889399999999995,33.56895,34.7254,33.56895,33.289500000000004L33.56895,25.4172L33.559349999999995,25.4172C33.444050000000004,23.4048,31.88195,21.636789999999998,29.53465,20.54139ZM27.063850000000002,29.5105L20.86888,29.5105C19.89243,29.5105,19.10087,28.718899999999998,19.10087,27.7425C19.10087,26.766,19.89243,25.9745,20.86888,25.9745L27.05695,25.9745C28.03335,25.9745,28.82495,26.766,28.82495,27.7425C28.82495,28.718899999999998,28.03335,29.5105,27.05695,29.5105L27.063850000000002,29.5105Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#72A1F7;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<g>
<g>
<g>
<g>
<path class="st1" d="M20.3,35.8h-4.7c-1.9,0-3.4-1.5-3.4-3.4v-4.7c0-0.4,0.3-0.8,0.8-0.8s0.8,0.3,0.8,0.8v4.7
c0,1.1,0.9,1.9,1.9,1.9h4.7c0.4,0,0.8,0.3,0.8,0.8S20.7,35.8,20.3,35.8z"/>
</g>
<g>
<path class="st1" d="M13,21.1c-0.4,0-0.8-0.3-0.8-0.8v-4.7c0-1.9,1.5-3.4,3.4-3.4h4.7c0.4,0,0.8,0.3,0.8,0.8s-0.3,0.8-0.8,0.8
h-4.7c-1.1,0-1.9,0.9-1.9,1.9v4.7C13.8,20.7,13.4,21.1,13,21.1z"/>
</g>
</g>
<g>
<g>
<path class="st1" d="M35,21.1c-0.4,0-0.8-0.3-0.8-0.8v-4.7c0-1.1-0.9-1.9-1.9-1.9h-4.7c-0.4,0-0.8-0.3-0.8-0.8s0.3-0.8,0.8-0.8
h4.7c1.9,0,3.4,1.5,3.4,3.4v4.7C35.8,20.7,35.4,21.1,35,21.1z"/>
</g>
<g>
<path class="st1" d="M32.3,35.8h-4.7c-0.4,0-0.8-0.3-0.8-0.8s0.3-0.8,0.8-0.8h4.7c1.1,0,1.9-0.9,1.9-1.9v-4.7
c0-0.4,0.3-0.8,0.8-0.8s0.8,0.3,0.8,0.8v4.7C35.8,34.2,34.2,35.8,32.3,35.8z"/>
</g>
</g>
</g>
<g>
<path class="st1" d="M29.5,16.5H18.5c-1.1,0-1.9,0.9-1.9,1.9v11.1c0,1.1,0.9,1.9,1.9,1.9h11.1c1.1,0,1.9-0.9,1.9-1.9V18.5
C31.5,17.4,30.6,16.5,29.5,16.5z M27.7,24.8h-7.3c-0.4,0-0.8-0.3-0.8-0.8s0.3-0.8,0.8-0.8h7.3c0.4,0,0.8,0.3,0.8,0.8
S28.1,24.8,27.7,24.8z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:#72A1F7;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M36,48H12C5.4,48,0,42.6,0,36V12C0,5.4,5.4,0,12,0h24c6.6,0,12,5.4,12,12v24C48,42.6,42.6,48,36,48z"/>
<g>
<path class="st1" d="M34.6,26.5c-1.9-1.1-1.9-3.8,0-4.9c0.6-0.3,0.8-1.1,0.5-1.7l-1.9-3.3c-0.3-0.6-1.1-0.8-1.7-0.5
c-1.9,1.1-4.3-0.3-4.3-2.5c0-0.7-0.6-1.3-1.3-1.3h-3.8c-0.7,0-1.3,0.6-1.3,1.3c0,2.2-2.4,3.6-4.3,2.5l0,0c-0.6-0.3-1.4-0.1-1.7,0.5
L13,19.8c-0.3,0.6-0.1,1.4,0.5,1.7l0,0c1.9,1.1,1.9,3.8,0,4.9c-0.6,0.3-0.8,1.1-0.5,1.7l1.9,3.3c0.3,0.6,1.1,0.8,1.7,0.5
c1.9-1.1,4.3,0.3,4.3,2.5v0c0,0.7,0.6,1.3,1.3,1.3h3.8c0.7,0,1.3-0.6,1.3-1.3v0c0-2.2,2.4-3.6,4.3-2.5h0c0.6,0.3,1.4,0.1,1.7-0.5
l1.9-3.3C35.4,27.6,35.2,26.8,34.6,26.5z M24,26.6c-1.4,0-2.6-1.2-2.6-2.6c0-1.4,1.2-2.6,2.6-2.6c1.4,0,2.6,1.2,2.6,2.6
C26.6,25.4,25.4,26.6,24,26.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="48" height="48" viewBox="0 0 48 48"><defs><clipPath id="master_svg0_3054_25526"><rect x="12" y="12" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="48" height="48" rx="12" fill="#72A1F7" fill-opacity="1"/><g clip-path="url(#master_svg0_3054_25526)"><g><g><path d="M34.0464,21.911920000000002C34.0464,16.439700000000002,29.336,12,23.523899999999998,12C17.71187,12,13,16.43682,13,21.911920000000002C13,24.689799999999998,14.21541,27.1998,16.16813,28.9999L23.5211,36L30.7962,29.0676C30.9402,28.9365,31.0842,28.8026,31.2196,28.6643C33.0192,26.8769,34.0359,24.4483,34.0464,21.911920000000002ZM23.397199999999998,26.7015C20.95156,26.6984,18.97124,24.7138,18.97336,22.2681C18.97548,19.82244,20.959229999999998,17.84126,23.404899999999998,17.84232C25.8506,17.84338,27.8326,19.82627,27.8326,22.271900000000002C27.8326,24.720599999999997,25.8459,26.704700000000003,23.397199999999998,26.7015Z" fill="#FFFFFF" fill-opacity="1"/></g><g><ellipse cx="23.397215843200684" cy="22.271930694580078" rx="2.733229160308838" ry="2.733229160308838" fill="#FFFFFF" fill-opacity="1"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

0
public/images/browser/360.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
public/images/browser/chrome.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

0
public/images/browser/firefox.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

0
public/images/browser/ie.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

0
public/images/browser/safari.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691334156944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20927" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M351.1 644.3m-40.2 0a40.2 40.2 0 1 0 80.4 0 40.2 40.2 0 1 0-80.4 0Z" p-id="20928" fill="#9D95E5"></path><path d="M489 764.8C489 638.1 592.1 535 718.9 535c69 0 130.2 31.2 172.4 79.4V178.7c0-50.6-41.4-91.9-91.9-91.9H224.7c-50.6 0-91.9 41.4-91.9 91.9v666.5c0 50.8 41.2 91.9 91.9 91.9h343.7C520.2 895 489 833.8 489 764.8z m69-471h183.9c15.8 0 28.7 12.9 28.7 28.7s-12.9 28.7-28.7 28.7H558c-15.8 0-28.7-12.9-28.7-28.7s12.8-28.7 28.7-28.7zM351.1 742c-53.9 0-97.7-43.8-97.7-97.7 0-53.9 43.8-97.7 97.7-97.7s97.7 43.8 97.7 97.7c0 53.9-43.8 97.7-97.7 97.7z m112.3-445.1L348.5 411.8c-5.6 5.6-13 8.4-20.3 8.4s-14.7-2.8-20.3-8.4l-46-46c-11.2-11.2-11.2-29.4 0-40.6 11.2-11.2 29.4-11.2 40.7 0l25.6 25.6 94.6-94.6c11.2-11.2 29.4-11.2 40.7 0 11.1 11.3 11.1 29.5-0.1 40.7z" p-id="20929" fill="#9D95E5"></path><path d="M718.9 592.4c-95.2 0-172.4 77.2-172.4 172.4s77.2 172.4 172.4 172.4S891.3 860 891.3 764.8c-0.1-95.2-77.2-172.4-172.4-172.4z m76.2 248.7c-5.6 5.6-12.9 8.4-20.3 8.4s-14.7-2.8-20.3-8.4l-55.9-55.9c-0.4-0.4-0.6-1-1-1.5-2.1-2.4-4-4.9-5.2-7.8-1.4-3.4-2.1-6.9-2.1-10.5 0-0.2-0.1-0.3-0.1-0.5V681c0-15.8 12.9-28.7 28.7-28.7 15.8 0 28.7 12.9 28.7 28.7v72l47.5 47.5c11.2 11.2 11.2 29.3 0 40.6z" p-id="20930" fill="#9D95E5"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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