Merge branch 'pro' into kuanfan-pro

This commit is contained in:
weifashi 2023-12-18 14:53:33 +08:00
commit fad98dcc9d
77 changed files with 4873 additions and 1002 deletions

View File

@ -134,6 +134,37 @@ class DialogController extends AbstractController
return Base::retSuccess('success', $list); return Base::retSuccess('success', $list);
} }
/**
* @api {get} api/dialog/search/tag 02. 搜索标注会话
*
* @apiDescription 根据消息关键词搜索相关会话需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName search__tag
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function search__tag()
{
$user = User::auth();
// 搜索会话
$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)
->where('m.tag', '>', 0)
->orderByDesc('m.id')
->take(50)
->get();
$msgs->transform(function (WebSocketDialog $item) use ($user) {
return $item->formatData($user->userid);
});
//
return Base::retSuccess('success', $msgs->toArray());
}
/** /**
* @api {get} api/dialog/one 03. 获取单个会话信息 * @api {get} api/dialog/one 03. 获取单个会话信息
* *
@ -1330,6 +1361,8 @@ class DialogController extends AbstractController
* @apiParam {Number} msg_id 消息ID * @apiParam {Number} msg_id 消息ID
* @apiParam {Array} dialogids 转发给的对话ID * @apiParam {Array} dialogids 转发给的对话ID
* @apiParam {Array} userids 转发给的成员ID * @apiParam {Array} userids 转发给的成员ID
* @apiParam {Number} show_source 是否显示原发送者信息
* @apiParam {Array} leave_message 转发留言
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
@ -1342,6 +1375,8 @@ class DialogController extends AbstractController
$msg_id = intval(Request::input("msg_id")); $msg_id = intval(Request::input("msg_id"));
$dialogids = Request::input('dialogids'); $dialogids = Request::input('dialogids');
$userids = Request::input('userids'); $userids = Request::input('userids');
$show_source = intval(Request::input("show_source"));
$leave_message = Request::input('leave_message');
// //
if (empty($dialogids) && empty($userids)) { if (empty($dialogids) && empty($userids)) {
return Base::retError("请选择转发对话或成员"); return Base::retError("请选择转发对话或成员");
@ -1353,7 +1388,7 @@ class DialogController extends AbstractController
} }
WebSocketDialog::checkDialog($msg->dialog_id); WebSocketDialog::checkDialog($msg->dialog_id);
// //
return $msg->forwardMsg($dialogids, $userids, $user); return $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
} }
/** /**
@ -1956,4 +1991,179 @@ class DialogController extends AbstractController
} }
return Base::retSuccess('success', $dialog); return Base::retSuccess('success', $dialog);
} }
/**
* @api {post} api/dialog/msg/wordchain 44. 发送接龙消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__wordchain
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} uuid 接龙ID
* @apiParam {String} text 接龙内容
* @apiParam {Array} list 接龙列表
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__wordchain()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$uuid = trim(Request::input('uuid'));
$text = trim(Request::input('text'));
$list = Request::input('list');
//
WebSocketDialog::checkDialog($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字');
}
//
$userid = $user->userid;
if ($uuid) {
$dialogMsg = WebSocketDialogMsg::whereDialogId($dialog_id)
->whereType('word-chain')
->orderByDesc('created_at')
->where('msg', 'like', "%$uuid%")
->value('msg');
$list = array_reverse(array_merge($dialogMsg['list'] ?? [], $list));
$list = array_reduce($list, function ($result, $item) {
$fieldValue = $item['id']; // 指定字段名
if (!isset($result[$fieldValue])) {
$result[$fieldValue] = $item;
}
return $result;
}, []);
$list = array_reverse(array_values($list));
}
//
$msgData = [
'text' => $text,
'list' => $list,
'userid' => $userid,
'uuid' => $uuid ?: Base::generatePassword(36),
];
return WebSocketDialogMsg::sendMsg(null, $dialog_id, 'word-chain', $msgData, $user->userid);
}
/**
* @api {post} api/dialog/msg/vote 45. 发起投票
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__vote
*
* @apiParam {Number} dialog_id 对话ID
* @apiParam {String} text 投票内容
* @apiParam {Array} type 投票类型
* @apiParam {String} [uuid] 投票ID
* @apiParam {Array} [list] 投票列表
* @apiParam {Number} [multiple] 多选
* @apiParam {Number} [anonymous] 匿名
* @apiParam {Array} [vote] 投票
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__vote()
{
$user = User::auth();
//
$dialog_id = intval(Request::input('dialog_id'));
$uuid = trim(Request::input('uuid'));
$text = trim(Request::input('text'));
$type = trim(Request::input('type', 'create'));
$multiple = intval(Request::input('multiple')) ?: 0;
$anonymous = intval(Request::input('anonymous')) ?: 0;
$list = Request::input('list');
$vote = Request::input('vote') ?: [];
$votes = is_array($vote) ? $vote : [$vote];
//
WebSocketDialog::checkDialog($dialog_id);
//
$action = null;
$userid = $user->userid;
$result = [];
if ($type != 'create') {
if ($type == 'vote' && empty($votes)) {
return Base::retError('参数错误');
}
if (empty($uuid)) {
return Base::retError('参数错误');
}
$dialogMsgs = WebSocketDialogMsg::whereDialogId($dialog_id)
->whereType('vote')
->orderByDesc('created_at')
->where('msg', 'like', "%$uuid%")
->get();
//
if ($type == 'again') {
$res = WebSocketDialogMsg::sendMsg(null, $dialog_id, 'vote', $dialogMsgs[0]->msg, $user->userid);
if (Base::isError($res)) {
return $res;
}
$result[] = $res['data'];
} else {
foreach ($dialogMsgs as $dialogMsg) {
$action = "change-{$dialogMsg->id}";
$msgData = $dialogMsg->msg;
if ($type == 'finish') {
$msgData['state'] = 0;
} else {
$msgDataVotes = $msgData['votes'] ?? [];
if (in_array($userid, array_column($msgDataVotes, 'userid'))) {
return Base::retError('不能重复投票');
}
$msgDataVotes[] = [
'userid' => $userid,
'votes' => $votes,
];
$msgData['votes'] = $msgDataVotes;
}
$res = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'vote', $msgData, $user->userid);
if (Base::isError($res)) {
return $res;
}
$result[] = $res['data'];
}
}
} else {
$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字');
}
$msgData = [
'text' => $text,
'list' => $list,
'userid' => $userid,
'uuid' => $uuid ?: Base::generatePassword(36),
'multiple' => $multiple,
'anonymous' => $anonymous,
'votes' => [],
'state' => 1
];
$res = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'vote', $msgData, $user->userid);
if (Base::isError($res)) {
return $res;
}
$result[] = $res['data'];
}
return Base::retSuccess('发送成功', $result);
}
} }

View File

@ -2,10 +2,10 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use Hhxsv5\LaravelS\Swoole\Task\Task; use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialog;
use App\Exceptions\ApiException; use App\Exceptions\ApiException;
use App\Models\AbstractModel; use App\Models\AbstractModel;
use App\Tasks\FilePackTask;
use App\Models\File; use App\Models\File;
use App\Models\FileContent; use App\Models\FileContent;
use App\Models\FileLink; use App\Models\FileLink;
@ -480,6 +480,8 @@ class FileController extends AbstractController
$only_update_at = Request::input('only_update_at', 'no'); $only_update_at = Request::input('only_update_at', 'no');
$history_id = intval(Request::input('history_id')); $history_id = intval(Request::input('history_id'));
// //
Base::checkClientVersion('0.31.75');
//
if (Base::isNumber($id)) { if (Base::isNumber($id)) {
$user = User::auth(); $user = User::auth();
$file = File::permissionFind(intval($id), $user, $down == 'yes' ? 1 : 0); $file = File::permissionFind(intval($id), $user, $down == 'yes' ? 1 : 0);
@ -1018,15 +1020,15 @@ class FileController extends AbstractController
} }
$zip = new \ZipArchive(); $zip = new \ZipArchive();
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName; $zipName = 'tmp/file/' . date("Ym") . '/' . $user->userid . '/' . $downName;
$zipPath = storage_path('app/'.$zipName); $zipPath = public_path($zipName);
Base::makeDir(dirname($zipPath)); Base::makeDir(dirname($zipPath));
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
return Base::retError('创建压缩文件失败'); return Base::retError('创建压缩文件失败');
} }
go(function() use ($zip, $files, $downName) { go(function() use ($user, $zip, $files, $downName, $zipName) {
Coroutine::sleep(0.1); Coroutine::sleep(0.1);
// 压缩进度 // 压缩进度
$progress = 0; $progress = 0;
@ -1049,6 +1051,19 @@ class FileController extends AbstractController
'progress' => 100 'progress' => 100
]); ]);
} }
//
$botUser = User::botGetOrCreate('system-msg');
if (empty($botUser)) {
return;
}
if ($dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
$text = "<b>文件下载打包已完成。</b>";
$text .= "\n\n";
$text .= "文件名:{$downName}";
$text .= "\n";
$text .= "下载地址:".Base::fillUrl($zipName);
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
}
}); });
return Base::retSuccess('success'); return Base::retSuccess('success');
@ -1072,8 +1087,8 @@ class FileController extends AbstractController
{ {
$user = User::auth(); $user = User::auth();
$downName = Request::input('name'); $downName = Request::input('name');
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName; $zipName = 'tmp/file/' . date("Ym") . '/' . $user->userid . '/' . $downName;
$zipPath = storage_path('app/'.$zipName); $zipPath = public_path($zipName);
if (!file_exists($zipPath)) { if (!file_exists($zipPath)) {
abort(403, "The file does not exist."); abort(403, "The file does not exist.");
} }

View File

@ -12,6 +12,7 @@ use App\Module\Doo;
use App\Models\File; use App\Models\File;
use App\Models\User; use App\Models\User;
use App\Module\Base; use App\Module\Base;
use Swoole\Coroutine;
use App\Models\Deleted; use App\Models\Deleted;
use App\Models\Project; use App\Models\Project;
use App\Module\TimeRange; use App\Module\TimeRange;
@ -30,7 +31,9 @@ use App\Models\ProjectTaskFile;
use App\Models\ProjectTaskUser; use App\Models\ProjectTaskUser;
use App\Models\WebSocketDialog; use App\Models\WebSocketDialog;
use App\Exceptions\ApiException; use App\Exceptions\ApiException;
use App\Models\ProjectPermission;
use App\Module\BillMultipleExport; use App\Module\BillMultipleExport;
use App\Models\WebSocketDialogMsg;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Models\ProjectTaskFlowChange; use App\Models\ProjectTaskFlowChange;
@ -575,6 +578,8 @@ class ProjectController extends AbstractController
$project = Project::userProject($project_id); $project = Project::userProject($project_id);
// //
if ($only_column) { if ($only_column) {
//
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_SORT);
// 排序列表 // 排序列表
$index = 0; $index = 0;
foreach ($sort as $item) { foreach ($sort as $item) {
@ -760,6 +765,8 @@ class ProjectController extends AbstractController
// 项目 // 项目
$project = Project::userProject($project_id); $project = Project::userProject($project_id);
// //
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_ADD);
//
if (empty($name)) { if (empty($name)) {
return Base::retError('列表名称不能为空'); return Base::retError('列表名称不能为空');
} }
@ -809,7 +816,9 @@ class ProjectController extends AbstractController
return Base::retError('列表不存在'); return Base::retError('列表不存在');
} }
// 项目 // 项目
Project::userProject($column->project_id); $project = Project::userProject($column->project_id);
//
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_UPDATE);
// //
if (Arr::exists($data, 'name') && $column->name != $data['name']) { if (Arr::exists($data, 'name') && $column->name != $data['name']) {
$column->addLog("修改列表名称:{$column->name} => {$data['name']}"); $column->addLog("修改列表名称:{$column->name} => {$data['name']}");
@ -849,7 +858,9 @@ class ProjectController extends AbstractController
return Base::retError('列表不存在'); return Base::retError('列表不存在');
} }
// 项目 // 项目
Project::userProject($column->project_id, true, true); $project = Project::userProject($column->project_id);
//
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_REMOVE);
// //
$column->deleteColumn(); $column->deleteColumn();
return Base::retSuccess('删除成功', ['id' => $column->id]); return Base::retSuccess('删除成功', ['id' => $column->id]);
@ -1180,6 +1191,8 @@ class ProjectController extends AbstractController
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) { if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
return Base::retError('时间范围限制最大90天'); return Base::retError('时间范围限制最大90天');
} }
go(function() use ($user, $userid, $time, $type) {
Coroutine::sleep(0.1);
$headings = []; $headings = [];
$headings[] = '任务ID'; $headings[] = '任务ID';
$headings[] = '父级任务ID'; $headings[] = '父级任务ID';
@ -1337,13 +1350,23 @@ class ProjectController extends AbstractController
'file' => $zipFile, 'file' => $zipFile,
])); ]));
Session::put('task::export:userid', $user->userid); Session::put('task::export:userid', $user->userid);
return Base::retSuccess('success', [ $botUser = User::botGetOrCreate('system-msg');
'size' => Base::twoFloat(filesize($zipPath) / 1024, true), if (empty($botUser)) {
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)), return;
]);
} else {
return Base::retError('打包失败,请稍后再试...');
} }
if ($dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
$text = "<b>导出任务统计已完成。</b>";
$text .= "\n\n";
$text .= "文件名:{$fileName}";
$text .= "\n";
$text .= "文件大小:".Base::twoFloat(filesize($zipPath) / 1024, true)."KB";
$text .= "\n";
$text .= "下载地址:".Base::fillUrl('api/project/task/down?key=' . urlencode($base64));
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
}
}
});
return Base::retSuccess('success',['msg' => '正在打包,请留意系统消息']);
} }
/** /**
@ -1507,7 +1530,7 @@ class ProjectController extends AbstractController
$archived = Request::input('archived', 'no'); $archived = Request::input('archived', 'no');
// //
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived); $isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
$task = ProjectTask::userTask($task_id, $isArchived, true, false, ['taskUser', 'taskTag']); $task = ProjectTask::userTask($task_id, $isArchived, true, ['taskUser', 'taskTag']);
// 项目可见性 // 项目可见性
$project_userid = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->value('userid'); // 项目负责人 $project_userid = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->value('userid'); // 项目负责人
if ($task->visibility != 1 && $user->userid != $project_userid) { if ($task->visibility != 1 && $user->userid != $project_userid) {
@ -1603,7 +1626,9 @@ class ProjectController extends AbstractController
return Base::retError('文件不存在或已被删除'); return Base::retError('文件不存在或已被删除');
} }
// //
$task = ProjectTask::userTask($file->task_id, true, true, true); $task = ProjectTask::userTask($file->task_id);
//
ProjectPermission::userTaskPermission(Project::userProject($task->project_id), ProjectPermission::TASK_UPDATE, $task);
// //
$task->pushMsg('filedelete', $file); $task->pushMsg('filedelete', $file);
$file->delete(); $file->delete();
@ -1732,6 +1757,8 @@ class ProjectController extends AbstractController
$column_id = $data['column_id']; $column_id = $data['column_id'];
// 项目 // 项目
$project = Project::userProject($project_id); $project = Project::userProject($project_id);
//
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_ADD);
// 列表 // 列表
$column = null; $column = null;
$newColumn = null; $newColumn = null;
@ -1808,11 +1835,13 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
$name = Request::input('name'); $name = Request::input('name');
// //
$task = ProjectTask::userTask($task_id, true, true, true); $task = ProjectTask::userTask($task_id);
if ($task->complete_at) { if ($task->complete_at) {
return Base::retError('主任务已完成无法添加子任务'); return Base::retError('主任务已完成无法添加子任务');
} }
// //
ProjectPermission::userTaskPermission(Project::userProject($task->project_id), ProjectPermission::TASK_ADD);
//
$task = ProjectTask::addTask([ $task = ProjectTask::addTask([
'name' => $name, 'name' => $name,
'parent_id' => $task->id, 'parent_id' => $task->id,
@ -1867,11 +1896,18 @@ class ProjectController extends AbstractController
$param = Request::input(); $param = Request::input();
$task_id = intval($param['task_id']); $task_id = intval($param['task_id']);
// //
$task = ProjectTask::userTask($task_id, true, true, 2); $task = ProjectTask::userTask($task_id);
//
$project = Project::userProject($task->project_id);
if (Arr::exists($param, 'flow_item_id')) {
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_STATUS, $task);
}else{
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_UPDATE, $task);
}
//
$taskUser = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($task_id)->get(); $taskUser = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($task_id)->get();
$owners = $taskUser->where('owner', 1)->pluck('userid')->toArray(); // 负责人 $owners = $taskUser->where('owner', 1)->pluck('userid')->toArray(); // 负责人
$assists = $taskUser->where('owner', 0)->pluck('userid')->toArray(); // 协助人 $assists = $taskUser->where('owner', 0)->pluck('userid')->toArray(); // 协助人
// 更新任务 // 更新任务
$updateMarking = []; $updateMarking = [];
$task->updateTask($param, $updateMarking); $task->updateTask($param, $updateMarking);
@ -2003,12 +2039,15 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
$type = Request::input('type', 'add'); $type = Request::input('type', 'add');
// //
$task = ProjectTask::userTask($task_id, $type == 'add', true, true); $task = ProjectTask::userTask($task_id, $type == 'add');
// //
if ($task->parent_id > 0) { if ($task->parent_id > 0) {
return Base::retError('子任务不支持此功能'); return Base::retError('子任务不支持此功能');
} }
// //
$project = Project::userProject($task->project_id);
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_ARCHIVED, $task);
//
if ($type == 'recovery') { if ($type == 'recovery') {
$task->archivedTask(null); $task->archivedTask(null);
} elseif ($type == 'add') { } elseif ($type == 'add') {
@ -2045,7 +2084,11 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
$type = Request::input('type', 'delete'); $type = Request::input('type', 'delete');
// //
$task = ProjectTask::userTask($task_id, null, $type !== 'recovery', true); $task = ProjectTask::userTask($task_id, null, $type !== 'recovery');
//
$project = Project::userProject($task->project_id);
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_REMOVE, $task);
//
if ($type == 'recovery') { if ($type == 'recovery') {
$task->restoreTask(); $task->restoreTask();
return Base::retSuccess('操作成功', ['id' => $task->id]); return Base::retSuccess('操作成功', ['id' => $task->id]);
@ -2080,7 +2123,7 @@ class ProjectController extends AbstractController
return Base::retError('记录不存在'); return Base::retError('记录不存在');
} }
// //
$task = ProjectTask::userTask($projectLog->task_id, true, true, true); $task = ProjectTask::userTask($projectLog->task_id);
// //
$record = $projectLog->record; $record = $projectLog->record;
if ($record['flow'] && is_array($record['flow'])) { if ($record['flow'] && is_array($record['flow'])) {
@ -2123,6 +2166,7 @@ class ProjectController extends AbstractController
* @apiName task__flow * @apiName task__flow
* *
* @apiParam {Number} task_id 任务ID * @apiParam {Number} task_id 任务ID
* @apiParam {Number} project_id 项目ID - 存在时只返回这个项目的
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
@ -2133,18 +2177,24 @@ class ProjectController extends AbstractController
User::auth(); User::auth();
// //
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
$project_id = intval(Request::input('project_id'));
// //
$projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->withTrashed()->find($task_id); $projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->withTrashed()->find($task_id);
if (empty($projectTask)) { if (empty($projectTask)) {
return Base::retError('任务不存在', ['task_id' => $task_id], -4002); return Base::retError('任务不存在', ['task_id' => $task_id], -4002);
} }
// //
$projectFlowItem = null;
if ($project_id) {
$projectFlow = ProjectFlow::whereProjectId($project_id)->orderByDesc('id')->first();
} else {
$projectFlowItem = $projectTask->flow_item_id ? ProjectFlowItem::with(['projectFlow'])->find($projectTask->flow_item_id) : null; $projectFlowItem = $projectTask->flow_item_id ? ProjectFlowItem::with(['projectFlow'])->find($projectTask->flow_item_id) : null;
if ($projectFlowItem?->projectFlow) { if ($projectFlowItem?->projectFlow) {
$projectFlow = $projectFlowItem->projectFlow; $projectFlow = $projectFlowItem->projectFlow;
} else { } else {
$projectFlow = ProjectFlow::whereProjectId($projectTask->project_id)->orderByDesc('id')->first(); $projectFlow = ProjectFlow::whereProjectId($projectTask->project_id)->orderByDesc('id')->first();
} }
}
if (empty($projectFlow)) { if (empty($projectFlow)) {
return Base::retSuccess('success', [ return Base::retSuccess('success', [
'task_id' => $projectTask->id, 'task_id' => $projectTask->id,
@ -2207,6 +2257,9 @@ class ProjectController extends AbstractController
* @apiParam {Number} task_id 任务ID * @apiParam {Number} task_id 任务ID
* @apiParam {Number} project_id 项目ID * @apiParam {Number} project_id 项目ID
* @apiParam {Number} column_id 列ID * @apiParam {Number} column_id 列ID
* @apiParam {Number} flow_item_id 工作流id
* @apiParam {Array} owner 负责人
* @apiParam {Array} assist 协助人
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
@ -2219,8 +2272,14 @@ class ProjectController extends AbstractController
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
$project_id = intval(Request::input('project_id')); $project_id = intval(Request::input('project_id'));
$column_id = intval(Request::input('column_id')); $column_id = intval(Request::input('column_id'));
$flow_item_id = intval(Request::input('flow_item_id'));
$owner = Request::input('owner', []);
$assist = Request::input('assist', []);
// //
$task = ProjectTask::userTask($task_id, true, true, 2); $task = ProjectTask::userTask($task_id);
//
$project = Project::userProject($task->project_id);
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_MOVE, $task);
// //
if( $task->project_id == $project_id && $task->column_id == $column_id){ if( $task->project_id == $project_id && $task->column_id == $column_id){
return Base::retSuccess('移动成功', ['id' => $task_id]); return Base::retSuccess('移动成功', ['id' => $task_id]);
@ -2231,10 +2290,18 @@ class ProjectController extends AbstractController
if (empty($column)) { if (empty($column)) {
return Base::retError('列表不存在'); return Base::retError('列表不存在');
} }
if($flow_item_id){
$flowItem = projectFlowItem::whereProjectId($project->id)->whereId($flow_item_id)->first();
if (empty($flowItem)) {
return Base::retError('任务状态不存在');
}
}
// //
$task->moveTask($project_id,$column_id); $task->moveTask($project_id, $column_id, $flow_item_id, $owner, $assist);
// //
return Base::retSuccess('移动成功', ['id' => $task_id]); $task = ProjectTask::userTask($task_id);
//
return Base::retSuccess('移动成功', $task);
} }
/** /**
@ -2411,4 +2478,74 @@ class ProjectController extends AbstractController
'top_at' => $projectUser->top_at?->toDateTimeString(), 'top_at' => $projectUser->top_at?->toDateTimeString(),
]); ]);
} }
/**
* @api {get} api/project/permission 43. 获取项目权限设置
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName permission
*
* @apiParam {Number} project_id 项目ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function permission()
{
$user = User::auth();
$projectId = intval(Request::input('project_id'), 0);
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
if (!$projectUser) {
return Base::retError("项目不存在");
}
$projectPermission = ProjectPermission::initPermissions($projectId);
return Base::retSuccess("success", $projectPermission);
}
/**
* @api {get} api/project/permission/update 44. 项目权限设置
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName permission__update
*
* @apiParam {Number} project_id 项目ID
* @apiParam {Array} task_add 添加任务权限
* @apiParam {Array} task_update 修改任务权限
* @apiParam {Array} task_remove 删除任务权限
* @apiParam {Array} task_update_complete 标记完成权限
* @apiParam {Array} task_archived 归档任务权限
* @apiParam {Array} task_move 移动任务权限
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function permission__update()
{
$user = User::auth();
$projectId = intval(Request::input('project_id'), 0);
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
if (!$projectUser) {
return Base::retError("项目不存在");
}
$permissions = Request::only([
ProjectPermission::TASK_LIST_ADD,
ProjectPermission::TASK_LIST_UPDATE,
ProjectPermission::TASK_LIST_REMOVE,
ProjectPermission::TASK_LIST_SORT,
ProjectPermission::TASK_ADD,
ProjectPermission::TASK_UPDATE,
ProjectPermission::TASK_REMOVE,
ProjectPermission::TASK_STATUS,
ProjectPermission::TASK_ARCHIVED,
ProjectPermission::TASK_MOVE,
]);
$projectPermission = ProjectPermission::updatePermissions($projectId, Base::newArrayRecursive('intval', $permissions));
return Base::retSuccess("success", $projectPermission);
}
} }

View File

@ -24,6 +24,7 @@ use Request;
* @property int|null $dialog_id 聊天会话ID * @property int|null $dialog_id 聊天会话ID
* @property string|null $archived_at 归档时间 * @property string|null $archived_at 归档时间
* @property int|null $archived_userid 归档会员 * @property int|null $archived_userid 归档会员
* @property int|null $is_fixed 是否固定
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
@ -47,6 +48,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDesc($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereDesc($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDialogId($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereIsFixed($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereName($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project wherePersonal($value) * @method static \Illuminate\Database\Eloquent\Builder|Project wherePersonal($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)

View File

@ -0,0 +1,202 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
/**
* App\Models\ProjectPermission
*
* @property int $id
* @property int|null $project_id 项目ID
* @property string|null $permissions 权限
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission wherePermissions($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereUpdatedAt($value)
* @mixin \Eloquent
*/
class ProjectPermission extends AbstractModel
{
const TASK_LIST_ADD = 'task_list_add'; // 添加列
const TASK_LIST_UPDATE = 'task_list_update'; // 修改列
const TASK_LIST_REMOVE = 'task_list_remove'; // 删除列
const TASK_LIST_SORT = 'task_list_sort'; // 列表排序
const TASK_ADD = 'task_add'; // 任务添加
const TASK_UPDATE = 'task_update'; // 任务更新
const TASK_STATUS = 'task_status'; // 任务状态
const TASK_REMOVE = 'task_remove'; // 任务删除
const TASK_ARCHIVED = 'task_archived'; // 任务归档
const TASK_MOVE = 'task_move'; // 任务移动
// 权限列表
const PERMISSIONS = [
'project_leader' => 1, // 项目负责人
'project_member' => 2, // 项目成员
'task_leader' => 3, // 任务负责人
'task_assist' => 4, // 任务协助人
];
// 权限描述
const PERMISSIONS_DESC = [
1 => "项目负责人",
2 => "项目成员",
3 => "任务负责人",
4 => "任务协助人",
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['project_id', 'permissions'];
/**
* 权限
* @param $value
* @return string
*/
public function getPermissionsAttribute($value)
{
return Base::json2array($value);
}
/**
* 获取权限值
*
* @param int $projectId
* @param string $key
* @return object|array
*/
public static function getPermission($projectId, $key = '')
{
$projectPermission = self::initPermissions($projectId);
$currentPermissions = $projectPermission->permissions;
if ($key) {
if (!isset($currentPermissions[$key])) {
throw new ApiException('项目权限设置不存在');
}
return $currentPermissions[$key];
}
return $projectPermission;
}
/**
* 初始化项目权限
*
* @param int $projectId
* @return ProjectPermission
*/
public static function initPermissions($projectId)
{
$permissions = [
self::TASK_LIST_ADD => $projectTaskList = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['project_member']],
self::TASK_LIST_UPDATE => $projectTaskList,
self::TASK_LIST_REMOVE => [self::PERMISSIONS['project_leader']],
self::TASK_LIST_SORT => $projectTaskList,
self::TASK_ADD => $projectTaskList,
self::TASK_UPDATE => [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader'], self::PERMISSIONS['task_assist']],
self::TASK_STATUS => $taskStatus = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader']],
self::TASK_REMOVE => $taskStatus,
self::TASK_ARCHIVED => $taskStatus,
self::TASK_MOVE => $taskStatus
];
return self::firstOrCreate(
['project_id' => $projectId],
['permissions' => Base::array2json($permissions)]
);
}
/**
* 更新项目权限
*
* @param int $projectId
* @param array $permissions
* @return ProjectPermission
*/
public static function updatePermissions($projectId, $newPermissions)
{
$projectPermission = self::initPermissions($projectId);
$currentPermissions = $projectPermission->permissions;
$mergedPermissions = empty($newPermissions) ? $currentPermissions : array_merge($currentPermissions, $newPermissions);
$projectPermission->permissions = Base::array2json($mergedPermissions);
$projectPermission->save();
return $projectPermission;
}
/**
* 检查用户是否有执行特定动作的权限
* @param string $action 动作名称
* @param Project $project 项目实例
* @param ProjectTask $task 任务实例
* @return bool
*/
public static function userTaskPermission(Project $project, $action, ProjectTask $task = null)
{
$userid = User::userid();
$permissions = self::getPermission($project->id, $action);
switch ($action) {
// 任务添加,任务更新, 任务状态, 任务删除, 任务完成, 任务归档, 任务移动
case self::TASK_LIST_ADD:
case self::TASK_LIST_UPDATE:
case self::TASK_LIST_REMOVE:
case self::TASK_LIST_SORT:
case self::TASK_ADD:
case self::TASK_UPDATE:
case self::TASK_STATUS:
case self::TASK_REMOVE:
case self::TASK_ARCHIVED:
case self::TASK_MOVE:
$verify = false;
// 项目负责人
if (in_array(self::PERMISSIONS['project_leader'], $permissions)) {
if ($project->owner) {
$verify = true;
}
}
// 项目成员
if (!$verify && in_array(self::PERMISSIONS['project_member'], $permissions)) {
$user = ProjectUser::whereProjectId($project->id)->whereUserid(intval($userid))->first();
if (!empty($user)) {
$verify = true;
}
}
// 任务负责人
if (!$verify && $task && in_array(self::PERMISSIONS['task_leader'], $permissions)) {
if ($task->isOwner()) {
$verify = true;
}
}
// 任务协助人
if (!$verify && $task && in_array(self::PERMISSIONS['task_assist'], $permissions)) {
if ($task->isAssister()) {
$verify = true;
}
}
//
if (!$verify) {
$desc = [];
rsort($permissions);
foreach ($permissions as $permission) {
$desc[] = self::PERMISSIONS_DESC[$permission];
}
$desc = array_reverse($desc);
throw new ApiException(sprintf("仅限%s操作", implode('、', $desc)));
}
break;
}
return true;
}
}

View File

@ -32,7 +32,8 @@ use Request;
* @property int|null $archived_follow 跟随项目归档(项目取消归档时任务也取消归档) * @property int|null $archived_follow 跟随项目归档(项目取消归档时任务也取消归档)
* @property string|null $complete_at 完成时间 * @property string|null $complete_at 完成时间
* @property int|null $userid 创建人 * @property int|null $userid 创建人
* @property int|null $is_all_visible 是否所有人可见 * @property int|null $visibility 任务可见性1-项目人员 2-任务人员 3-指定成员
* @property int|null $is_default 是否默认任务
* @property int|null $p_level 优先级 * @property int|null $p_level 优先级
* @property string|null $p_name 优先级名称 * @property string|null $p_name 优先级名称
* @property string|null $p_color 优先级颜色 * @property string|null $p_color 优先级颜色
@ -81,7 +82,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereIsAllVisible($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereIsDefault($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoop($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoop($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoopAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
@ -94,6 +95,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereVisibility($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
@ -549,15 +551,12 @@ class ProjectTask extends AbstractModel
*/ */
public function updateTask($data, &$updateMarking = []) public function updateTask($data, &$updateMarking = [])
{ {
//
AbstractModel::transaction(function () use ($data, &$updateMarking) { AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 主任务 // 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null; $mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流 // 工作流
if (Arr::exists($data, 'flow_item_id')) { if (Arr::exists($data, 'flow_item_id')) {
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
if (!$isProjectOwner && !$this->isOwner()) {
throw new ApiException('仅限项目或任务负责人修改任务状态');
}
if ($this->flow_item_id == $data['flow_item_id']) { if ($this->flow_item_id == $data['flow_item_id']) {
throw new ApiException('任务状态未发生改变'); throw new ApiException('任务状态未发生改变');
} }
@ -578,6 +577,7 @@ class ProjectTask extends AbstractModel
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]"); throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
} }
if ($currentFlowItem->userlimit) { if ($currentFlowItem->userlimit) {
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) { if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改"); throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
} }
@ -1669,11 +1669,19 @@ class ProjectTask extends AbstractModel
* 移动任务 * 移动任务
* @param int $project_id * @param int $project_id
* @param int $column_id * @param int $column_id
* @param int $flowItemId
* @param array $owner
* @param array $assist
* @return bool * @return bool
*/ */
public function moveTask(int $projectId, int $columnId) public function moveTask(int $projectId, int $columnId,int $flowItemId = 0,array $owner = [], array $assist = [])
{ {
AbstractModel::transaction(function () use($projectId, $columnId) { AbstractModel::transaction(function () use($projectId, $columnId, $flowItemId, $owner, $assist) {
$newTaskUser = array_merge($owner, $assist);
//
$this->project_id = $projectId;
$this->column_id = $columnId;
$this->flow_item_id = $flowItemId;
// 任务内容 // 任务内容
if($this->content){ if($this->content){
$this->content->project_id = $projectId; $this->content->project_id = $projectId;
@ -1690,13 +1698,22 @@ class ProjectTask extends AbstractModel
$taskTag->save(); $taskTag->save();
} }
// 任务用户 // 任务用户
$this->updateTask(['owner' => $owner]);
$this->updateTask(['assist' => $assist]);
foreach ($this->taskUser as $taskUser){ foreach ($this->taskUser as $taskUser){
if( in_array($taskUser->id, $newTaskUser) ){
$taskUser->project_id = $projectId; $taskUser->project_id = $projectId;
$taskUser->save(); $taskUser->save();
} }
}
//
if($flowItemId){
$flowItem = projectFlowItem::whereProjectId($projectId)->whereId($flowItemId)->first();
$this->flow_item_name = $flowItem->status . "|" . $flowItem->name;
}else{
$this->flow_item_name = '';
}
// //
$this->project_id = $projectId;
$this->column_id = $columnId;
$this->save(); $this->save();
// //
$this->addLog("移动{任务}"); $this->addLog("移动{任务}");
@ -1728,14 +1745,10 @@ class ProjectTask extends AbstractModel
* @param int $task_id * @param int $task_id
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制 * @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制 * @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
* @param int|bool $permission
* - 0|false 限制:项目成员、任务成员、任务群聊成员(任务成员 = 任务创建人+任务协助人+任务负责人)
* - 1|true 限制:项目负责人、任务成员
* - 2 已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param array $with * @param array $with
* @return self * @return self
*/ */
public static function userTask($task_id, $archived = true, $trashed = true, $permission = false, $with = []) public static function userTask($task_id, $archived = true, $trashed = true, $with = [])
{ {
$builder = self::with($with)->allData()->where("project_tasks.id", intval($task_id)); $builder = self::with($with)->allData()->where("project_tasks.id", intval($task_id));
if ($trashed === false) { if ($trashed === false) {
@ -1758,7 +1771,7 @@ class ProjectTask extends AbstractModel
try { try {
$project = Project::userProject($task->project_id); $project = Project::userProject($task->project_id);
} catch (\Throwable $e) { } catch (\Throwable $e) {
if ($task->owner !== null || (!$permission && $task->permission(4))) { if ($task->owner !== null || $task->permission(4)) {
$project = Project::find($task->project_id); $project = Project::find($task->project_id);
if (empty($project)) { if (empty($project)) {
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002); throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
@ -1768,13 +1781,6 @@ class ProjectTask extends AbstractModel
} }
} }
// //
if ($permission >= 2) {
$permission = $task->hasOwner() ? 1 : 0;
}
if ($permission && !$project->owner && !$task->permission(3)) {
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
}
//
return $task; return $task;
} }
} }

View File

@ -60,6 +60,8 @@ class ProjectTaskContent extends AbstractModel
*/ */
public static function saveContent($task_id, $content) public static function saveContent($task_id, $content)
{ {
@ini_set("pcre.backtrack_limit", 999999999);
//
$oldContent = $content; $oldContent = $content;
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/'; $path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
// //
@ -81,9 +83,6 @@ class ProjectTaskContent extends AbstractModel
Base::makeDir(dirname($publicPath)); Base::makeDir(dirname($publicPath));
$result = file_put_contents($publicPath, $content); $result = file_put_contents($publicPath, $content);
if(!$result && $oldContent){ if(!$result && $oldContent){
info("保存任务详情至文件失败");
info($publicPath);
info($oldContent);
throw new ApiException("保存任务详情至文件失败,请重试"); throw new ApiException("保存任务详情至文件失败,请重试");
} }
// //

View File

@ -72,6 +72,7 @@ class WebSocketDialogMsg extends AbstractModel
protected $appends = [ protected $appends = [
'percentage', 'percentage',
'reply_data', 'reply_data',
'forward_data',
]; ];
protected $hidden = [ protected $hidden = [
@ -114,6 +115,21 @@ class WebSocketDialogMsg extends AbstractModel
return $this->appendattrs['reply_data']; return $this->appendattrs['reply_data'];
} }
/**
* 转发消息详情
* @return WebSocketDialogMsg|null
*/
public function getForwardDataAttribute()
{
if (!isset($this->appendattrs['forward_data'])) {
$this->appendattrs['forward_data'] = null;
if ($this->forward_id > 0) {
$this->appendattrs['forward_data'] = self::find($this->forward_id, ['id', 'userid', 'type', 'msg'])?->cancelAppend() ?: null;
}
}
return $this->appendattrs['forward_data'];
}
/** /**
* 消息格式化 * 消息格式化
* @param $value * @param $value
@ -369,11 +385,13 @@ class WebSocketDialogMsg extends AbstractModel
* @param array|int $dialogids * @param array|int $dialogids
* @param array|int $userids * @param array|int $userids
* @param User $user 发送的会员 * @param User $user 发送的会员
* @param int $showSource 是否显示原发送者信息
* @param string $leaveMessage 转发留言
* @return mixed * @return mixed
*/ */
public function forwardMsg($dialogids, $userids, $user) public function forwardMsg($dialogids, $userids, $user, $showSource = 1, $leaveMessage = '')
{ {
return AbstractModel::transaction(function() use ($dialogids, $user, $userids) { return AbstractModel::transaction(function() use ($dialogids, $user, $userids, $showSource, $leaveMessage) {
$originalMsg = Base::json2array($this->getRawOriginal('msg')); $originalMsg = Base::json2array($this->getRawOriginal('msg'));
$msgs = []; $msgs = [];
$already = []; $already = [];
@ -382,11 +400,14 @@ class WebSocketDialogMsg extends AbstractModel
$dialogids = [$dialogids]; $dialogids = [$dialogids];
} }
foreach ($dialogids as $dialogid) { foreach ($dialogids as $dialogid) {
$res = self::sendMsg(null, $dialogid, $this->type, $originalMsg, $user->userid); $res = self::sendMsg('forward-'.( $showSource ? 1 : 0).'-'.($this->forward_id ?: $this->id), $dialogid, $this->type, $originalMsg, $user->userid);
if (Base::isSuccess($res)) { if (Base::isSuccess($res)) {
$msgs[] = $res['data']; $msgs[] = $res['data'];
$already[] = $dialogid; $already[] = $dialogid;
} }
if ($leaveMessage) {
self::sendMsg(null, $dialogid, 'text', ['text' => $leaveMessage], $user->userid);
}
} }
} }
if ($userids) { if ($userids) {
@ -399,10 +420,13 @@ class WebSocketDialogMsg extends AbstractModel
} }
$dialog = WebSocketDialog::checkUserDialog($user, $userid); $dialog = WebSocketDialog::checkUserDialog($user, $userid);
if ($dialog && !in_array($dialog->id, $already)) { if ($dialog && !in_array($dialog->id, $already)) {
$res = self::sendMsg(null, $dialog->id, $this->type, $originalMsg, $user->userid); $res = self::sendMsg('forward-'.( $showSource ? 1 : 0).'-'.($this->forward_id ?: $this->id), $dialog->id, $this->type, $originalMsg, $user->userid);
if (Base::isSuccess($res)) { if (Base::isSuccess($res)) {
$msgs[] = $res['data']; $msgs[] = $res['data'];
} }
if ($leaveMessage) {
self::sendMsg(null, $dialog->id, 'text', ['text' => $leaveMessage], $user->userid);
}
} }
} }
} }
@ -497,6 +521,8 @@ class WebSocketDialogMsg extends AbstractModel
} }
switch ($data['type']) { switch ($data['type']) {
case 'text': case 'text':
case 'word-chain':
case 'vote':
return $this->previewTextMsg($data['msg']['text'], $preserveHtml); return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
case 'record': case 'record':
return "[语音]"; return "[语音]";
@ -772,6 +798,7 @@ class WebSocketDialogMsg extends AbstractModel
* - reply-98回复消息ID=98 * - reply-98回复消息ID=98
* - update-99更新消息ID=99(标记修改) * - update-99更新消息ID=99(标记修改)
* - change-99更新消息ID=99(不标记修改) * - change-99更新消息ID=99(不标记修改)
* - forward-99转发消息ID=99
* @param int $dialog_id 会话ID 聊天室ID * @param int $dialog_id 会话ID 聊天室ID
* @param string $type 消息类型 * @param string $type 消息类型
* @param array $msg 发送的消息 * @param array $msg 发送的消息
@ -812,6 +839,7 @@ class WebSocketDialogMsg extends AbstractModel
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0; $update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
$change_id = preg_match("/^change-(\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; $reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
$forward_id = preg_match("/^forward-(\d+)-(\d+)$/", $action, $match) ? $match[2] : 0;
$sender = $sender === null ? User::userid() : $sender; $sender = $sender === null ? User::userid() : $sender;
// //
$dialog = WebSocketDialog::find($dialog_id); $dialog = WebSocketDialog::find($dialog_id);
@ -833,10 +861,10 @@ class WebSocketDialogMsg extends AbstractModel
if (empty($dialogMsg)) { if (empty($dialogMsg)) {
throw new ApiException('消息不存在'); throw new ApiException('消息不存在');
} }
if ($dialogMsg->type !== 'text') { if ($dialogMsg->type !== 'text' && $dialogMsg->type !== 'vote') {
throw new ApiException('此消息不支持此操作'); throw new ApiException('此消息不支持此操作');
} }
if ($dialogMsg->userid != $sender) { if ($dialogMsg->userid != $sender && $dialogMsg->type !== 'vote') {
throw new ApiException('仅支持修改自己的消息'); throw new ApiException('仅支持修改自己的消息');
} }
// //
@ -862,6 +890,10 @@ class WebSocketDialogMsg extends AbstractModel
if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) { if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) {
throw new ApiException('回复的消息不存在'); throw new ApiException('回复的消息不存在');
} }
// 转发
if ($forward_id && !self::whereId($forward_id)->increment('forward_num')) {
throw new ApiException('转发的消息不存在');
}
// //
$dialogMsg = self::createInstance([ $dialogMsg = self::createInstance([
'dialog_id' => $dialog_id, 'dialog_id' => $dialog_id,
@ -873,6 +905,8 @@ class WebSocketDialogMsg extends AbstractModel
'link' => $link, 'link' => $link,
'msg' => $msg, 'msg' => $msg,
'read' => 0, 'read' => 0,
'forward_id' => $forward_id,
'forward_show' => $forward_id ? $match[1] : 1,
]); ]);
AbstractModel::transaction(function () use ($dialog, $dialogMsg) { AbstractModel::transaction(function () use ($dialog, $dialogMsg) {
$dialog->last_at = Carbon::now(); $dialog->last_at = Carbon::now();

View File

@ -687,6 +687,22 @@ class Base
return $string; return $string;
} }
/**
* 递归处理数组
*
* @param string $callback 如:'intval''trim''addslashes''stripslashes''htmlspecialchars'
* @param array $array
* @return array
*/
public static function newArrayRecursive($callback, $array)
{
$func = function ($item) use (&$func, &$callback) {
return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
};
return array_map($func, $array);
}
/** /**
* 重MD5加密 * 重MD5加密
* @param $text * @param $text

View File

@ -7,6 +7,7 @@ use App\Models\TaskWorker;
use App\Models\Tmp; use App\Models\Tmp;
use App\Models\WebSocketTmpMsg; use App\Models\WebSocketTmpMsg;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\File as SupportFile;
/** /**
* 删除过期临时数据任务 * 删除过期临时数据任务
@ -102,10 +103,12 @@ class DeleteTmpTask extends AbstractTask
*/ */
case 'file_pack': case 'file_pack':
{ {
$path = storage_path('app/temp/download/'); $path = public_path('tmp/file/');
if (!SupportFile::exists($path)) {
return;
}
$dirIterator = new \RecursiveDirectoryIterator($path); $dirIterator = new \RecursiveDirectoryIterator($path);
$iterator = new \RecursiveIteratorIterator($dirIterator); $iterator = new \RecursiveIteratorIterator($dirIterator);
foreach ($iterator as $file) { foreach ($iterator as $file) {
if ($file->isFile()) { if ($file->isFile()) {
$time = $file->getMTime(); $time = $file->getMTime();

View File

@ -38,6 +38,10 @@ class WebSocketDialogMsgTask extends AbstractTask
{ {
parent::__construct(...func_get_args()); parent::__construct(...func_get_args());
$this->id = $id; $this->id = $id;
// 判断是否有Request方法兼容go协程请求
$this->ignoreFd = $ignoreFd;
$this->client = [];
if (method_exists(new Request,"header")) {
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd; $this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
$this->client = [ $this->client = [
'version' => Base::headerOrInput('version'), 'version' => Base::headerOrInput('version'),
@ -45,6 +49,7 @@ class WebSocketDialogMsgTask extends AbstractTask
'platform' => Base::headerOrInput('platform'), 'platform' => Base::headerOrInput('platform'),
]; ];
} }
}
/** /**
* @param $ignoreFd * @param $ignoreFd

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectPermissionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasTable('project_permissions'))
return;
Schema::create('project_permissions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->text('permissions')->nullable()->comment('权限');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_permissions');
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogMsgsAddForwardId extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'forward_id')) {
$table->bigInteger('forward_id')->nullable()->default(0)->after('reply_id')->comment('转发ID');
$table->bigInteger('forward_num')->nullable()->default(0)->after('forward_id')->comment('被转发多少次');
$table->boolean('forward_show')->nullable()->default(1)->after('forward_num')->comment('是否显示转发的来源');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("forward_id");
$table->dropColumn("forward_num");
$table->dropColumn("forward_show");
});
}
}

View File

@ -150,7 +150,7 @@ services:
approve: approve:
container_name: "dootask-approve-${APP_ID}" container_name: "dootask-approve-${APP_ID}"
image: "kuaifan/dooapprove:0.0.8" image: "kuaifan/dooapprove:0.0.9"
environment: environment:
TZ: "${TIMEZONE:-PRC}" TZ: "${TIMEZONE:-PRC}"
MYSQL_HOST: "${DB_HOST}" MYSQL_HOST: "${DB_HOST}"

View File

@ -480,3 +480,5 @@ Api接口文档
保存任务详情至文件失败 保存任务详情至文件失败
保存任务详情至文件失败,请重试 保存任务详情至文件失败,请重试
移动成功 移动成功
不能重复投票

View File

@ -1252,6 +1252,7 @@ Markdown 格式发送
OKR管理 OKR管理
OKR结果 OKR结果
OKR结果分析
计划时间冲突提示 计划时间冲突提示
忽略并继续 忽略并继续
你确定要清除缓存吗? 你确定要清除缓存吗?
@ -1412,4 +1413,70 @@ APP推送
你确定取消待办吗? 你确定取消待办吗?
取消成功 取消成功
请等待打包完成
选择一个项目查看更多任务
首页
无相关数据
当前环境
权限设置
任务列权限
添加列
修改列
删除列
排序列
任务权限
修改任务
修改状态
移动任务
任务协助人
搜索项目名称
服务器版本过低,请升级服务器。
不显示原发送者信息
转发给:
留言
多选
@我的
移动前
移动后
状态
协助人
未变更移动项
接龙
参与接龙
发起接龙
发起接龙,参与接龙目前共(*)人
请输入接龙主题
请输入接龙内容
可填写接龙格式
重复内容将不再计入接龙结果
返回编辑
继续发送
接龙结果
选择群组发起接龙
来自
发起投票
投票结果
发起
请输入投票主题
请输入选项内容
允许多选
匿名投票
投票
匿名
实名
单选
多选
请选择后投票
立即投票
再次发送
再次发送投票?
结束投票
确定结束投票?
已发送
选择群组发起投票
以下为新消息 以下为新消息

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.4.1, 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>
<g>
<g>
<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="M34.4,33.5H25l9.3-9.3c1.2-1.2,1.2-3.3,0-4.5l-6.8-6.8c-1.2-1.2-3.3-1.2-4.5,0L12.9,23.1
c-1.2,1.2-1.2,3.3,0,4.5l6,6h-6c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H22c0,0,0,0,0,0c0,0,0,0,0,0h12.4
c0.5,0,0.9-0.4,0.9-0.9C35.3,33.9,34.9,33.5,34.4,33.5z M17.1,25.5c0.3-0.3,0.9-0.3,1.2,0l3.4,3.4c0.1,0.1,0.3,0.1,0.4,0l6.7-6.7
c0.3-0.3,0.9-0.3,1.2,0c0.3,0.3,0.3,0.9,0,1.2l-6.7,6.7c-0.4,0.4-0.9,0.6-1.5,0.6c-0.5,0-1.1-0.2-1.5-0.6l-3.4-3.4
C16.8,26.4,16.8,25.9,17.1,25.5L17.1,25.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.4.1, 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{clip-path:url(#SVGID_00000018941778426702510620000006481262296390689922_);}
.st2{fill:#FFFFFF;}
</style>
<g>
<g>
<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>
<defs>
<rect id="SVGID_1_" x="12" y="12" width="24" height="24"/>
</defs>
<clipPath id="SVGID_00000141444668484078751200000003089482387513471677_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000141444668484078751200000003089482387513471677_);">
<g>
<path class="st2" d="M28.5,21c-4.1,0-7.5,3.4-7.5,7.5c0,4.1,3.4,7.5,7.5,7.5c4.1,0,7.5-3.4,7.5-7.5C36,24.4,32.6,21,28.5,21z
M30.6,29.4h-1.2v1.2c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9v-1.2h-1.2c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9
h1.2v-1.2c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9v1.2h1.2c0.5,0,0.9,0.4,0.9,0.9C31.5,29,31,29.4,30.6,29.4z"/>
</g>
<g>
<path class="st2" d="M15.3,23.9c0-4.8,3.9-8.7,8.7-8.7c0.6,0,1.2,0.1,1.8,0.2c-1.3-2.1-3.7-3.4-6.3-3.4c-4.1,0-7.5,3.4-7.5,7.5
c0,2.7,1.4,5,3.5,6.3C15.3,25.2,15.3,24.6,15.3,23.9z"/>
</g>
<g>
<path class="st2" d="M19.8,28.5c0-4.8,3.9-8.7,8.7-8.7c0.6,0,1.3,0.1,1.9,0.2c-1.3-2.1-3.7-3.6-6.4-3.6c-4.1,0-7.5,3.4-7.5,7.5
c0,2.7,1.4,5,3.5,6.3C19.8,29.7,19.8,29.1,19.8,28.5z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""] ["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]

View File

@ -470,6 +470,11 @@ img {
grid-template-columns: 480px 266px 266px 266px; grid-template-columns: 480px 266px 266px 266px;
} }
.grid-5 {
display: grid !important;
grid-template-columns: 336px 236px 236px 236px 236px;
}
/* padding样式 */ /* padding样式 */
.pl-26 { .pl-26 {
padding-left: 26px; padding-left: 26px;

View File

@ -139,18 +139,26 @@
} }
.plans-ol-item-h6{ .plans-ol-item-h6{
color: var(--txt-4ca5); color: var(--txt-4ca5);
line-height: normal;
} }
.plans-ol-item-icon{ .plans-ol-item-icon{
display: block; display: block;
height: 24px; height: 24px;
} }
.plans-ol-item-icon2{
display: block;
height: 24px;
opacity: 0.5;
}
.plans-ul-b{ .plans-ul-b{
display: grid; display: grid;
grid-template-columns: 506px 266px 266px 266px; grid-template-columns: 506px 266px 266px 266px;
} }
.plans-ul-b-item-btn{ .plans-ul-b-item-btn{
display: inline-block; display: inline-block;
width: 218px; width: 160px;
} }
.flex-s-c{ .flex-s-c{
display: flex; display: flex;
@ -202,6 +210,13 @@
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0 4px 12px rgba(0,0,0,.15); box-shadow: 0 4px 12px rgba(0,0,0,.15);
} }
@media screen and (max-width: 600px) {
.BulletBox1{
width: 80%;
}
}
.BulletBox1 .top{ .BulletBox1 .top{
width: 382px; width: 382px;
line-height: 20px; line-height: 20px;
@ -225,13 +240,12 @@
color: rgb(96, 98, 102); color: rgb(96, 98, 102);
} }
.BulletBox1 .bottom{ .BulletBox1 .bottom{
width: 370px; display: flex;
height: 34px; justify-content: end;
padding: 20px 30px 22px 30px; padding: 20px 30px 22px 30px;
position: relative; position: relative;
} }
.BulletBox1 .bottom .BulletBox1Btn{ .BulletBox1 .bottom .BulletBox1Btn{
position: absolute;
min-width: 100px; min-width: 100px;
right: 20px; right: 20px;
height: 34px; height: 34px;
@ -243,3 +257,11 @@
border-radius: 8px; border-radius: 8px;
padding: 5px; padding: 5px;
} }
.price-ceiling {
position: sticky;
top: 80px;
background-color: #fff;
border-bottom: 1px solid var(--border-color);
z-index: 9999;
}

View File

@ -80,6 +80,9 @@
.grid-4{ .grid-4{
grid-template-columns: 32vw 18vw 18vw 18vw !important; grid-template-columns: 32vw 18vw 18vw 18vw !important;
} }
.grid-5{
grid-template-columns: 26vw 18.5vw 18.5vw 18.5vw 18.5vw !important;
}
.plans-ul-b{ .plans-ul-b{
grid-template-columns: calc(34vw + 4px) 18vw 18vw 18vw !important; grid-template-columns: calc(34vw + 4px) 18vw 18vw 18vw !important;
} }

View File

@ -183,7 +183,7 @@
</ol> </ol>
</li> </li>
<li class="price-card-item price-animate-box" style="--delay: 0.3s;"> <li class="price-card-item price-animate-box" style="--delay: 0.3s;">
<h4 class="txt-5002025 price-card-h4 mb-24">customised version</h4> <h4 class="txt-5002025 price-card-h4 mb-24">Customised version</h4>
<div class="price-card-money mb-12"> <div class="price-card-money mb-12">
<h2 class="txt-6003645 price-card-h2">Custom</h2> <h2 class="txt-6003645 price-card-h2">Custom</h2>
</div> </div>
@ -308,14 +308,25 @@
<h2 class="txt-5004455 plans-tit-h2 mb-16">Compare all plans</h2> <h2 class="txt-5004455 plans-tit-h2 mb-16">Compare all plans</h2>
<h5 class="txt-4001830 plans-tit-h5 mb-80">The product supports a variety of application scenarios to help team collaboration</h5> <h5 class="txt-4001830 plans-tit-h5 mb-80">The product supports a variety of application scenarios to help team collaboration</h5>
<ul class="plans-ul-t"> <ul class="plans-ul-t">
<li class="plans-ul-t-item grid-4" style="padding: 20px 24px;"> <div class="price-ceiling">
<li class="plans-ul-t-item grid-5" style="padding: 20px 24px;">
<h4 class="txt-5002024 plans-ul-t-item-h4">Feature</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">Feature</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">Regular version</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">Professional Edition</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">Professional Edition</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">Professional Edition</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">Professional Edition</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">customised version</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">Customised version</h4>
</li> </li>
<li class="plans-ul-t-item grid-4" style="padding: 32px 24px;"> <li class="plans-ul-t-item grid-5" style="padding: 32px 24px;">
<h4 class="txt-5001616 plans-ul-t-item-h4">Pricing</h4> <h4 class="txt-5001616 plans-ul-t-item-h4">Pricing</h4>
<div class="plans-ul-t-item-h4">
<div class="price-card-money mb-24">
<h2 class="txt-6003645 price-card-h2">¥0</h2>
<i class="txt-5001628 price-card-unit">/month</i>
</div>
<span class="plans-ul-b-item-btn">
<a href="https://github.com/kuaifan/dootask/tree/v0.13.0" target="_blank"> <button class="btn btn-primary">Deploy Now</button></a>
</span>
</div>
<div class="plans-ul-t-item-h4"> <div class="plans-ul-t-item-h4">
<div class="price-card-money mb-24"> <div class="price-card-money mb-24">
<h2 class="txt-6003645 price-card-h2">¥0</h2> <h2 class="txt-6003645 price-card-h2">¥0</h2>
@ -342,34 +353,39 @@
</span> </span>
</div> </div>
</li> </li>
</div>
<li class="plans-ul-t-item"> <li class="plans-ul-t-item">
<ol class="plans-ol"> <ol class="plans-ol">
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-4001616 plans-ul-t-item-h4">System usage</h6> <h6 class="txt-4001616 plans-ul-t-item-h4">System usage</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Number of users</h6> <h6 class="txt-4001616 plans-ol-item-h6">Number of users</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">≤3</h6> <h6 class="txt-4001616 plans-ol-item-h6">≤3</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Customizable</h6> <h6 class="txt-4001616 plans-ol-item-h6">Customizable</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Number of projects</h6> <h6 class="txt-4001616 plans-ol-item-h6">Number of projects</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Number of tasks</h6> <h6 class="txt-4001616 plans-ol-item-h6">Number of tasks</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Unlimited</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Offline Deployment</h6> <h6 class="txt-4001616 plans-ol-item-h6">Offline Deployment</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Support</h6> <h6 class="txt-4001616 plans-ol-item-h6">Support</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Support</h6> <h6 class="txt-4001616 plans-ol-item-h6">Support</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Support</h6> <h6 class="txt-4001616 plans-ol-item-h6">Official Support</h6>
<h6 class="txt-4001616 plans-ol-item-h6">Official Support</h6>
</li> </li>
</ol> </ol>
</li> </li>
@ -378,35 +394,33 @@
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-4001616 plans-ul-t-item-h4">Task Collaboration</h6> <h6 class="txt-4001616 plans-ul-t-item-h4">Task Collaboration</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Multi-view presentation (Kanban/calendar/list)</h6> <h6 class="txt-4001616 plans-ol-item-h6">Multi-view presentation</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Task custom Fields</h6> <h6 class="txt-4001616 plans-ol-item-h6">Custom column</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Task loop</h6> <h6 class="txt-4001616 plans-ol-item-h6">Visibility setting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Visibility setting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Task mapping</h6> <h6 class="txt-4001616 plans-ol-item-h6">Repetition cycle</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Repetition cycle">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
</li> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
<li class="plans-ol-item grid-4">
<h6 class="txt-4001616 plans-ol-item-h6">Task dependencies</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies">
</li> </li>
</ol> </ol>
</li> </li>
@ -415,33 +429,156 @@
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-4001616 plans-ul-t-item-h4">Project management</h6> <h6 class="txt-4001616 plans-ul-t-item-h4">Project management</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Project progress</h6> <h6 class="txt-4001616 plans-ol-item-h6">Project progress</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Project templates</h6> <h6 class="txt-4001616 plans-ol-item-h6">Project templates</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Project custom Views</h6> <h6 class="txt-4001616 plans-ol-item-h6">Gantt chart</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
</li> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
<li class="plans-ol-item grid-4">
<h6 class="txt-4001616 plans-ol-item-h6">Timeline view (Gantt chart)</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)">
</li> </li>
</ol> </ol>
</li> </li>
<li class="plans-ul-t-item grid-4" style="padding: 24px;"> <li class="plans-ul-t-item">
<ol class="plans-ol">
<li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">Application</h6>
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Approval center</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Approval Center">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval Center">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval Center">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval Center">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">OKR management</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">AI robot</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="AI robot">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Meeting</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Meeting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">OKR results analysis</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR results analysis">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Email</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">App Push Notifications</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="App Push Notifications">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Team management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
</li>
</ol>
</li>
<li class="plans-ul-t-item">
<ol class="plans-ol">
<li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">Chat</h6>
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">@ Features</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="@ Features">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Linked Task</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Linked Task">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Emoji</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Emoji">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Message categorization</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message Categorization">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Categorization">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Categorization">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Categorization">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Message Right-Click Functionality</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message Right-Click Functionality">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Message Do Not Disturb</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message Do Not Disturb">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">Color Coding</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Color Coding">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
</li>
</ol>
</li>
<!-- <li class="plans-ul-t-item grid-5" style="padding: 24px;">
<h4 class="plans-ul-t-item-h4"></h4> <h4 class="plans-ul-t-item-h4"></h4>
<div class="plans-ul-t-item-h4"> <div class="plans-ul-t-item-h4">
<span class="plans-ul-b-item-btn"> <span class="plans-ul-b-item-btn">
@ -458,11 +595,158 @@
<button class="btn btn-primary" onclick="showBox('Customised consultancy')">Consult</button> <button class="btn btn-primary" onclick="showBox('Customised consultancy')">Consult</button>
</span> </span>
</div> </div>
</li> </li> -->
</ul> </ul>
<ul class="plans-ul-768"> <ul class="plans-ul-768">
<li class="plans-ul-768-item mb-36"> <li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">Free</h5> <h5 class="txt-5001822 help-h5 mb-16">Regular version</h5>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">System usage</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of users</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of projects</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of tasks</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Offline Deployment</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Support</h6>
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Custom column</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Visibility setting</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Visibility setting">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Repetition cycle</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Repetition cycle">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Project management</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project progress</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project templates</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Gantt chart</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Application</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Approval center</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Approval center">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR management</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR management">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI robot</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="AI robot">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Meeting</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Meeting">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR results analysis</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR results analysis">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Email</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">App Push Notifications</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="App Push Notifications">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Team management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Chat</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@ Features</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="@ Features">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Linked Task</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Linked Task">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Emoji</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Emoji">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message categorization</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message categorization">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Right-Click Functionality</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message Right-Click Functionality">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Do Not Disturb</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Message Do Not Disturb">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Color Coding</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="Color Coding">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥0</h2>
<i class="txt-5001528 price-card-unit">/month</i>
</li>
<li>
<span style="display: inline-block; width: 100%;">
<a href="https://github.com/kuaifan/dootask/tree/v0.13.0" target="_blank" class="start_a"> <button class="btn btn-primary">Deploy Now</button> </a>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">Professional Edition</h5>
<ol class="plans-ol-768"> <ol class="plans-ol-768">
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
@ -481,7 +765,7 @@
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Offline Deployments</h6> <h6 class="txt-4001516 plans-ol-item-h6">Offline Deployment</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Support</h6> <h6 class="txt-4001516 plans-ol-item-h6">Support</h6>
</div> </div>
</li> </li>
@ -490,24 +774,20 @@
<h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6> <h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation (Kanban/calendar/list)</h6> <h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task custom Fields</h6> <h6 class="txt-4001516 plans-ol-item-h6">Custom column</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task loop</h6> <h6 class="txt-4001516 plans-ol-item-h6">Visibility setting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task mapping</h6> <h6 class="txt-4001516 plans-ol-item-h6">Repetition cycle</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task dependencies</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -523,12 +803,82 @@
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project custom Views</h6> <h6 class="txt-4001516 plans-ol-item-h6">Gantt chart</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Application</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Timeline view (Gantt chart)</h6> <h6 class="txt-4001516 plans-ol-item-h6">Approval center</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval center">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI robot</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Meeting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR results analysis</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Email</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">App Push Notifications</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Team management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Chat</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@ Features</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Linked Task</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Emoji</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message categorization</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message categorization">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Right-Click Functionality</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Do Not Disturb</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Color Coding</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
</div> </div>
</li> </li>
<li class="price-card-money mb-16"> <li class="price-card-money mb-16">
@ -537,13 +887,13 @@
</li> </li>
<li> <li>
<span style="display: inline-block; width: 100%;"> <span style="display: inline-block; width: 100%;">
<a href="../../manage/dashboard"> <button class="btn btn-primary">Get Started</button></a> <a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank" class="start_a"> <button class="btn btn-primary">Deploy Now</button> </a>
</span> </span>
</li> </li>
</ol> </ol>
</li> </li>
<li class="plans-ul-768-item mb-36"> <li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">Annual</h5> <h5 class="txt-5001822 help-h5 mb-16">Regular version</h5>
<ol class="plans-ol-768"> <ol class="plans-ol-768">
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
@ -562,8 +912,8 @@
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6> <h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Offline Deployments</h6> <h6 class="txt-4001516 plans-ol-item-h6">Offline Deployment</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Support</h6> <h6 class="txt-4001516 plans-ol-item-h6">Official Support</h6>
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -571,24 +921,20 @@
<h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6> <h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation (Kanban/calendar/list)</h6> <h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task custom Fields</h6> <h6 class="txt-4001516 plans-ol-item-h6">Custom column</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task loop</h6> <h6 class="txt-4001516 plans-ol-item-h6">Visibility setting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task mapping</h6> <h6 class="txt-4001516 plans-ol-item-h6">Repetition cycle</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task dependencies</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -604,101 +950,237 @@
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project custom Views</h6> <h6 class="txt-4001516 plans-ol-item-h6">Gantt chart</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Timeline view (Gantt chart)</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥3,000</h2>
<i class="txt-5001528 price-card-unit">/year</i>
</li>
<li>
<span style="display: inline-block; width: 100%;">
<a href="../../manage/dashboard"> <button class="btn btn-primary">Get Started</button></a>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-24">
<h4 class="txt-5001822 plans-ul-t-item-h4 flex-s-c mb-16">Lifetime<i class="rec-icon">Rec.</i></h4>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">System usage</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of users</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of projects</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of tasks</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Offline Deployments</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Support</h6>
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6> <h6 class="txt-5001616 plans-ol-item-h4">Application</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation (Kanban/calendar/list)</h6> <h6 class="txt-4001516 plans-ol-item-h6">Approval center</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation (Kanban/calendar/list)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval center">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task custom Fields</h6> <h6 class="txt-4001516 plans-ol-item-h6">OKR management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task custom Fields"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task loop</h6> <h6 class="txt-4001516 plans-ol-item-h6">AI robot</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task loop"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task mapping</h6> <h6 class="txt-4001516 plans-ol-item-h6">Meeting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task mapping"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Task dependencies</h6> <h6 class="txt-4001516 plans-ol-item-h6">OKR results analysis</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Task dependencies"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Email</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">App Push Notifications</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Team management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Project management</h6> <h6 class="txt-5001616 plans-ol-item-h4">Chat</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project progress</h6> <h6 class="txt-4001516 plans-ol-item-h6">@ Features</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project templates</h6> <h6 class="txt-4001516 plans-ol-item-h6">Linked Task</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project custom Views</h6> <h6 class="txt-4001516 plans-ol-item-h6">Emoji</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project custom Views"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Timeline view (Gantt chart)</h6> <h6 class="txt-4001516 plans-ol-item-h6">Message categorization</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Timeline view (Gantt chart)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message categorization">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Right-Click Functionality</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Do Not Disturb</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Color Coding</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
</div> </div>
</li> </li>
<li class="price-card-money mb-16"> <li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥18,888</h2> <h2 class="txt-6002430 price-card-h2">¥18,888</h2>
<i class="txt-5001528 price-card-unit">/perpetual</i>
</li> </li>
<li> <li>
<span style="display: inline-block; width: 100%;"> <span style="display: inline-block; width: 100%;">
<a href="../../manage/dashboard"> <button class="btn btn-primary">Get Started</button></a> <button class="btn btn-primary mb-24" onclick="showBox('Contact Us')">Contact Us</button>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">Customised version</h5>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">System usage</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of users</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of projects</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Number of tasks</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Unlimited</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Offline Deployment</h6>
<h6 class="txt-4001516 plans-ol-item-h6">Official Support</h6>
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Task Collaboration</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Multi-view presentation</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Multi-view presentation">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Custom column</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Custom column">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Visibility setting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Visibility setting">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Repetition cycle</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Repetition cycle">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Project management</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project progress</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project progress">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Project templates</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Project templates">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Gantt chart</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Gantt chart">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Application</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Approval center</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Approval center">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR management">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI robot</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI robot">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Meeting</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Meeting">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR results analysis</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR results analysis">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Email</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Email">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">App Push Notifications</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="App Push Notifications">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Team management</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Team management">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">Chat</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@ Features</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@ Features">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Linked Task</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Linked Task">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Emoji</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Emoji">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message categorization</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message categorization">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Right-Click Functionality</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Right-Click Functionality">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Message Do Not Disturb</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Message Do Not Disturb">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">Color Coding</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="Color Coding">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">Consult</h2>
</li>
<li>
<span style="display: inline-block; width: 100%;">
<button class="btn btn-primary mb-24" onclick="showBox('Customised consultancy')">Consult</button>
</span> </span>
</li> </li>
</ol> </ol>
@ -822,7 +1304,7 @@
name.innerHTML = text name.innerHTML = text
} }
document.querySelector('.BulletBox1Btn').addEventListener('click',function(){ document.querySelector('.BulletBox1Btn').addEventListener('click',function(){
console.log("aaaaa");
const box = document.querySelector('.BulletBox1') const box = document.querySelector('.BulletBox1')
box.style.display = 'none' box.style.display = 'none'
}) })

View File

@ -0,0 +1,11 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1026_5003)">
<path d="M4 4L15.4141 15.4141" stroke="#727570" stroke-width="2" stroke-linecap="round"/>
<path d="M15.4141 4L4 15.4141" stroke="#727570" stroke-width="2" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_1026_5003">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 429 B

View File

@ -309,14 +309,25 @@
<h2 class="txt-5004455 plans-tit-h2 mb-16">对比所有计划</h2> <h2 class="txt-5004455 plans-tit-h2 mb-16">对比所有计划</h2>
<h5 class="txt-4001830 plans-tit-h5 mb-80">该产品支持各种应用场景,帮助团队协作</h5> <h5 class="txt-4001830 plans-tit-h5 mb-80">该产品支持各种应用场景,帮助团队协作</h5>
<ul class="plans-ul-t"> <ul class="plans-ul-t">
<li class="plans-ul-t-item grid-4" style="padding: 20px 24px;"> <div class="price-ceiling ">
<li class="plans-ul-t-item grid-5" style="padding: 20px 24px;">
<h4 class="txt-5002024 plans-ul-t-item-h4">特点</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">特点</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">普通版</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">专业版</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">专业版</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">专业版</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">专业版</h4>
<h4 class="txt-5002024 plans-ul-t-item-h4">定制版</h4> <h4 class="txt-5002024 plans-ul-t-item-h4">定制版</h4>
</li> </li>
<li class="plans-ul-t-item grid-4" style="padding: 32px 24px;"> <li class="plans-ul-t-item grid-5" style="padding: 32px 24px;">
<h4 class="txt-5001616 plans-ul-t-item-h4">定价</h4> <h4 class="txt-5001616 plans-ul-t-item-h4">定价</h4>
<div class="plans-ul-t-item-h4">
<div class="price-card-money mb-24">
<h2 class="txt-6003645 price-card-h2">¥0</h2>
<i class="txt-5001628 price-card-unit">/月</i>
</div>
<span class="plans-ul-b-item-btn">
<a href="https://github.com/kuaifan/dootask/tree/v0.13.0" target="_blank"> <button class="btn btn-primary">立即部署</button> </a>
</span>
</div>
<div class="plans-ul-t-item-h4"> <div class="plans-ul-t-item-h4">
<div class="price-card-money mb-24"> <div class="price-card-money mb-24">
<h2 class="txt-6003645 price-card-h2">¥0</h2> <h2 class="txt-6003645 price-card-h2">¥0</h2>
@ -343,34 +354,40 @@
</span> </span>
</div> </div>
</li> </li>
</div>
<li class="plans-ul-t-item"> <li class="plans-ul-t-item">
<ol class="plans-ol"> <ol class="plans-ol">
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">系统使用</h6> <h6 class="txt-5001616 plans-ul-t-item-h4">系统使用</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">用户数量</h6> <h6 class="txt-4001616 plans-ol-item-h6">用户数量</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">≤3</h6> <h6 class="txt-4001616 plans-ol-item-h6">≤3</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">可定制</h6> <h6 class="txt-4001616 plans-ol-item-h6">可定制</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">项目数量</h6> <h6 class="txt-4001616 plans-ol-item-h6">项目数量</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">任务数量</h6> <h6 class="txt-4001616 plans-ol-item-h6">任务数量</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6> <h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
<h6 class="txt-4001616 plans-ol-item-h6">无限制</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">离线部署</h6> <h6 class="txt-4001616 plans-ol-item-h6">离线部署</h6>
<h6 class="txt-4001616 plans-ol-item-h6">支持</h6> <h6 class="txt-4001616 plans-ol-item-h6">支持</h6>
<h6 class="txt-4001616 plans-ol-item-h6">支持</h6> <h6 class="txt-4001616 plans-ol-item-h6">支持</h6>
<h6 class="txt-4001616 plans-ol-item-h6">支持</h6> <h6 class="txt-4001616 plans-ol-item-h6">官方支持</h6>
<h6 class="txt-4001616 plans-ol-item-h6">官方支持</h6>
</li> </li>
</ol> </ol>
</li> </li>
@ -379,35 +396,33 @@
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">任务协作</h6> <h6 class="txt-5001616 plans-ul-t-item-h4">任务协作</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">多视图展示(看板/日历/列表)</h6> <h6 class="txt-4001616 plans-ol-item-h6">多视图展示</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">任务自定义字段</h6> <h6 class="txt-4001616 plans-ol-item-h6">自定义栏目</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">任务循环</h6> <h6 class="txt-4001616 plans-ol-item-h6">可见性设置</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="可见性设置">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">任务映射</h6> <h6 class="txt-4001616 plans-ol-item-h6">重复周期</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="重复周期">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
</li> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
<li class="plans-ol-item grid-4">
<h6 class="txt-4001616 plans-ol-item-h6">任务依赖性</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性">
</li> </li>
</ol> </ol>
</li> </li>
@ -416,39 +431,167 @@
<li class="plans-ol-item"> <li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">项目管理</h6> <h6 class="txt-5001616 plans-ul-t-item-h4">项目管理</h6>
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">项目进展</h6> <h6 class="txt-4001616 plans-ol-item-h6">项目进度</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进展"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进展"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进展"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">项目模板</h6> <h6 class="txt-4001616 plans-ol-item-h6">项目模板</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
</li> </li>
<li class="plans-ol-item grid-4"> <li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">项目自定义视图</h6> <h6 class="txt-4001616 plans-ol-item-h6">甘特图</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
</li> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
<li class="plans-ol-item grid-4">
<h6 class="txt-4001616 plans-ol-item-h6">时间轴视图(甘特图)</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)">
</li> </li>
</ol> </ol>
</li> </li>
<li class="plans-ul-t-item grid-4" style="padding: 24px;"> <li class="plans-ul-t-item">
<ol class="plans-ol">
<li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">应用</h6>
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">审批中心</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="审批中心">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">OKR管理</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">AI机器人</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="AI机器人">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">会议</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="会议">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">OKR结果分析</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR结果分析">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">邮件</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">APP推送</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="APP推送">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">团队管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
</li>
</ol>
</li>
<li class="plans-ul-t-item">
<ol class="plans-ol">
<li class="plans-ol-item">
<h6 class="txt-5001616 plans-ul-t-item-h4">聊天</h6>
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">@功能</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="@功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">关联任务</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="关联任务">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">表情</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="表情">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">消息分类</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息分类">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">消息右键功能</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息右键功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">消息免打扰</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息免打扰">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
</li>
<li class="plans-ol-item grid-5">
<h6 class="txt-4001616 plans-ol-item-h6">颜色标注</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="颜色标注">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
</li>
</ol>
</li>
<!-- <li class="plans-ul-t-item grid-5" style="padding: 24px;">
<h4 class="plans-ul-t-item-h4"></h4> <h4 class="plans-ul-t-item-h4"></h4>
<div class="plans-ul-t-item-h4"> <div class="plans-ul-t-item-h4">
<span class="plans-ul-b-item-btn"> <span class="plans-ul-b-item-btn">
<a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank"> <button class="btn btn-primary">立即部署</button> </a> <a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank"> <button class="btn btn-primary">立即部署</button> </a>
</span> </span>
</div> </div>
<div class="plans-ul-t-item-h4">
<span class="plans-ul-b-item-btn">
<a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank"> <button class="btn btn-primary">立即部署</button> </a>
</span>
</div>
<div class="plans-ul-t-item-h4"> <div class="plans-ul-t-item-h4">
<span class="plans-ul-b-item-btn"> <span class="plans-ul-b-item-btn">
<button class="btn btn-primary" onclick="showBox('联系我们')">联系我们</button> <button class="btn btn-primary" onclick="showBox('联系我们')">联系我们</button>
@ -459,15 +602,162 @@
<button class="btn btn-primary" onclick="showBox('定制咨询')">定制咨询</button> <button class="btn btn-primary" onclick="showBox('定制咨询')">定制咨询</button>
</span> </span>
</div> </div>
</li> </li> -->
</ul> </ul>
<ul class="plans-ul-768"> <ul class="plans-ul-768">
<li class="plans-ul-768-item mb-36"> <li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">免费</h5> <h5 class="txt-5001822 help-h5 mb-16">普通版</h5>
<ol class="plans-ol-768"> <ol class="plans-ol-768">
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">系统使用情况</h6> <h6 class="txt-5001616 plans-ol-item-h4">系统使用</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">离线部署</h6>
<h6 class="txt-4001516 plans-ol-item-h6">支持</h6>
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">多视图展示</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">自定义栏目</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">可见性设置</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="可见性设置">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">重复周期</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="重复周期">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目进度</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">甘特图</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">应用</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">审批中心</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="审批中心">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR管理</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR管理">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI机器人</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="AI机器人">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">会议</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="会议">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR结果分析</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="OKR结果分析">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">邮件</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">APP推送</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="APP推送">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">团队管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">聊天</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@功能</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="@功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">关联任务</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="关联任务">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">表情</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="表情">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息分类</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息分类">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息右键功能</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息右键功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息免打扰</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="消息免打扰">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">颜色标注</h6>
<img class="plans-ol-item-icon2" src="../img/price_icon2.svg" alt="颜色标注">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥0</h2>
<i class="txt-5001528 price-card-unit">/月</i>
</li>
<li>
<span style="display: inline-block; width: 100%;">
<a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank" class="start_a"> <button class="btn btn-primary">立即部署</button> </a>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">专业版</h5>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">系统使用</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6> <h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6>
@ -491,24 +781,20 @@
<h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6> <h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">多视图展示(看板/日历/列表)</h6> <h6 class="txt-4001516 plans-ol-item-h6">多视图展示</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务自定义字段</h6> <h6 class="txt-4001516 plans-ol-item-h6">自定义栏目</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务循环</h6> <h6 class="txt-4001516 plans-ol-item-h6">可见性设置</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务映射</h6> <h6 class="txt-4001516 plans-ol-item-h6">重复周期</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务依赖性</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -516,20 +802,90 @@
<h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6> <h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目进</h6> <h6 class="txt-4001516 plans-ol-item-h6">项目进</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6> <h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目自定义视图</h6> <h6 class="txt-4001516 plans-ol-item-h6">甘特图</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">应用</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">时间轴视图(甘特图)</h6> <h6 class="txt-4001516 plans-ol-item-h6">审批中心</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI机器人</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">会议</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR结果分析</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">邮件</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">APP推送</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">团队管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">聊天</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">关联任务</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">表情</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息分类</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息右键功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息免打扰</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">颜色标注</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
</div> </div>
</li> </li>
<li class="price-card-money mb-16"> <li class="price-card-money mb-16">
@ -544,11 +900,11 @@
</ol> </ol>
</li> </li>
<li class="plans-ul-768-item mb-36"> <li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">年度</h5> <h5 class="txt-5001822 help-h5 mb-16">专业版</h5>
<ol class="plans-ol-768"> <ol class="plans-ol-768">
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">系统使用情况</h6> <h6 class="txt-5001616 plans-ol-item-h4">系统使用</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6> <h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6>
@ -564,7 +920,7 @@
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">离线部署</h6> <h6 class="txt-4001516 plans-ol-item-h6">离线部署</h6>
<h6 class="txt-4001516 plans-ol-item-h6">支持</h6> <h6 class="txt-4001516 plans-ol-item-h6">官方支持</h6>
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -572,24 +928,20 @@
<h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6> <h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">多视图展示(看板/日历/列表)</h6> <h6 class="txt-4001516 plans-ol-item-h6">多视图展示</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务自定义字段</h6> <h6 class="txt-4001516 plans-ol-item-h6">自定义栏目</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务循环</h6> <h6 class="txt-4001516 plans-ol-item-h6">可见性设置</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务映射</h6> <h6 class="txt-4001516 plans-ol-item-h6">重复周期</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务依赖性</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
@ -597,109 +949,246 @@
<h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6> <h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目进</h6> <h6 class="txt-4001516 plans-ol-item-h6">项目进</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6> <h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目自定义视图</h6> <h6 class="txt-4001516 plans-ol-item-h6">甘特图</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">时间轴视图(甘特图)</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥3,000</h2>
<i class="txt-5001528 price-card-unit">/年</i>
</li>
<li>
<span style="display: inline-block; width: 100%;">
<a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank"> <button class="btn btn-primary">立即部署</button> </a>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-24">
<h4 class="txt-5001822 plans-ul-t-item-h4 flex-s-c mb-16">终身<i class="rec-icon">Rec.</i></h4>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">系统使用情况</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">离线部署</h6>
<h6 class="txt-4001516 plans-ol-item-h6">支持</h6>
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6> <h6 class="txt-5001616 plans-ol-item-h4">应用</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">多视图展示(看板/日历/列表)</h6> <h6 class="txt-4001516 plans-ol-item-h6">审批中心</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示(看板/日历/列表)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务自定义字段</h6> <h6 class="txt-4001516 plans-ol-item-h6">OKR管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务自定义字段"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务循环</h6> <h6 class="txt-4001516 plans-ol-item-h6">AI机器人</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务循环"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务映射</h6> <h6 class="txt-4001516 plans-ol-item-h6">会议</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务映射"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务依赖性</h6> <h6 class="txt-4001516 plans-ol-item-h6">OKR结果分析</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="任务依赖性"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">邮件</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">APP推送</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">团队管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
</div> </div>
</li> </li>
<li class="plans-ol-768-item"> <li class="plans-ol-768-item">
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6> <h6 class="txt-5001616 plans-ol-item-h4">聊天</h6>
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目进展</h6> <h6 class="txt-4001516 plans-ol-item-h6">@功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进展"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6> <h6 class="txt-4001516 plans-ol-item-h6">关联任务</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目自定义视图</h6> <h6 class="txt-4001516 plans-ol-item-h6">表情</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目自定义视图"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
</div> </div>
<div class="plans-ol-768-content"> <div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">时间轴视图(甘特图)</h6> <h6 class="txt-4001516 plans-ol-item-h6">消息分类</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="时间轴视图(甘特图)"> <img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息右键功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息免打扰</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">颜色标注</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
</div> </div>
</li> </li>
<li class="price-card-money mb-16"> <li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">¥18,888</h2> <h2 class="txt-6002430 price-card-h2">¥18,888</h2>
<i class="txt-5001528 price-card-unit">/永久</i>
</li> </li>
<li> <li>
<span style="display: inline-block; width: 100%;"> <span style="display: inline-block; width: 100%;">
<a href="https://github.com/kuaifan/dootask/tree/pro" target="_blank"> <button class="btn btn-primary">立即部署</button> </a> <button class="btn btn-primary mb-24" onclick="showBox('联系我们')">联系我们</button>
</span>
</li>
</ol>
</li>
<li class="plans-ul-768-item mb-36">
<h5 class="txt-5001822 help-h5 mb-16">定制版</h5>
<ol class="plans-ol-768">
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">系统使用</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">用户数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">可定制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">任务数量</h6>
<h6 class="txt-4001516 plans-ol-item-h6">无限制</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">离线部署</h6>
<h6 class="txt-4001516 plans-ol-item-h6">官方支持</h6>
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">任务协作</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">多视图展示</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="多视图展示">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">自定义栏目</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="自定义栏目">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">可见性设置</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="可见性设置">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">重复周期</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="重复周期">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">项目管理</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目进度</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目进度">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">项目模板</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="项目模板">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">甘特图</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="甘特图">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">应用</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">审批中心</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="审批中心">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR管理">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">AI机器人</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="AI机器人">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">会议</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="会议">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">OKR结果分析</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="OKR结果分析">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">LDAP</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="LDAP">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">邮件</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="邮件">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">APP推送</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="APP推送">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">团队管理</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="团队管理">
</div>
</li>
<li class="plans-ol-768-item">
<div class="plans-ol-768-content">
<h6 class="txt-5001616 plans-ol-item-h4">聊天</h6>
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">@功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="@功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">关联任务</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="关联任务">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">表情</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="表情">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息分类</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息分类">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息右键功能</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息右键功能">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">消息免打扰</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="消息免打扰">
</div>
<div class="plans-ol-768-content">
<h6 class="txt-4001516 plans-ol-item-h6">颜色标注</h6>
<img class="plans-ol-item-icon" src="../img/price_icon1.svg" alt="颜色标注">
</div>
</li>
<li class="price-card-money mb-16">
<h2 class="txt-6002430 price-card-h2">定制</h2>
<!-- <i class="txt-5001528 price-card-unit">/月</i> -->
</li>
<li>
<span style="display: inline-block; width: 100%;">
<button class="btn btn-primary mb-24" onclick="showBox('定制咨询')">定制咨询</button>
</span> </span>
</li> </li>
</ol> </ol>
@ -822,7 +1311,6 @@
name.innerHTML = text name.innerHTML = text
} }
document.querySelector('.BulletBox1Btn').addEventListener('click',function(){ document.querySelector('.BulletBox1Btn').addEventListener('click',function(){
console.log("aaaaa");
const box = document.querySelector('.BulletBox1') const box = document.querySelector('.BulletBox1')
box.style.display = 'none' box.style.display = 'none'
}) })

View File

@ -2,7 +2,7 @@ const isElectron = !!(window && window.process && window.process.type);
const isEEUiApp = window && window.navigator && /eeui/i.test(window.navigator.userAgent); const isEEUiApp = window && window.navigator && /eeui/i.test(window.navigator.userAgent);
import microappInit from "./microapp" import microappInit from "./microapp"
import {switchLanguage as $L} from "./language"; import {switchLanguage as $L, setLanguage, getLanguage} from "./language";
import './functions/common' import './functions/common'
import './functions/eeui' import './functions/eeui'
@ -91,17 +91,18 @@ if (!isElectron && !isEEUiApp) {
ViewUI.LoadingBar._load = true; ViewUI.LoadingBar._load = true;
ViewUI.LoadingBar.start(); ViewUI.LoadingBar.start();
}, 300) }, 300)
//
if (to.query?.theme) { if (to.query?.theme) {
store.dispatch("setTheme", typeof to.query?.theme == 'string' ? to.query?.theme : to.query?.theme[0]) store.dispatch("setTheme", typeof to.query?.theme == 'string' ? to.query?.theme : to.query?.theme[0])
} }
if (to.query?.lang) { if (to.query?.lang) {
let lang = typeof to.query?.lang == 'string' ? to.query?.lang : to.query?.lang[0] let lang = typeof to.query?.lang == 'string' ? to.query?.lang : to.query?.lang[0]
if (window.localStorage.getItem("__language:type__") != lang) { if(lang && lang != getLanguage()){
window.localStorage.setItem("__language:type__", to.query?.lang); setLanguage(lang, true)
window.location.reload(); return;
return false;
} }
} }
//
next(); next();
}); });
router.afterEach(() => { router.afterEach(() => {
@ -157,6 +158,7 @@ $A.Platform = "web";
$A.isMainElectron = false; $A.isMainElectron = false;
$A.isSubElectron = false; $A.isSubElectron = false;
$A.isEEUiApp = isEEUiApp; $A.isEEUiApp = isEEUiApp;
$A.isElectron = isElectron;
$A.openLog = false; $A.openLog = false;
if (isElectron) { if (isElectron) {
$A.Electron = electron; $A.Electron = electron;

View File

@ -163,7 +163,9 @@ export default {
} }
const documentKey = this.documentKey(); const documentKey = this.documentKey();
if (documentKey && documentKey.then) { if (documentKey && documentKey.then) {
documentKey.then(this.loadFile); documentKey.then(this.loadFile).catch(({msg})=>{
$A.modalError({content: msg});
});
} else { } else {
this.loadFile(); this.loadFile();
} }
@ -280,6 +282,14 @@ export default {
} }
// //
}).catch(({msg}) => { }).catch(({msg}) => {
if (msg.indexOf("404 not found") !== -1) {
$A.modalInfo({
language: false,
title: '版本过低',
content: '服务器版本过低,请升级服务器。',
})
return;
}
$A.modalError({content: msg}); $A.modalError({content: msg});
}); });
}) })

View File

@ -19,15 +19,20 @@
<!-- 顶部 --> <!-- 顶部 -->
<template #header> <template #header>
<div v-if="isFullscreen" class="user-modal-header"> <div v-if="isFullscreen" class="user-modal-header">
<div class="user-modal-close" @click="showModal=false">{{$L('关闭')}}</div> <div class="user-modal-close" @click="[ !multipleChoice && showMultiple ? showMultiple=false : showModal=false]">
{{ !multipleChoice && showMultiple ? $L('取消') : $L('关闭') }}
</div>
<div class="user-modal-title"><span>{{localTitle}}</span></div> <div class="user-modal-title"><span>{{localTitle}}</span></div>
<div class="user-modal-submit" @click="onSubmit"> <div v-if="showMultiple" class="user-modal-submit" @click="onSubmit(1)">
<div v-if="submittIng > 0" class="submit-loading"><Loading /></div> <div v-if="submittIng > 0" class="submit-loading"><Loading /></div>
{{$L('确定')}} {{$L('确定')}}
<template v-if="selects.length > 0"> <template v-if="selects.length > 0">
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>) ({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
</template> </template>
</div> </div>
<div v-else-if="!forcedRadio" class="user-modal-submit" @click="showMultiple = true">
{{$L('多选')}}
</div>
</div> </div>
<div v-else class="ivu-modal-header-inner">{{localTitle}}</div> <div v-else class="ivu-modal-header-inner">{{localTitle}}</div>
</template> </template>
@ -37,7 +42,7 @@
<!-- 搜索 --> <!-- 搜索 -->
<div class="user-modal-search"> <div class="user-modal-search">
<Scrollbar ref="selected" class="search-selected" v-if="selects.length > 0" enable-x :enable-y="false"> <Scrollbar ref="selected" class="search-selected" v-if="showMultiple && selects.length > 0" enable-x :enable-y="false">
<ul> <ul>
<li v-for="item in formatSelect(selects)" :data-id="item.userid" @click.stop="onRemoveItem(item.userid)"> <li v-for="item in formatSelect(selects)" :data-id="item.userid" @click.stop="onRemoveItem(item.userid)">
<template v-if="item.type=='group'"> <template v-if="item.type=='group'">
@ -76,15 +81,19 @@
v-for="item in lists" v-for="item in lists"
:class="selectClass(item.userid_list)" :class="selectClass(item.userid_list)"
@click="onSelectProject(item.userid_list)"> @click="onSelectProject(item.userid_list)">
<template v-if="showMultiple">
<Icon class="user-modal-icon" :type="selectIcon(item.userid_list)" /> <Icon class="user-modal-icon" :type="selectIcon(item.userid_list)" />
</template>
<div class="user-modal-avatar"> <div class="user-modal-avatar">
<i class="taskfont icon-avatar">&#xe6f9;</i> <i class="taskfont icon-avatar">&#xe6f9;</i>
<div class="project-name"> <div class="project-name">
<div class="label">{{item.name}}</div> <div class="label">{{item.name}}</div>
<div class="subtitle"> <div class="subtitle">
{{item.userid_list.length}} {{$L('项目成员')}} {{item.userid_list.length}} {{$L('项目成员')}}
<template v-if="showMultiple">
<em class="all">{{$L('已全选')}}</em> <em class="all">{{$L('已全选')}}</em>
<em class="some">{{$L('已选部分')}}</em> <em class="some">{{$L('已选部分')}}</em>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -93,7 +102,7 @@
<!-- 会员会话 --> <!-- 会员会话 -->
<ul v-else> <ul v-else>
<li <li
v-if="showSelectAll" v-if="showMultiple && showSelectAll"
:class="selectClass('all')" :class="selectClass('all')"
@click="onSelectAll"> @click="onSelectAll">
<Icon class="user-modal-icon" :type="selectIcon('all')" /> <Icon class="user-modal-icon" :type="selectIcon('all')" />
@ -106,8 +115,10 @@
disabled: isUncancelable(item.userid) || isDisabled(item.userid) disabled: isUncancelable(item.userid) || isDisabled(item.userid)
}" }"
@click="onSelectItem(item)"> @click="onSelectItem(item)">
<template v-if="showMultiple">
<Icon v-if="selects.includes(item.userid)" class="user-modal-icon" type="ios-checkmark-circle" /> <Icon v-if="selects.includes(item.userid)" class="user-modal-icon" type="ios-checkmark-circle" />
<Icon v-else class="user-modal-icon" type="ios-radio-button-off" /> <Icon v-else class="user-modal-icon" type="ios-radio-button-off" />
</template>
<div v-if="item.type=='group'" class="user-modal-avatar"> <div v-if="item.type=='group'" class="user-modal-avatar">
<EAvatar v-if="item.avatar" class="img-avatar" :src="item.avatar" :size="40"></EAvatar> <EAvatar v-if="item.avatar" class="img-avatar" :src="item.avatar" :size="40"></EAvatar>
<i v-else-if="item.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i> <i v-else-if="item.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i>
@ -134,12 +145,57 @@
<!-- 底部 --> <!-- 底部 -->
<template #footer> <template #footer>
<Button type="primary" :loading="submittIng > 0" @click="onSubmit"> <Button v-if="!forcedRadio && !multipleChoice && showMultiple" @click="showMultiple = false">
{{$L('取消')}}
</Button>
<Button v-if="!forcedRadio && showMultiple" type="primary" :loading="submittIng > 0" @click="onSubmit(1)">
{{$L('确定')}} {{$L('确定')}}
<template v-if="selects.length > 0"> <template v-if="selects.length > 0">
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>) ({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
</template> </template>
</Button> </Button>
<Button v-else-if="!forcedRadio" type="primary" @click="showMultiple = true">
{{$L('多选')}}
</Button>
</template>
</Modal>
<!-- 二次确认 -->
<Modal
v-model="showAffirmModal"
:title="twiceAffirmTitle"
class-name="common-user-select-modal twice-affirm-modal"
:mask-closable="false"
width="420">
<div class="user-modal-search twice-affirm">
<Scrollbar class="search-selected" v-if="selects?.length > 0" enable-x :enable-y="false">
<ul>
<li v-for="item in formatSelect(selects)" :data-id="item.userid">
<div v-if="item.type=='group'" class="user-modal-avatar">
<EAvatar v-if="item.avatar" class="img-avatar" :src="item.avatar" :size="32"></EAvatar>
<i v-else-if="item.group_type=='department'" class="taskfont icon-avatar department">&#xe75c;</i>
<i v-else-if="item.group_type=='project'" class="taskfont icon-avatar project">&#xe6f9;</i>
<i v-else-if="item.group_type=='task'" class="taskfont icon-avatar task">&#xe6f4;</i>
<i v-else-if="item.group_type=='okr'" class="taskfont icon-avatar task">&#xe6f4;</i>
<Icon v-else class="icon-avatar" type="ios-people" />
<div v-if="selects?.length == 1" class="avatar-name">
<span>{{item.name}}</span>
</div>
</div>
<UserAvatar v-else :userid="item.userid" :size="32" :show-name="selects?.length == 1" tooltip-disabled />
</li>
</ul>
</Scrollbar>
</div>
<div class="twice-affirm-body-extend">
<slot name="twice-affirm-body-extend"></slot>
</div>
<template #footer>
<slot name="twice-affirm-footer-extend"></slot>
<Button type="primary" :loading="submittIng > 0" @click="onSubmit(2)">
{{$L('确定')}}
<template v-if="selects.length > 0">({{selects.length}})</template>
</Button>
</template> </template>
</Modal> </Modal>
</div> </div>
@ -260,6 +316,38 @@ export default {
default: false default: false
}, },
//
submitBtnTwoText: {
type: String,
default: ''
},
//
twiceAffirm: {
type: Boolean,
default: false
},
//
twiceAffirmTitle: {
type: String,
default: ''
},
//
multipleChoice: {
type: Boolean,
default: true
},
//
forcedRadio: {
type: Boolean,
default: false
},
//
group: {
type: Boolean,
default: false
},
// //
beforeSubmit: Function beforeSubmit: Function
}, },
@ -271,11 +359,9 @@ export default {
{ key: 'project', label: '项目成员' }, { key: 'project', label: '项目成员' },
], ],
switchActive: 'recent', switchActive: 'recent',
loadIng: 0,
loadIng: 0, // waitIng: 0,
waitIng: 0, // submittIng: 0,
submittIng: 0, //
values: [], values: [],
selects: [], selects: [],
@ -287,7 +373,10 @@ export default {
searchKey: null, searchKey: null,
searchCache: [], searchCache: [],
}
showAffirmModal: false,
showMultiple: true,
};
}, },
watch: { watch: {
value: { value: {
@ -305,7 +394,7 @@ export default {
isWhole: { isWhole: {
handler(value) { handler(value) {
if (value) { if (value || this.group) {
this.switchActive = 'recent' this.switchActive = 'recent'
} else { } else {
this.switchActive = 'contact' this.switchActive = 'contact'
@ -317,6 +406,10 @@ export default {
showModal(value) { showModal(value) {
if (value) { if (value) {
this.searchBefore() this.searchBefore()
this.showMultiple = this.multipleChoice
if(this.forcedRadio){
this.showMultiple = false
}
} else { } else {
this.searchKey = "" this.searchKey = ""
} }
@ -341,7 +434,7 @@ export default {
}, },
isWhole({ projectId, noProjectId, dialogId }) { isWhole({ projectId, noProjectId, dialogId }) {
return projectId === 0 && noProjectId === 0 && dialogId === 0 return projectId === 0 && noProjectId === 0 && dialogId === 0 && !this.group
}, },
lists({ switchActive, searchKey, recents, contacts, projects }) { lists({ switchActive, searchKey, recents, contacts, projects }) {
@ -466,6 +559,9 @@ export default {
searchRecent() { searchRecent() {
this.recents = this.cacheDialogs.filter(dialog => { this.recents = this.cacheDialogs.filter(dialog => {
if(this.group && dialog.type != 'group'){
return false
}
if (dialog.name === undefined || dialog.dialog_delete === 1) { if (dialog.name === undefined || dialog.dialog_delete === 1) {
return false return false
} }
@ -570,7 +666,7 @@ export default {
}, },
}).then(({ data }) => { }).then(({ data }) => {
data = data.data.map(item => Object.assign(item, { type: 'project' })) data = data.data.map(item => Object.assign(item, { type: 'project' }))
this.projects = data this.projects = data;
// //
const index = this.searchCache.findIndex(item => item.key == key); const index = this.searchCache.findIndex(item => item.key == key);
const tmpData = { type: 'project', key, data, time: $A.Time() }; const tmpData = { type: 'project', key, data, time: $A.Time() };
@ -619,6 +715,9 @@ export default {
}, },
onSelectItem({ userid }) { onSelectItem({ userid }) {
if (!this.showMultiple) {
this.selects = []
}
if (this.selects.includes(userid)) { if (this.selects.includes(userid)) {
if (this.isUncancelable(userid)) { if (this.isUncancelable(userid)) {
return return
@ -635,12 +734,18 @@ export default {
this.selects.push(userid) this.selects.push(userid)
// //
this.$nextTick(() => { this.$nextTick(() => {
$A.scrollIntoViewIfNeeded(this.$refs.selected.querySelector(`li[data-id="${userid}"]`)) $A.scrollIntoViewIfNeeded(this.$refs.selected?.querySelector(`li[data-id="${userid}"]`))
}) })
} }
if(!this.showMultiple){
this.onSubmit(1)
}
}, },
onSelectProject(userid_list) { onSelectProject(userid_list) {
if (!this.showMultiple) {
this.selects = []
}
switch (this.selectIcon(userid_list)) { switch (this.selectIcon(userid_list)) {
case 'ios-checkmark-circle': case 'ios-checkmark-circle':
// //
@ -664,6 +769,9 @@ export default {
} }
break; break;
} }
if (!this.showMultiple) {
this.onSubmit(1)
}
}, },
onRemoveItem(userid) { onRemoveItem(userid) {
@ -673,17 +781,28 @@ export default {
this.selects = this.selects.filter(value => value != userid) this.selects = this.selects.filter(value => value != userid)
}, },
onSubmit() { onSubmit(index) {
if (this.submittIng > 0) { if (this.submittIng > 0) {
return return
} }
const clone = $A.cloneJSON(this.values) const clone = $A.cloneJSON(this.values)
this.values = $A.cloneJSON(this.selects) this.values = $A.cloneJSON(this.selects)
//
if (index !=2 && this.twiceAffirm) {
if (this.values.length < 1) {
$A.messageError("请选择对话或成员")
return
}
this.showAffirmModal = true
return
}
//
this.$emit('input', this.values) this.$emit('input', this.values)
this.$emit('onSubmit', this.values) this.$emit('onSubmit', this.values)
if (!this.beforeSubmit) { if (!this.beforeSubmit) {
this.showModal = false this.showModal = false
this.showAffirmModal = false
return return
} }
const before = this.beforeSubmit(); const before = this.beforeSubmit();
@ -691,6 +810,7 @@ export default {
this.submittIng++ this.submittIng++
before.then(() => { before.then(() => {
this.showModal = false this.showModal = false
this.showAffirmModal = false
}).catch(() => { }).catch(() => {
this.values = clone this.values = clone
this.$emit('input', this.values) this.$emit('input', this.values)
@ -699,6 +819,7 @@ export default {
}) })
} else { } else {
this.showModal = false this.showModal = false
this.showAffirmModal = false
} }
}, },
} }

View File

@ -735,8 +735,9 @@
// 处理内容连接 // 处理内容连接
if (/https*:\/\//.test(text)) { if (/https*:\/\//.test(text)) {
const urlMatch = $.apiUrl('../').match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/im); const urlMatch = $.apiUrl('../').match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/im);
// const theme = window.localStorage.getItem("__theme:mode__") const isMentionFile = text.indexOf('class="mention file"') !== -1 && ($A.isEEUiApp || $A.isElectron)
// const lang = window.localStorage.getItem("__language:type__") const theme = isMentionFile ? $A.dark.isDarkEnabled() ? 'dark' : 'light' : '';
const lang = isMentionFile ? window.localStorage.getItem("__language:type__") : ''
text = text.split(/(<[^>]*>)/g).map(string => { text = text.split(/(<[^>]*>)/g).map(string => {
if (string && !/<[^>]*>/.test(string)) { if (string && !/<[^>]*>/.test(string)) {
string = string.replace(/(^|[^'"])((https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+))/g, "$1<a href=\"$2\" target=\"_blank\">$2</a>") string = string.replace(/(^|[^'"])((https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+))/g, "$1<a href=\"$2\" target=\"_blank\">$2</a>")
@ -745,8 +746,8 @@
const href = string.match(/href="([^"]+)"/)?.[1] || '' const href = string.match(/href="([^"]+)"/)?.[1] || ''
if (urlMatch?.[1] && href.indexOf(urlMatch[1]) !== -1) { if (urlMatch?.[1] && href.indexOf(urlMatch[1]) !== -1) {
const searchParams = new URLSearchParams() const searchParams = new URLSearchParams()
// href.indexOf("theme=") === -1 && searchParams.append('theme', theme); theme && href.indexOf("theme=") === -1 && searchParams.append('theme', theme);
// href.indexOf("lang=") === -1 && searchParams.append('lang', lang); lang && href.indexOf("lang=") === -1 && searchParams.append('lang', lang);
const prefix = searchParams.toString() ? (href.indexOf("?") === -1 ? '?' : '&') : ''; const prefix = searchParams.toString() ? (href.indexOf("?") === -1 ? '?' : '&') : '';
string = string.replace(/(href="[^"]*)/g, '$1' + prefix + searchParams.toString()) string = string.replace(/(href="[^"]*)/g, '$1' + prefix + searchParams.toString())
} }
@ -819,6 +820,10 @@
switch (data.type) { switch (data.type) {
case 'text': case 'text':
return $A.getMsgTextPreview(data.msg.text, imgClassName) return $A.getMsgTextPreview(data.msg.text, imgClassName)
case 'word-chain':
return `[${$A.L('接龙')}]` + $A.getMsgTextPreview(data.msg.text, imgClassName)
case 'vote':
return `[${$A.L('投票')}]` + $A.getMsgTextPreview(data.msg.text, imgClassName)
case 'record': case 'record':
return `[${$A.L('语音')}]` return `[${$A.L('语音')}]`
case 'meeting': case 'meeting':

View File

@ -30,11 +30,16 @@ function addLanguage(data) {
/** /**
* 设置语言 * 设置语言
* @param language * @param language
* @param silence
*/ */
function setLanguage(language) { function setLanguage(language, silence = false) {
if (language === undefined) { if (language === undefined) {
return return
} }
if(silence){
window.localStorage.setItem("__language:type__", language)
$A.reloadUrl()
}else{
$A.modalConfirm({ $A.modalConfirm({
content: '切换语言需要刷新后生效,是否确定刷新?', content: '切换语言需要刷新后生效,是否确定刷新?',
cancelText: '取消', cancelText: '取消',
@ -45,6 +50,14 @@ function setLanguage(language) {
} }
}) })
} }
}
/**
* 获取最新语言
*/
function getLanguage() {
return utils.getLanguage();
}
/** /**
* 转换语言 * 转换语言
@ -111,4 +124,4 @@ function switchLanguage(text) {
return text return text
} }
export { languageType, languageList, addLanguage, setLanguage, switchLanguage } export { languageType, languageList, addLanguage, setLanguage, getLanguage, switchLanguage }

View File

@ -188,6 +188,19 @@
</div> </div>
</Modal> </Modal>
<!-- 发起接龙 -->
<UserSelect
ref="wordChainAndVoteRef"
v-model="sendData"
:multiple-max="50"
:title="sendType == 'vote' ? $L('选择群组发起投票') : $L('选择群组发起接龙')"
:before-submit="goWordChainAndVote"
:show-select-all="false"
:forced-radio="true"
:group="true"
show-dialog
module/>
</div> </div>
</template> </template>
@ -278,6 +291,9 @@ export default {
scanLoginShow: false, scanLoginShow: false,
scanLoginLoad: false, scanLoginLoad: false,
scanLoginCode: '', scanLoginCode: '',
//
sendData: [],
sendType: '',
} }
}, },
activated() { activated() {
@ -302,43 +318,52 @@ export default {
}, },
methods: { methods: {
initList() { initList() {
const applyList = [ let applyList = [
{value: "approve", label: "审批中心"}, { value: "approve", label: "审批中心", sort: 3 },
{value: "okr", label: "OKR管理"}, { value: "report", label: "工作报告", sort: 5 },
{value: "report", label: "工作报告"}, { value: "okr", label: "OKR管理", sort: 4 },
{value: "robot", label: "AI机器人"}, { value: "robot", label: "AI机器人", sort: 6 },
{value: "signin", label: "签到"}, { value: "signin", label: "签到", sort: 7 },
{value: "meeting", label: "会议"} { value: "meeting", label: "会议", sort: 8 },
{ value: "word-chain", label: "接龙", sort: 9 },
{ value: "vote", label: "投票", sort: 10 },
]; ];
// wap
if (this.windowOrientation == 'landscape') { if (this.windowOrientation == 'landscape') {
// //
applyList.push({value: "scan", label: "扫一扫", show: $A.isEEUiApp}) applyList.push({ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 })
} else { } else {
// //
applyList.unshift(...[
{value: "calendar", label: "日历"},
{value: "file", label: "文件"}
])
applyList.push(...[ applyList.push(...[
{value: "addProject", label: "创建项目"}, { value: "calendar", label: "日历", sort: 1 },
{value: "addTask", label: "添加任务"}, { value: "file", label: "文件", sort: 2 },
{value: "scan", label: "扫一扫", show: $A.isEEUiApp}, { value: "addProject", label: "创建项目", sort: 11 },
{value: "setting", label: "设置"} { value: "addTask", label: "添加任务", sort: 12 },
{ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 },
{ value: "setting", label: "设置", sort: 14 }
]) ])
} }
// //
const adminApplyList = !this.userIsAdmin ? [] : [ let adminApplyList = !this.userIsAdmin ? [] : [
{value: "okrAnalyze", label: "OKR结果"}, { value: "okrAnalyze", label: "OKR结果", sort: 15 },
{value: "ldap", label: "LDAP"}, { value: "ldap", label: "LDAP", sort: 16 },
{value: "mail", label: "邮件通知"}, { value: "mail", label: "邮件通知", sort: 17 },
{value: "appPush", label: "APP推送"}, { value: "appPush", label: "APP推送", sort: 18 },
{value: "allUser", label: "团队管理"} { value: "allUser", label: "团队管理", sort: 19 }
].map((h) => { ].map((h) => {
h.type = 'admin'; h.type = 'admin';
return h; return h;
}); });
// //
this.applyList = [...applyList, ...adminApplyList]; this.applyList = [...applyList, ...adminApplyList].sort((a, b) => {
if (a.sort < b.sort) {
return -1;
} else if (a.sort > b.sort) {
return 1;
} else {
return 0;
}
});
}, },
getLogoPath(name) { getLogoPath(name) {
name = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); name = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
@ -402,6 +427,12 @@ export default {
case 'scan': case 'scan':
$A.eeuiAppScan(this.scanResult); $A.eeuiAppScan(this.scanResult);
return; return;
case 'word-chain':
case 'vote':
this.sendData = [];
this.sendType = item.value;
this.$refs.wordChainAndVoteRef.onSelection()
return;
} }
this.$emit("on-click", item.value) this.$emit("on-click", item.value)
}, },
@ -520,7 +551,7 @@ export default {
this.scanLoginLoad = false this.scanLoginLoad = false
}); });
}, },
// //
openDetail(desc){ openDetail(desc){
$A.modalInfo({ $A.modalInfo({
content: desc, content: desc,
@ -541,6 +572,26 @@ export default {
}) })
}, },
}); });
},
//
goWordChainAndVote(){
const dialog_id = Number(this.sendData[0].replace('d:', ''))
if(this.windowPortrait){
this.$store.dispatch("openDialog", dialog_id ).then(() => {
this.$store.state[ this.sendType == 'word-chain' ?'dialogDroupWordChain' : 'dialogGroupVote'] = {
type: 'create',
dialog_id: dialog_id
}
})
}else{
this.goForward({ name: 'manage-messenger', params: { dialog_id: dialog_id}});
setTimeout(()=>{
this.$store.state[ this.sendType == 'word-chain' ?'dialogDroupWordChain' : 'dialogGroupVote'] = {
type: 'create',
dialog_id: dialog_id
}
},100)
}
} }
} }
} }

View File

@ -4,7 +4,7 @@
<div class="calendar-head"> <div class="calendar-head">
<div class="calendar-titbox"> <div class="calendar-titbox">
<div class="calendar-title"> <div class="calendar-title">
<div class="common-nav-back portrait" @click="goForward({name: 'manage-application'},true)"><i class="taskfont">&#xe676;</i></div> <div class="common-nav-back" @click="goForward({name: 'manage-application'},true)"><i class="taskfont">&#xe676;</i></div>
<h1>{{rangeText}}</h1> <h1>{{rangeText}}</h1>
</div> </div>
<ButtonGroup class="calendar-arrow" size="small"> <ButtonGroup class="calendar-arrow" size="small">
@ -44,10 +44,6 @@
</template> </template>
<script> <script>
import 'tui-date-picker/dist/tui-date-picker.css';
import 'tui-time-picker/dist/tui-time-picker.css';
import 'tui-calendar-hi/dist/tui-calendar-hi.css'
import {mapState, mapGetters} from "vuex"; import {mapState, mapGetters} from "vuex";
import Calendar from "./components/Calendar"; import Calendar from "./components/Calendar";
import moment from "moment"; import moment from "moment";
@ -363,7 +359,10 @@ export default {
if (!data) { if (!data) {
return; return;
} }
if (changes.start || changes.end) { if(changes?.start?.getTime() == schedule?.start?.getTime() && changes?.end?.getTime() == schedule?.end?.getTime()){
return;
}
if (changes?.start || changes?.end) {
const cal = this.$refs.cal.getInstance(); const cal = this.$refs.cal.getInstance();
cal.updateSchedule(schedule.id, schedule.calendarId, changes); cal.updateSchedule(schedule.id, schedule.calendarId, changes);
// //

View File

@ -1,7 +1,11 @@
<template> <template>
<div ref="tuiCalendar" class="calendar-wrapper"></div> <div ref="tuiCalendar" class="calendar-wrapper"></div>
</template> </template>
<script> <script>
import 'tui-date-picker/dist/tui-date-picker.css';
import 'tui-time-picker/dist/tui-time-picker.css';
import 'tui-calendar-hi/dist/tui-calendar-hi.css'
import Calendar from 'tui-calendar-hi'; import Calendar from 'tui-calendar-hi';
export default { export default {
@ -148,7 +152,13 @@ export default {
}, },
isReadOnly(newValue) { isReadOnly(newValue) {
this.calendarInstance.setOptions({isReadOnly: newValue}); this.calendarInstance.setOptions({isReadOnly: newValue});
} },
windowPortrait: {
handler(v) {
this.resetRender()
},
immediate: true
},
}, },
mounted() { mounted() {
this.calendarInstance = new Calendar(this.$refs.tuiCalendar, { this.calendarInstance = new Calendar(this.$refs.tuiCalendar, {
@ -170,10 +180,13 @@ export default {
}); });
this.addEventListeners(); this.addEventListeners();
this.reflectSchedules(); this.reflectSchedules();
//
window.addEventListener('resize',this.resetRender);
}, },
beforeDestroy() { beforeDestroy() {
this.calendarInstance.off(); this.calendarInstance.off();
this.calendarInstance.destroy(); this.calendarInstance.destroy();
window.removeEventListener('resize',this.resetRender);
}, },
methods: { methods: {
addEventListeners() { addEventListeners() {
@ -193,8 +206,10 @@ export default {
return this.calendarInstance; return this.calendarInstance;
}, },
resetRender() { resetRender() {
if(this.calendarInstance){
this.calendarInstance.clear(); this.calendarInstance.clear();
this.reflectSchedules(); this.reflectSchedules();
}
}, },
invoke(methodName, ...args) { invoke(methodName, ...args) {
let result; let result;

View File

@ -102,6 +102,14 @@
<i class="taskfont">&#xe690;</i> <i class="taskfont">&#xe690;</i>
{{$L('匿名消息')}} {{$L('匿名消息')}}
</div> </div>
<div v-if="dialogData.type == 'group'" class="chat-input-popover-item" @click="onToolbar('word-chain')">
<i class="taskfont">&#xe807;</i>
{{$L('发起接龙')}}
</div>
<div v-if="dialogData.type == 'group'" class="chat-input-popover-item" @click="onToolbar('vote')">
<i class="taskfont">&#xe806;</i>
{{$L('发起投票')}}
</div>
<div class="chat-input-popover-item" @click="onToolbar('full')"> <div class="chat-input-popover-item" @click="onToolbar('full')">
<i class="taskfont">&#xe6a7;</i> <i class="taskfont">&#xe6a7;</i>
{{$L('全屏输入')}} {{$L('全屏输入')}}
@ -1224,6 +1232,21 @@ export default {
case 'anon': case 'anon':
this.$emit('on-more', action) this.$emit('on-more', action)
break; break;
case 'word-chain':
this.$store.state.dialogDroupWordChain = {
type: 'create',
dialog_id: this.dialogId
}
break;
case 'vote':
this.$store.state.dialogGroupVote = {
type: 'create',
dialog_id: this.dialogId
}
break;
} }
}, },

View File

@ -0,0 +1,225 @@
<template>
<Modal class-name="dialog-droup-word-chain"
v-model="show"
:mask-closable="false"
:title="dialogGroupVote.type == 'create' ? $L('发起投票') : $L('投票结果')"
:closable="!isFullscreen"
:fullscreen="isFullscreen"
:footer-hide="isFullscreen">
<!-- 顶部 -->
<template #header>
<div v-if="isFullscreen" class="chain-modal-header">
<div class="chain-modal-close" @click="show = false">
{{ $L('取消') }}
</div>
<div class="chain-modal-title">
{{ dialogGroupVote.type == 'create' ? $L('发起投票') : $L('投票结果') }}
</div>
<div class="chain-modal-submit" :class="{'disabled': !isEdit}" @click="onSend" >
<div v-if="loadIng > 0" class="submit-loading"><Loading /></div>
{{$L('发送')}}
</div>
</div>
</template>
<template #close>
<i class="ivu-icon ivu-icon-ios-close"></i>
</template>
<div ref="wordChainBodyRef" class="word-chain-body">
<div class="source" v-if="dialogGroupVote.type == 'create'">
{{$L('来自')}}
<span>{{ dialog.name }}</span>
</div>
<div class="initiate">
<span>{{ $L('由') }}</span>
<UserAvatar :userid="createId" :size="22" :showName="true" tooltipDisabled/>
<span> {{ $L('发起') }}</span>
</div>
<div class="textarea">
<Input ref="wordChainTextareaRef"
v-model="value"
type="textarea"
:placeholder="$L('请输入投票主题')"
:autosize="{minRows: 3,maxRows: 5}"
:disabled="dialogGroupVote.type != 'create'" />
</div>
<ul ref="wordChainListRef">
<li v-for="(item,index) in list">
<i class="taskfont" :class="{'disabled': list.length <= 2}" @click="onDel(index)">&#xe680;</i>
<Input v-model="item.text" :placeholder="$L('请输入选项内容')"/>
</li>
<li class="add">
<i class="taskfont" @click="onAdd">&#xe78c;</i>
</li>
</ul>
<div class="switch-row" v-if="dialogGroupVote.type == 'create'">
<span class="label">{{ $L('允许多选') }}</span>
<iSwitch v-model="multiple" :true-value="1" :false-value="0"/>
</div>
<div class="switch-row" v-if="dialogGroupVote.type == 'create'">
<span class="label">{{ $L('匿名投票') }}</span>
<iSwitch v-model="anonymous" :true-value="1" :false-value="0"/>
</div>
</div>
<div slot="footer">
<Button type="default" @click="show=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onSend" :disabled="!isEdit">{{$L('发送')}}</Button>
</div>
</Modal>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'DialogGroupVote',
data() {
return {
show: false,
createId: 0,
value: "",
list: [],
multiple: 0,
anonymous: 0,
oldData: '',
loadIng: 0,
}
},
computed: {
...mapState(['dialogGroupVote', 'userInfo', 'dialogMsgs', 'cacheDialogs']),
isFullscreen({ windowWidth }) {
return windowWidth < 576;
},
allList(){
const msg = this.dialogGroupVote.msgData?.msg || {};
let list = JSON.parse(JSON.stringify(msg.list || []));
this.dialogMsgs.filter(h=>{
return h.type == "word-chain" && h.msg?.uuid == msg.uuid
}).forEach((h)=>{
(h.msg.list || []).forEach(k=>{
if(list.map(j=>j.id).indexOf(k.id) == -1){
list.push(k)
}
})
});
return list;
},
isEdit(){
return this.oldData != JSON.stringify(this.list);
},
dialog(){
return this.cacheDialogs.find(h=>h.id == this.dialogGroupVote.dialog_id) || {}
},
},
watch: {
show(val){
if(!val){
this.value = "";
this.list = [];
}else{
if(this.dialogGroupVote.type == 'create'){
this.$nextTick(()=>{
this.$refs.wordChainTextareaRef.focus()
})
}
this.scrollTo();
}
},
dialogGroupVote(data) {
if(data.type == 'create' && data.dialog_id){
this.show = true;
this.createId = this.userId;
this.list.push({
id: Date.now(),
text: ""
});
this.list.push({
id: Date.now() + 1,
text: ""
});
}
if(data.type == 'participate' && data.dialog_id && data.msgData){
this.show = true;
this.createId = data.msgData.msg.userid;
this.value = data.msgData.msg.text;
this.list = this.allList;
this.oldData = JSON.stringify(this.list);
}
}
},
methods: {
onAdd(){
this.list.push({
id: Date.now(),
text: "",
});
this.scrollTo();
},
onDel(index){
if( this.list.length > 2 ){
this.list.splice(index, 1);
}
},
scrollTo(){
this.$nextTick(()=>{
this.$refs.wordChainListRef.scrollTo(0, 99999);
});
},
onSend() {
if(!this.isEdit){
return;
}
//
if(!this.value){
$A.messageError("请输入投票主题");
return;
}
if(this.list.find(h=> !h.text)){
$A.messageError("请输入选项内容");
return;
}
//
this.loadIng++;
this.$store.dispatch("call", {
url: 'dialog/msg/vote',
method: 'post',
data: {
dialog_id: this.dialogGroupVote.dialog_id,
text: this.value,
list: this.list,
uuid: this.dialogGroupVote.msgData?.msg?.uuid || '',
multiple: this.multiple,
anonymous: this.anonymous
}
}).then(({data}) => {
this.show = false;
this.$store.dispatch("saveDialogMsg", data);
}).catch(({msg}) => {
if( msg.indexOf("System error") !== -1){
$A.modalInfo({
language: false,
title: '版本过低',
content: '服务器版本过低,请升级服务器。',
})
return;
}
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
}
}
}
</script>

View File

@ -0,0 +1,248 @@
<template>
<Modal class-name="dialog-droup-word-chain"
v-model="show"
:mask-closable="false"
:title="dialogDroupWordChain.type == 'create' ? $L('发起接龙') : $L('接龙结果')"
:closable="!isFullscreen"
:fullscreen="isFullscreen"
:footer-hide="isFullscreen">
<!-- 顶部 -->
<template #header>
<div v-if="isFullscreen" class="chain-modal-header">
<div class="chain-modal-close" @click="show = false">
{{ $L('取消') }}
</div>
<div class="chain-modal-title">
{{ dialogDroupWordChain.type == 'create' ? $L('发起接龙') : $L('接龙结果') }}
</div>
<div class="chain-modal-submit" :class="{'disabled': !isEdit}" @click="onSend" >
<div v-if="loadIng > 0" class="submit-loading"><Loading /></div>
{{$L('发送')}}
</div>
</div>
</template>
<template #close>
<i class="ivu-icon ivu-icon-ios-close"></i>
</template>
<div ref="wordChainBodyRef" class="word-chain-body">
<div class="source" v-if="dialogDroupWordChain.type == 'create'">
{{$L('来自')}}
<span>{{ dialog.name }}</span>
</div>
<div class="initiate">
<span>{{ $L('由') }}</span>
<UserAvatar :userid="createId" :size="22" :showName="true" tooltipDisabled/>
<span> {{ $L('发起,参与接龙目前共'+num+'人') }}</span>
</div>
<div class="textarea">
<Input ref="wordChainTextareaRef"
v-model="value"
type="textarea"
:autosize="{minRows: 3,maxRows: 5}"
:disabled="dialogDroupWordChain.type != 'create'"
:placeholder="$L('请输入接龙主题')"
/>
</div>
<ul ref="wordChainListRef">
<li v-for="(item) in list" v-if="item.type == 'case' && (dialogDroupWordChain.type == 'create' || item.text)">
<span>{{ $L('例') }}</span>
<Input v-model="item.text" :placeholder="$L('可填写接龙格式')" :disabled="dialogDroupWordChain.type != 'create'" />
</li>
<li v-for="(item,index) in list.filter(h=>h.type != 'case')">
<span>{{index + 1}}</span>
<Input v-model="item.text" :disabled="item.userid != userId" :placeholder="$L('请输入接龙内容')"/>
</li>
<li class="add">
<i class="taskfont" @click="onAdd">&#xe78c;</i>
</li>
</ul>
</div>
<div slot="footer">
<Button type="default" @click="show=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onSend" :disabled="!isEdit">{{$L('发送')}}</Button>
</div>
</Modal>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'DialogDroupWordChain',
data() {
return {
show: false,
createId: 0,
value: "#" + this.$L('接龙') + " \n",
list: [],
oldData: '',
loadIng: 0,
}
},
computed: {
...mapState(['dialogDroupWordChain', 'userInfo', 'dialogMsgs', 'cacheDialogs']),
isFullscreen({ windowWidth }) {
return windowWidth < 576;
},
num(){
return this.list.filter(h=>h.type != 'case')?.length || 0;
},
allList(){
const msg = this.dialogDroupWordChain.msgData?.msg || {};
let list = JSON.parse(JSON.stringify(msg.list || []));
this.dialogMsgs.filter(h=>{
return h.type == "word-chain" && h.msg?.uuid == msg.uuid
}).forEach((h)=>{
(h.msg.list || []).forEach(k=>{
if( k.type != 'case' && list.map(j=>j.id).indexOf(k.id) == -1 ){
list.push(k)
}
})
});
return list;
},
isEdit(){
return this.oldData != JSON.stringify(this.list);
},
dialog(){
return this.cacheDialogs.find(h=>h.id == this.dialogDroupWordChain.dialog_id) || {}
},
},
watch: {
show(val){
if(!val){
this.value = "#" + this.$L('接龙') + " \n";
this.list = [];
}else{
if(this.dialogDroupWordChain.type == 'create'){
this.$nextTick(()=>{
this.$refs.wordChainTextareaRef.focus()
})
}
this.scrollTo();
}
},
dialogDroupWordChain(data) {
if(data.type == 'create' && data.dialog_id){
this.show = true;
this.createId = this.userId;
this.list.push({
id: Date.now(),
type: "case",
userid: this.userId,
text: ""
});
this.list.push({
id: Date.now() + 1,
type: "text",
userid: this.userId,
text: this.userInfo.nickname
});
}
if(data.type == 'participate' && data.dialog_id && data.msgData){
this.show = true;
this.createId = data.msgData.msg.userid;
this.value = data.msgData.msg.text;
this.list = this.allList;
this.oldData = JSON.stringify(this.list);
}
}
},
methods: {
onAdd(){
this.list.push({
id: Date.now(),
type: 'text',
userid: this.userId,
text: this.userInfo.nickname,
});
this.scrollTo();
},
scrollTo(){
this.$nextTick(()=>{
this.$refs.wordChainListRef.scrollTo(0, 99999);
});
},
onSend() {
if( !this.isEdit ){
return;
}
//
if(!this.value){
$A.messageError("请输入接龙主题");
return;
}
if( this.list.find(h=> !h.text && h.type != "case") ){
$A.messageError("请输入接龙内容");
return;
}
//
const texts = this.list.map(h=> h.text);
if( texts.length != [...new Set(texts)].length ){
$A.modalConfirm({
content: '重复内容将不再计入接龙结果',
cancelText: '返回编辑',
okText: '继续发送',
onOk: () => {
this.send()
}
})
return;
}
this.send();
},
/**
* 发送消息
*/
send() {
const list = [];
this.list.forEach(h=>{
if(h.text && list.map(h=> h.text).indexOf(h.text) == -1){
list.push(h);
}
});
//
this.loadIng++;
this.$store.dispatch("call", {
url: 'dialog/msg/wordchain',
method: 'post',
data: {
dialog_id: this.dialogDroupWordChain.dialog_id,
text: this.value,
list: list,
uuid: this.dialogDroupWordChain.msgData?.msg?.uuid || ''
}
}).then(({data}) => {
this.show = false;
this.$store.dispatch("saveDialogMsg", data);
}).catch(({msg}) => {
if( msg.indexOf("System error") !== -1){
$A.modalInfo({
language: false,
title: '版本过低',
content: '服务器版本过低,请升级服务器。',
})
return;
}
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
}
}
}
</script>

View File

@ -14,6 +14,10 @@
<UserAvatar :userid="msgData.reply_data.userid" :show-icon="false" :show-name="true"/> <UserAvatar :userid="msgData.reply_data.userid" :show-icon="false" :show-name="true"/>
<div class="reply-desc" v-html="$A.getMsgSimpleDesc(msgData.reply_data, 'image-preview')"></div> <div class="reply-desc" v-html="$A.getMsgSimpleDesc(msgData.reply_data, 'image-preview')"></div>
</div> </div>
<!--转发-->
<div v-if="msgData.forward_show && msgData.forward_data && msgData.forward_data.userid" class="dialog-reply no-dark-content" @click="openDialog(msgData.forward_data.userid)">
<UserAvatar :userid="msgData.forward_data.userid" :show-icon="false" :show-name="true" :tooltip-disabled="true"/>
</div>
<!--详情--> <!--详情-->
<div ref="content" class="dialog-content" :class="contentClass"> <div ref="content" class="dialog-content" :class="contentClass">
<!--文本--> <!--文本-->
@ -62,6 +66,72 @@
</li> </li>
</ul> </ul>
</div> </div>
<!--接龙-->
<div v-else-if="msgData.type === 'word-chain'" class="content-text content-word-chain no-dark-content">
<pre v-html="$A.formatTextMsg(msgData.msg.text, userId)"></pre>
<ul>
<li v-for="(item) in (msgData.msg.list || []).filter(h=>h.type == 'case')">
{{ $L('例') }} {{ item.text }}
</li>
<li v-for="(item,index) in (msgData.msg.list || []).filter(h=>h.type != 'case')">
<span class="expand" v-if="index == 2 && msgData.msg.list.length > 4" @click="unfoldWordChain">
...{{$L('展开')}}...
</span>
<span :class="{'shrink': index >= 2 && msgData.msg.list.length > 4 } ">
{{index + 1}}. {{item.text}}
</span>
</li>
<li @click="onWordChain" class="participate">{{ $L('参与接龙') }}<span>></span></li>
</ul>
</div>
<!--投票-->
<div v-else-if="msgData.type === 'vote'" class="content-text content-word-vote no-dark-content">
<div class="vote-msg-head">
<i class="taskfont">&#xe7fd;</i> {{ $L('投票') }}
<span>{{ msgData.msg.multiple == 1 ? $L('多选') : $L('单选')}}</span>
<span>{{ msgData.msg.multiple == 1 ? $L('匿名') : $L('实名')}}</span>
</div>
<pre v-html="$A.formatTextMsg(msgData.msg.text, userId)"></pre>
<template v-if="(msgData.msg.votes || []).filter(h=>h.userid == userId).length == 0">
<RadioGroup v-if="msgData.msg.multiple == 0" v-model="msgData.msg._vote" vertical>
<Radio v-for="(item,index) in (msgData.msg.list || [])" :label="item.id" :key="index">
{{item.text}}
</Radio>
</RadioGroup>
<CheckboxGroup v-else v-model="msgData.msg._vote">
<Checkbox v-for="(item,index) in (msgData.msg.list || [])" :label="item.id" :key="index">
{{item.text}}
</Checkbox>
</CheckboxGroup>
<div class="btn-row no-dark-content">
<Button v-if="(msgData.msg._vote || []).length == 0" class="ivu-btn" disabled>{{$L("请选择后投票")}}</Button>
<Button v-else class="ivu-btn" :loading="msgData.msg._loadIng > 0" @click="onVote('vote',msgData)">{{$L("立即投票")}}</Button>
</div>
</template>
<template v-else>
<div class="vote-result-body">
<ul>
<li v-for="item in (msgData.msg.list || [])">
<div class="vote-option-title">{{ item.text }}</div>
<div class="ticket-num">
<span>{{ getVoteProgress(msgData.msg,item.id).num }}{{$L('票')}}</span>
<span>{{ getVoteProgress(msgData.msg,item.id).progress + '%' }}</span>
</div>
<Progress :percent="Number(getVoteProgress(msgData.msg,item.id).progress)" :stroke-width="5" hide-info/>
<div v-if="msgData.msg.anonymous" class="avatar-row">
<template v-for="votes in (msgData.msg.votes || []).filter(h=>h.votes.indexOf(item.id) != -1)">
<UserAvatar :userid="votes.userid" :size="18" />
</template>
</div>
</li>
</ul>
</div>
<div class="btn-row no-dark-content" v-if="msgData.msg.state == 1 && msgData.msg.userid == userId">
<Button class="ivu-btn" :loading="msgData.msg._loadIng > 0" @click="onVote('again',msgData)">{{$L("再次发送")}}</Button>
<Button class="ivu-btn" :loading="msgData.msg._loadIng > 0" @click="onVote('finish',msgData)">{{$L("结束投票")}}</Button>
</div>
</template>
</div>
<!--等待--> <!--等待-->
<div v-else-if="msgData.type === 'loading'" class="content-loading"> <div v-else-if="msgData.type === 'loading'" class="content-loading">
<Icon v-if="msgData.error === true" type="ios-alert-outline" /> <Icon v-if="msgData.error === true" type="ios-alert-outline" />
@ -453,6 +523,14 @@ export default {
}); });
}, },
openDialog(userid) {
this.$store.dispatch("openDialogUserid", userid).then(_ => {
this.goForward({name: 'manage-messenger'})
}).catch(({msg}) => {
$A.modalError(msg)
});
},
viewReply() { viewReply() {
this.$emit("on-view-reply", { this.$emit("on-view-reply", {
msg_id: this.msgData.id, msg_id: this.msgData.id,
@ -492,6 +570,67 @@ export default {
onShowEmojiUser(item) { onShowEmojiUser(item) {
this.$emit("on-show-emoji-user", item) this.$emit("on-show-emoji-user", item)
}, },
onWordChain(){
this.$store.state.dialogDroupWordChain = {
type: 'participate',
dialog_id: this.msgData.dialog_id,
msgData: this.msgData,
}
},
unfoldWordChain(e){
e.target.parentNode?.parentNode?.classList.add('expand')
},
onVote(type,msgData){
if(type != 'vote'){
$A.modalConfirm({
content: type == 'finish' ? '确定结束投票?': '再次发送投票?',
cancelText: '取消',
okText: '确定',
onOk: () => {
this.vote(type,msgData);
}
});
return;
}
this.vote(type,msgData);
},
vote(type,msgData){
this.$set(msgData.msg,'_loadIng',1)
this.$store.dispatch("call", {
url: 'dialog/msg/vote',
method: 'post',
data: {
dialog_id: msgData.dialog_id,
uuid: msgData.msg.uuid,
vote: msgData.msg._vote || [],
type: type
}
}).then(({data}) => {
if(type == 'again'){
$A.messageSuccess("已发送");
}
data.forEach(d => {
this.$store.dispatch("saveDialogMsg", d );
});
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.$set(msgData.msg,'_loadIng',0)
});
},
getVoteProgress(msgData, id){
const num = msgData.votes.filter(h=>(h.votes || '').indexOf(id) != -1).length
let progress = '0.00';
if(num){
progress = (msgData.votes.length / num * 100).toFixed(2)
}
return {num, progress};
}
} }
} }
</script> </script>

View File

@ -257,7 +257,7 @@
<i class="taskfont" v-html="item.icon"></i> <i class="taskfont" v-html="item.icon"></i>
<span>{{ $L(item.label) }}</span> <span>{{ $L(item.label) }}</span>
</li> </li>
<li @click="onOperate('forward')"> <li v-if="operateItem.type !== 'word-chain' && operateItem.type !== 'vote'" @click="onOperate('forward')">
<i class="taskfont">&#xe638;</i> <i class="taskfont">&#xe638;</i>
<span>{{ $L('转发') }}</span> <span>{{ $L('转发') }}</span>
</li> </li>
@ -392,10 +392,33 @@
v-model="forwardData" v-model="forwardData"
:multiple-max="50" :multiple-max="50"
:title="$L('转发')" :title="$L('转发')"
:twice-affirm="true"
:twice-affirm-title="$L('转发给:')"
:before-submit="onForward" :before-submit="onForward"
:show-select-all="false" :show-select-all="false"
:multiple-choice="false"
show-dialog show-dialog
module/> module>
<template #twice-affirm-body-extend>
<div class="dialog-wrapper-forward-body">
<div class="dialog-wrapper ">
<div class="dialog-scroller">
<DialogItem :source="operateItem" simpleView :dialogAvatar="false"/>
</div>
</div>
<div class="leave-message">
<Input type="textarea" :autosize="{minRows: 1,maxRows: 3}" v-model="forwardLeaveMessage" :placeholder="$L('留言')" clearable />
</div>
</div>
</template>
<template #twice-affirm-footer-extend>
<div class="dialog-wrapper-forward-footer" :class="{'selected': !forwardShowOriginal}" @click="forwardShowOriginal = !forwardShowOriginal">
<Icon v-if="!forwardShowOriginal" class="user-modal-icon" type="ios-checkmark-circle" />
<Icon v-else class="user-modal-icon" type="ios-radio-button-off" />
{{$L('不显示原发送者信息')}}
</div>
</template>
</UserSelect>
<!-- 设置待办 --> <!-- 设置待办 -->
<Modal <Modal
@ -508,6 +531,13 @@
<DrawerOverlay v-model="approveDetailsShow" placement="right" :size="600"> <DrawerOverlay v-model="approveDetailsShow" placement="right" :size="600">
<ApproveDetails v-if="approveDetailsShow" :data="approveDetails" style="height: 100%;border-radius: 10px;"></ApproveDetails> <ApproveDetails v-if="approveDetailsShow" :data="approveDetails" style="height: 100%;border-radius: 10px;"></ApproveDetails>
</DrawerOverlay> </DrawerOverlay>
<!-- 群接龙 -->
<DialogGroupWordChain/>
<!-- 群投票 -->
<DialogGroupVote/>
</div> </div>
</template> </template>
@ -528,6 +558,9 @@ import {choiceEmojiOne} from "./ChatInput/one";
import ApproveDetails from "../../../pages/manage/approve/details.vue"; import ApproveDetails from "../../../pages/manage/approve/details.vue";
import UserSelect from "../../../components/UserSelect.vue"; import UserSelect from "../../../components/UserSelect.vue";
import UserAvatarTip from "../../../components/UserAvatar/tip.vue"; import UserAvatarTip from "../../../components/UserAvatar/tip.vue";
import DialogGroupWordChain from "./DialogGroupWordChain";
import DialogGroupVote from "./DialogGroupVote";
export default { export default {
name: "DialogWrapper", name: "DialogWrapper",
@ -542,7 +575,9 @@ export default {
DialogGroupInfo, DialogGroupInfo,
DrawerOverlay, DrawerOverlay,
DialogUpload, DialogUpload,
ApproveDetails ApproveDetails,
DialogGroupWordChain,
DialogGroupVote,
}, },
props: { props: {
@ -598,6 +633,8 @@ export default {
modifyLoad: 0, modifyLoad: 0,
forwardData: [], forwardData: [],
forwardShowOriginal: true,
forwardLeaveMessage: '',
openId: 0, openId: 0,
dialogDrag: false, dialogDrag: false,
@ -1138,6 +1175,9 @@ export default {
}, },
allMsgList(newList, oldList) { allMsgList(newList, oldList) {
if(JSON.stringify(newList) == JSON.stringify(oldList)){
return;
}
const {tail} = this.scrollInfo(); const {tail} = this.scrollInfo();
if ($A.isIos() && newList.length !== oldList.length) { if ($A.isIos() && newList.length !== oldList.length) {
// iOS // iOS
@ -2249,7 +2289,9 @@ export default {
data: { data: {
dialogids, dialogids,
userids, userids,
msg_id: this.operateItem.id msg_id: this.operateItem.id,
show_source: this.forwardShowOriginal ? 1 : 0,
leave_message: this.forwardLeaveMessage
} }
}).then(({data, msg}) => { }).then(({data, msg}) => {
this.$store.dispatch("saveDialogMsg", data.msgs); this.$store.dispatch("saveDialogMsg", data.msgs);
@ -2382,14 +2424,21 @@ export default {
value: $A.thumbRestore(event.target.currentSrc), value: $A.thumbRestore(event.target.currentSrc),
}) })
} else if (event.target.nodeName === 'A') { } else if (event.target.nodeName === 'A') {
let href = event.target.href;
if (event.target.classList.contains("mention") && event.target.classList.contains("file")) { if (event.target.classList.contains("mention") && event.target.classList.contains("file")) {
this.findOperateFile(this.operateItem.id, event.target.href) if(this.isEEUiApp || this.$Electron){
const url = new URL(href);
const params = new URLSearchParams(url.search);
params.delete('theme'); params.delete('lang');
href = url.origin + url.pathname + (params.toString() ? ('?' + params.toString()) : '');
}
this.findOperateFile(this.operateItem.id, href)
} }
this.operateCopys.push({ this.operateCopys.push({
type: 'link', type: 'link',
icon: '&#xe7cb;', icon: '&#xe7cb;',
label: '复制链接', label: '复制链接',
value: event.target.href, value: href,
}) })
} }
if (msgData.type === 'text') { if (msgData.type === 'text') {
@ -2454,6 +2503,8 @@ export default {
case "forward": case "forward":
this.forwardData = []; this.forwardData = [];
this.forwardLeaveMessage = '';
this.forwardShowOriginal = true;
this.$refs.forwardSelect.onSelection() this.$refs.forwardSelect.onSelection()
break; break;

View File

@ -599,7 +599,7 @@ export default {
}, },
documentKey() { documentKey() {
return new Promise(resolve => { return new Promise((resolve,reject) => {
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'file/content', url: 'file/content',
data: { data: {
@ -608,8 +608,8 @@ export default {
}, },
}).then(({data}) => { }).then(({data}) => {
resolve(`${data.id}-${$A.Time(data.update_at)}`) resolve(`${data.id}-${$A.Time(data.update_at)}`)
}).catch(() => { }).catch((res) => {
resolve(0) reject(res)
}); });
}) })
}, },

View File

@ -144,7 +144,7 @@ export default {
}, },
documentKey() { documentKey() {
return new Promise(resolve => { return new Promise((resolve,reject) => {
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'file/content', url: 'file/content',
data: { data: {
@ -153,8 +153,8 @@ export default {
}, },
}).then(({data}) => { }).then(({data}) => {
resolve(`${data.id}-${$A.Time(data.update_at)}`) resolve(`${data.id}-${$A.Time(data.update_at)}`)
}).catch(() => { }).catch((res) => {
resolve(0) reject(res)
}); });
}) })
}, },

View File

@ -52,6 +52,7 @@
<Icon class="menu-icon" type="ios-more" /> <Icon class="menu-icon" type="ios-more" />
<EDropdownMenu v-if="projectData.owner_userid === userId" slot="dropdown"> <EDropdownMenu v-if="projectData.owner_userid === userId" slot="dropdown">
<EDropdownItem command="setting">{{$L('项目设置')}}</EDropdownItem> <EDropdownItem command="setting">{{$L('项目设置')}}</EDropdownItem>
<EDropdownItem command="permissions">{{$L('权限设置')}}</EDropdownItem>
<EDropdownItem command="workflow">{{$L('工作流设置')}}</EDropdownItem> <EDropdownItem command="workflow">{{$L('工作流设置')}}</EDropdownItem>
<EDropdownItem command="user" divided>{{$L('成员管理')}}</EDropdownItem> <EDropdownItem command="user" divided>{{$L('成员管理')}}</EDropdownItem>
<EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem> <EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem>
@ -80,7 +81,7 @@
<div v-if="completedCount > 0" class="project-checkbox"> <div v-if="completedCount > 0" class="project-checkbox">
<Checkbox :value="projectData.cacheParameter.completedTask" @on-change="toggleCompleted">{{$L('显示已完成')}}</Checkbox> <Checkbox :value="projectData.cacheParameter.completedTask" @on-change="toggleCompleted">{{$L('显示已完成')}}</Checkbox>
</div> </div>
<div v-if="flowList.length > 0" class="project-select"> <div class="project-select">
<Cascader ref="flow" :data="flowData" @on-change="flowChange" transfer-class-name="project-panel-flow-cascader" transfer> <Cascader ref="flow" :data="flowData" @on-change="flowChange" transfer-class-name="project-panel-flow-cascader" transfer>
<span :class="`project-flow ${flowInfo.status || ''}`">{{ flowTitle }}</span> <span :class="`project-flow ${flowInfo.status || ''}`">{{ flowTitle }}</span>
</Cascader> </Cascader>
@ -236,11 +237,11 @@
<Scrollbar v-else-if="tabTypeActive === 'table'" class="project-table" enable-x> <Scrollbar v-else-if="tabTypeActive === 'table'" class="project-table" enable-x>
<div class="project-table-head"> <div class="project-table-head">
<Row class="task-row"> <Row class="task-row">
<Col span="12"># {{$L('任务名称')}}</Col> <Col span="12"><span class="head-title"># {{$L('任务名称')}}</span></Col>
<Col span="3">{{$L('列表')}}</Col> <Col span="3"><span class="head-title">{{$L('列表')}}</span></Col>
<Col span="3"> <Col span="3">
<div class="sort" @click="onSort('level')"> <div class="sort" @click="onSort('level')">
{{$L('优先级')}} <span class="head-title">{{$L('优先级')}}</span>
<div class="task-sort"> <div class="task-sort">
<Icon :class="{on:sortField=='level' && sortType=='asc'}" type="md-arrow-dropup" /> <Icon :class="{on:sortField=='level' && sortType=='asc'}" type="md-arrow-dropup" />
<Icon :class="{on:sortField=='level' && sortType=='desc'}" type="md-arrow-dropdown" /> <Icon :class="{on:sortField=='level' && sortType=='desc'}" type="md-arrow-dropdown" />
@ -250,7 +251,7 @@
<Col span="3">{{$L('负责人')}}</Col> <Col span="3">{{$L('负责人')}}</Col>
<Col span="3"> <Col span="3">
<div class="sort" @click="onSort('end_at')"> <div class="sort" @click="onSort('end_at')">
{{$L('到期时间')}} <span class="head-title">{{$L('到期时间')}}</span>
<div class="task-sort"> <div class="task-sort">
<Icon :class="{on:sortField=='end_at' && sortType=='asc'}" type="md-arrow-dropup" /> <Icon :class="{on:sortField=='end_at' && sortType=='asc'}" type="md-arrow-dropup" />
<Icon :class="{on:sortField=='end_at' && sortType=='desc'}" type="md-arrow-dropdown" /> <Icon :class="{on:sortField=='end_at' && sortType=='desc'}" type="md-arrow-dropdown" />
@ -344,6 +345,14 @@
</div> </div>
</Modal> </Modal>
<!--项目权限-->
<DrawerOverlay
v-model="permissionShow"
placement="right"
:size="650">
<ProjectPermission ref="permission" v-if="permissionShow" @close="()=>{ this.permissionShow = false }" :project-id="projectId"/>
</DrawerOverlay>
<!--成员管理--> <!--成员管理-->
<Modal <Modal
v-model="userShow" v-model="userShow"
@ -474,6 +483,7 @@ import TaskArchived from "./TaskArchived";
import ProjectLog from "./ProjectLog"; import ProjectLog from "./ProjectLog";
import DrawerOverlay from "../../../components/DrawerOverlay"; import DrawerOverlay from "../../../components/DrawerOverlay";
import ProjectWorkflow from "./ProjectWorkflow"; import ProjectWorkflow from "./ProjectWorkflow";
import ProjectPermission from "./ProjectPermission";
import TaskMenu from "./TaskMenu"; import TaskMenu from "./TaskMenu";
import TaskDeleted from "./TaskDeleted"; import TaskDeleted from "./TaskDeleted";
import ProjectGantt from "./ProjectGantt"; import ProjectGantt from "./ProjectGantt";
@ -489,6 +499,7 @@ export default {
MarkdownPreviewNostyle, MarkdownPreviewNostyle,
TaskMenu, TaskMenu,
ProjectWorkflow, ProjectWorkflow,
ProjectPermission,
DrawerOverlay, DrawerOverlay,
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, TaskPriority, TaskDeleted, ProjectGantt}, ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, TaskPriority, TaskDeleted, ProjectGantt},
data() { data() {
@ -516,6 +527,10 @@ export default {
settingData: {}, settingData: {},
settingLoad: 0, settingLoad: 0,
permissionShow: false,
permissionShowData: {},
permissionShowLoad: 0,
userShow: false, userShow: false,
userData: {}, userData: {},
userLoad: 0, userLoad: 0,
@ -846,7 +861,7 @@ export default {
// //
const {project_user} = this.projectData; const {project_user} = this.projectData;
if ($A.isArray(project_user)) { if ($A.isArray(project_user)) {
const userItems = project_user.map((item, index) => { let userItems = project_user.map((item, index) => {
const userInfo = cacheUserBasic.find(({userid}) => userid === item.userid) || {} const userInfo = cacheUserBasic.find(({userid}) => userid === item.userid) || {}
const length = allTask.filter(({task_user, complete_at}) => { const length = allTask.filter(({task_user, complete_at}) => {
if (!this.projectData.cacheParameter.completedTask) { if (!this.projectData.cacheParameter.completedTask) {
@ -859,12 +874,18 @@ export default {
return { return {
value: `user:${userInfo.userid}`, value: `user:${userInfo.userid}`,
label: `${userInfo.nickname} (${length})`, label: `${userInfo.nickname} (${length})`,
class: `user-${index}`,
userid: userInfo.userid || 0, userid: userInfo.userid || 0,
length, length,
} }
}).filter(({userid, length}) => userid > 0 && length > 0) }).filter(({userid, length}) => userid > 0 && length > 0)
if (userItems.length > 0) { if (userItems.length > 0) {
userItems.sort((a, b) => {
return a.userid == this.userId ? -1 : 1
})
userItems = userItems.map((item, index)=>{
item.class = `user-${index}`
return item;
})
list.push(...userItems) list.push(...userItems)
} }
} }
@ -1253,6 +1274,16 @@ export default {
}); });
break; break;
case "permissions":
// this.$set(this.settingData, 'name', this.projectData.name);
// this.$set(this.settingData, 'desc', this.projectData.desc);
this.permissionShow = true;
// this.$nextTick(() => {
// this.$refs.projectName.focus()
// setTimeout(this.$refs.projectDesc.resizeTextarea, 0)
// });
break;
case "user": case "user":
if (this.projectData.owner_userid !== this.userId) { if (this.projectData.owner_userid !== this.userId) {
return; return;

View File

@ -0,0 +1,177 @@
<template>
<div class="project-permission">
<div class="permission-title">
{{$L('权限设置')}}
<div class="title-icon">
<Loading v-if="loadIng > 0"/>
<Icon v-else type="ios-refresh" @click="getData()"/>
</div>
</div>
<div class="permission-content">
<Form :model="formData" label-width="90" label-position="right">
<!-- 项目权限 -->
<div class="project-permission-title" >{{$L('任务列权限')}}:</div>
<FormItem :label="$L('添加列')">
<CheckboxGroup v-model="formData.task_list_add">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('修改列')">
<CheckboxGroup v-model="formData.task_list_update">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('删除列')">
<CheckboxGroup v-model="formData.task_list_remove">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('排序列')">
<CheckboxGroup v-model="formData.task_list_sort">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<!-- 任务权限 -->
<div class="project-permission-title" >{{$L('任务权限')}}:</div>
<FormItem :label="$L('添加任务')">
<CheckboxGroup v-model="formData.task_add">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('修改任务')">
<CheckboxGroup v-model="formData.task_update">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="3">{{ $L('任务负责人') }}</Checkbox>
<Checkbox :label="4">{{ $L('任务协助人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('修改状态')">
<CheckboxGroup v-model="formData.task_status">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="3">{{ $L('任务负责人') }}</Checkbox>
<Checkbox :label="4">{{ $L('任务协助人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('归档任务')">
<CheckboxGroup v-model="formData.task_archived">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="3">{{ $L('任务负责人') }}</Checkbox>
<Checkbox :label="4">{{ $L('任务协助人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('删除任务')">
<CheckboxGroup v-model="formData.task_remove">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="3">{{ $L('任务负责人') }}</Checkbox>
<Checkbox :label="4">{{ $L('任务协助人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem :label="$L('移动任务')">
<CheckboxGroup v-model="formData.task_move">
<Checkbox :label="1" disabled>{{ $L('项目负责人') }}</Checkbox>
<Checkbox :label="3">{{ $L('任务负责人') }}</Checkbox>
<Checkbox :label="4">{{ $L('任务协助人') }}</Checkbox>
<Checkbox :label="2">{{ $L('项目成员') }}</Checkbox>
</CheckboxGroup>
</FormItem>
</Form>
</div>
<div slot="footer" class="project-permission-footer">
<Button type="default" @click="onClose()">{{$L('取消')}}</Button>
<Button type="primary" @click="updateData()" :loading="loadIng > 0">{{$L('修改')}}</Button>
</div>
</div>
</template>
<script>
export default {
name: "ProjectPermission",
props: {
projectId: {
type: Number,
default: 0
},
},
data() {
return {
loadIng: 0,
formData: {
project_task_list: [],
task_add: [],
task_update: [],
task_status: [],
task_archived: [],
task_remove: [],
task_move: []
}
}
},
mounted() {
},
watch: {
projectId: {
handler(val) {
if (val) {
this.getData()
}
},
immediate: true
},
},
methods: {
getData() {
this.loadIng++;
this.$store.dispatch("call", {
url: 'project/permission',
data: {
project_id: this.projectId,
},
}).then(({data}) => {
this.formData = data.permissions;
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
updateData() {
this.loadIng++;
this.$store.dispatch("call", {
url: 'project/permission/update',
method: 'post',
data: {
project_id: this.projectId,
...this.formData
},
}).then(({data}) => {
this.formData = data.permissions;
this.$Message.success(this.$L('修改成功'));
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
onClose() {
this.$emit('close')
},
}
}
</script>

View File

@ -230,10 +230,33 @@
</EDropdown> </EDropdown>
</div> </div>
<div class="item-content user"> <div class="item-content user">
<span @click="showCisibleDropdown" v-if="taskDetail.visibility == 1" class="visibility-text">{{$L('项目人员可见')}}</span> <EDropdown v-if="taskDetail.visibility == 1 || taskDetail.visibility == 2" trigger="click" placement="bottom" @command="dropVisible">
<span @click="showCisibleDropdown" v-else-if="taskDetail.visibility == 2" class="visibility-text">{{$L('任务人员可见')}}</span> <span class="visibility-text">{{ taskDetail.visibility == 1 ? $L('项目人员可见') : $L('任务人员可见') }}</span>
<UserSelect <EDropdownMenu slot="dropdown">
v-else <EDropdownItem :command="1">
<div class="task-menu-icon" >
<Icon v-if="taskDetail.visibility == 1" class="completed" :type="'md-checkmark-circle'"/>
<Icon v-else class="uncomplete" :type="'md-radio-button-off'"/>
{{$L('项目人员')}}
</div>
</EDropdownItem>
<EDropdownItem :command="2">
<div class="task-menu-icon" >
<Icon v-if="taskDetail.visibility == 2" class="completed" :type="'md-checkmark-circle'"/>
<Icon v-else class="uncomplete" :type="'md-radio-button-off'"/>
{{$L('任务人员')}}
</div>
</EDropdownItem>
<EDropdownItem :command="3">
<div class="task-menu-icon" >
<Icon v-if="taskDetail.visibility == 3" class="completed" :type="'md-checkmark-circle'"/>
<Icon v-else class="uncomplete" :type="'md-radio-button-off'"/>
{{$L('指定成员')}}
</div>
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
<UserSelect v-else
ref="visibleUserSelectRef" ref="visibleUserSelectRef"
v-model="taskDetail.visibility_appointor" v-model="taskDetail.visibility_appointor"
:avatar-size="28" :avatar-size="28"
@ -1096,6 +1119,10 @@ export default {
} }
this.$set(this.taskDetail, 'content', contentSave) this.$set(this.taskDetail, 'content', contentSave)
action = 'content'; action = 'content';
if (content == this.taskContent.replace(/original-width="[^"]*"/g, "").replace(/original-height="[^"]*"/g, "").replace(/\" \//g, "\" /")) {
return;
}
this.$set(this.taskDetail, 'content', content)
successCallback = () => { successCallback = () => {
this.$store.dispatch("saveTaskContent", { this.$store.dispatch("saveTaskContent", {
task_id: this.taskId, task_id: this.taskId,

View File

@ -102,9 +102,7 @@ export default {
data: this.formData, data: this.formData,
}).then(({data}) => { }).then(({data}) => {
this.show = false; this.show = false;
this.$store.dispatch('downUrl', { $A.messageSuccess(data.msg);
url: data.url
});
}).catch(({msg}) => { }).catch(({msg}) => {
$A.modalError(msg); $A.modalError(msg);
}).finally(_ => { }).finally(_ => {

View File

@ -28,6 +28,10 @@ export default {
type: Boolean, type: Boolean,
default: true default: true
}, },
operationShow: {
type: Boolean,
default: true
},
updateBefore: { updateBefore: {
type: Boolean, type: Boolean,
default: false default: false
@ -48,6 +52,10 @@ export default {
type: String, type: String,
default: 'md-checkmark-circle' default: 'md-checkmark-circle'
}, },
projectId:{
type: Number,
default: 0
}
}, },
computed: { computed: {
...mapState(['loads', 'taskFlows']), ...mapState(['loads', 'taskFlows']),
@ -67,9 +75,11 @@ export default {
task: this.task, task: this.task,
loadStatus: this.loadStatus, loadStatus: this.loadStatus,
colorShow: this.colorShow, colorShow: this.colorShow,
operationShow: this.operationShow,
updateBefore: this.updateBefore, updateBefore: this.updateBefore,
disabled: this.disabled, disabled: this.disabled,
size: this.size, size: this.size,
projectId: this.projectId,
onUpdate: data => { onUpdate: data => {
this.$emit("on-update", data) this.$emit("on-update", data)
} }

View File

@ -1,14 +1,91 @@
<template> <template>
<div class="task-add"> <div class="task-move">
<Cascader <Cascader
v-model="cascader" v-model="cascader"
:data="cascaderData" :data="cascaderData"
:clearable="false" :clearable="false"
:placeholder="$L('请选择项目')" :placeholder="$L('请选择项目')"
:load-data="cascaderLoadData" :load-data="cascaderLoadData"
@on-input-change="cascaderInputChange"
@on-visible-change="cascaderShow=!cascaderShow" @on-visible-change="cascaderShow=!cascaderShow"
filterable/> filterable/>
<div class="task-move-content">
<div class="task-move-content-old">
<div class="task-move-title">{{ $L('移动前') }}</div>
<div class="task-move-row">
<span class="label">{{$L('状态')}}:</span>
<div v-if="task.flow_item_name" class="flow">
<span :class="task.flow_item_status">{{task.flow_item_name}}</span>
</div>
</div>
<div class="task-move-row" :class="{'not-flex': windowPortrait}">
<span class="label">{{$L('负责人')}}:</span>
<UserSelect class="item-content user"
v-model="ownerUserids"
:avatar-size="28"
:project-id="task.project_id"
:add-icon="false"
disable
/>
</div>
<div class="task-move-row" :class="{'not-flex': windowPortrait}">
<span class="label">{{$L('协助人')}}:</span>
<UserSelect class="item-content user"
v-model="assistUserids"
:avatar-size="28"
:project-id="task.project_id"
:add-icon="false"
disable
/>
</div>
</div>
<div class="task-move-content-new">
<div class="task-move-title">{{ $L('移动后') }}</div>
<div class="task-move-row">
<span class="label"> {{$L('状态:')}} </span>
<TaskMenu
:ref="`taskMenu_${task.id}`"
:task="tasks"
:project-id="cascader[0]"
:color-show="false"
:operation-show="false"
:load-status="task.loading === true"
@on-update="onStatusUpdate"
/>
<div v-if="updateData.flow.flow_item_name" class="flow">
<span :class="updateData.flow.flow_item_status" @click.stop="openMenu($event, tasks)">{{updateData.flow.flow_item_name}}</span>
</div>
</div>
<div class="task-move-row" :class="{'not-flex': windowPortrait}">
<span class="label">{{$L('负责人:')}}</span>
<div>
<UserSelect
class="item-content user"
v-model="updateData.owner_userids"
:multiple-max="10"
:avatar-size="28"
:project-id="cascader[0]"
:add-icon="false"
/>
</div>
</div>
<div class="task-move-row" :class="{'not-flex': windowPortrait}">
<span class="label">{{$L('协助人:')}}</span>
<div>
<UserSelect
class="item-content user"
v-model="updateData.assist_userids"
:multiple-max="10"
:avatar-size="28"
:project-id="cascader[0]"
:add-icon="false"
/>
</div>
</div>
</div>
</div>
<div class="ivu-modal-footer"> <div class="ivu-modal-footer">
<div class="adaption"> <div class="adaption">
<Button type="default" @click="close">{{$L('取消')}}</Button> <Button type="default" @click="close">{{$L('取消')}}</Button>
@ -20,9 +97,15 @@
<script> <script>
import {mapState} from "vuex"; import {mapState} from "vuex";
import TaskMenu from "./TaskMenu";
import UserSelect from "../../../components/UserSelect.vue";
export default { export default {
name: "TaskMove", name: "TaskMove",
components: {
TaskMenu,
UserSelect,
},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,
@ -36,6 +119,7 @@ export default {
}, },
data() { data() {
return { return {
tasks: {},
cascader: [], cascader: [],
cascaderShow: false, cascaderShow: false,
cascaderData: [], cascaderData: [],
@ -44,19 +128,21 @@ export default {
cascaderAlready: [], cascaderAlready: [],
loadIng: 0, loadIng: 0,
beforeClose: [],
flowItemId: 0,
ownerUserids: [],
assistUserids: [],
updateData:{
flow: {},
owner_userids: [],
assist_userids: []
}
} }
}, },
async mounted() { async mounted() {
this.initCascaderData(); this.initData();
},
beforeDestroy() {
this.beforeClose.some(func => {
typeof func === "function" && func()
})
this.beforeClose = [];
}, },
computed: { computed: {
@ -64,20 +150,63 @@ export default {
}, },
watch: { watch: {
task: { cascader(val){
handler: function (val) { this.tasks.flow_item_id = this.flowItemId;
this.cascader = [val.project_id, val.column_id]; if(val[0] != this.task.project_id){
}, this.updateData.flow.flow_item_id = 0;
deep: true, this.updateData.flow.flow_item_name = '';
immediate: true this.updateData.flow.flow_item_status = '';
}else{
this.updateData.flow.flow_item_id = this.flowItemId;
this.updateData.flow.flow_item_name = this.task.flow_item_name;
this.updateData.flow.flow_item_status = this.task.flow_item_status;
}
//
const projectUserIds = this.cacheProjects.find(project => project.id == val[0])?.project_user?.map(h=>{
return h.userid
}) || [];
//
this.updateData.owner_userids = (this.task.task_user || []).filter(h=>{
return h.owner && projectUserIds.indexOf(h.userid) !== -1
}).sort((a, b) => {
return a.id - b.id;
}).map(h=>{
return h.userid
});
//
this.updateData.assist_userids = (this.task.task_user || []).filter(h=>{
return !h.owner && projectUserIds.indexOf(h.userid) !== -1
}).sort((a, b) => {
return a.id - b.id;
}).map(h=>{
return h.userid
});
}, },
}, },
methods: { methods: {
/** /**
* 初始化级联数据 * 初始化数据
*/ */
initCascaderData() { initData() {
this.flowItemId = this.task.flow_item_id;
this.cascader = [this.task.project_id, this.task.column_id];
this.ownerUserids = (this.task.task_user || []).filter(h=>{
return h.owner
}).sort((a, b) => {
return a.id - b.id;
}).map(h=>{
return h.userid
});
this.assistUserids = (this.task.task_user || []).filter(h=>{
return !h.owner
}).sort((a, b) => {
return a.id - b.id;
}).map(h=>{
return h.userid
});
this.tasks = JSON.parse(JSON.stringify(this.task));
//
const data = $A.cloneJSON(this.cacheProjects).sort((a, b) => { const data = $A.cloneJSON(this.cacheProjects).sort((a, b) => {
if (a.top_at || b.top_at) { if (a.top_at || b.top_at) {
return $A.Date(b.top_at) - $A.Date(a.top_at); return $A.Date(b.top_at) - $A.Date(a.top_at);
@ -120,32 +249,15 @@ export default {
}); });
}, },
cascaderInputChange(key) { async onConfirm() {
this.cascaderValue = key || ""; if( this.task.project_id == this.cascader[0] && this.task.column_id == this.cascader[1]){
// $A.messageError("未变更移动项");
if (this.cascaderAlready[this.cascaderValue] === true) { return;
}
if( !this.updateData.flow.flow_item_id ){
$A.messageError("请选择移动后状态");
return; return;
} }
this.cascaderAlready[this.cascaderValue] = true;
//
setTimeout(() => {
this.cascaderLoading++;
}, 1000)
this.$store.dispatch("getProjects", {
keys: {
name: this.cascaderValue,
},
getcolumn: 'yes'
}).then(() => {
this.cascaderLoading--;
this.initCascaderData();
}).catch(() => {
this.cascaderLoading--;
});
},
async onConfirm() {
this.loadIng++; this.loadIng++;
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: "project/task/move", url: "project/task/move",
@ -153,14 +265,13 @@ export default {
task_id: this.task.id, task_id: this.task.id,
project_id: this.cascader[0], project_id: this.cascader[0],
column_id: this.cascader[1], column_id: this.cascader[1],
flow_item_id: this.updateData.flow.flow_item_id,
owner: this.updateData.owner_userids,
assist: this.updateData.assist_userids,
} }
}).then(({msg}) => { }).then(({data,msg}) => {
this.loadIng--; this.loadIng--;
this.$store.dispatch("saveTask", { this.$store.dispatch("saveTask", data);
id: this.task.id,
project_id: this.cascader[0],
column_id: this.cascader[1],
});
$A.messageSuccess(msg); $A.messageSuccess(msg);
this.close() this.close()
}).catch(({msg}) => { }).catch(({msg}) => {
@ -172,6 +283,16 @@ export default {
close() { close() {
this.$emit("input", !this.value) this.$emit("input", !this.value)
}, },
openMenu(event, task) {
const el = this.$refs[`taskMenu_${task.id}`];
el && el.handleClick(event)
},
onStatusUpdate(val) {
this.tasks.flow_item_id = val.flow_item_id;
this.updateData.flow = val
}
} }
} }
</script> </script>

View File

@ -42,6 +42,7 @@
</template> </template>
<template v-if="task.parent_id === 0"> <template v-if="task.parent_id === 0">
<template v-if="operationShow">
<EDropdownItem :divided="turns.length > 0" command="archived"> <EDropdownItem :divided="turns.length > 0" command="archived">
<div class="item"> <div class="item">
<Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}} <Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}}
@ -53,10 +54,11 @@
</div> </div>
</EDropdownItem> </EDropdownItem>
<EDropdownItem command="remove"> <EDropdownItem command="remove">
<div class="item"> <div class="item hover-del">
<Icon type="md-trash" />{{$L('删除')}} <Icon type="md-trash" />{{$L('删除')}}
</div> </div>
</EDropdownItem> </EDropdownItem>
</template>
<template v-if="colorShow"> <template v-if="colorShow">
<EDropdownItem v-for="(c, k) in taskColorList" :key="'c_' + k" :divided="k==0" :command="c"> <EDropdownItem v-for="(c, k) in taskColorList" :key="'c_' + k" :divided="k==0" :command="c">
<div class="item"> <div class="item">
@ -65,7 +67,7 @@
</EDropdownItem> </EDropdownItem>
</template> </template>
</template> </template>
<EDropdownItem v-else command="remove" :divided="turns.length > 0"> <EDropdownItem v-else-if="operationShow" command="remove" :divided="turns.length > 0">
<div class="item"> <div class="item">
<Icon type="md-trash" />{{$L('删除')}} <Icon type="md-trash" />{{$L('删除')}}
</div> </div>
@ -104,9 +106,11 @@ export default {
task: {}, task: {},
loadStatus: false, loadStatus: false,
colorShow: true, colorShow: true,
operationShow: true,
updateBefore: false, updateBefore: false,
disabled: false, disabled: false,
size: 'small', size: 'small',
projectId: 0,
onUpdate: null, onUpdate: null,
element: null, element: null,
@ -164,15 +168,17 @@ export default {
this.task = data.task; this.task = data.task;
this.loadStatus = typeof data.loadStatus === "undefined" ? false : data.loadStatus; this.loadStatus = typeof data.loadStatus === "undefined" ? false : data.loadStatus;
this.colorShow = typeof data.colorShow === "undefined" ? true : data.colorShow; this.colorShow = typeof data.colorShow === "undefined" ? true : data.colorShow;
this.operationShow = typeof data.operationShow === "undefined" ? true : data.operationShow;
this.updateBefore = typeof data.updateBefore === "undefined" ? false : data.updateBefore; this.updateBefore = typeof data.updateBefore === "undefined" ? false : data.updateBefore;
this.disabled = typeof data.disabled === "undefined" ? false : data.disabled; this.disabled = typeof data.disabled === "undefined" ? false : data.disabled;
this.size = typeof data.size === "undefined" ? "small" : data.size; this.size = typeof data.size === "undefined" ? "small" : data.size;
this.projectId = typeof data.projectId === "undefined" ? 0 : data.projectId;
this.onUpdate = typeof data.onUpdate === "function" ? data.onUpdate : null; this.onUpdate = typeof data.onUpdate === "function" ? data.onUpdate : null;
// //
this.$refs.icon.focus(); this.$refs.icon.focus();
this.updatePopper(); this.updatePopper();
this.show(); this.show();
this.$store.dispatch("getTaskFlow", this.task.id).finally(this.updatePopper) this.$store.dispatch("getTaskFlow", {task_id: this.task.id, project_id: this.projectId}).finally(this.updatePopper)
this.setupEventListeners(data.event) this.setupEventListeners(data.event)
} else { } else {
this.hide(); this.hide();
@ -223,7 +229,9 @@ export default {
} }
} }
this.updateTask({ this.updateTask({
flow_item_id flow_item_id,
flow_item_status: updateFlow.status,
flow_item_name: updateFlow.name
}).then(() => { }).then(() => {
if (isComplete) { if (isComplete) {
completeTemp(true) completeTemp(true)
@ -291,11 +299,19 @@ export default {
return; return;
} }
// //
Object.keys(updata).forEach(key => this.$set(this.task, key, updata[key]));
//
const updateData = Object.assign(updata, { const updateData = Object.assign(updata, {
task_id: this.task.id, task_id: this.task.id,
}); });
if(!this.operationShow){
if (typeof this.onUpdate === "function") {
this.onUpdate(updateData)
}
reject()
return;
}
//
Object.keys(updata).forEach(key => this.$set(this.task, key, updata[key]));
//
this.$store.dispatch("taskUpdate", updateData).then(({data, msg}) => { this.$store.dispatch("taskUpdate", updateData).then(({data, msg}) => {
$A.messageSuccess(msg); $A.messageSuccess(msg);
resolve() resolve()

View File

@ -58,8 +58,8 @@
@touchstart.native="listTouch" @touchstart.native="listTouch"
@on-scroll="listScroll"> @on-scroll="listScroll">
<ul v-if="tabActive==='dialog'" ref="ul" class="dialog"> <ul v-if="tabActive==='dialog'" ref="ul" class="dialog">
<template v-if="dialogList.length > 0">
<li <li
v-if="dialogList.length > 0"
v-for="(dialog, key) in dialogList" v-for="(dialog, key) in dialogList"
:ref="`dialog_${dialog.id}`" :ref="`dialog_${dialog.id}`"
:key="key" :key="key"
@ -67,7 +67,8 @@
:class="dialogClass(dialog)" :class="dialogClass(dialog)"
@click="openDialog({ @click="openDialog({
dialog_id: dialog.id, dialog_id: dialog.id,
search_msg_id: dialog.search_msg_id dialog_msg_id: dialog.search_msg_id,
search_msg_id: dialog.search_msg_id,
})" })"
v-longpress="handleLongpress" v-longpress="handleLongpress"
:style="{'background-color':dialog.color}"> :style="{'background-color':dialog.color}">
@ -114,9 +115,13 @@
<Badge class="dialog-num" :type="dialog.silence ? 'normal' : 'error'" :overflow-count="999" :count="$A.getDialogUnread(dialog, true)"/> <Badge class="dialog-num" :type="dialog.silence ? 'normal' : 'error'" :overflow-count="999" :count="$A.getDialogUnread(dialog, true)"/>
<div class="dialog-line"></div> <div class="dialog-line"></div>
</li> </li>
<li v-else-if="dialogSearchLoad === 0" class="nothing"> </template>
<li v-else-if="dialogSearchLoad === 0 && dialogMarkLoad === 0" class="nothing">
{{$L(dialogSearchKey ? `没有任何与"${dialogSearchKey}"相关的会话` : `没有任何会话`)}} {{$L(dialogSearchKey ? `没有任何与"${dialogSearchKey}"相关的会话` : `没有任何会话`)}}
</li> </li>
<li v-else class="nothing">
<Loading/>
</li>
</ul> </ul>
<ul v-else class="contacts"> <ul v-else class="contacts">
<template v-if="contactsFilter.length === 0"> <template v-if="contactsFilter.length === 0">
@ -235,6 +240,8 @@ export default {
{type: 'user', name: '单聊'}, {type: 'user', name: '单聊'},
{type: 'group', name: '群聊'}, {type: 'group', name: '群聊'},
{type: 'bot', name: '机器人'}, {type: 'bot', name: '机器人'},
{type: 'mark', name: '标注'},
{type: '@', name: '@我的'},
], ],
dialogHistory: MessengerObject.menuHistory, dialogHistory: MessengerObject.menuHistory,
@ -251,6 +258,8 @@ export default {
operateVisible: false, operateVisible: false,
clickAgainSubscribe: null, clickAgainSubscribe: null,
dialogMarkLoad: 0,
} }
}, },
@ -291,7 +300,7 @@ export default {
}, },
computed: { computed: {
...mapState(['cacheDialogs', 'loadDialogs', 'dialogId', 'messengerSearchKey', 'appNotificationPermission', 'taskColorList']), ...mapState(['cacheDialogs', 'loadDialogs', 'dialogId', 'dialogMsgId', 'dialogMsgs', 'messengerSearchKey', 'appNotificationPermission', 'taskColorList']),
routeName() { routeName() {
return this.$route.name return this.$route.name
@ -337,6 +346,18 @@ export default {
if (dialogActive == '' && dialogSearchKey == '') { if (dialogActive == '' && dialogSearchKey == '') {
return this.cacheDialogs.filter(dialog => this.filterDialog(dialog)).sort(this.dialogSort); return this.cacheDialogs.filter(dialog => this.filterDialog(dialog)).sort(this.dialogSort);
} }
if(dialogActive == 'mark' && !dialogSearchKey){
const lists = [];
this.dialogMsgs.filter(h=>h.tag).forEach(h=>{
let dialog = $A.cloneJSON(this.cacheDialogs).find(p=>p.id == h.dialog_id)
if(dialog){
dialog.last_msg = h;
dialog.search_msg_id = h.id;
lists.push(dialog);
}
});
return lists;
}
const list = this.cacheDialogs.filter(dialog => { const list = this.cacheDialogs.filter(dialog => {
if (!this.filterDialog(dialog)) { if (!this.filterDialog(dialog)) {
return false; return false;
@ -381,6 +402,11 @@ export default {
return false; return false;
} }
break; break;
case '@':
if (!$A.getDialogMention(dialog)) {
return false;
}
break;
default: default:
return false; return false;
} }
@ -547,6 +573,13 @@ export default {
}, },
immediate: true immediate: true
}, },
dialogActive(){
this.dialogSearchList = [];
if(this.dialogActive == 'mark' && !this.dialogSearchKey){
this.searchTagDialog()
}
}
}, },
methods: { methods: {
@ -623,7 +656,7 @@ export default {
} }
return { return {
top: dialog.top_at, top: dialog.top_at,
active: dialog.id == this.dialogId, active: dialog.id == this.dialogId && (dialog.search_msg_id == this.dialogMsgId || !this.dialogMsgId),
operate: this.operateVisible && dialog.id == this.operateItem.id, operate: this.operateVisible && dialog.id == this.operateItem.id,
completed: $A.dialogCompleted(dialog) completed: $A.dialogCompleted(dialog)
} }
@ -769,6 +802,29 @@ export default {
}); });
}, },
searchTagDialog() {
//
this.dialogMarkLoad++;
this.$store.dispatch("call", {
url: 'dialog/search/tag',
}).then(({data}) => {
const msgIds = [];
const lists = [];
this.dialogList.forEach(h=>{
lists.push(h);
msgIds.push(h.search_msg_id)
});
data.some(item => {
if (!item.last_msg || !msgIds.includes(item.search_msg_id)) {
lists.push(Object.assign(item, {is_search: true}))
}
})
this.dialogSearchList = lists;
}).finally(_ => {
this.dialogMarkLoad--;
});
},
getContactsList(page) { getContactsList(page) {
this.contactsLoad++; this.contactsLoad++;
const key = this.contactsKey const key = this.contactsKey

View File

@ -4,7 +4,7 @@
<FormItem label="License" prop="license"> <FormItem label="License" prop="license">
<Input v-model="formData.license" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" /> <Input v-model="formData.license" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
</FormItem> </FormItem>
<FormItem :label="$L('详细信息')"> <FormItem>
<div class="license-box"> <div class="license-box">
<ul v-if="formData.info.sn"> <ul v-if="formData.info.sn">
<li> <li>
@ -51,7 +51,6 @@
<Icon class="information" type="ios-information-circle-outline" /> <Icon class="information" type="ios-information-circle-outline" />
</ETooltip> </ETooltip>
</li> </li>
<li v-for="tip in formData.error" class="warning">{{tip}}</li>
</ul> </ul>
<ul v-else> <ul v-else>
<li> <li>
@ -60,6 +59,21 @@
</ul> </ul>
</div> </div>
</FormItem> </FormItem>
<FormItem :label="$L('当前环境')" v-if="formData.error?.length > 0">
<div class="license-box">
<ul>
<li>
<em>SN:</em>
<span>{{formData.doo_sn}}</span>
</li>
<li>
<em>MAC:</em>
<span>{{infoJoin(formData.macs)}}</span>
</li>
<li v-for="tip in formData.error" class="warning">{{tip}}</li>
</ul>
</div>
</FormItem>
</Form> </Form>
<div class="setting-footer"> <div class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button> <Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>

View File

@ -168,7 +168,7 @@ export default {
}, },
documentKey() { documentKey() {
return new Promise(resolve => { return new Promise((resolve,reject) => {
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'dialog/msg/detail', url: 'dialog/msg/detail',
data: { data: {
@ -177,8 +177,8 @@ export default {
}, },
}).then(({data}) => { }).then(({data}) => {
resolve(`${data.id}-${$A.Time(data.update_at)}`) resolve(`${data.id}-${$A.Time(data.update_at)}`)
}).catch(() => { }).catch((res) => {
resolve(0) reject(res)
}); });
}); });
}, },

View File

@ -155,7 +155,7 @@ export default {
}); });
}, },
documentKey() { documentKey() {
return new Promise(resolve => { return new Promise((resolve,reject) => {
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'project/task/filedetail', url: 'project/task/filedetail',
data: { data: {
@ -164,8 +164,8 @@ export default {
}, },
}).then(({data}) => { }).then(({data}) => {
resolve(`${data.id}-${$A.Time(data.update_at)}`) resolve(`${data.id}-${$A.Time(data.update_at)}`)
}).catch(() => { }).catch((res) => {
resolve(0) reject(res)
}); });
}) })
} }

View File

@ -1920,6 +1920,8 @@ export default {
} }
}); });
}); });
} else {
state.taskOperation = {};
} }
}, },
@ -2126,14 +2128,16 @@ export default {
* @param state * @param state
* @param dispatch * @param dispatch
* @param task_id * @param task_id
* @param project_id
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
getTaskFlow({state, dispatch}, task_id) { getTaskFlow({state, dispatch}, {task_id, project_id}) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
dispatch("call", { dispatch("call", {
url: 'project/task/flow', url: 'project/task/flow',
data: { data: {
task_id: task_id task_id: task_id,
project_id: project_id || 0
}, },
}).then(result => { }).then(result => {
let task = state.cacheTasks.find(({id}) => id == task_id) let task = state.cacheTasks.find(({id}) => id == task_id)
@ -2500,13 +2504,16 @@ export default {
openDialog({state, dispatch}, dialog_id) { openDialog({state, dispatch}, dialog_id) {
return new Promise(resolve => { return new Promise(resolve => {
let search_msg_id; let search_msg_id;
let dialog_msg_id;
if ($A.isJson(dialog_id)) { if ($A.isJson(dialog_id)) {
search_msg_id = dialog_id.search_msg_id; search_msg_id = dialog_id.search_msg_id;
dialog_msg_id = dialog_id.dialog_msg_id;
dialog_id = dialog_id.dialog_id; dialog_id = dialog_id.dialog_id;
} }
// //
requestAnimationFrame(_ => { requestAnimationFrame(_ => {
state.dialogSearchMsgId = /^\d+$/.test(search_msg_id) ? search_msg_id : 0; state.dialogSearchMsgId = /^\d+$/.test(search_msg_id) ? search_msg_id : 0;
state.dialogMsgId = /^\d+$/.test(dialog_msg_id) ? dialog_msg_id : 0;
state.dialogId = /^\d+$/.test(dialog_id) ? dialog_id : 0; state.dialogId = /^\d+$/.test(dialog_id) ? dialog_id : 0;
resolve() resolve()
}) })

View File

@ -105,6 +105,7 @@ export default {
// 会话聊天 // 会话聊天
dialogId: 0, dialogId: 0,
dialogMsgId: 0,
dialogSearchMsgId: 0, dialogSearchMsgId: 0,
dialogIns: [], dialogIns: [],
dialogMsgs: [], dialogMsgs: [],
@ -113,6 +114,8 @@ export default {
dialogDraftTimer: {}, dialogDraftTimer: {},
dialogMsgTransfer: {time: 0}, dialogMsgTransfer: {time: 0},
dialogSseList: [], dialogSseList: [],
dialogDroupWordChain: {},
dialogGroupVote: {},
// 搜索关键词(主要用于移动端判断滑动返回) // 搜索关键词(主要用于移动端判断滑动返回)
messengerSearchKey: {dialog: '', contacts: ''}, messengerSearchKey: {dialog: '', contacts: ''},

View File

@ -172,6 +172,21 @@
} }
} }
} }
&.twice-affirm{
padding-bottom: 20px;
.search-selected{
max-width: 100%;
}
.user-modal-avatar{
display: flex;
align-items: center;
gap: 5px;
.avatar-name{
max-width: 90%;
}
}
}
} }
.user-modal-switch { .user-modal-switch {
@ -416,12 +431,19 @@
border-radius: 14px; border-radius: 14px;
} }
} }
.twice-affirm-body-extend{
margin: 0 24px;
}
} }
.ivu-modal-footer { .ivu-modal-footer {
border-top: 1px solid #f2f2f2 !important; border-top: 1px solid #f2f2f2 !important;
padding: 12px 0 !important; padding: 12px 0 !important;
margin: 0 24px !important; margin: 0 24px !important;
display: flex;
justify-content: flex-end;
gap: 20px;
} }
&.ivu-modal-fullscreen { &.ivu-modal-fullscreen {
@ -462,4 +484,10 @@
} }
} }
} }
&.twice-affirm-modal{
.ivu-modal {
max-width: 90%;
margin: 10px auto;
}
}
} }

View File

@ -13,6 +13,7 @@
@import "project-management"; @import "project-management";
@import "project-panel"; @import "project-panel";
@import "project-workflow"; @import "project-workflow";
@import "project-permission";
@import "task-add"; @import "task-add";
@import "task-add-simple"; @import "task-add-simple";
@import "task-archived"; @import "task-archived";
@ -21,6 +22,9 @@
@import "task-menu"; @import "task-menu";
@import "task-operation"; @import "task-operation";
@import "task-priority"; @import "task-priority";
@import "task-move";
@import "team-management"; @import "team-management";
@import "update-log"; @import "update-log";
@import "task-exist-tips"; @import "task-exist-tips";
@import "calendar";
@import "dialog-droup-word-chain";

View File

@ -0,0 +1,162 @@
.calendar-wrapper {
flex: 1;
position: relative;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background-color: #ffffff;
z-index: 1;
}
.tui-full-calendar-popup {
box-shadow: none;
.tui-full-calendar-section-header {
.tui-full-calendar-ic-checkbox-checked {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAhFBMVEUAAACLz3CLz3CLz3CKzm6Gy2+Lz3CLz3CL0HCLz3CLz3CLz3CLz3CMz3GLz3CKz3CLz3CL0HCJ0G+KznCN0HCL0HCLz3CKz3CLz3CLz3CLz3CMz3CLz3CLz3GL0XCL0HCN0XKLz3CLz3CMz3CLz3CM0HCM0G+FzHCLz3CKz3CMz3CLz3Bod5CFAAAAK3RSTlMA18RAOQ3s8+Pc0rmyq3tpiUwTgBnovyDMjmNSRjUvJQX5yKB0WisKppuUFLaY7gAAAotJREFUeNrtm+FymkAUhc8KqIAgSkyUtkmsmqa97/9+HWeSudpCd8qZ7E0m+73A9/1gxmXx4IK0nbipvDFTN2lT9JDVToLh6gx/sHQSFLfEFZUEp8IFczFgrv5CTChe/TsxYvfy/IkZy7M/c2KGywDUYkgNIBdDciARUxI0YkqDiZgygRNTHKZiyhRiTAyIATEgBsSAGBADYsAnCJgdmtIywKUAOrsA94QzJyKA95/pbALcPV7piADGryzDB+Rnv3IkAhi/UhMBjF9pQwbk3/A3JRXA+7GjAng/DqEC5v3+dMYE8P6nQsIEzFOPnw7g/QpC+R8K4QJ4f5iAzaA/TMAm6fdvZQgY+wW2fn9A0a66Bee/2xLH8kp/Rr1MB/3jAxaP0ALCPzagzPBCM9r/XcYH3K4BLfD4vw75xwdMbqB4CmaD/vEB2ztc0RD+MQHFg+c85fffDPv9AbPUd6Lz+ydCBDyjh1//6WcCGngK1L8a8lMBJTwFfj8XIC16+SnX7Af9bIDUngLSrwFEwaD/VogA5YheDj7/Wv1MgL9g/8XjpwNk+c8Cyq8BYwqePX46QOkGCmi/BowrqFi/Bvg4wYv6y7e4Kz4Rfj5AnzY/WSlcwHAB5+cD9ivKzwfIbEX4qQA99RB+NkDfO4b5sRAigHjzpP0C6u6D9wuo2z/eL+DvXx8Zv4C4gVd/qABxCeFnApQT5+cDpOP8fICcLv2VxbfjlQZUNh+vU/XbBIj6jQLm9wDWlZgFyOaYHJ3Ix/gDQwyIATEgBsSAGODBfuBgPvEwH7m0YkqLVExJYfsQuPcwdjOf+5kPHoFKjKio0e3Hn90WUCyGt7v3Nf0GsjqXYOR1hh6SJsz8v0mg/AZRXmaRKXtJBwAAAABJRU5ErkJggg==);
}
}
.tui-full-calendar-popup-container {
word-break: break-all;
border: 0;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
.tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-border {
top: -8px;
border-bottom-color: rgba(217, 217, 217, .5);
}
}
.tui-full-calendar-dropdown-menu {
border-color: #e8e8e8;
width: calc(100% - 14px);
}
.tui-full-calendar-popup-creation {
.tui-full-calendar-icon {
&.tui-full-calendar-ic-title,
&.tui-full-calendar-calendar-dot {
display: none;
}
&.tui-full-calendar-ic-date {
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5NjcwNjA3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE2Mzg4IiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTk2MCAxMjhIODMzYzAtNTMtNDMtOTYtOTYtOTZoLTE2Yy01MyAwLTk2IDQzLTk2IDk2SDQwMGMwLTI2LjUtMTAuNy01MC41LTI4LjEtNjcuOUMzNTQuNSA0Mi43IDMzMC41IDMyIDMwNCAzMmgtMTZjLTUzIDAtOTYgNDMtOTYgOTZINjRjLTM1LjMgMC02NCAyOC42LTY0IDY0djczNmMwIDM1LjMgMjguNyA2NCA2NCA2NGg4OTZjMzUuMyAwIDY0LTI4LjcgNjQtNjRWMTkyYzAtMzUuNC0yOC43LTY0LTY0LTY0eiBtLTI3MSA4YzAtMjIuMSAxNy45LTQwIDQwLTQwczQwIDE3LjkgNDAgNDB2ODBjMCAyMi4xLTE3LjkgNDAtNDAgNDAtMTEgMC0yMS00LjUtMjguMy0xMS43QzY5My41IDIzNyA2ODkgMjI3IDY4OSAyMTZ2LTgweiBtLTQzMyAwYzAtMjIuMSAxNy45LTQwIDQwLTQwczQwIDE3LjkgNDAgNDB2ODBjMCAyMi4xLTE3LjkgNDAtNDAgNDAtMTEgMC0yMS00LjUtMjguMy0xMS43QzI2MC41IDIzNyAyNTYgMjI3IDI1NiAyMTZ2LTgweiBtNzA0IDc2MGMwIDE3LjctMTQuMyAzMi0zMiAzMkg5NmMtMTcuNyAwLTMyLTE0LjMtMzItMzJWNDQ4aDg5NnY0NDh6IiBwLWlkPSIxNjM4OSIgZmlsbD0iIzUxNTE1MSI+PC9wYXRoPjwvc3ZnPg==");
background-size: contain;
}
}
.tui-full-calendar-content {
padding-left: 0;
}
.tui-full-calendar-popup-section {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.tui-full-calendar-popup-section-item {
height: 36px;
line-height: 34px;
border-color: #e8e8e8;
border-radius: 4px;
}
.tui-full-calendar-popup-section-item input {
height: 34px;
}
}
.tui-full-calendar-section-title {
width: 100%;
input {
width: 100%;
}
}
.tui-full-calendar-section-start-date,
.tui-full-calendar-section-end-date {
width: 210px;
.tui-full-calendar-content {
padding-left: 8px;
}
}
.tui-full-calendar-popup-location,
.tui-full-calendar-section-private,
.tui-full-calendar-section-allday,
.tui-full-calendar-section-state {
display: none;
}
}
.tui-full-calendar-popup-task {
.priority {
color: #ffffff;
padding: 2px 4px;
border-radius: 4px;
margin-right: 6px;
}
.overdue {
color: #f5222d;
background: #fff1f0;
border: 1px solid #ffa39e;
padding: 1px 3px;
border-radius: 4px;
margin-right: 6px;
}
.tui-full-calendar-calendar-dot,
.tui-full-calendar-ic-priority {
opacity: 0;
}
.tui-full-calendar-ic-edit {
top: -2px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5MzY4MTg5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjExMTkiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNODMzLjQyODU3MTY4IDYySDE5MC41NzE0MjgzMmExMjguNTcxNDI4MzIgMTI4LjU3MTQyODMyIDAgMCAwLTEyOC41NzE0MjgzMiAxMjguNTcxNDI4MzJ2NjQyLjg1NzE0MzM2YTEyOC41NzE0MjgzMiAxMjguNTcxNDI4MzIgMCAwIDAgMTI4LjU3MTQyODMyIDEyOC41NzE0MjgzMmg2NDIuODU3MTQzMzZhMTI4LjU3MTQyODMyIDEyOC41NzE0MjgzMiAwIDAgMCAxMjguNTcxNDI4MzItMTI4LjU3MTQyODMyVjE5MC41NzE0MjgzMmExMjguNTcxNDI4MzIgMTI4LjU3MTQyODMyIDAgMCAwLTEyOC41NzE0MjgzMi0xMjguNTcxNDI4MzJ6IG02NC4yODU3MTQxNiA3NzEuNDI4NTcxNjhhNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTYgMCAwIDEtNjQuMjg1NzE0MTcgNjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyYTY0LjI4NTcxNDE2IDY0LjI4NTcxNDE2IDAgMCAxLTY0LjI4NTcxNDE2LTY0LjI4NTcxNDE2VjE5MC41NzE0MjgzMmE2NC4yODU3MTQxNiA2NC4yODU3MTQxNiAwIDAgMSA2NC4yODU3MTQxNy02NC4yODU3MTQxNmg2NDIuODU3MTQzMzVhNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTYgMCAwIDEgNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTd6IiBwLWlkPSIxMTIwIiBmaWxsPSIjNTE1MTUxIj48L3BhdGg+PHBhdGggZD0iTTE5MC41NzE0MjgzMiAyNTQuODU3MTQyNDhoNjQuMjg1NzE0MTZ2NjQuMjg1NzE1MDRIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgMjU0Ljg1NzE0MjQ4aDQ1MHY2NC4yODU3MTUwNEgzMTkuMTQyODU3NTJ6TTE5MC41NzE0MjgzMiA0NDcuNzE0Mjg1ODRoNjQuMjg1NzE0MTZ2NjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgNDQ3LjcxNDI4NTg0aDQ1MHY2NC4yODU3MTQxNkgzMTkuMTQyODU3NTJ6TTE5MC41NzE0MjgzMiA2NDAuNTcxNDI4MzJoNjQuMjg1NzE0MTZ2NjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgNjQwLjU3MTQyODMyaDMyMS40Mjg1NzA4djY0LjI4NTcxNDE2SDMxOS4xNDI4NTc1MnoiIHAtaWQ9IjExMjEiIGZpbGw9IiM1MTUxNTEiPjwvcGF0aD48L3N2Zz4=");
}
.tui-full-calendar-ic-delete {
top: -2px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5MzMwMTc2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijc5MiIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiPjxwYXRoIGQ9Ik04OTIuMjg4IDI1NmgtMTkxLjE2OEEyMDIuMjQgMjAyLjI0IDAgMCAwIDUwOS42MzIgNjIuMDggMjAxLjIxNiAyMDEuMjE2IDAgMCAwIDMxOC44NDggMjU2SDEyOGMtMTguNjg4IDAtNjYuMDQ4LTQuMjI0LTY2LjA0OCAyNC43NjhDNjEuOTUyIDMyNy43NDQgMTA5LjM3NiAzMjAgMTI4IDMyMGg2NHY1MTJhMTQ2LjQ5NiAxNDYuNDk2IDAgMCAwIDEyNy40MjQgMTI4aDM4Mi4yNzJBMTUwLjAxNiAxNTAuMDE2IDAgMCAwIDgzMiA4MzJsLTMuMzkyLTUxMmg2NGMxOC4zNjggMCA2NS4wMjQgMS40NzIgNjUuMDI0LTM5Ljc0NEE3Mi4zODQgNzIuMzg0IDAgMCAwIDg5Mi4yODggMjU2ek01MDkuNjMyIDEyOC41MTJBMTM4LjE3NiAxMzguMTc2IDAgMCAxIDYzNy40NCAyNTZIMzgyLjU5MmExMzcuOTIgMTM3LjkyIDAgMCAxIDEyNy4wNC0xMjcuNDg4ek03NjggODMyYTk3Ljk4NCA5Ny45ODQgMCAwIDEtNjYuODggNjRIMzE4Ljg0OGE5My41NjggOTMuNTY4IDAgMCAxLTY0LTY0VjMyMEg3Njh2NTEyeiBtLTM4NS40MDgtNjRWNTEyYzAtMTguNDk2IDAuOTYtNjAuOTkyIDM2LjczNi02MC45OTIgMjcuMzI4IDAgMjYuNDk2IDQzLjAwOCAyNi45NDQgNjAuOTkydjI1NmMwIDE4LjQ5Ni02LjQgMjAuMDMyLTI0Ljk2IDIwLjAzMnMtMzguNzItMS41MzYtMzguNzItMjAuMDMyeiBtMTkxLjE2OCAwVjUxMmE2NCA2NCAwIDAgMSAyMy44MDgtNjAuOTkyYzQyLjQzMiAwIDM5LjM2IDQzLjAwOCAzOS44NzIgNjAuOTkydjI1NmMwIDE4LjQ5Ni0xOS41ODQgMjAuMDMyLTM3Ljk1MiAyMC4wMzJzLTI1Ljc5Mi0xLjUzNi0yNS43OTItMjAuMDMyeiIgcC1pZD0iNzkzIiBmaWxsPSIjNTE1MTUxIj48L3BhdGg+PC9zdmc+");
}
.tui-full-calendar-popup-detail-item-separate {
padding-left: 22px;
}
}
.tui-datepicker {
border-color: #e8e8e8;
.tui-calendar {
th,
td {
height: 32px;
}
.tui-calendar-prev-month.tui-calendar-date,
.tui-calendar-next-month.tui-calendar-date {
visibility: visible;
}
}
.tui-datepicker-body .tui-timepicker,
.tui-datepicker-footer .tui-timepicker {
padding: 16px 46px 16px 47px;
}
}
.tui-full-calendar-week-container{
min-height: 100px;
}
}
body.window-portrait {
.calendar-wrapper {
.tui-full-calendar-popup {
font-weight: normal;
}
.tui-full-calendar-section-button {
> button {
.tui-full-calendar-icon {
width: 14px;
height: 14px;
background-size: 14px;
}
.tui-full-calendar-content {
font-size: 14px;
}
}
}
}
@media (max-width: 640px) {
.calendar-wrapper {
.tui-full-calendar-popup-arrow {
display: none;
}
}
}
}

View File

@ -0,0 +1,156 @@
.dialog-droup-word-chain {
.ivu-modal-body{
max-height: calc(100vh - 260px);
overflow: auto;
padding-top: 0 !important;
padding-right: 0 !important;
}
.chain-modal-header {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
font-weight: 500;
.chain-modal-close {
color: $primary-text-color;
}
.chain-modal-title {
position: absolute;
top: 0;
bottom: 0;
left: 100px;
right: 100px;
display: flex;
justify-content: center;
align-items: center;
> span {
font-size: 16px;
color: $primary-title-color;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.chain-modal-submit {
color: $primary-color;
display: flex;
align-items: center;
.submit-loading {
width: 14px;
height: 14px;
margin-right: 6px;
}
&.disabled{
color: #9c9c9c;
}
}
}
.word-chain-body{
display: flex;
flex-direction: column;
height: 100%;
max-height: calc(100vh - 265px);
.source{
margin-right: 32px;
span{
color: #84C56A;
}
}
.initiate{
gap: 5px;
white-space: nowrap;
text-overflow: ellipsis;
overflow-y: auto;
min-height: 26px;
margin: 10px 32px 20px 0;
.ivu-input{
border-color: #fff !important;
}
.avatar-wrapper{
margin: 0 4px 4px;
}
>span,>div{
float: left;
}
}
.textarea{
padding-right: 32px;
}
ul{
margin: 20px 0;
list-style-type:none;
flex: 1;
overflow: auto;
padding-right: 32px;
li{
display: flex;
gap: 10px;
padding: 5px 0;
color: #7f7f7f;
span{
min-width: 28px;
height: 28px;
line-height: 28px;
margin-top: 2px;
background-color: #f2f2f2;
border-radius: 100%;
text-align: center;
font-size: 12px;
}
.taskfont{
font-size: 28px;
cursor: pointer;
line-height: 34px;
user-select: none;
transform: scale(0.92);
&.disabled{
opacity: 0.5;
cursor: no-drop;
}
}
&.add{
.taskfont{
line-height: 32px;
transform: scale(1);
}
}
}
}
.switch-row{
padding: 10px 5px;
margin: 0 32px 0 0;
display: flex;
border-top: 1px solid #f0f0f0;
span.label{
flex: 1;
}
&:last-child{
border-bottom: 1px solid #f0f0f0;
}
}
}
}
body.window-portrait {
.dialog-droup-word-chain {
.ivu-modal-fullscreen{
.ivu-modal-body{
padding-top: 10px !important;
max-height: 100%;
}
.word-chain-body{
max-height: 100%;
ul{
flex: none;
}
}
}
}
}

View File

@ -447,7 +447,8 @@
.dialog-tag, .dialog-tag,
.dialog-todo, .dialog-todo,
.dialog-notice { .dialog-notice,
.dialog-new {
font-size: 12px; font-size: 12px;
max-width: 80%; max-width: 80%;
margin: 0 auto; margin: 0 auto;
@ -963,6 +964,120 @@
text-decoration: underline; text-decoration: underline;
color: $primary-title-color; color: $primary-title-color;
} }
.content-word-chain{
ul{
list-style-type:none;
margin-top: 20px;
li{
margin-top: 5px;
.expand{
cursor: pointer;
color: #0bc037;
}
.shrink{
display: none;
}
}
li.participate{
cursor: pointer;
margin-top: 10px;
color: #0bc037;
>span{
font-size: 12px;
margin-left: 2px;
}
}
&.expand,li:nth-last-child(2){
.expand{
display: none;
}
.shrink{
display: block;
}
}
}
}
.content-word-vote{
min-width: 200px;
max-width: 260px;
.vote-msg-head{
margin-bottom: 8px;
color: #0bc037;
line-height: 18px;
span{
padding: 2px 4px;
border-radius: 3px;
background-color: #dee2fa;
margin: 0 4px;
font-size: 12px;
color: #7076e4;
}
}
.ivu-checkbox-group,.ivu-radio-group{
margin-top: 10px;
width: 100%;
.ivu-checkbox-wrapper,.ivu-radio-wrapper{
display: block;
width: 100%;
height: 34px;
line-height: 34px;
.ivu-checkbox-inner{
border-radius: 100%;
}
}
}
.vote-result-body{
font-size: 12px;
margin-top: 14px;
ul{
list-style-type:none;
li{
margin-bottom: 14px;
.vote-option-title{
margin-bottom: 3px;
}
.ivu-progress-inner{
background-color: #e2e2e2;
}
.avatar-row{
gap: 2px;
margin-top: 2px;
display: flex;
overflow: auto;
padding-bottom: 4px;
&::-webkit-scrollbar {
background: none;
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: #d4d4d4;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
}
}
li:last-child{
margin-bottom: 10px;
}
}
>span,.ticket-num span{
margin-right: 10px;
}
}
.btn-row{
display: flex;
text-align: center;
padding: 10px 0 5px 0;
gap: 10px;
.ivu-btn{
flex: 1;
}
}
}
} }
.dialog-emoji { .dialog-emoji {
@ -1237,6 +1352,20 @@
.content-unknown { .content-unknown {
color: #ffffff; color: #ffffff;
} }
.content-word-chain{
ul{
li.participate, li .expand{
color: #23241f;
}
}
}
.content-word-vote{
.vote-msg-head{
color: #23241f;
}
}
} }
.dialog-emoji { .dialog-emoji {
@ -1301,6 +1430,9 @@
height: 14px; height: 14px;
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
&.down{
transform: rotate(-180deg);
}
} }
} }
} }
@ -1485,6 +1617,73 @@
} }
} }
.dialog-wrapper-forward-body{
.dialog-wrapper{
position: relative !important;
.dialog-scroller{
padding: 0 !important;
.dialog-item{
.dialog-view{
width: 100%;
max-width: 100% !important;
margin: 0 !important;
.dialog-head{
width: 100%;
border-radius: 8px !important;
}
.dialog-foot{
display: none !important;
}
}
&.self {
.dialog-head{
background-color: #F4F5F7;
.content-text{
color: #303133 !important;
.markdown-body{
color: #303133 !important;
}
}
.dialog-reply{
&:after{
background-color: rgba(132, 197, 106, 0.7);
}
.bot,.common-avatar{
color: #84C56A !important;
}
}
}
}
}
}
}
.leave-message{
padding-bottom: 16px;
textarea{
background: #f7f7f7;
}
}
}
.dialog-wrapper-forward-footer{
display: flex;
line-height: 34px;
cursor: pointer;
.user-modal-icon {
flex-shrink: 0;
font-size: 22px;
margin-right: 5px;
color: rgba($primary-desc-color, 0.7);
margin-top: 6px;
}
&.selected {
.user-modal-icon {
color: $primary-color;
}
}
}
.dialog-wrapper-read-poptip { .dialog-wrapper-read-poptip {
width: 360px; width: 360px;
max-width: 72%; max-width: 72%;

View File

@ -659,6 +659,9 @@
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
} }
.head-title{
white-space: nowrap;
}
.task-sort { .task-sort {
display: inline-block; display: inline-block;
width: 14px; width: 14px;

View File

@ -0,0 +1,77 @@
.project-permission {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
.permission-title {
color: $primary-title-color;
font-size: 20px;
font-weight: 500;
line-height: 1;
padding: 20px 20px 24px;
display: flex;
align-items: center;
.title-icon {
display: flex;
align-items: center;
width: 14px;
height: 14px;
margin-left: 4px;
margin-top: 2px;
> i {
cursor: pointer;
}
}
}
.permission-content {
flex: 1;
padding: 0 25px;
overflow: auto;
margin-bottom: 20px;
height: 100%;
position: relative;
.project-permission-title{
font-weight: 500;
padding:20px 0 10px 0;
}
.ivu-form-item {
margin-bottom: 5px;
}
.form-placeholder {
font-size: 12px;
color: #999;
}
}
.project-permission-footer{
flex-shrink: 0;
position: static;
padding: 16px 26px;
border-top: 1px solid #F4F4F5;
display: flex;
align-items: center;
gap: 16px;
button {
min-width: 120px;
height: 38px;
line-height: 36px;
}
}
}
body.window-portrait {
.project-permission {
.project-permission-footer{
button {
width: 50%;
}
}
}
}

View File

@ -0,0 +1,105 @@
.task-move {
.task-move-content{
display: flex;
gap: 10px;
margin-top: 16px;
>div{
flex: 1;
padding: 5px;
border-radius: 5px;
.task-move-title{
margin-bottom: 10px;
font-weight: bold;
}
.task-move-row{
padding: 5px 0;
display: flex;
line-height: 36px;
&.not-flex{
display: block;
}
.label{
width: 60px;
min-width: 60px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.task-menu-icon{
padding-right: 5px;
}
.flow {
cursor: pointer;
> span{
font-size: 12px;
height: 18px;
min-width: 20px;
line-height: 16px;
padding: 0 2px;
border-radius: 3px;
color: $primary-color;
border: 1px solid $primary-color;
display: inline-block;
vertical-align: top;
margin-top: 8px;
text-align: center;
&.start {
background-color: rgba($flow-status-start-color, 0.1);
border-color: rgba($flow-status-start-color, 0.1);
color: $flow-status-start-color;
}
&.progress {
background-color: rgba($flow-status-progress-color, 0.1);
border-color: rgba($flow-status-progress-color, 0.1);
color: $flow-status-progress-color;
}
&.test {
background-color: rgba($flow-status-test-color, 0.1);
border-color: rgba($flow-status-test-color, 0.1);
color: $flow-status-test-color;
}
&.end {
background-color: rgba($flow-status-end-color, 0.1);
border-color: rgba($flow-status-end-color, 0.1);
color: $flow-status-end-color;
}
}
}
}
&.task-move-content-old{
.task-move-row{
>div{
opacity: 0.5;
}
.common-user-select > ul > li, .flow{
cursor: initial;
}
}
}
}
}
.ivu-modal-footer {
padding: 26px 0 22px !important;
}
}
body.window-portrait {
.task-move {
.ivu-select-dropdown{
max-width: 100%;
overflow: auto;
.ivu-cascader-menu:last-child{
margin-right: 0px;
}
}
.task-move-row{
.label{
width: auto;
min-width: 50px;
}
}
}
}

View File

@ -5,7 +5,7 @@
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
margin: 32px 32px 16px; margin: 32px 20px 16px;
border-bottom: 1px solid #F4F4F5; border-bottom: 1px solid #F4F4F5;
.calendar-titbox { .calendar-titbox {
flex: 1; flex: 1;
@ -50,137 +50,6 @@
flex-direction: column; flex-direction: column;
padding: 0 48px 6px; padding: 0 48px 6px;
overflow: hidden; overflow: hidden;
.calendar-wrapper {
flex: 1;
position: relative;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background-color: #ffffff;
z-index: 1;
}
.tui-full-calendar-popup {
box-shadow: none;
.tui-full-calendar-section-header {
.tui-full-calendar-ic-checkbox-checked {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAhFBMVEUAAACLz3CLz3CLz3CKzm6Gy2+Lz3CLz3CL0HCLz3CLz3CLz3CLz3CMz3GLz3CKz3CLz3CL0HCJ0G+KznCN0HCL0HCLz3CKz3CLz3CLz3CLz3CMz3CLz3CLz3GL0XCL0HCN0XKLz3CLz3CMz3CLz3CM0HCM0G+FzHCLz3CKz3CMz3CLz3Bod5CFAAAAK3RSTlMA18RAOQ3s8+Pc0rmyq3tpiUwTgBnovyDMjmNSRjUvJQX5yKB0WisKppuUFLaY7gAAAotJREFUeNrtm+FymkAUhc8KqIAgSkyUtkmsmqa97/9+HWeSudpCd8qZ7E0m+73A9/1gxmXx4IK0nbipvDFTN2lT9JDVToLh6gx/sHQSFLfEFZUEp8IFczFgrv5CTChe/TsxYvfy/IkZy7M/c2KGywDUYkgNIBdDciARUxI0YkqDiZgygRNTHKZiyhRiTAyIATEgBsSAGBADYsAnCJgdmtIywKUAOrsA94QzJyKA95/pbALcPV7piADGryzDB+Rnv3IkAhi/UhMBjF9pQwbk3/A3JRXA+7GjAng/DqEC5v3+dMYE8P6nQsIEzFOPnw7g/QpC+R8K4QJ4f5iAzaA/TMAm6fdvZQgY+wW2fn9A0a66Bee/2xLH8kp/Rr1MB/3jAxaP0ALCPzagzPBCM9r/XcYH3K4BLfD4vw75xwdMbqB4CmaD/vEB2ztc0RD+MQHFg+c85fffDPv9AbPUd6Lz+ydCBDyjh1//6WcCGngK1L8a8lMBJTwFfj8XIC16+SnX7Af9bIDUngLSrwFEwaD/VogA5YheDj7/Wv1MgL9g/8XjpwNk+c8Cyq8BYwqePX46QOkGCmi/BowrqFi/Bvg4wYv6y7e4Kz4Rfj5AnzY/WSlcwHAB5+cD9ivKzwfIbEX4qQA99RB+NkDfO4b5sRAigHjzpP0C6u6D9wuo2z/eL+DvXx8Zv4C4gVd/qABxCeFnApQT5+cDpOP8fICcLv2VxbfjlQZUNh+vU/XbBIj6jQLm9wDWlZgFyOaYHJ3Ix/gDQwyIATEgBsSAGODBfuBgPvEwH7m0YkqLVExJYfsQuPcwdjOf+5kPHoFKjKio0e3Hn90WUCyGt7v3Nf0GsjqXYOR1hh6SJsz8v0mg/AZRXmaRKXtJBwAAAABJRU5ErkJggg==);
}
}
.tui-full-calendar-popup-container {
border: 0;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
.tui-full-calendar-arrow-top .tui-full-calendar-popup-arrow-border {
top: -8px;
border-bottom-color: rgba(217, 217, 217, .5);
}
}
.tui-full-calendar-dropdown-menu {
border-color: #e8e8e8;
width: calc(100% - 14px);
}
.tui-full-calendar-popup-creation {
.tui-full-calendar-icon {
&.tui-full-calendar-ic-title,
&.tui-full-calendar-calendar-dot {
display: none;
}
&.tui-full-calendar-ic-date {
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5NjcwNjA3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE2Mzg4IiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTk2MCAxMjhIODMzYzAtNTMtNDMtOTYtOTYtOTZoLTE2Yy01MyAwLTk2IDQzLTk2IDk2SDQwMGMwLTI2LjUtMTAuNy01MC41LTI4LjEtNjcuOUMzNTQuNSA0Mi43IDMzMC41IDMyIDMwNCAzMmgtMTZjLTUzIDAtOTYgNDMtOTYgOTZINjRjLTM1LjMgMC02NCAyOC42LTY0IDY0djczNmMwIDM1LjMgMjguNyA2NCA2NCA2NGg4OTZjMzUuMyAwIDY0LTI4LjcgNjQtNjRWMTkyYzAtMzUuNC0yOC43LTY0LTY0LTY0eiBtLTI3MSA4YzAtMjIuMSAxNy45LTQwIDQwLTQwczQwIDE3LjkgNDAgNDB2ODBjMCAyMi4xLTE3LjkgNDAtNDAgNDAtMTEgMC0yMS00LjUtMjguMy0xMS43QzY5My41IDIzNyA2ODkgMjI3IDY4OSAyMTZ2LTgweiBtLTQzMyAwYzAtMjIuMSAxNy45LTQwIDQwLTQwczQwIDE3LjkgNDAgNDB2ODBjMCAyMi4xLTE3LjkgNDAtNDAgNDAtMTEgMC0yMS00LjUtMjguMy0xMS43QzI2MC41IDIzNyAyNTYgMjI3IDI1NiAyMTZ2LTgweiBtNzA0IDc2MGMwIDE3LjctMTQuMyAzMi0zMiAzMkg5NmMtMTcuNyAwLTMyLTE0LjMtMzItMzJWNDQ4aDg5NnY0NDh6IiBwLWlkPSIxNjM4OSIgZmlsbD0iIzUxNTE1MSI+PC9wYXRoPjwvc3ZnPg==");
background-size: contain;
}
}
.tui-full-calendar-content {
padding-left: 0;
}
.tui-full-calendar-popup-section {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.tui-full-calendar-popup-section-item {
height: 36px;
line-height: 34px;
border-color: #e8e8e8;
border-radius: 4px;
}
.tui-full-calendar-popup-section-item input {
height: 34px;
}
}
.tui-full-calendar-section-title {
width: 100%;
input {
width: 100%;
}
}
.tui-full-calendar-section-start-date,
.tui-full-calendar-section-end-date {
width: 210px;
.tui-full-calendar-content {
padding-left: 8px;
}
}
.tui-full-calendar-popup-location,
.tui-full-calendar-section-private,
.tui-full-calendar-section-allday,
.tui-full-calendar-section-state {
display: none;
}
}
.tui-full-calendar-popup-task {
.priority {
color: #ffffff;
padding: 2px 4px;
border-radius: 4px;
margin-right: 6px;
}
.overdue {
color: #f5222d;
background: #fff1f0;
border: 1px solid #ffa39e;
padding: 1px 3px;
border-radius: 4px;
margin-right: 6px;
}
.tui-full-calendar-calendar-dot,
.tui-full-calendar-ic-priority {
opacity: 0;
}
.tui-full-calendar-ic-edit {
top: -2px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5MzY4MTg5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjExMTkiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNODMzLjQyODU3MTY4IDYySDE5MC41NzE0MjgzMmExMjguNTcxNDI4MzIgMTI4LjU3MTQyODMyIDAgMCAwLTEyOC41NzE0MjgzMiAxMjguNTcxNDI4MzJ2NjQyLjg1NzE0MzM2YTEyOC41NzE0MjgzMiAxMjguNTcxNDI4MzIgMCAwIDAgMTI4LjU3MTQyODMyIDEyOC41NzE0MjgzMmg2NDIuODU3MTQzMzZhMTI4LjU3MTQyODMyIDEyOC41NzE0MjgzMiAwIDAgMCAxMjguNTcxNDI4MzItMTI4LjU3MTQyODMyVjE5MC41NzE0MjgzMmExMjguNTcxNDI4MzIgMTI4LjU3MTQyODMyIDAgMCAwLTEyOC41NzE0MjgzMi0xMjguNTcxNDI4MzJ6IG02NC4yODU3MTQxNiA3NzEuNDI4NTcxNjhhNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTYgMCAwIDEtNjQuMjg1NzE0MTcgNjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyYTY0LjI4NTcxNDE2IDY0LjI4NTcxNDE2IDAgMCAxLTY0LjI4NTcxNDE2LTY0LjI4NTcxNDE2VjE5MC41NzE0MjgzMmE2NC4yODU3MTQxNiA2NC4yODU3MTQxNiAwIDAgMSA2NC4yODU3MTQxNy02NC4yODU3MTQxNmg2NDIuODU3MTQzMzVhNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTYgMCAwIDEgNjQuMjg1NzE0MTYgNjQuMjg1NzE0MTd6IiBwLWlkPSIxMTIwIiBmaWxsPSIjNTE1MTUxIj48L3BhdGg+PHBhdGggZD0iTTE5MC41NzE0MjgzMiAyNTQuODU3MTQyNDhoNjQuMjg1NzE0MTZ2NjQuMjg1NzE1MDRIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgMjU0Ljg1NzE0MjQ4aDQ1MHY2NC4yODU3MTUwNEgzMTkuMTQyODU3NTJ6TTE5MC41NzE0MjgzMiA0NDcuNzE0Mjg1ODRoNjQuMjg1NzE0MTZ2NjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgNDQ3LjcxNDI4NTg0aDQ1MHY2NC4yODU3MTQxNkgzMTkuMTQyODU3NTJ6TTE5MC41NzE0MjgzMiA2NDAuNTcxNDI4MzJoNjQuMjg1NzE0MTZ2NjQuMjg1NzE0MTZIMTkwLjU3MTQyODMyek0zMTkuMTQyODU3NTIgNjQwLjU3MTQyODMyaDMyMS40Mjg1NzA4djY0LjI4NTcxNDE2SDMxOS4xNDI4NTc1MnoiIHAtaWQ9IjExMjEiIGZpbGw9IiM1MTUxNTEiPjwvcGF0aD48L3N2Zz4=");
}
.tui-full-calendar-ic-delete {
top: -2px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIzODU5MzMwMTc2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijc5MiIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiPjxwYXRoIGQ9Ik04OTIuMjg4IDI1NmgtMTkxLjE2OEEyMDIuMjQgMjAyLjI0IDAgMCAwIDUwOS42MzIgNjIuMDggMjAxLjIxNiAyMDEuMjE2IDAgMCAwIDMxOC44NDggMjU2SDEyOGMtMTguNjg4IDAtNjYuMDQ4LTQuMjI0LTY2LjA0OCAyNC43NjhDNjEuOTUyIDMyNy43NDQgMTA5LjM3NiAzMjAgMTI4IDMyMGg2NHY1MTJhMTQ2LjQ5NiAxNDYuNDk2IDAgMCAwIDEyNy40MjQgMTI4aDM4Mi4yNzJBMTUwLjAxNiAxNTAuMDE2IDAgMCAwIDgzMiA4MzJsLTMuMzkyLTUxMmg2NGMxOC4zNjggMCA2NS4wMjQgMS40NzIgNjUuMDI0LTM5Ljc0NEE3Mi4zODQgNzIuMzg0IDAgMCAwIDg5Mi4yODggMjU2ek01MDkuNjMyIDEyOC41MTJBMTM4LjE3NiAxMzguMTc2IDAgMCAxIDYzNy40NCAyNTZIMzgyLjU5MmExMzcuOTIgMTM3LjkyIDAgMCAxIDEyNy4wNC0xMjcuNDg4ek03NjggODMyYTk3Ljk4NCA5Ny45ODQgMCAwIDEtNjYuODggNjRIMzE4Ljg0OGE5My41NjggOTMuNTY4IDAgMCAxLTY0LTY0VjMyMEg3Njh2NTEyeiBtLTM4NS40MDgtNjRWNTEyYzAtMTguNDk2IDAuOTYtNjAuOTkyIDM2LjczNi02MC45OTIgMjcuMzI4IDAgMjYuNDk2IDQzLjAwOCAyNi45NDQgNjAuOTkydjI1NmMwIDE4LjQ5Ni02LjQgMjAuMDMyLTI0Ljk2IDIwLjAzMnMtMzguNzItMS41MzYtMzguNzItMjAuMDMyeiBtMTkxLjE2OCAwVjUxMmE2NCA2NCAwIDAgMSAyMy44MDgtNjAuOTkyYzQyLjQzMiAwIDM5LjM2IDQzLjAwOCAzOS44NzIgNjAuOTkydjI1NmMwIDE4LjQ5Ni0xOS41ODQgMjAuMDMyLTM3Ljk1MiAyMC4wMzJzLTI1Ljc5Mi0xLjUzNi0yNS43OTItMjAuMDMyeiIgcC1pZD0iNzkzIiBmaWxsPSIjNTE1MTUxIj48L3BhdGg+PC9zdmc+");
}
.tui-full-calendar-popup-detail-item-separate {
padding-left: 22px;
}
}
.tui-datepicker {
border-color: #e8e8e8;
.tui-calendar {
th,
td {
height: 32px;
}
.tui-calendar-prev-month.tui-calendar-date,
.tui-calendar-next-month.tui-calendar-date {
visibility: visible;
}
}
.tui-datepicker-body .tui-timepicker,
.tui-datepicker-footer .tui-timepicker {
padding: 16px 46px 16px 47px;
}
}
}
} }
.calendar-menu { .calendar-menu {
position: absolute; position: absolute;

View File

@ -240,6 +240,7 @@
flex: 1; flex: 1;
padding: 24px 40px; padding: 24px 40px;
overflow: auto; overflow: auto;
padding-bottom: 0;
} }
.ivu-tabs { .ivu-tabs {
flex: 1; flex: 1;

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.4.1, 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>
<g>
<g>
<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="M34.4,33.5H25l9.3-9.3c1.2-1.2,1.2-3.3,0-4.5l-6.8-6.8c-1.2-1.2-3.3-1.2-4.5,0L12.9,23.1
c-1.2,1.2-1.2,3.3,0,4.5l6,6h-6c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H22c0,0,0,0,0,0c0,0,0,0,0,0h12.4
c0.5,0,0.9-0.4,0.9-0.9C35.3,33.9,34.9,33.5,34.4,33.5z M17.1,25.5c0.3-0.3,0.9-0.3,1.2,0l3.4,3.4c0.1,0.1,0.3,0.1,0.4,0l6.7-6.7
c0.3-0.3,0.9-0.3,1.2,0c0.3,0.3,0.3,0.9,0,1.2l-6.7,6.7c-0.4,0.4-0.9,0.6-1.5,0.6c-0.5,0-1.1-0.2-1.5-0.6l-3.4-3.4
C16.8,26.4,16.8,25.9,17.1,25.5L17.1,25.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.4.1, 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{clip-path:url(#SVGID_00000018941778426702510620000006481262296390689922_);}
.st2{fill:#FFFFFF;}
</style>
<g>
<g>
<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>
<defs>
<rect id="SVGID_1_" x="12" y="12" width="24" height="24"/>
</defs>
<clipPath id="SVGID_00000141444668484078751200000003089482387513471677_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_00000141444668484078751200000003089482387513471677_);">
<g>
<path class="st2" d="M28.5,21c-4.1,0-7.5,3.4-7.5,7.5c0,4.1,3.4,7.5,7.5,7.5c4.1,0,7.5-3.4,7.5-7.5C36,24.4,32.6,21,28.5,21z
M30.6,29.4h-1.2v1.2c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9v-1.2h-1.2c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9
h1.2v-1.2c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9v1.2h1.2c0.5,0,0.9,0.4,0.9,0.9C31.5,29,31,29.4,30.6,29.4z"/>
</g>
<g>
<path class="st2" d="M15.3,23.9c0-4.8,3.9-8.7,8.7-8.7c0.6,0,1.2,0.1,1.8,0.2c-1.3-2.1-3.7-3.4-6.3-3.4c-4.1,0-7.5,3.4-7.5,7.5
c0,2.7,1.4,5,3.5,6.3C15.3,25.2,15.3,24.6,15.3,23.9z"/>
</g>
<g>
<path class="st2" d="M19.8,28.5c0-4.8,3.9-8.7,8.7-8.7c0.6,0,1.3,0.1,1.9,0.2c-1.3-2.1-3.7-3.6-6.4-3.6c-4.1,0-7.5,3.4-7.5,7.5
c0,2.7,1.4,5,3.5,6.3C19.8,29.7,19.8,29.1,19.8,28.5z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB