Merge branch 'pro' into kuanfan-pro
@ -134,6 +134,37 @@ class DialogController extends AbstractController
|
||||
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. 获取单个会话信息
|
||||
*
|
||||
@ -1327,9 +1358,11 @@ class DialogController extends AbstractController
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__forward
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {Array} dialogids 转发给的对话ID
|
||||
* @apiParam {Array} userids 转发给的成员ID
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {Array} dialogids 转发给的对话ID
|
||||
* @apiParam {Array} userids 转发给的成员ID
|
||||
* @apiParam {Number} show_source 是否显示原发送者信息
|
||||
* @apiParam {Array} leave_message 转发留言
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -1342,6 +1375,8 @@ class DialogController extends AbstractController
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$dialogids = Request::input('dialogids');
|
||||
$userids = Request::input('userids');
|
||||
$show_source = intval(Request::input("show_source"));
|
||||
$leave_message = Request::input('leave_message');
|
||||
//
|
||||
if (empty($dialogids) && empty($userids)) {
|
||||
return Base::retError("请选择转发对话或成员");
|
||||
@ -1353,7 +1388,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
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\Models\AbstractModel;
|
||||
use App\Tasks\FilePackTask;
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Models\FileLink;
|
||||
@ -480,6 +480,8 @@ class FileController extends AbstractController
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
$history_id = intval(Request::input('history_id'));
|
||||
//
|
||||
Base::checkClientVersion('0.31.75');
|
||||
//
|
||||
if (Base::isNumber($id)) {
|
||||
$user = User::auth();
|
||||
$file = File::permissionFind(intval($id), $user, $down == 'yes' ? 1 : 0);
|
||||
@ -1018,15 +1020,15 @@ class FileController extends AbstractController
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName;
|
||||
$zipPath = storage_path('app/'.$zipName);
|
||||
$zipName = 'tmp/file/' . date("Ym") . '/' . $user->userid . '/' . $downName;
|
||||
$zipPath = public_path($zipName);
|
||||
Base::makeDir(dirname($zipPath));
|
||||
|
||||
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
return Base::retError('创建压缩文件失败');
|
||||
}
|
||||
|
||||
go(function() use ($zip, $files, $downName) {
|
||||
go(function() use ($user, $zip, $files, $downName, $zipName) {
|
||||
Coroutine::sleep(0.1);
|
||||
// 压缩进度
|
||||
$progress = 0;
|
||||
@ -1049,6 +1051,19 @@ class FileController extends AbstractController
|
||||
'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');
|
||||
@ -1072,8 +1087,8 @@ class FileController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
$downName = Request::input('name');
|
||||
$zipName = 'temp/download/' . date("Ym") . '/' . $user->userid . '/' . $downName;
|
||||
$zipPath = storage_path('app/'.$zipName);
|
||||
$zipName = 'tmp/file/' . date("Ym") . '/' . $user->userid . '/' . $downName;
|
||||
$zipPath = public_path($zipName);
|
||||
if (!file_exists($zipPath)) {
|
||||
abort(403, "The file does not exist.");
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use Swoole\Coroutine;
|
||||
use App\Models\Deleted;
|
||||
use App\Models\Project;
|
||||
use App\Module\TimeRange;
|
||||
@ -30,7 +31,9 @@ use App\Models\ProjectTaskFile;
|
||||
use App\Models\ProjectTaskUser;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\ProjectPermission;
|
||||
use App\Module\BillMultipleExport;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\ProjectTaskFlowChange;
|
||||
|
||||
@ -575,6 +578,8 @@ class ProjectController extends AbstractController
|
||||
$project = Project::userProject($project_id);
|
||||
//
|
||||
if ($only_column) {
|
||||
//
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_SORT);
|
||||
// 排序列表
|
||||
$index = 0;
|
||||
foreach ($sort as $item) {
|
||||
@ -760,6 +765,8 @@ class ProjectController extends AbstractController
|
||||
// 项目
|
||||
$project = Project::userProject($project_id);
|
||||
//
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_LIST_ADD);
|
||||
//
|
||||
if (empty($name)) {
|
||||
return Base::retError('列表名称不能为空');
|
||||
}
|
||||
@ -809,7 +816,9 @@ class ProjectController extends AbstractController
|
||||
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']) {
|
||||
$column->addLog("修改列表名称:{$column->name} => {$data['name']}");
|
||||
@ -849,7 +858,9 @@ class ProjectController extends AbstractController
|
||||
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();
|
||||
return Base::retSuccess('删除成功', ['id' => $column->id]);
|
||||
@ -876,10 +887,10 @@ class ProjectController extends AbstractController
|
||||
public function column__one()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
//
|
||||
$column_id = intval(Request::input('column_id'));
|
||||
$deleted = Request::input('deleted', 'no');
|
||||
//
|
||||
//
|
||||
$builder = ProjectColumn::whereId($column_id);
|
||||
if ($deleted == 'all') {
|
||||
$builder->withTrashed();
|
||||
@ -890,10 +901,10 @@ class ProjectController extends AbstractController
|
||||
if (empty($column)) {
|
||||
return Base::retError('列表不存在');
|
||||
}
|
||||
//
|
||||
//
|
||||
return Base::retSuccess('success', $column);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/lists 19. 任务列表
|
||||
@ -1180,170 +1191,182 @@ class ProjectController extends AbstractController
|
||||
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
|
||||
return Base::retError('时间范围限制最大90天');
|
||||
}
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '父级任务ID';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '归档时间';
|
||||
$headings[] = '任务计划用时';
|
||||
$headings[] = '实际完成用时';
|
||||
$headings[] = '超时时间';
|
||||
$headings[] = '开发用时';
|
||||
$headings[] = '验收/测试用时';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$headings[] = '状态';
|
||||
$datas = [];
|
||||
//
|
||||
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.owner', 1)
|
||||
->whereIn('project_task_users.userid', $userid)
|
||||
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay(), $type);
|
||||
$builder->orderByDesc('project_tasks.id')->chunk(100, function ($tasks) use (&$datas) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$flowChanges = ProjectTaskFlowChange::whereTaskId($task->id)->get();
|
||||
$testTime = 0;//验收/测试时间
|
||||
$taskStartTime = $task->start_at ? Carbon::parse($task->start_at)->timestamp : Carbon::parse($task->created_at)->timestamp;
|
||||
$taskCompleteTime = $task->complete_at ? Carbon::parse($task->complete_at)->timestamp : time();
|
||||
$totalTime = $taskCompleteTime - $taskStartTime; //开发测试总用时
|
||||
foreach ($flowChanges as $change) {
|
||||
if (!str_contains($change->before_flow_item_name, 'end')) {
|
||||
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
if ($upOne) {
|
||||
if (str_contains($change->before_flow_item_name, 'test') || str_contains($change->before_flow_item_name, '测试') || strpos($change->before_flow_item_name, '验收') !== false) {
|
||||
$testCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$tTime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$tMinusNum = $testCtime - $tTime;
|
||||
$testTime += $tMinusNum;
|
||||
go(function() use ($user, $userid, $time, $type) {
|
||||
Coroutine::sleep(0.1);
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '父级任务ID';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '归档时间';
|
||||
$headings[] = '任务计划用时';
|
||||
$headings[] = '实际完成用时';
|
||||
$headings[] = '超时时间';
|
||||
$headings[] = '开发用时';
|
||||
$headings[] = '验收/测试用时';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$headings[] = '状态';
|
||||
$datas = [];
|
||||
//
|
||||
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.owner', 1)
|
||||
->whereIn('project_task_users.userid', $userid)
|
||||
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay(), $type);
|
||||
$builder->orderByDesc('project_tasks.id')->chunk(100, function ($tasks) use (&$datas) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$flowChanges = ProjectTaskFlowChange::whereTaskId($task->id)->get();
|
||||
$testTime = 0;//验收/测试时间
|
||||
$taskStartTime = $task->start_at ? Carbon::parse($task->start_at)->timestamp : Carbon::parse($task->created_at)->timestamp;
|
||||
$taskCompleteTime = $task->complete_at ? Carbon::parse($task->complete_at)->timestamp : time();
|
||||
$totalTime = $taskCompleteTime - $taskStartTime; //开发测试总用时
|
||||
foreach ($flowChanges as $change) {
|
||||
if (!str_contains($change->before_flow_item_name, 'end')) {
|
||||
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
if ($upOne) {
|
||||
if (str_contains($change->before_flow_item_name, 'test') || str_contains($change->before_flow_item_name, '测试') || strpos($change->before_flow_item_name, '验收') !== false) {
|
||||
$testCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$tTime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$tMinusNum = $testCtime - $tTime;
|
||||
$testTime += $tMinusNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$task->complete_at) {
|
||||
$lastChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
$nowTime = time();
|
||||
$unFinishTime = $nowTime - Carbon::parse($lastChange->created_at)->timestamp;
|
||||
if (str_contains($lastChange->after_flow_item_name, 'test') || str_contains($lastChange->after_flow_item_name, '测试') || strpos($lastChange->after_flow_item_name, '验收') !== false) {
|
||||
$testTime += $unFinishTime;
|
||||
if (!$task->complete_at) {
|
||||
$lastChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
$nowTime = time();
|
||||
$unFinishTime = $nowTime - Carbon::parse($lastChange->created_at)->timestamp;
|
||||
if (str_contains($lastChange->after_flow_item_name, 'test') || str_contains($lastChange->after_flow_item_name, '测试') || strpos($lastChange->after_flow_item_name, '验收') !== false) {
|
||||
$testTime += $unFinishTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
$developTime = $totalTime - $testTime;//开发时间
|
||||
$planTime = '-';//任务计划用时
|
||||
$overTime = '-';//超时时间
|
||||
if ($task->end_at) {
|
||||
$startTime = Carbon::parse($task->start_at)->timestamp;
|
||||
$endTime = Carbon::parse($task->end_at)->timestamp;
|
||||
$planTotalTime = $endTime - $startTime;
|
||||
$residueTime = $planTotalTime - $totalTime;
|
||||
if ($residueTime < 0) {
|
||||
$overTime = Base::timeFormat(abs($residueTime));
|
||||
$developTime = $totalTime - $testTime;//开发时间
|
||||
$planTime = '-';//任务计划用时
|
||||
$overTime = '-';//超时时间
|
||||
if ($task->end_at) {
|
||||
$startTime = Carbon::parse($task->start_at)->timestamp;
|
||||
$endTime = Carbon::parse($task->end_at)->timestamp;
|
||||
$planTotalTime = $endTime - $startTime;
|
||||
$residueTime = $planTotalTime - $totalTime;
|
||||
if ($residueTime < 0) {
|
||||
$overTime = Base::timeFormat(abs($residueTime));
|
||||
}
|
||||
$planTime = Base::timeDiff($startTime, $endTime);
|
||||
}
|
||||
$planTime = Base::timeDiff($startTime, $endTime);
|
||||
}
|
||||
$actualTime = $task->complete_at ? $totalTime : 0;//实际完成用时
|
||||
$statusText = '未完成';
|
||||
if ($task->flow_item_name) {
|
||||
if (str_contains($task->flow_item_name, '已取消')) {
|
||||
$statusText = '已取消';
|
||||
$actualTime = 0;
|
||||
$testTime = 0;
|
||||
$developTime = 0;
|
||||
$overTime = '-';
|
||||
} elseif (str_contains($task->flow_item_name, '已完成')) {
|
||||
$actualTime = $task->complete_at ? $totalTime : 0;//实际完成用时
|
||||
$statusText = '未完成';
|
||||
if ($task->flow_item_name) {
|
||||
if (str_contains($task->flow_item_name, '已取消')) {
|
||||
$statusText = '已取消';
|
||||
$actualTime = 0;
|
||||
$testTime = 0;
|
||||
$developTime = 0;
|
||||
$overTime = '-';
|
||||
} elseif (str_contains($task->flow_item_name, '已完成')) {
|
||||
$statusText = '已完成';
|
||||
}
|
||||
} elseif ($task->complete_at) {
|
||||
$statusText = '已完成';
|
||||
}
|
||||
} elseif ($task->complete_at) {
|
||||
$statusText = '已完成';
|
||||
if (!isset($datas[$task->ownerid])) {
|
||||
$datas[$task->ownerid] = [
|
||||
'index' => 1,
|
||||
'nickname' => Base::filterEmoji(User::userid2nickname($task->ownerid)),
|
||||
'styles' => ["A1:P1" => ["font" => ["bold" => true]]],
|
||||
'data' => [],
|
||||
];
|
||||
}
|
||||
$datas[$task->ownerid]['index']++;
|
||||
if ($statusText === '未完成') {
|
||||
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "ff0000"]]]; // 未完成
|
||||
} elseif ($statusText === '已完成' && $task->end_at && Carbon::parse($task->complete_at)->gt($task->end_at)) {
|
||||
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "436FF6"]]]; // 已完成超期
|
||||
}
|
||||
$datas[$task->ownerid]['data'][] = [
|
||||
$task->id,
|
||||
$task->parent_id ?: '-',
|
||||
Base::filterEmoji($task->project?->name) ?: '-',
|
||||
Base::filterEmoji($task->name),
|
||||
$task->start_at ?: '-',
|
||||
$task->end_at ?: '-',
|
||||
$task->complete_at ?: '-',
|
||||
$task->archived_at ?: '-',
|
||||
$planTime ?: '-',
|
||||
$actualTime ? Base::timeFormat($actualTime) : '-',
|
||||
$overTime,
|
||||
$developTime > 0 ? Base::timeFormat($developTime) : '-',
|
||||
$testTime > 0 ? Base::timeFormat($testTime) : '-',
|
||||
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
$statusText
|
||||
];
|
||||
}
|
||||
if (!isset($datas[$task->ownerid])) {
|
||||
$datas[$task->ownerid] = [
|
||||
'index' => 1,
|
||||
'nickname' => Base::filterEmoji(User::userid2nickname($task->ownerid)),
|
||||
});
|
||||
if (empty($datas)) {
|
||||
return Base::retError('没有任何数据');
|
||||
}
|
||||
//
|
||||
$sheets = [];
|
||||
foreach ($userid as $ownerid) {
|
||||
$data = $datas[$ownerid] ?? [
|
||||
'nickname' => Base::filterEmoji(User::userid2nickname($ownerid)),
|
||||
'styles' => ["A1:P1" => ["font" => ["bold" => true]]],
|
||||
'data' => [],
|
||||
];
|
||||
$title = (count($sheets) + 1) . "." . ($data['nickname'] ?: $ownerid);
|
||||
$sheets[] = BillExport::create()->setTitle($title)->setHeadings($headings)->setData($data['data'])->setStyles($data['styles']);
|
||||
}
|
||||
//
|
||||
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
|
||||
if (count($userid) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
}
|
||||
$fileName .= '任务统计_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
return Base::retError('导出失败,' . $fileName . '!');
|
||||
}
|
||||
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
|
||||
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
|
||||
$zipPath = storage_path($zipFile);
|
||||
if (file_exists($zipPath)) {
|
||||
Base::deleteDirAndFile($zipPath, true);
|
||||
}
|
||||
try {
|
||||
Madzipper::make($zipPath)->add($xlsPath)->close();
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
//
|
||||
if (file_exists($zipPath)) {
|
||||
$base64 = base64_encode(Base::array2string([
|
||||
'file' => $zipFile,
|
||||
]));
|
||||
Session::put('task::export:userid', $user->userid);
|
||||
$botUser = User::botGetOrCreate('system-msg');
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
$datas[$task->ownerid]['index']++;
|
||||
if ($statusText === '未完成') {
|
||||
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "ff0000"]]]; // 未完成
|
||||
} elseif ($statusText === '已完成' && $task->end_at && Carbon::parse($task->complete_at)->gt($task->end_at)) {
|
||||
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "436FF6"]]]; // 已完成超期
|
||||
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);
|
||||
}
|
||||
$datas[$task->ownerid]['data'][] = [
|
||||
$task->id,
|
||||
$task->parent_id ?: '-',
|
||||
Base::filterEmoji($task->project?->name) ?: '-',
|
||||
Base::filterEmoji($task->name),
|
||||
$task->start_at ?: '-',
|
||||
$task->end_at ?: '-',
|
||||
$task->complete_at ?: '-',
|
||||
$task->archived_at ?: '-',
|
||||
$planTime ?: '-',
|
||||
$actualTime ? Base::timeFormat($actualTime) : '-',
|
||||
$overTime,
|
||||
$developTime > 0 ? Base::timeFormat($developTime) : '-',
|
||||
$testTime > 0 ? Base::timeFormat($testTime) : '-',
|
||||
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
$statusText
|
||||
];
|
||||
}
|
||||
});
|
||||
if (empty($datas)) {
|
||||
return Base::retError('没有任何数据');
|
||||
}
|
||||
//
|
||||
$sheets = [];
|
||||
foreach ($userid as $ownerid) {
|
||||
$data = $datas[$ownerid] ?? [
|
||||
'nickname' => Base::filterEmoji(User::userid2nickname($ownerid)),
|
||||
'styles' => ["A1:P1" => ["font" => ["bold" => true]]],
|
||||
'data' => [],
|
||||
];
|
||||
$title = (count($sheets) + 1) . "." . ($data['nickname'] ?: $ownerid);
|
||||
$sheets[] = BillExport::create()->setTitle($title)->setHeadings($headings)->setData($data['data'])->setStyles($data['styles']);
|
||||
}
|
||||
//
|
||||
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
|
||||
if (count($userid) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
}
|
||||
$fileName .= '任务统计_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
return Base::retError('导出失败,' . $fileName . '!');
|
||||
}
|
||||
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
|
||||
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
|
||||
$zipPath = storage_path($zipFile);
|
||||
if (file_exists($zipPath)) {
|
||||
Base::deleteDirAndFile($zipPath, true);
|
||||
}
|
||||
try {
|
||||
Madzipper::make($zipPath)->add($xlsPath)->close();
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
//
|
||||
if (file_exists($zipPath)) {
|
||||
$base64 = base64_encode(Base::array2string([
|
||||
'file' => $zipFile,
|
||||
]));
|
||||
Session::put('task::export:userid', $user->userid);
|
||||
return Base::retSuccess('success', [
|
||||
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
|
||||
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
|
||||
]);
|
||||
} else {
|
||||
return Base::retError('打包失败,请稍后再试...');
|
||||
}
|
||||
return Base::retSuccess('success',['msg' => '正在打包,请留意系统消息']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1507,7 +1530,7 @@ class ProjectController extends AbstractController
|
||||
$archived = Request::input('archived', 'no');
|
||||
//
|
||||
$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'); // 项目负责人
|
||||
if ($task->visibility != 1 && $user->userid != $project_userid) {
|
||||
@ -1603,7 +1626,9 @@ class ProjectController extends AbstractController
|
||||
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);
|
||||
$file->delete();
|
||||
@ -1732,6 +1757,8 @@ class ProjectController extends AbstractController
|
||||
$column_id = $data['column_id'];
|
||||
// 项目
|
||||
$project = Project::userProject($project_id);
|
||||
//
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_ADD);
|
||||
// 列表
|
||||
$column = null;
|
||||
$newColumn = null;
|
||||
@ -1808,11 +1835,13 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$name = Request::input('name');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, true, true, true);
|
||||
$task = ProjectTask::userTask($task_id);
|
||||
if ($task->complete_at) {
|
||||
return Base::retError('主任务已完成无法添加子任务');
|
||||
}
|
||||
//
|
||||
ProjectPermission::userTaskPermission(Project::userProject($task->project_id), ProjectPermission::TASK_ADD);
|
||||
//
|
||||
$task = ProjectTask::addTask([
|
||||
'name' => $name,
|
||||
'parent_id' => $task->id,
|
||||
@ -1867,11 +1896,18 @@ class ProjectController extends AbstractController
|
||||
$param = Request::input();
|
||||
$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();
|
||||
$owners = $taskUser->where('owner', 1)->pluck('userid')->toArray(); // 负责人
|
||||
$assists = $taskUser->where('owner', 0)->pluck('userid')->toArray(); // 协助人
|
||||
|
||||
// 更新任务
|
||||
$updateMarking = [];
|
||||
$task->updateTask($param, $updateMarking);
|
||||
@ -2003,12 +2039,15 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$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) {
|
||||
return Base::retError('子任务不支持此功能');
|
||||
}
|
||||
//
|
||||
$project = Project::userProject($task->project_id);
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_ARCHIVED, $task);
|
||||
//
|
||||
if ($type == 'recovery') {
|
||||
$task->archivedTask(null);
|
||||
} elseif ($type == 'add') {
|
||||
@ -2045,7 +2084,11 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$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') {
|
||||
$task->restoreTask();
|
||||
return Base::retSuccess('操作成功', ['id' => $task->id]);
|
||||
@ -2080,7 +2123,7 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('记录不存在');
|
||||
}
|
||||
//
|
||||
$task = ProjectTask::userTask($projectLog->task_id, true, true, true);
|
||||
$task = ProjectTask::userTask($projectLog->task_id);
|
||||
//
|
||||
$record = $projectLog->record;
|
||||
if ($record['flow'] && is_array($record['flow'])) {
|
||||
@ -2123,6 +2166,7 @@ class ProjectController extends AbstractController
|
||||
* @apiName task__flow
|
||||
*
|
||||
* @apiParam {Number} task_id 任务ID
|
||||
* @apiParam {Number} project_id 项目ID - 存在时只返回这个项目的
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -2133,17 +2177,23 @@ class ProjectController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$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);
|
||||
if (empty($projectTask)) {
|
||||
return Base::retError('任务不存在', [ 'task_id' => $task_id ], -4002);
|
||||
return Base::retError('任务不存在', ['task_id' => $task_id], -4002);
|
||||
}
|
||||
//
|
||||
$projectFlowItem = $projectTask->flow_item_id ? ProjectFlowItem::with(['projectFlow'])->find($projectTask->flow_item_id) : null;
|
||||
if ($projectFlowItem?->projectFlow) {
|
||||
$projectFlow = $projectFlowItem->projectFlow;
|
||||
$projectFlowItem = null;
|
||||
if ($project_id) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($project_id)->orderByDesc('id')->first();
|
||||
} else {
|
||||
$projectFlow = ProjectFlow::whereProjectId($projectTask->project_id)->orderByDesc('id')->first();
|
||||
$projectFlowItem = $projectTask->flow_item_id ? ProjectFlowItem::with(['projectFlow'])->find($projectTask->flow_item_id) : null;
|
||||
if ($projectFlowItem?->projectFlow) {
|
||||
$projectFlow = $projectFlowItem->projectFlow;
|
||||
} else {
|
||||
$projectFlow = ProjectFlow::whereProjectId($projectTask->project_id)->orderByDesc('id')->first();
|
||||
}
|
||||
}
|
||||
if (empty($projectFlow)) {
|
||||
return Base::retSuccess('success', [
|
||||
@ -2207,6 +2257,9 @@ class ProjectController extends AbstractController
|
||||
* @apiParam {Number} task_id 任务ID
|
||||
* @apiParam {Number} project_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 {String} msg 返回信息(错误描述)
|
||||
@ -2219,22 +2272,36 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$column_id = intval(Request::input('column_id'));
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, true, true, 2);
|
||||
//
|
||||
$flow_item_id = intval(Request::input('flow_item_id'));
|
||||
$owner = Request::input('owner', []);
|
||||
$assist = Request::input('assist', []);
|
||||
//
|
||||
$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){
|
||||
return Base::retSuccess('移动成功', ['id' => $task_id]);
|
||||
}
|
||||
//
|
||||
//
|
||||
$project = Project::userProject($project_id);
|
||||
$column = ProjectColumn::whereProjectId($project->id)->whereId($column_id)->first();
|
||||
if (empty($column)) {
|
||||
return Base::retError('列表不存在');
|
||||
}
|
||||
//
|
||||
$task->moveTask($project_id,$column_id);
|
||||
//
|
||||
return Base::retSuccess('移动成功', ['id' => $task_id]);
|
||||
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, $flow_item_id, $owner, $assist);
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id);
|
||||
//
|
||||
return Base::retSuccess('移动成功', $task);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2411,4 +2478,74 @@ class ProjectController extends AbstractController
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ use Request;
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property string|null $archived_at 归档时间
|
||||
* @property int|null $archived_userid 归档会员
|
||||
* @property int|null $is_fixed 是否固定
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_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 whereDialogId($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 wherePersonal($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
|
||||
|
||||
202
app/Models/ProjectPermission.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,8 @@ use Request;
|
||||
* @property int|null $archived_follow 跟随项目归档(项目取消归档时任务也取消归档)
|
||||
* @property string|null $complete_at 完成时间
|
||||
* @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 string|null $p_name 优先级名称
|
||||
* @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 whereFlowItemName($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 whereLoopAt($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 whereUpdatedAt($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 withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
@ -549,15 +551,12 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function updateTask($data, &$updateMarking = [])
|
||||
{
|
||||
//
|
||||
AbstractModel::transaction(function () use ($data, &$updateMarking) {
|
||||
// 主任务
|
||||
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
|
||||
// 工作流
|
||||
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']) {
|
||||
throw new ApiException('任务状态未发生改变');
|
||||
}
|
||||
@ -578,6 +577,7 @@ class ProjectTask extends AbstractModel
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
|
||||
}
|
||||
if ($currentFlowItem->userlimit) {
|
||||
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
|
||||
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
|
||||
}
|
||||
@ -1667,13 +1667,21 @@ class ProjectTask extends AbstractModel
|
||||
|
||||
/**
|
||||
* 移动任务
|
||||
* @param int $project_id
|
||||
* @param int $column_id
|
||||
* @param int $project_id
|
||||
* @param int $column_id
|
||||
* @param int $flowItemId
|
||||
* @param array $owner
|
||||
* @param array $assist
|
||||
* @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){
|
||||
$this->content->project_id = $projectId;
|
||||
@ -1690,15 +1698,24 @@ class ProjectTask extends AbstractModel
|
||||
$taskTag->save();
|
||||
}
|
||||
// 任务用户
|
||||
$this->updateTask(['owner' => $owner]);
|
||||
$this->updateTask(['assist' => $assist]);
|
||||
foreach ($this->taskUser as $taskUser){
|
||||
$taskUser->project_id = $projectId;
|
||||
$taskUser->save();
|
||||
if( in_array($taskUser->id, $newTaskUser) ){
|
||||
$taskUser->project_id = $projectId;
|
||||
$taskUser->save();
|
||||
}
|
||||
}
|
||||
//
|
||||
$this->project_id = $projectId;
|
||||
$this->column_id = $columnId;
|
||||
//
|
||||
if($flowItemId){
|
||||
$flowItem = projectFlowItem::whereProjectId($projectId)->whereId($flowItemId)->first();
|
||||
$this->flow_item_name = $flowItem->status . "|" . $flowItem->name;
|
||||
}else{
|
||||
$this->flow_item_name = '';
|
||||
}
|
||||
//
|
||||
$this->save();
|
||||
//
|
||||
//
|
||||
$this->addLog("移动{任务}");
|
||||
});
|
||||
$this->pushMsg('update');
|
||||
@ -1728,14 +1745,10 @@ class ProjectTask extends AbstractModel
|
||||
* @param int $task_id
|
||||
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
|
||||
* @param int|bool $permission
|
||||
* - 0|false 限制:项目成员、任务成员、任务群聊成员(任务成员 = 任务创建人+任务协助人+任务负责人)
|
||||
* - 1|true 限制:项目负责人、任务成员
|
||||
* - 2 已有负责人才限制true (子任务时如果是主任务负责人也可以)
|
||||
* @param array $with
|
||||
* @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));
|
||||
if ($trashed === false) {
|
||||
@ -1758,7 +1771,7 @@ class ProjectTask extends AbstractModel
|
||||
try {
|
||||
$project = Project::userProject($task->project_id);
|
||||
} catch (\Throwable $e) {
|
||||
if ($task->owner !== null || (!$permission && $task->permission(4))) {
|
||||
if ($task->owner !== null || $task->permission(4)) {
|
||||
$project = Project::find($task->project_id);
|
||||
if (empty($project)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,8 @@ class ProjectTaskContent extends AbstractModel
|
||||
*/
|
||||
public static function saveContent($task_id, $content)
|
||||
{
|
||||
@ini_set("pcre.backtrack_limit", 999999999);
|
||||
//
|
||||
$oldContent = $content;
|
||||
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
|
||||
//
|
||||
@ -81,9 +83,6 @@ class ProjectTaskContent extends AbstractModel
|
||||
Base::makeDir(dirname($publicPath));
|
||||
$result = file_put_contents($publicPath, $content);
|
||||
if(!$result && $oldContent){
|
||||
info("保存任务详情至文件失败");
|
||||
info($publicPath);
|
||||
info($oldContent);
|
||||
throw new ApiException("保存任务详情至文件失败,请重试");
|
||||
}
|
||||
//
|
||||
|
||||
@ -72,6 +72,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
protected $appends = [
|
||||
'percentage',
|
||||
'reply_data',
|
||||
'forward_data',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -114,6 +115,21 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
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
|
||||
@ -369,11 +385,13 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
* @param array|int $dialogids
|
||||
* @param array|int $userids
|
||||
* @param User $user 发送的会员
|
||||
* @param int $showSource 是否显示原发送者信息
|
||||
* @param string $leaveMessage 转发留言
|
||||
* @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'));
|
||||
$msgs = [];
|
||||
$already = [];
|
||||
@ -382,11 +400,14 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$dialogids = [$dialogids];
|
||||
}
|
||||
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)) {
|
||||
$msgs[] = $res['data'];
|
||||
$already[] = $dialogid;
|
||||
}
|
||||
if ($leaveMessage) {
|
||||
self::sendMsg(null, $dialogid, 'text', ['text' => $leaveMessage], $user->userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($userids) {
|
||||
@ -399,10 +420,13 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
$dialog = WebSocketDialog::checkUserDialog($user, $userid);
|
||||
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)) {
|
||||
$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']) {
|
||||
case 'text':
|
||||
case 'word-chain':
|
||||
case 'vote':
|
||||
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
|
||||
case 'record':
|
||||
return "[语音]";
|
||||
@ -772,6 +798,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
* - reply-98:回复消息ID=98
|
||||
* - update-99:更新消息ID=99(标记修改)
|
||||
* - change-99:更新消息ID=99(不标记修改)
|
||||
* - forward-99:转发消息ID=99
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param string $type 消息类型
|
||||
* @param array $msg 发送的消息
|
||||
@ -812,6 +839,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$change_id = preg_match("/^change-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$forward_id = preg_match("/^forward-(\d+)-(\d+)$/", $action, $match) ? $match[2] : 0;
|
||||
$sender = $sender === null ? User::userid() : $sender;
|
||||
//
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
@ -833,10 +861,10 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (empty($dialogMsg)) {
|
||||
throw new ApiException('消息不存在');
|
||||
}
|
||||
if ($dialogMsg->type !== 'text') {
|
||||
if ($dialogMsg->type !== 'text' && $dialogMsg->type !== 'vote') {
|
||||
throw new ApiException('此消息不支持此操作');
|
||||
}
|
||||
if ($dialogMsg->userid != $sender) {
|
||||
if ($dialogMsg->userid != $sender && $dialogMsg->type !== 'vote') {
|
||||
throw new ApiException('仅支持修改自己的消息');
|
||||
}
|
||||
//
|
||||
@ -862,6 +890,10 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) {
|
||||
throw new ApiException('回复的消息不存在');
|
||||
}
|
||||
// 转发
|
||||
if ($forward_id && !self::whereId($forward_id)->increment('forward_num')) {
|
||||
throw new ApiException('转发的消息不存在');
|
||||
}
|
||||
//
|
||||
$dialogMsg = self::createInstance([
|
||||
'dialog_id' => $dialog_id,
|
||||
@ -873,6 +905,8 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
'link' => $link,
|
||||
'msg' => $msg,
|
||||
'read' => 0,
|
||||
'forward_id' => $forward_id,
|
||||
'forward_show' => $forward_id ? $match[1] : 1,
|
||||
]);
|
||||
AbstractModel::transaction(function () use ($dialog, $dialogMsg) {
|
||||
$dialog->last_at = Carbon::now();
|
||||
|
||||
@ -687,6 +687,22 @@ class Base
|
||||
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加密
|
||||
* @param $text
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Models\TaskWorker;
|
||||
use App\Models\Tmp;
|
||||
use App\Models\WebSocketTmpMsg;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\File as SupportFile;
|
||||
|
||||
/**
|
||||
* 删除过期临时数据任务
|
||||
@ -102,10 +103,12 @@ class DeleteTmpTask extends AbstractTask
|
||||
*/
|
||||
case 'file_pack':
|
||||
{
|
||||
$path = storage_path('app/temp/download/');
|
||||
$path = public_path('tmp/file/');
|
||||
if (!SupportFile::exists($path)) {
|
||||
return;
|
||||
}
|
||||
$dirIterator = new \RecursiveDirectoryIterator($path);
|
||||
$iterator = new \RecursiveIteratorIterator($dirIterator);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$time = $file->getMTime();
|
||||
|
||||
@ -38,12 +38,17 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->id = $id;
|
||||
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
|
||||
$this->client = [
|
||||
'version' => Base::headerOrInput('version'),
|
||||
'language' => Base::headerOrInput('language'),
|
||||
'platform' => Base::headerOrInput('platform'),
|
||||
];
|
||||
// 判断是否有Request方法,兼容go协程请求
|
||||
$this->ignoreFd = $ignoreFd;
|
||||
$this->client = [];
|
||||
if (method_exists(new Request,"header")) {
|
||||
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
|
||||
$this->client = [
|
||||
'version' => Base::headerOrInput('version'),
|
||||
'language' => Base::headerOrInput('language'),
|
||||
'platform' => Base::headerOrInput('platform'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -150,7 +150,7 @@ services:
|
||||
|
||||
approve:
|
||||
container_name: "dootask-approve-${APP_ID}"
|
||||
image: "kuaifan/dooapprove:0.0.8"
|
||||
image: "kuaifan/dooapprove:0.0.9"
|
||||
environment:
|
||||
TZ: "${TIMEZONE:-PRC}"
|
||||
MYSQL_HOST: "${DB_HOST}"
|
||||
|
||||
@ -480,3 +480,5 @@ Api接口文档
|
||||
保存任务详情至文件失败
|
||||
保存任务详情至文件失败,请重试
|
||||
移动成功
|
||||
|
||||
不能重复投票
|
||||
|
||||
@ -1252,6 +1252,7 @@ Markdown 格式发送
|
||||
|
||||
OKR管理
|
||||
OKR结果
|
||||
OKR结果分析
|
||||
计划时间冲突提示
|
||||
忽略并继续
|
||||
你确定要清除缓存吗?
|
||||
@ -1412,4 +1413,70 @@ APP推送
|
||||
你确定取消待办吗?
|
||||
取消成功
|
||||
|
||||
请等待打包完成
|
||||
选择一个项目查看更多任务
|
||||
首页
|
||||
无相关数据
|
||||
当前环境
|
||||
|
||||
权限设置
|
||||
任务列权限
|
||||
添加列
|
||||
修改列
|
||||
删除列
|
||||
排序列
|
||||
任务权限
|
||||
修改任务
|
||||
修改状态
|
||||
移动任务
|
||||
任务协助人
|
||||
搜索项目名称
|
||||
服务器版本过低,请升级服务器。
|
||||
|
||||
不显示原发送者信息
|
||||
转发给:
|
||||
留言
|
||||
多选
|
||||
@我的
|
||||
移动前
|
||||
移动后
|
||||
状态
|
||||
协助人
|
||||
未变更移动项
|
||||
接龙
|
||||
参与接龙
|
||||
发起接龙
|
||||
由
|
||||
发起接龙,参与接龙目前共(*)人
|
||||
请输入接龙主题
|
||||
请输入接龙内容
|
||||
可填写接龙格式
|
||||
重复内容将不再计入接龙结果
|
||||
返回编辑
|
||||
继续发送
|
||||
例
|
||||
接龙结果
|
||||
选择群组发起接龙
|
||||
来自
|
||||
发起投票
|
||||
投票结果
|
||||
发起
|
||||
请输入投票主题
|
||||
请输入选项内容
|
||||
允许多选
|
||||
匿名投票
|
||||
投票
|
||||
匿名
|
||||
实名
|
||||
单选
|
||||
多选
|
||||
请选择后投票
|
||||
立即投票
|
||||
票
|
||||
再次发送
|
||||
再次发送投票?
|
||||
结束投票
|
||||
确定结束投票?
|
||||
已发送
|
||||
选择群组发起投票
|
||||
以下为新消息
|
||||
|
||||
21
public/images/application/vote.svg
Normal 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 |
38
public/images/application/word-chain.svg
Normal 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 |
1
public/images/empty/complete.svg
Normal file
|
After Width: | Height: | Size: 31 KiB |
1
public/images/empty/empty.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
@ -1 +1 @@
|
||||
["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]
|
||||
["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]
|
||||
5
public/site/css/common.css
vendored
@ -470,6 +470,11 @@ img {
|
||||
grid-template-columns: 480px 266px 266px 266px;
|
||||
}
|
||||
|
||||
.grid-5 {
|
||||
display: grid !important;
|
||||
grid-template-columns: 336px 236px 236px 236px 236px;
|
||||
}
|
||||
|
||||
/* padding样式 */
|
||||
.pl-26 {
|
||||
padding-left: 26px;
|
||||
|
||||
30
public/site/css/price.css
vendored
@ -139,18 +139,26 @@
|
||||
}
|
||||
.plans-ol-item-h6{
|
||||
color: var(--txt-4ca5);
|
||||
line-height: normal;
|
||||
}
|
||||
.plans-ol-item-icon{
|
||||
display: block;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.plans-ol-item-icon2{
|
||||
display: block;
|
||||
height: 24px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.plans-ul-b{
|
||||
display: grid;
|
||||
grid-template-columns: 506px 266px 266px 266px;
|
||||
}
|
||||
.plans-ul-b-item-btn{
|
||||
display: inline-block;
|
||||
width: 218px;
|
||||
width: 160px;
|
||||
}
|
||||
.flex-s-c{
|
||||
display: flex;
|
||||
@ -202,6 +210,13 @@
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.BulletBox1{
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.BulletBox1 .top{
|
||||
width: 382px;
|
||||
line-height: 20px;
|
||||
@ -225,13 +240,12 @@
|
||||
color: rgb(96, 98, 102);
|
||||
}
|
||||
.BulletBox1 .bottom{
|
||||
width: 370px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
padding: 20px 30px 22px 30px;
|
||||
position: relative;
|
||||
}
|
||||
.BulletBox1 .bottom .BulletBox1Btn{
|
||||
position: absolute;
|
||||
min-width: 100px;
|
||||
right: 20px;
|
||||
height: 34px;
|
||||
@ -243,3 +257,11 @@
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.price-ceiling {
|
||||
position: sticky;
|
||||
top: 80px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
z-index: 9999;
|
||||
}
|
||||
3
public/site/css/rem.css
vendored
@ -80,6 +80,9 @@
|
||||
.grid-4{
|
||||
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{
|
||||
grid-template-columns: calc(34vw + 4px) 18vw 18vw 18vw !important;
|
||||
}
|
||||
|
||||
11
public/site/img/price_icon2.svg
Normal 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 |
12
resources/assets/js/app.js
vendored
@ -2,7 +2,7 @@ const isElectron = !!(window && window.process && window.process.type);
|
||||
const isEEUiApp = window && window.navigator && /eeui/i.test(window.navigator.userAgent);
|
||||
|
||||
import microappInit from "./microapp"
|
||||
import {switchLanguage as $L} from "./language";
|
||||
import {switchLanguage as $L, setLanguage, getLanguage} from "./language";
|
||||
|
||||
import './functions/common'
|
||||
import './functions/eeui'
|
||||
@ -91,17 +91,18 @@ if (!isElectron && !isEEUiApp) {
|
||||
ViewUI.LoadingBar._load = true;
|
||||
ViewUI.LoadingBar.start();
|
||||
}, 300)
|
||||
//
|
||||
if (to.query?.theme) {
|
||||
store.dispatch("setTheme", typeof to.query?.theme == 'string' ? to.query?.theme : to.query?.theme[0])
|
||||
}
|
||||
if (to.query?.lang) {
|
||||
let lang = typeof to.query?.lang == 'string' ? to.query?.lang : to.query?.lang[0]
|
||||
if (window.localStorage.getItem("__language:type__") != lang) {
|
||||
window.localStorage.setItem("__language:type__", to.query?.lang);
|
||||
window.location.reload();
|
||||
return false;
|
||||
if(lang && lang != getLanguage()){
|
||||
setLanguage(lang, true)
|
||||
return;
|
||||
}
|
||||
}
|
||||
//
|
||||
next();
|
||||
});
|
||||
router.afterEach(() => {
|
||||
@ -157,6 +158,7 @@ $A.Platform = "web";
|
||||
$A.isMainElectron = false;
|
||||
$A.isSubElectron = false;
|
||||
$A.isEEUiApp = isEEUiApp;
|
||||
$A.isElectron = isElectron;
|
||||
$A.openLog = false;
|
||||
if (isElectron) {
|
||||
$A.Electron = electron;
|
||||
|
||||
@ -163,7 +163,9 @@ export default {
|
||||
}
|
||||
const documentKey = this.documentKey();
|
||||
if (documentKey && documentKey.then) {
|
||||
documentKey.then(this.loadFile);
|
||||
documentKey.then(this.loadFile).catch(({msg})=>{
|
||||
$A.modalError({content: msg});
|
||||
});
|
||||
} else {
|
||||
this.loadFile();
|
||||
}
|
||||
@ -280,6 +282,14 @@ export default {
|
||||
}
|
||||
//
|
||||
}).catch(({msg}) => {
|
||||
if (msg.indexOf("404 not found") !== -1) {
|
||||
$A.modalInfo({
|
||||
language: false,
|
||||
title: '版本过低',
|
||||
content: '服务器版本过低,请升级服务器。',
|
||||
})
|
||||
return;
|
||||
}
|
||||
$A.modalError({content: msg});
|
||||
});
|
||||
})
|
||||
|
||||
@ -19,15 +19,20 @@
|
||||
<!-- 顶部 -->
|
||||
<template #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-submit" @click="onSubmit">
|
||||
<div v-if="showMultiple" class="user-modal-submit" @click="onSubmit(1)">
|
||||
<div v-if="submittIng > 0" class="submit-loading"><Loading /></div>
|
||||
{{$L('确定')}}
|
||||
<template v-if="selects.length > 0">
|
||||
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="!forcedRadio" class="user-modal-submit" @click="showMultiple = true">
|
||||
{{$L('多选')}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="ivu-modal-header-inner">{{localTitle}}</div>
|
||||
</template>
|
||||
@ -37,7 +42,7 @@
|
||||
|
||||
<!-- 搜索 -->
|
||||
<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>
|
||||
<li v-for="item in formatSelect(selects)" :data-id="item.userid" @click.stop="onRemoveItem(item.userid)">
|
||||
<template v-if="item.type=='group'">
|
||||
@ -76,15 +81,19 @@
|
||||
v-for="item in lists"
|
||||
:class="selectClass(item.userid_list)"
|
||||
@click="onSelectProject(item.userid_list)">
|
||||
<Icon class="user-modal-icon" :type="selectIcon(item.userid_list)" />
|
||||
<template v-if="showMultiple">
|
||||
<Icon class="user-modal-icon" :type="selectIcon(item.userid_list)" />
|
||||
</template>
|
||||
<div class="user-modal-avatar">
|
||||
<i class="taskfont icon-avatar"></i>
|
||||
<div class="project-name">
|
||||
<div class="label">{{item.name}}</div>
|
||||
<div class="subtitle">
|
||||
{{item.userid_list.length}} {{$L('项目成员')}}
|
||||
<em class="all">{{$L('已全选')}}</em>
|
||||
<em class="some">{{$L('已选部分')}}</em>
|
||||
<template v-if="showMultiple">
|
||||
<em class="all">{{$L('已全选')}}</em>
|
||||
<em class="some">{{$L('已选部分')}}</em>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -93,7 +102,7 @@
|
||||
<!-- 会员、会话 -->
|
||||
<ul v-else>
|
||||
<li
|
||||
v-if="showSelectAll"
|
||||
v-if="showMultiple && showSelectAll"
|
||||
:class="selectClass('all')"
|
||||
@click="onSelectAll">
|
||||
<Icon class="user-modal-icon" :type="selectIcon('all')" />
|
||||
@ -106,8 +115,10 @@
|
||||
disabled: isUncancelable(item.userid) || isDisabled(item.userid)
|
||||
}"
|
||||
@click="onSelectItem(item)">
|
||||
<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" />
|
||||
<template v-if="showMultiple">
|
||||
<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" />
|
||||
</template>
|
||||
<div v-if="item.type=='group'" class="user-modal-avatar">
|
||||
<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"></i>
|
||||
@ -134,12 +145,57 @@
|
||||
|
||||
<!-- 底部 -->
|
||||
<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('确定')}}
|
||||
<template v-if="selects.length > 0">
|
||||
({{selects.length}}<span v-if="multipleMax">/{{multipleMax}}</span>)
|
||||
</template>
|
||||
</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"></i>
|
||||
<i v-else-if="item.group_type=='project'" class="taskfont icon-avatar project"></i>
|
||||
<i v-else-if="item.group_type=='task'" class="taskfont icon-avatar task"></i>
|
||||
<i v-else-if="item.group_type=='okr'" class="taskfont icon-avatar task"></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>
|
||||
</Modal>
|
||||
</div>
|
||||
@ -260,22 +316,52 @@ export default {
|
||||
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
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchItems: [
|
||||
{key: 'recent', label: '最近'},
|
||||
{key: 'contact', label: '通讯录'},
|
||||
{key: 'project', label: '项目成员'},
|
||||
{ key: 'recent', label: '最近' },
|
||||
{ key: 'contact', label: '通讯录' },
|
||||
{ key: 'project', label: '项目成员' },
|
||||
],
|
||||
switchActive: 'recent',
|
||||
|
||||
loadIng: 0, // 搜索框等待效果
|
||||
waitIng: 0, // 页面等待效果
|
||||
submittIng: 0, // 提交按钮等待效果
|
||||
|
||||
loadIng: 0,
|
||||
waitIng: 0,
|
||||
submittIng: 0,
|
||||
values: [],
|
||||
selects: [],
|
||||
|
||||
@ -287,7 +373,10 @@ export default {
|
||||
|
||||
searchKey: null,
|
||||
searchCache: [],
|
||||
}
|
||||
|
||||
showAffirmModal: false,
|
||||
showMultiple: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
@ -305,7 +394,7 @@ export default {
|
||||
|
||||
isWhole: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
if (value || this.group) {
|
||||
this.switchActive = 'recent'
|
||||
} else {
|
||||
this.switchActive = 'contact'
|
||||
@ -317,10 +406,14 @@ export default {
|
||||
showModal(value) {
|
||||
if (value) {
|
||||
this.searchBefore()
|
||||
this.showMultiple = this.multipleChoice
|
||||
if(this.forcedRadio){
|
||||
this.showMultiple = false
|
||||
}
|
||||
} else {
|
||||
this.searchKey = ""
|
||||
}
|
||||
this.$emit("on-show-change",value,this.values)
|
||||
this.$emit("on-show-change", value, this.values)
|
||||
},
|
||||
|
||||
searchKey() {
|
||||
@ -336,15 +429,15 @@ export default {
|
||||
'cacheDialogs',
|
||||
]),
|
||||
|
||||
isFullscreen({windowWidth}) {
|
||||
isFullscreen({ windowWidth }) {
|
||||
return windowWidth < 576
|
||||
},
|
||||
|
||||
isWhole({projectId, noProjectId, dialogId}) {
|
||||
return projectId === 0 && noProjectId === 0 && dialogId === 0
|
||||
isWhole({ projectId, noProjectId, dialogId }) {
|
||||
return projectId === 0 && noProjectId === 0 && dialogId === 0 && !this.group
|
||||
},
|
||||
|
||||
lists({switchActive, searchKey, recents, contacts, projects}) {
|
||||
lists({ switchActive, searchKey, recents, contacts, projects }) {
|
||||
switch (switchActive) {
|
||||
case 'recent':
|
||||
if (searchKey) {
|
||||
@ -363,7 +456,7 @@ export default {
|
||||
return []
|
||||
},
|
||||
|
||||
isSelectAll({lists, selects}) {
|
||||
isSelectAll({ lists, selects }) {
|
||||
return lists.length > 0 && lists.filter(item => selects.includes(item.userid)).length === lists.length;
|
||||
},
|
||||
|
||||
@ -375,14 +468,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
addStyle({avatarSize}) {
|
||||
addStyle({ avatarSize }) {
|
||||
return {
|
||||
width: avatarSize + 'px',
|
||||
height: avatarSize + 'px',
|
||||
}
|
||||
},
|
||||
|
||||
localTitle({title}) {
|
||||
localTitle({ title }) {
|
||||
if (title === undefined) {
|
||||
return this.$L('选择会员')
|
||||
} else {
|
||||
@ -390,7 +483,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
localPlaceholder({placeholder}) {
|
||||
localPlaceholder({ placeholder }) {
|
||||
if (placeholder === undefined) {
|
||||
return this.$L('搜索')
|
||||
} else {
|
||||
@ -466,6 +559,9 @@ export default {
|
||||
|
||||
searchRecent() {
|
||||
this.recents = this.cacheDialogs.filter(dialog => {
|
||||
if(this.group && dialog.type != 'group'){
|
||||
return false
|
||||
}
|
||||
if (dialog.name === undefined || dialog.dialog_delete === 1) {
|
||||
return false
|
||||
}
|
||||
@ -481,7 +577,7 @@ export default {
|
||||
return b.todo_num - a.todo_num;
|
||||
}
|
||||
return $A.Date(b.last_at) - $A.Date(a.last_at);
|
||||
}).map(({id, name, type, group_type, avatar, dialog_user}) => {
|
||||
}).map(({ id, name, type, group_type, avatar, dialog_user }) => {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
@ -521,18 +617,18 @@ export default {
|
||||
},
|
||||
take: 50
|
||||
},
|
||||
}).then(({data}) => {
|
||||
data = data.map(item => Object.assign(item, {type: 'user'}))
|
||||
}).then(({ data }) => {
|
||||
data = data.map(item => Object.assign(item, { type: 'user' }))
|
||||
this.contacts = data
|
||||
//
|
||||
const index = this.searchCache.findIndex(item => item.key == key);
|
||||
const tmpData = {type: 'contact', key, data, time: $A.Time()};
|
||||
const tmpData = { type: 'contact', key, data, time: $A.Time() };
|
||||
if (index > -1) {
|
||||
this.searchCache.splice(index, 1, tmpData)
|
||||
} else {
|
||||
this.searchCache.push(tmpData)
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
}).catch(({ msg }) => {
|
||||
this.contacts = []
|
||||
$A.messageWarning(msg)
|
||||
}).finally(_ => {
|
||||
@ -568,18 +664,18 @@ export default {
|
||||
getuserid: 'yes',
|
||||
getstatistics: 'no'
|
||||
},
|
||||
}).then(({data}) => {
|
||||
data = data.data.map(item => Object.assign(item, {type: 'project'}))
|
||||
this.projects = data
|
||||
}).then(({ data }) => {
|
||||
data = data.data.map(item => Object.assign(item, { type: 'project' }))
|
||||
this.projects = data;
|
||||
//
|
||||
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() };
|
||||
if (index > -1) {
|
||||
this.searchCache.splice(index, 1, tmpData)
|
||||
} else {
|
||||
this.searchCache.push(tmpData)
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
}).catch(({ msg }) => {
|
||||
this.projects = []
|
||||
$A.messageWarning(msg)
|
||||
}).finally(_ => {
|
||||
@ -590,7 +686,7 @@ export default {
|
||||
},
|
||||
|
||||
onSelection() {
|
||||
if(this.disable){
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
this.$nextTick(_ => {
|
||||
@ -618,7 +714,10 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
onSelectItem({userid}) {
|
||||
onSelectItem({ userid }) {
|
||||
if (!this.showMultiple) {
|
||||
this.selects = []
|
||||
}
|
||||
if (this.selects.includes(userid)) {
|
||||
if (this.isUncancelable(userid)) {
|
||||
return
|
||||
@ -635,12 +734,18 @@ export default {
|
||||
this.selects.push(userid)
|
||||
// 滚动到选中的位置
|
||||
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) {
|
||||
if (!this.showMultiple) {
|
||||
this.selects = []
|
||||
}
|
||||
switch (this.selectIcon(userid_list)) {
|
||||
case 'ios-checkmark-circle':
|
||||
// 去除
|
||||
@ -664,6 +769,9 @@ export default {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!this.showMultiple) {
|
||||
this.onSubmit(1)
|
||||
}
|
||||
},
|
||||
|
||||
onRemoveItem(userid) {
|
||||
@ -673,17 +781,28 @@ export default {
|
||||
this.selects = this.selects.filter(value => value != userid)
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
onSubmit(index) {
|
||||
if (this.submittIng > 0) {
|
||||
return
|
||||
}
|
||||
const clone = $A.cloneJSON(this.values)
|
||||
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('onSubmit', this.values)
|
||||
|
||||
if (!this.beforeSubmit) {
|
||||
this.showModal = false
|
||||
this.showAffirmModal = false
|
||||
return
|
||||
}
|
||||
const before = this.beforeSubmit();
|
||||
@ -691,6 +810,7 @@ export default {
|
||||
this.submittIng++
|
||||
before.then(() => {
|
||||
this.showModal = false
|
||||
this.showAffirmModal = false
|
||||
}).catch(() => {
|
||||
this.values = clone
|
||||
this.$emit('input', this.values)
|
||||
@ -699,6 +819,7 @@ export default {
|
||||
})
|
||||
} else {
|
||||
this.showModal = false
|
||||
this.showAffirmModal = false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
13
resources/assets/js/functions/web.js
vendored
@ -735,8 +735,9 @@
|
||||
// 处理内容连接
|
||||
if (/https*:\/\//.test(text)) {
|
||||
const urlMatch = $.apiUrl('../').match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/im);
|
||||
// const theme = window.localStorage.getItem("__theme:mode__")
|
||||
// const lang = window.localStorage.getItem("__language:type__")
|
||||
const isMentionFile = text.indexOf('class="mention file"') !== -1 && ($A.isEEUiApp || $A.isElectron)
|
||||
const theme = isMentionFile ? $A.dark.isDarkEnabled() ? 'dark' : 'light' : '';
|
||||
const lang = isMentionFile ? window.localStorage.getItem("__language:type__") : ''
|
||||
text = text.split(/(<[^>]*>)/g).map(string => {
|
||||
if (string && !/<[^>]*>/.test(string)) {
|
||||
string = string.replace(/(^|[^'"])((https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+))/g, "$1<a href=\"$2\" target=\"_blank\">$2</a>")
|
||||
@ -745,8 +746,8 @@
|
||||
const href = string.match(/href="([^"]+)"/)?.[1] || ''
|
||||
if (urlMatch?.[1] && href.indexOf(urlMatch[1]) !== -1) {
|
||||
const searchParams = new URLSearchParams()
|
||||
// href.indexOf("theme=") === -1 && searchParams.append('theme', theme);
|
||||
// href.indexOf("lang=") === -1 && searchParams.append('lang', lang);
|
||||
theme && href.indexOf("theme=") === -1 && searchParams.append('theme', theme);
|
||||
lang && href.indexOf("lang=") === -1 && searchParams.append('lang', lang);
|
||||
const prefix = searchParams.toString() ? (href.indexOf("?") === -1 ? '?' : '&') : '';
|
||||
string = string.replace(/(href="[^"]*)/g, '$1' + prefix + searchParams.toString())
|
||||
}
|
||||
@ -819,6 +820,10 @@
|
||||
switch (data.type) {
|
||||
case 'text':
|
||||
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':
|
||||
return `[${$A.L('语音')}]`
|
||||
case 'meeting':
|
||||
|
||||
35
resources/assets/js/language/index.js
vendored
@ -30,20 +30,33 @@ function addLanguage(data) {
|
||||
/**
|
||||
* 设置语言
|
||||
* @param language
|
||||
* @param silence
|
||||
*/
|
||||
function setLanguage(language) {
|
||||
function setLanguage(language, silence = false) {
|
||||
if (language === undefined) {
|
||||
return
|
||||
}
|
||||
$A.modalConfirm({
|
||||
content: '切换语言需要刷新后生效,是否确定刷新?',
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk: () => {
|
||||
window.localStorage.setItem("__language:type__", language)
|
||||
$A.reloadUrl()
|
||||
}
|
||||
})
|
||||
if(silence){
|
||||
window.localStorage.setItem("__language:type__", language)
|
||||
$A.reloadUrl()
|
||||
}else{
|
||||
$A.modalConfirm({
|
||||
content: '切换语言需要刷新后生效,是否确定刷新?',
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk: () => {
|
||||
window.localStorage.setItem("__language:type__", language)
|
||||
$A.reloadUrl()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新语言
|
||||
*/
|
||||
function getLanguage() {
|
||||
return utils.getLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,4 +124,4 @@ function switchLanguage(text) {
|
||||
return text
|
||||
}
|
||||
|
||||
export { languageType, languageList, addLanguage, setLanguage, switchLanguage }
|
||||
export { languageType, languageList, addLanguage, setLanguage, getLanguage, switchLanguage }
|
||||
|
||||
@ -188,6 +188,19 @@
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -278,6 +291,9 @@ export default {
|
||||
scanLoginShow: false,
|
||||
scanLoginLoad: false,
|
||||
scanLoginCode: '',
|
||||
//
|
||||
sendData: [],
|
||||
sendType: '',
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
@ -302,43 +318,52 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
initList() {
|
||||
const applyList = [
|
||||
{value: "approve", label: "审批中心"},
|
||||
{value: "okr", label: "OKR管理"},
|
||||
{value: "report", label: "工作报告"},
|
||||
{value: "robot", label: "AI机器人"},
|
||||
{value: "signin", label: "签到"},
|
||||
{value: "meeting", label: "会议"}
|
||||
let applyList = [
|
||||
{ value: "approve", label: "审批中心", sort: 3 },
|
||||
{ value: "report", label: "工作报告", sort: 5 },
|
||||
{ value: "okr", label: "OKR管理", sort: 4 },
|
||||
{ value: "robot", label: "AI机器人", sort: 6 },
|
||||
{ value: "signin", label: "签到", sort: 7 },
|
||||
{ value: "meeting", label: "会议", sort: 8 },
|
||||
{ value: "word-chain", label: "接龙", sort: 9 },
|
||||
{ value: "vote", label: "投票", sort: 10 },
|
||||
];
|
||||
// wap模式
|
||||
if (this.windowOrientation == 'landscape') {
|
||||
// 横屏模式
|
||||
applyList.push({value: "scan", label: "扫一扫", show: $A.isEEUiApp})
|
||||
applyList.push({ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 })
|
||||
} else {
|
||||
// 竖屏模式
|
||||
applyList.unshift(...[
|
||||
{value: "calendar", label: "日历"},
|
||||
{value: "file", label: "文件"}
|
||||
])
|
||||
applyList.push(...[
|
||||
{value: "addProject", label: "创建项目"},
|
||||
{value: "addTask", label: "添加任务"},
|
||||
{value: "scan", label: "扫一扫", show: $A.isEEUiApp},
|
||||
{value: "setting", label: "设置"}
|
||||
{ value: "calendar", label: "日历", sort: 1 },
|
||||
{ value: "file", label: "文件", sort: 2 },
|
||||
{ value: "addProject", label: "创建项目", sort: 11 },
|
||||
{ value: "addTask", label: "添加任务", sort: 12 },
|
||||
{ value: "scan", label: "扫一扫", show: $A.isEEUiApp, sort: 13 },
|
||||
{ value: "setting", label: "设置", sort: 14 }
|
||||
])
|
||||
}
|
||||
// 管理员
|
||||
const adminApplyList = !this.userIsAdmin ? [] : [
|
||||
{value: "okrAnalyze", label: "OKR结果"},
|
||||
{value: "ldap", label: "LDAP"},
|
||||
{value: "mail", label: "邮件通知"},
|
||||
{value: "appPush", label: "APP推送"},
|
||||
{value: "allUser", label: "团队管理"}
|
||||
let adminApplyList = !this.userIsAdmin ? [] : [
|
||||
{ value: "okrAnalyze", label: "OKR结果", sort: 15 },
|
||||
{ value: "ldap", label: "LDAP", sort: 16 },
|
||||
{ value: "mail", label: "邮件通知", sort: 17 },
|
||||
{ value: "appPush", label: "APP推送", sort: 18 },
|
||||
{ value: "allUser", label: "团队管理", sort: 19 }
|
||||
].map((h) => {
|
||||
h.type = 'admin';
|
||||
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) {
|
||||
name = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
@ -402,6 +427,12 @@ export default {
|
||||
case 'scan':
|
||||
$A.eeuiAppScan(this.scanResult);
|
||||
return;
|
||||
case 'word-chain':
|
||||
case 'vote':
|
||||
this.sendData = [];
|
||||
this.sendType = item.value;
|
||||
this.$refs.wordChainAndVoteRef.onSelection()
|
||||
return;
|
||||
}
|
||||
this.$emit("on-click", item.value)
|
||||
},
|
||||
@ -520,7 +551,7 @@ export default {
|
||||
this.scanLoginLoad = false
|
||||
});
|
||||
},
|
||||
// 打开明显
|
||||
// 打开明细
|
||||
openDetail(desc){
|
||||
$A.modalInfo({
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="calendar-head">
|
||||
<div class="calendar-titbox">
|
||||
<div class="calendar-title">
|
||||
<div class="common-nav-back portrait" @click="goForward({name: 'manage-application'},true)"><i class="taskfont"></i></div>
|
||||
<div class="common-nav-back" @click="goForward({name: 'manage-application'},true)"><i class="taskfont"></i></div>
|
||||
<h1>{{rangeText}}</h1>
|
||||
</div>
|
||||
<ButtonGroup class="calendar-arrow" size="small">
|
||||
@ -44,10 +44,6 @@
|
||||
</template>
|
||||
|
||||
<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 Calendar from "./components/Calendar";
|
||||
import moment from "moment";
|
||||
@ -363,7 +359,10 @@ export default {
|
||||
if (!data) {
|
||||
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();
|
||||
cal.updateSchedule(schedule.id, schedule.calendarId, changes);
|
||||
//
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div ref="tuiCalendar" class="calendar-wrapper"></div>
|
||||
</template>
|
||||
|
||||
<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';
|
||||
|
||||
export default {
|
||||
@ -148,7 +152,13 @@ export default {
|
||||
},
|
||||
isReadOnly(newValue) {
|
||||
this.calendarInstance.setOptions({isReadOnly: newValue});
|
||||
}
|
||||
},
|
||||
windowPortrait: {
|
||||
handler(v) {
|
||||
this.resetRender()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.calendarInstance = new Calendar(this.$refs.tuiCalendar, {
|
||||
@ -170,10 +180,13 @@ export default {
|
||||
});
|
||||
this.addEventListeners();
|
||||
this.reflectSchedules();
|
||||
//
|
||||
window.addEventListener('resize',this.resetRender);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.calendarInstance.off();
|
||||
this.calendarInstance.destroy();
|
||||
window.removeEventListener('resize',this.resetRender);
|
||||
},
|
||||
methods: {
|
||||
addEventListeners() {
|
||||
@ -193,8 +206,10 @@ export default {
|
||||
return this.calendarInstance;
|
||||
},
|
||||
resetRender() {
|
||||
this.calendarInstance.clear();
|
||||
this.reflectSchedules();
|
||||
if(this.calendarInstance){
|
||||
this.calendarInstance.clear();
|
||||
this.reflectSchedules();
|
||||
}
|
||||
},
|
||||
invoke(methodName, ...args) {
|
||||
let result;
|
||||
|
||||
@ -102,6 +102,14 @@
|
||||
<i class="taskfont"></i>
|
||||
{{$L('匿名消息')}}
|
||||
</div>
|
||||
<div v-if="dialogData.type == 'group'" class="chat-input-popover-item" @click="onToolbar('word-chain')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('发起接龙')}}
|
||||
</div>
|
||||
<div v-if="dialogData.type == 'group'" class="chat-input-popover-item" @click="onToolbar('vote')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('发起投票')}}
|
||||
</div>
|
||||
<div class="chat-input-popover-item" @click="onToolbar('full')">
|
||||
<i class="taskfont"></i>
|
||||
{{$L('全屏输入')}}
|
||||
@ -1224,6 +1232,21 @@ export default {
|
||||
case 'anon':
|
||||
this.$emit('on-more', action)
|
||||
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;
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
225
resources/assets/js/pages/manage/components/DialogGroupVote.vue
Normal 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)"></i>
|
||||
<Input v-model="item.text" :placeholder="$L('请输入选项内容')"/>
|
||||
</li>
|
||||
<li class="add">
|
||||
<i class="taskfont" @click="onAdd"></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>
|
||||
@ -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"></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>
|
||||
@ -14,6 +14,10 @@
|
||||
<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>
|
||||
<!--转发-->
|
||||
<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">
|
||||
<!--文本-->
|
||||
@ -62,6 +66,72 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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"></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">
|
||||
<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() {
|
||||
this.$emit("on-view-reply", {
|
||||
msg_id: this.msgData.id,
|
||||
@ -492,6 +570,67 @@ export default {
|
||||
onShowEmojiUser(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>
|
||||
|
||||
@ -257,7 +257,7 @@
|
||||
<i class="taskfont" v-html="item.icon"></i>
|
||||
<span>{{ $L(item.label) }}</span>
|
||||
</li>
|
||||
<li @click="onOperate('forward')">
|
||||
<li v-if="operateItem.type !== 'word-chain' && operateItem.type !== 'vote'" @click="onOperate('forward')">
|
||||
<i class="taskfont"></i>
|
||||
<span>{{ $L('转发') }}</span>
|
||||
</li>
|
||||
@ -392,10 +392,33 @@
|
||||
v-model="forwardData"
|
||||
:multiple-max="50"
|
||||
:title="$L('转发')"
|
||||
:twice-affirm="true"
|
||||
:twice-affirm-title="$L('转发给:')"
|
||||
:before-submit="onForward"
|
||||
:show-select-all="false"
|
||||
:multiple-choice="false"
|
||||
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
|
||||
@ -508,6 +531,13 @@
|
||||
<DrawerOverlay v-model="approveDetailsShow" placement="right" :size="600">
|
||||
<ApproveDetails v-if="approveDetailsShow" :data="approveDetails" style="height: 100%;border-radius: 10px;"></ApproveDetails>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!-- 群接龙 -->
|
||||
<DialogGroupWordChain/>
|
||||
|
||||
<!-- 群投票 -->
|
||||
<DialogGroupVote/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -528,6 +558,9 @@ import {choiceEmojiOne} from "./ChatInput/one";
|
||||
import ApproveDetails from "../../../pages/manage/approve/details.vue";
|
||||
import UserSelect from "../../../components/UserSelect.vue";
|
||||
import UserAvatarTip from "../../../components/UserAvatar/tip.vue";
|
||||
import DialogGroupWordChain from "./DialogGroupWordChain";
|
||||
import DialogGroupVote from "./DialogGroupVote";
|
||||
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
@ -542,7 +575,9 @@ export default {
|
||||
DialogGroupInfo,
|
||||
DrawerOverlay,
|
||||
DialogUpload,
|
||||
ApproveDetails
|
||||
ApproveDetails,
|
||||
DialogGroupWordChain,
|
||||
DialogGroupVote,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -598,6 +633,8 @@ export default {
|
||||
modifyLoad: 0,
|
||||
|
||||
forwardData: [],
|
||||
forwardShowOriginal: true,
|
||||
forwardLeaveMessage: '',
|
||||
|
||||
openId: 0,
|
||||
dialogDrag: false,
|
||||
@ -1138,6 +1175,9 @@ export default {
|
||||
},
|
||||
|
||||
allMsgList(newList, oldList) {
|
||||
if(JSON.stringify(newList) == JSON.stringify(oldList)){
|
||||
return;
|
||||
}
|
||||
const {tail} = this.scrollInfo();
|
||||
if ($A.isIos() && newList.length !== oldList.length) {
|
||||
// 隐藏区域,让iOS断触
|
||||
@ -2249,7 +2289,9 @@ export default {
|
||||
data: {
|
||||
dialogids,
|
||||
userids,
|
||||
msg_id: this.operateItem.id
|
||||
msg_id: this.operateItem.id,
|
||||
show_source: this.forwardShowOriginal ? 1 : 0,
|
||||
leave_message: this.forwardLeaveMessage
|
||||
}
|
||||
}).then(({data, msg}) => {
|
||||
this.$store.dispatch("saveDialogMsg", data.msgs);
|
||||
@ -2382,14 +2424,21 @@ export default {
|
||||
value: $A.thumbRestore(event.target.currentSrc),
|
||||
})
|
||||
} else if (event.target.nodeName === 'A') {
|
||||
let href = event.target.href;
|
||||
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({
|
||||
type: 'link',
|
||||
icon: '',
|
||||
label: '复制链接',
|
||||
value: event.target.href,
|
||||
value: href,
|
||||
})
|
||||
}
|
||||
if (msgData.type === 'text') {
|
||||
@ -2454,6 +2503,8 @@ export default {
|
||||
|
||||
case "forward":
|
||||
this.forwardData = [];
|
||||
this.forwardLeaveMessage = '';
|
||||
this.forwardShowOriginal = true;
|
||||
this.$refs.forwardSelect.onSelection()
|
||||
break;
|
||||
|
||||
|
||||
@ -599,7 +599,7 @@ export default {
|
||||
},
|
||||
|
||||
documentKey() {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve,reject) => {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'file/content',
|
||||
data: {
|
||||
@ -608,8 +608,8 @@ export default {
|
||||
},
|
||||
}).then(({data}) => {
|
||||
resolve(`${data.id}-${$A.Time(data.update_at)}`)
|
||||
}).catch(() => {
|
||||
resolve(0)
|
||||
}).catch((res) => {
|
||||
reject(res)
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
@ -144,7 +144,7 @@ export default {
|
||||
},
|
||||
|
||||
documentKey() {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve,reject) => {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'file/content',
|
||||
data: {
|
||||
@ -153,8 +153,8 @@ export default {
|
||||
},
|
||||
}).then(({data}) => {
|
||||
resolve(`${data.id}-${$A.Time(data.update_at)}`)
|
||||
}).catch(() => {
|
||||
resolve(0)
|
||||
}).catch((res) => {
|
||||
reject(res)
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
<Icon class="menu-icon" type="ios-more" />
|
||||
<EDropdownMenu v-if="projectData.owner_userid === userId" slot="dropdown">
|
||||
<EDropdownItem command="setting">{{$L('项目设置')}}</EDropdownItem>
|
||||
<EDropdownItem command="permissions">{{$L('权限设置')}}</EDropdownItem>
|
||||
<EDropdownItem command="workflow">{{$L('工作流设置')}}</EDropdownItem>
|
||||
<EDropdownItem command="user" divided>{{$L('成员管理')}}</EDropdownItem>
|
||||
<EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem>
|
||||
@ -80,7 +81,7 @@
|
||||
<div v-if="completedCount > 0" class="project-checkbox">
|
||||
<Checkbox :value="projectData.cacheParameter.completedTask" @on-change="toggleCompleted">{{$L('显示已完成')}}</Checkbox>
|
||||
</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>
|
||||
<span :class="`project-flow ${flowInfo.status || ''}`">{{ flowTitle }}</span>
|
||||
</Cascader>
|
||||
@ -236,11 +237,11 @@
|
||||
<Scrollbar v-else-if="tabTypeActive === 'table'" class="project-table" enable-x>
|
||||
<div class="project-table-head">
|
||||
<Row class="task-row">
|
||||
<Col span="12"># {{$L('任务名称')}}</Col>
|
||||
<Col span="3">{{$L('列表')}}</Col>
|
||||
<Col span="12"><span class="head-title"># {{$L('任务名称')}}</span></Col>
|
||||
<Col span="3"><span class="head-title">{{$L('列表')}}</span></Col>
|
||||
<Col span="3">
|
||||
<div class="sort" @click="onSort('level')">
|
||||
{{$L('优先级')}}
|
||||
<span class="head-title">{{$L('优先级')}}</span>
|
||||
<div class="task-sort">
|
||||
<Icon :class="{on:sortField=='level' && sortType=='asc'}" type="md-arrow-dropup" />
|
||||
<Icon :class="{on:sortField=='level' && sortType=='desc'}" type="md-arrow-dropdown" />
|
||||
@ -250,7 +251,7 @@
|
||||
<Col span="3">{{$L('负责人')}}</Col>
|
||||
<Col span="3">
|
||||
<div class="sort" @click="onSort('end_at')">
|
||||
{{$L('到期时间')}}
|
||||
<span class="head-title">{{$L('到期时间')}}</span>
|
||||
<div class="task-sort">
|
||||
<Icon :class="{on:sortField=='end_at' && sortType=='asc'}" type="md-arrow-dropup" />
|
||||
<Icon :class="{on:sortField=='end_at' && sortType=='desc'}" type="md-arrow-dropdown" />
|
||||
@ -344,6 +345,14 @@
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!--项目权限-->
|
||||
<DrawerOverlay
|
||||
v-model="permissionShow"
|
||||
placement="right"
|
||||
:size="650">
|
||||
<ProjectPermission ref="permission" v-if="permissionShow" @close="()=>{ this.permissionShow = false }" :project-id="projectId"/>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--成员管理-->
|
||||
<Modal
|
||||
v-model="userShow"
|
||||
@ -474,6 +483,7 @@ import TaskArchived from "./TaskArchived";
|
||||
import ProjectLog from "./ProjectLog";
|
||||
import DrawerOverlay from "../../../components/DrawerOverlay";
|
||||
import ProjectWorkflow from "./ProjectWorkflow";
|
||||
import ProjectPermission from "./ProjectPermission";
|
||||
import TaskMenu from "./TaskMenu";
|
||||
import TaskDeleted from "./TaskDeleted";
|
||||
import ProjectGantt from "./ProjectGantt";
|
||||
@ -489,6 +499,7 @@ export default {
|
||||
MarkdownPreviewNostyle,
|
||||
TaskMenu,
|
||||
ProjectWorkflow,
|
||||
ProjectPermission,
|
||||
DrawerOverlay,
|
||||
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, TaskPriority, TaskDeleted, ProjectGantt},
|
||||
data() {
|
||||
@ -516,6 +527,10 @@ export default {
|
||||
settingData: {},
|
||||
settingLoad: 0,
|
||||
|
||||
permissionShow: false,
|
||||
permissionShowData: {},
|
||||
permissionShowLoad: 0,
|
||||
|
||||
userShow: false,
|
||||
userData: {},
|
||||
userLoad: 0,
|
||||
@ -846,7 +861,7 @@ export default {
|
||||
//
|
||||
const {project_user} = this.projectData;
|
||||
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 length = allTask.filter(({task_user, complete_at}) => {
|
||||
if (!this.projectData.cacheParameter.completedTask) {
|
||||
@ -859,12 +874,18 @@ export default {
|
||||
return {
|
||||
value: `user:${userInfo.userid}`,
|
||||
label: `${userInfo.nickname} (${length})`,
|
||||
class: `user-${index}`,
|
||||
userid: userInfo.userid || 0,
|
||||
length,
|
||||
}
|
||||
}).filter(({userid, length}) => userid > 0 && 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)
|
||||
}
|
||||
}
|
||||
@ -1253,6 +1274,16 @@ export default {
|
||||
});
|
||||
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":
|
||||
if (this.projectData.owner_userid !== this.userId) {
|
||||
return;
|
||||
|
||||
@ -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>
|
||||
@ -230,10 +230,33 @@
|
||||
</EDropdown>
|
||||
</div>
|
||||
<div class="item-content user">
|
||||
<span @click="showCisibleDropdown" v-if="taskDetail.visibility == 1" class="visibility-text">{{$L('项目人员可见')}}</span>
|
||||
<span @click="showCisibleDropdown" v-else-if="taskDetail.visibility == 2" class="visibility-text">{{$L('任务人员可见')}}</span>
|
||||
<UserSelect
|
||||
v-else
|
||||
<EDropdown v-if="taskDetail.visibility == 1 || taskDetail.visibility == 2" trigger="click" placement="bottom" @command="dropVisible">
|
||||
<span class="visibility-text">{{ taskDetail.visibility == 1 ? $L('项目人员可见') : $L('任务人员可见') }}</span>
|
||||
<EDropdownMenu slot="dropdown">
|
||||
<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"
|
||||
v-model="taskDetail.visibility_appointor"
|
||||
:avatar-size="28"
|
||||
@ -1096,6 +1119,10 @@ export default {
|
||||
}
|
||||
this.$set(this.taskDetail, 'content', contentSave)
|
||||
action = 'content';
|
||||
if (content == this.taskContent.replace(/original-width="[^"]*"/g, "").replace(/original-height="[^"]*"/g, "").replace(/\" \//g, "\" /")) {
|
||||
return;
|
||||
}
|
||||
this.$set(this.taskDetail, 'content', content)
|
||||
successCallback = () => {
|
||||
this.$store.dispatch("saveTaskContent", {
|
||||
task_id: this.taskId,
|
||||
|
||||
@ -102,9 +102,7 @@ export default {
|
||||
data: this.formData,
|
||||
}).then(({data}) => {
|
||||
this.show = false;
|
||||
this.$store.dispatch('downUrl', {
|
||||
url: data.url
|
||||
});
|
||||
$A.messageSuccess(data.msg);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
}).finally(_ => {
|
||||
|
||||
@ -28,6 +28,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
operationShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
updateBefore: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -48,6 +52,10 @@ export default {
|
||||
type: String,
|
||||
default: 'md-checkmark-circle'
|
||||
},
|
||||
projectId:{
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['loads', 'taskFlows']),
|
||||
@ -67,9 +75,11 @@ export default {
|
||||
task: this.task,
|
||||
loadStatus: this.loadStatus,
|
||||
colorShow: this.colorShow,
|
||||
operationShow: this.operationShow,
|
||||
updateBefore: this.updateBefore,
|
||||
disabled: this.disabled,
|
||||
size: this.size,
|
||||
projectId: this.projectId,
|
||||
onUpdate: data => {
|
||||
this.$emit("on-update", data)
|
||||
}
|
||||
|
||||
@ -1,14 +1,91 @@
|
||||
<template>
|
||||
<div class="task-add">
|
||||
<div class="task-move">
|
||||
|
||||
<Cascader
|
||||
v-model="cascader"
|
||||
:data="cascaderData"
|
||||
:clearable="false"
|
||||
:placeholder="$L('请选择项目')"
|
||||
:load-data="cascaderLoadData"
|
||||
@on-input-change="cascaderInputChange"
|
||||
@on-visible-change="cascaderShow=!cascaderShow"
|
||||
filterable/>
|
||||
v-model="cascader"
|
||||
:data="cascaderData"
|
||||
:clearable="false"
|
||||
:placeholder="$L('请选择项目')"
|
||||
:load-data="cascaderLoadData"
|
||||
@on-visible-change="cascaderShow=!cascaderShow"
|
||||
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="adaption">
|
||||
<Button type="default" @click="close">{{$L('取消')}}</Button>
|
||||
@ -20,9 +97,15 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import TaskMenu from "./TaskMenu";
|
||||
import UserSelect from "../../../components/UserSelect.vue";
|
||||
|
||||
export default {
|
||||
name: "TaskMove",
|
||||
components: {
|
||||
TaskMenu,
|
||||
UserSelect,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
@ -36,6 +119,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tasks: {},
|
||||
cascader: [],
|
||||
cascaderShow: false,
|
||||
cascaderData: [],
|
||||
@ -44,19 +128,21 @@ export default {
|
||||
cascaderAlready: [],
|
||||
|
||||
loadIng: 0,
|
||||
beforeClose: [],
|
||||
|
||||
flowItemId: 0,
|
||||
ownerUserids: [],
|
||||
assistUserids: [],
|
||||
updateData:{
|
||||
flow: {},
|
||||
owner_userids: [],
|
||||
assist_userids: []
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.initCascaderData();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.beforeClose.some(func => {
|
||||
typeof func === "function" && func()
|
||||
})
|
||||
this.beforeClose = [];
|
||||
this.initData();
|
||||
},
|
||||
|
||||
computed: {
|
||||
@ -64,20 +150,63 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
task: {
|
||||
handler: function (val) {
|
||||
this.cascader = [val.project_id, val.column_id];
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
cascader(val){
|
||||
this.tasks.flow_item_id = this.flowItemId;
|
||||
if(val[0] != this.task.project_id){
|
||||
this.updateData.flow.flow_item_id = 0;
|
||||
this.updateData.flow.flow_item_name = '';
|
||||
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: {
|
||||
/**
|
||||
* 初始化级联数据
|
||||
* 初始化数据
|
||||
*/
|
||||
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) => {
|
||||
if (a.top_at || b.top_at) {
|
||||
return $A.Date(b.top_at) - $A.Date(a.top_at);
|
||||
@ -120,32 +249,15 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
cascaderInputChange(key) {
|
||||
this.cascaderValue = key || "";
|
||||
//
|
||||
if (this.cascaderAlready[this.cascaderValue] === true) {
|
||||
async onConfirm() {
|
||||
if( this.task.project_id == this.cascader[0] && this.task.column_id == this.cascader[1]){
|
||||
$A.messageError("未变更移动项");
|
||||
return;
|
||||
}
|
||||
if( !this.updateData.flow.flow_item_id ){
|
||||
$A.messageError("请选择移动后状态");
|
||||
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.$store.dispatch("call", {
|
||||
url: "project/task/move",
|
||||
@ -153,14 +265,13 @@ export default {
|
||||
task_id: this.task.id,
|
||||
project_id: this.cascader[0],
|
||||
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.$store.dispatch("saveTask", {
|
||||
id: this.task.id,
|
||||
project_id: this.cascader[0],
|
||||
column_id: this.cascader[1],
|
||||
});
|
||||
this.$store.dispatch("saveTask", data);
|
||||
$A.messageSuccess(msg);
|
||||
this.close()
|
||||
}).catch(({msg}) => {
|
||||
@ -172,6 +283,16 @@ export default {
|
||||
close() {
|
||||
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>
|
||||
|
||||
@ -42,21 +42,23 @@
|
||||
</template>
|
||||
|
||||
<template v-if="task.parent_id === 0">
|
||||
<EDropdownItem :divided="turns.length > 0" command="archived">
|
||||
<div class="item">
|
||||
<Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="move">
|
||||
<div class="item">
|
||||
<i class="taskfont movefont"></i>{{$L('移动')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="remove">
|
||||
<div class="item">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<template v-if="operationShow">
|
||||
<EDropdownItem :divided="turns.length > 0" command="archived">
|
||||
<div class="item">
|
||||
<Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="move">
|
||||
<div class="item">
|
||||
<i class="taskfont movefont"></i>{{$L('移动')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="remove">
|
||||
<div class="item hover-del">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</template>
|
||||
<template v-if="colorShow">
|
||||
<EDropdownItem v-for="(c, k) in taskColorList" :key="'c_' + k" :divided="k==0" :command="c">
|
||||
<div class="item">
|
||||
@ -65,7 +67,7 @@
|
||||
</EDropdownItem>
|
||||
</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">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
@ -104,9 +106,11 @@ export default {
|
||||
task: {},
|
||||
loadStatus: false,
|
||||
colorShow: true,
|
||||
operationShow: true,
|
||||
updateBefore: false,
|
||||
disabled: false,
|
||||
size: 'small',
|
||||
projectId: 0,
|
||||
onUpdate: null,
|
||||
|
||||
element: null,
|
||||
@ -164,15 +168,17 @@ export default {
|
||||
this.task = data.task;
|
||||
this.loadStatus = typeof data.loadStatus === "undefined" ? false : data.loadStatus;
|
||||
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.disabled = typeof data.disabled === "undefined" ? false : data.disabled;
|
||||
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.$refs.icon.focus();
|
||||
this.updatePopper();
|
||||
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)
|
||||
} else {
|
||||
this.hide();
|
||||
@ -223,7 +229,9 @@ export default {
|
||||
}
|
||||
}
|
||||
this.updateTask({
|
||||
flow_item_id
|
||||
flow_item_id,
|
||||
flow_item_status: updateFlow.status,
|
||||
flow_item_name: updateFlow.name
|
||||
}).then(() => {
|
||||
if (isComplete) {
|
||||
completeTemp(true)
|
||||
@ -291,11 +299,19 @@ export default {
|
||||
return;
|
||||
}
|
||||
//
|
||||
Object.keys(updata).forEach(key => this.$set(this.task, key, updata[key]));
|
||||
//
|
||||
const updateData = Object.assign(updata, {
|
||||
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}) => {
|
||||
$A.messageSuccess(msg);
|
||||
resolve()
|
||||
|
||||
@ -58,65 +58,70 @@
|
||||
@touchstart.native="listTouch"
|
||||
@on-scroll="listScroll">
|
||||
<ul v-if="tabActive==='dialog'" ref="ul" class="dialog">
|
||||
<li
|
||||
v-if="dialogList.length > 0"
|
||||
v-for="(dialog, key) in dialogList"
|
||||
:ref="`dialog_${dialog.id}`"
|
||||
:key="key"
|
||||
:data-id="dialog.id"
|
||||
:class="dialogClass(dialog)"
|
||||
@click="openDialog({
|
||||
dialog_id: dialog.id,
|
||||
search_msg_id: dialog.search_msg_id
|
||||
})"
|
||||
v-longpress="handleLongpress"
|
||||
:style="{'background-color':dialog.color}">
|
||||
<template v-if="dialog.type=='group'">
|
||||
<EAvatar v-if="dialog.avatar" class="img-avatar" :src="dialog.avatar" :size="42"></EAvatar>
|
||||
<i v-else-if="dialog.group_type=='department'" class="taskfont icon-avatar department"></i>
|
||||
<i v-else-if="dialog.group_type=='project'" class="taskfont icon-avatar project"></i>
|
||||
<i v-else-if="dialog.group_type=='task'" class="taskfont icon-avatar task"></i>
|
||||
<i v-else-if="dialog.group_type=='okr'" class="taskfont icon-avatar task"></i>
|
||||
<Icon v-else class="icon-avatar" type="ios-people" />
|
||||
</template>
|
||||
<div v-else-if="dialog.dialog_user" class="user-avatar"><UserAvatar :userid="dialog.dialog_user.userid" :size="42"/></div>
|
||||
<Icon v-else class="icon-avatar" type="md-person" />
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-title">
|
||||
<div v-if="dialog.todo_num" class="todo">[{{$L('待办')}}{{formatTodoNum(dialog.todo_num)}}]</div>
|
||||
<div v-if="$A.getDialogMention(dialog) > 0" class="mention">[@{{$A.getDialogMention(dialog)}}]</div>
|
||||
<div v-if="dialog.bot" class="taskfont bot"></div>
|
||||
<template v-for="tag in $A.dialogTags(dialog)" v-if="tag.color != 'success'">
|
||||
<Tag :color="tag.color" :fade="false" @on-click="openDialog(dialog.id)">{{$L(tag.text)}}</Tag>
|
||||
</template>
|
||||
<span>{{dialog.name}}</span>
|
||||
<Icon v-if="dialog.type == 'user' && lastMsgReadDone(dialog.last_msg) && dialog.dialog_user.userid != userId" :type="lastMsgReadDone(dialog.last_msg)"/>
|
||||
<em v-if="dialog.last_at">{{$A.formatTime(dialog.last_at)}}</em>
|
||||
</div>
|
||||
<div class="dialog-text no-dark-content">
|
||||
<template v-if="dialog.extra_draft_has && dialog.id != dialogId">
|
||||
<div class="last-draft">[{{$L('草稿')}}]</div>
|
||||
<div class="last-text"><span>{{formatDraft(dialog.extra_draft_content)}}</span></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="dialog.type=='group' && dialog.last_msg && dialog.last_msg.userid">
|
||||
<div v-if="dialog.last_msg.userid == userId" class="last-self">{{$L('你')}}</div>
|
||||
<UserAvatar v-else :userid="dialog.last_msg.userid" :show-name="true" :show-icon="false"/>
|
||||
<template v-if="dialogList.length > 0">
|
||||
<li
|
||||
v-for="(dialog, key) in dialogList"
|
||||
:ref="`dialog_${dialog.id}`"
|
||||
:key="key"
|
||||
:data-id="dialog.id"
|
||||
:class="dialogClass(dialog)"
|
||||
@click="openDialog({
|
||||
dialog_id: dialog.id,
|
||||
dialog_msg_id: dialog.search_msg_id,
|
||||
search_msg_id: dialog.search_msg_id,
|
||||
})"
|
||||
v-longpress="handleLongpress"
|
||||
:style="{'background-color':dialog.color}">
|
||||
<template v-if="dialog.type=='group'">
|
||||
<EAvatar v-if="dialog.avatar" class="img-avatar" :src="dialog.avatar" :size="42"></EAvatar>
|
||||
<i v-else-if="dialog.group_type=='department'" class="taskfont icon-avatar department"></i>
|
||||
<i v-else-if="dialog.group_type=='project'" class="taskfont icon-avatar project"></i>
|
||||
<i v-else-if="dialog.group_type=='task'" class="taskfont icon-avatar task"></i>
|
||||
<i v-else-if="dialog.group_type=='okr'" class="taskfont icon-avatar task"></i>
|
||||
<Icon v-else class="icon-avatar" type="ios-people" />
|
||||
</template>
|
||||
<div v-else-if="dialog.dialog_user" class="user-avatar"><UserAvatar :userid="dialog.dialog_user.userid" :size="42"/></div>
|
||||
<Icon v-else class="icon-avatar" type="md-person" />
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-title">
|
||||
<div v-if="dialog.todo_num" class="todo">[{{$L('待办')}}{{formatTodoNum(dialog.todo_num)}}]</div>
|
||||
<div v-if="$A.getDialogMention(dialog) > 0" class="mention">[@{{$A.getDialogMention(dialog)}}]</div>
|
||||
<div v-if="dialog.bot" class="taskfont bot"></div>
|
||||
<template v-for="tag in $A.dialogTags(dialog)" v-if="tag.color != 'success'">
|
||||
<Tag :color="tag.color" :fade="false" @on-click="openDialog(dialog.id)">{{$L(tag.text)}}</Tag>
|
||||
</template>
|
||||
<div class="last-text">
|
||||
<em v-if="formatMsgEmojiDesc(dialog.last_msg)">{{formatMsgEmojiDesc(dialog.last_msg)}}</em>
|
||||
<span>{{$A.getMsgSimpleDesc(dialog.last_msg)}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="dialog.silence" class="taskfont last-silence"></div>
|
||||
<span>{{dialog.name}}</span>
|
||||
<Icon v-if="dialog.type == 'user' && lastMsgReadDone(dialog.last_msg) && dialog.dialog_user.userid != userId" :type="lastMsgReadDone(dialog.last_msg)"/>
|
||||
<em v-if="dialog.last_at">{{$A.formatTime(dialog.last_at)}}</em>
|
||||
</div>
|
||||
<div class="dialog-text no-dark-content">
|
||||
<template v-if="dialog.extra_draft_has && dialog.id != dialogId">
|
||||
<div class="last-draft">[{{$L('草稿')}}]</div>
|
||||
<div class="last-text"><span>{{formatDraft(dialog.extra_draft_content)}}</span></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="dialog.type=='group' && dialog.last_msg && dialog.last_msg.userid">
|
||||
<div v-if="dialog.last_msg.userid == userId" class="last-self">{{$L('你')}}</div>
|
||||
<UserAvatar v-else :userid="dialog.last_msg.userid" :show-name="true" :show-icon="false"/>
|
||||
</template>
|
||||
<div class="last-text">
|
||||
<em v-if="formatMsgEmojiDesc(dialog.last_msg)">{{formatMsgEmojiDesc(dialog.last_msg)}}</em>
|
||||
<span>{{$A.getMsgSimpleDesc(dialog.last_msg)}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="dialog.silence" class="taskfont last-silence"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge class="dialog-num" :type="dialog.silence ? 'normal' : 'error'" :overflow-count="999" :count="$A.getDialogUnread(dialog, true)"/>
|
||||
<div class="dialog-line"></div>
|
||||
</li>
|
||||
<li v-else-if="dialogSearchLoad === 0" class="nothing">
|
||||
<Badge class="dialog-num" :type="dialog.silence ? 'normal' : 'error'" :overflow-count="999" :count="$A.getDialogUnread(dialog, true)"/>
|
||||
<div class="dialog-line"></div>
|
||||
</li>
|
||||
</template>
|
||||
<li v-else-if="dialogSearchLoad === 0 && dialogMarkLoad === 0" class="nothing">
|
||||
{{$L(dialogSearchKey ? `没有任何与"${dialogSearchKey}"相关的会话` : `没有任何会话`)}}
|
||||
</li>
|
||||
<li v-else class="nothing">
|
||||
<Loading/>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-else class="contacts">
|
||||
<template v-if="contactsFilter.length === 0">
|
||||
@ -235,6 +240,8 @@ export default {
|
||||
{type: 'user', name: '单聊'},
|
||||
{type: 'group', name: '群聊'},
|
||||
{type: 'bot', name: '机器人'},
|
||||
{type: 'mark', name: '标注'},
|
||||
{type: '@', name: '@我的'},
|
||||
],
|
||||
dialogHistory: MessengerObject.menuHistory,
|
||||
|
||||
@ -251,6 +258,8 @@ export default {
|
||||
operateVisible: false,
|
||||
|
||||
clickAgainSubscribe: null,
|
||||
|
||||
dialogMarkLoad: 0,
|
||||
}
|
||||
},
|
||||
|
||||
@ -291,7 +300,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['cacheDialogs', 'loadDialogs', 'dialogId', 'messengerSearchKey', 'appNotificationPermission', 'taskColorList']),
|
||||
...mapState(['cacheDialogs', 'loadDialogs', 'dialogId', 'dialogMsgId', 'dialogMsgs', 'messengerSearchKey', 'appNotificationPermission', 'taskColorList']),
|
||||
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
@ -337,6 +346,18 @@ export default {
|
||||
if (dialogActive == '' && dialogSearchKey == '') {
|
||||
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 => {
|
||||
if (!this.filterDialog(dialog)) {
|
||||
return false;
|
||||
@ -381,6 +402,11 @@ export default {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '@':
|
||||
if (!$A.getDialogMention(dialog)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -547,6 +573,13 @@ export default {
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
|
||||
dialogActive(){
|
||||
this.dialogSearchList = [];
|
||||
if(this.dialogActive == 'mark' && !this.dialogSearchKey){
|
||||
this.searchTagDialog()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -623,7 +656,7 @@ export default {
|
||||
}
|
||||
return {
|
||||
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,
|
||||
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) {
|
||||
this.contactsLoad++;
|
||||
const key = this.contactsKey
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<FormItem label="License" prop="license">
|
||||
<Input v-model="formData.license" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
|
||||
</FormItem>
|
||||
<FormItem :label="$L('详细信息')">
|
||||
<FormItem>
|
||||
<div class="license-box">
|
||||
<ul v-if="formData.info.sn">
|
||||
<li>
|
||||
@ -51,7 +51,6 @@
|
||||
<Icon class="information" type="ios-information-circle-outline" />
|
||||
</ETooltip>
|
||||
</li>
|
||||
<li v-for="tip in formData.error" class="warning">{{tip}}</li>
|
||||
</ul>
|
||||
<ul v-else>
|
||||
<li>
|
||||
@ -60,6 +59,21 @@
|
||||
</ul>
|
||||
</div>
|
||||
</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>
|
||||
<div class="setting-footer">
|
||||
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
|
||||
|
||||
@ -168,7 +168,7 @@ export default {
|
||||
},
|
||||
|
||||
documentKey() {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve,reject) => {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/msg/detail',
|
||||
data: {
|
||||
@ -177,8 +177,8 @@ export default {
|
||||
},
|
||||
}).then(({data}) => {
|
||||
resolve(`${data.id}-${$A.Time(data.update_at)}`)
|
||||
}).catch(() => {
|
||||
resolve(0)
|
||||
}).catch((res) => {
|
||||
reject(res)
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -155,7 +155,7 @@ export default {
|
||||
});
|
||||
},
|
||||
documentKey() {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve,reject) => {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/task/filedetail',
|
||||
data: {
|
||||
@ -164,8 +164,8 @@ export default {
|
||||
},
|
||||
}).then(({data}) => {
|
||||
resolve(`${data.id}-${$A.Time(data.update_at)}`)
|
||||
}).catch(() => {
|
||||
resolve(0)
|
||||
}).catch((res) => {
|
||||
reject(res)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
11
resources/assets/js/store/actions.js
vendored
@ -1920,6 +1920,8 @@ export default {
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
state.taskOperation = {};
|
||||
}
|
||||
},
|
||||
|
||||
@ -2126,14 +2128,16 @@ export default {
|
||||
* @param state
|
||||
* @param dispatch
|
||||
* @param task_id
|
||||
* @param project_id
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
getTaskFlow({state, dispatch}, task_id) {
|
||||
getTaskFlow({state, dispatch}, {task_id, project_id}) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
dispatch("call", {
|
||||
url: 'project/task/flow',
|
||||
data: {
|
||||
task_id: task_id
|
||||
task_id: task_id,
|
||||
project_id: project_id || 0
|
||||
},
|
||||
}).then(result => {
|
||||
let task = state.cacheTasks.find(({id}) => id == task_id)
|
||||
@ -2500,13 +2504,16 @@ export default {
|
||||
openDialog({state, dispatch}, dialog_id) {
|
||||
return new Promise(resolve => {
|
||||
let search_msg_id;
|
||||
let dialog_msg_id;
|
||||
if ($A.isJson(dialog_id)) {
|
||||
search_msg_id = dialog_id.search_msg_id;
|
||||
dialog_msg_id = dialog_id.dialog_msg_id;
|
||||
dialog_id = dialog_id.dialog_id;
|
||||
}
|
||||
//
|
||||
requestAnimationFrame(_ => {
|
||||
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;
|
||||
resolve()
|
||||
})
|
||||
|
||||
3
resources/assets/js/store/state.js
vendored
@ -105,6 +105,7 @@ export default {
|
||||
|
||||
// 会话聊天
|
||||
dialogId: 0,
|
||||
dialogMsgId: 0,
|
||||
dialogSearchMsgId: 0,
|
||||
dialogIns: [],
|
||||
dialogMsgs: [],
|
||||
@ -113,6 +114,8 @@ export default {
|
||||
dialogDraftTimer: {},
|
||||
dialogMsgTransfer: {time: 0},
|
||||
dialogSseList: [],
|
||||
dialogDroupWordChain: {},
|
||||
dialogGroupVote: {},
|
||||
|
||||
// 搜索关键词(主要用于移动端判断滑动返回)
|
||||
messengerSearchKey: {dialog: '', contacts: ''},
|
||||
|
||||
@ -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 {
|
||||
@ -416,12 +431,19 @@
|
||||
border-radius: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.twice-affirm-body-extend{
|
||||
margin: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.ivu-modal-footer {
|
||||
border-top: 1px solid #f2f2f2 !important;
|
||||
padding: 12px 0 !important;
|
||||
margin: 0 24px !important;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
&.ivu-modal-fullscreen {
|
||||
@ -462,4 +484,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.twice-affirm-modal{
|
||||
.ivu-modal {
|
||||
max-width: 90%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
@import "project-management";
|
||||
@import "project-panel";
|
||||
@import "project-workflow";
|
||||
@import "project-permission";
|
||||
@import "task-add";
|
||||
@import "task-add-simple";
|
||||
@import "task-archived";
|
||||
@ -21,6 +22,9 @@
|
||||
@import "task-menu";
|
||||
@import "task-operation";
|
||||
@import "task-priority";
|
||||
@import "task-move";
|
||||
@import "team-management";
|
||||
@import "update-log";
|
||||
@import "task-exist-tips";
|
||||
@import "calendar";
|
||||
@import "dialog-droup-word-chain";
|
||||
|
||||
162
resources/assets/sass/pages/components/calendar.scss
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
resources/assets/sass/pages/components/dialog-droup-word-chain.scss
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -447,7 +447,8 @@
|
||||
|
||||
.dialog-tag,
|
||||
.dialog-todo,
|
||||
.dialog-notice {
|
||||
.dialog-notice,
|
||||
.dialog-new {
|
||||
font-size: 12px;
|
||||
max-width: 80%;
|
||||
margin: 0 auto;
|
||||
@ -963,6 +964,120 @@
|
||||
text-decoration: underline;
|
||||
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 {
|
||||
@ -1237,6 +1352,20 @@
|
||||
.content-unknown {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.content-word-chain{
|
||||
ul{
|
||||
li.participate, li .expand{
|
||||
color: #23241f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-word-vote{
|
||||
.vote-msg-head{
|
||||
color: #23241f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-emoji {
|
||||
@ -1301,6 +1430,9 @@
|
||||
height: 14px;
|
||||
font-size: 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 {
|
||||
width: 360px;
|
||||
max-width: 72%;
|
||||
|
||||
@ -659,6 +659,9 @@
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.head-title{
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-sort {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
|
||||
77
resources/assets/sass/pages/components/project-permission.scss
vendored
Normal 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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
resources/assets/sass/pages/components/task-move.scss
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
resources/assets/sass/pages/page-calendar.scss
vendored
@ -5,7 +5,7 @@
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 32px 32px 16px;
|
||||
margin: 32px 20px 16px;
|
||||
border-bottom: 1px solid #F4F4F5;
|
||||
.calendar-titbox {
|
||||
flex: 1;
|
||||
@ -50,137 +50,6 @@
|
||||
flex-direction: column;
|
||||
padding: 0 48px 6px;
|
||||
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 {
|
||||
position: absolute;
|
||||
|
||||
@ -240,6 +240,7 @@
|
||||
flex: 1;
|
||||
padding: 24px 40px;
|
||||
overflow: auto;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ivu-tabs {
|
||||
flex: 1;
|
||||
|
||||
21
resources/assets/statics/public/images/application/vote.svg
Normal 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 |
@ -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 |
|
After Width: | Height: | Size: 31 KiB |
1
resources/assets/statics/public/images/empty/empty.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |