Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	app/Models/Project.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/js/app.js
#	public/js/build/146.js
#	public/js/build/188.js
#	public/js/build/188.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/278.js
#	public/js/build/278.js.LICENSE.txt
#	public/js/build/30.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/525.js
#	public/js/build/644.js.LICENSE.txt
#	public/js/build/660.js
#	public/js/build/766.js
#	public/js/build/831.js
#	public/js/build/893.js
#	public/js/build/919.js
#	public/js/build/919.js.LICENSE.txt
#	public/js/build/934.js
#	public/js/build/934.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/DialogList.vue
#	resources/assets/js/pages/manage/components/DialogWrapper.vue
#	resources/assets/js/pages/manage/components/TaskAdd.vue
#	resources/assets/sass/components/report.scss
This commit is contained in:
韦荣超 2022-02-07 10:50:38 +08:00
commit 3d6df3cc09
67 changed files with 1630 additions and 849 deletions

View File

@ -8,9 +8,7 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Models\WebSocketDialogUser;
use App\Module\Base;
use Carbon\Carbon;
use Request;
use Response;
@ -162,16 +160,28 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/sendtext 05. 未读消息
* @api {get} api/dialog/msg/unread 05. 获取未读消息数量
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__sendtext
* @apiName msg__unread
*
* @apiParam {Number} [dialog_id] 对话ID留空获取总未读消息数量
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__unread()
{
$unread = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null)->count();
$dialog_id = intval(Request::input('dialog_id'));
//
$builder = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null);
if ($dialog_id > 0) {
$builder->whereDialogId($dialog_id);
}
$unread = $builder->count();
return Base::retSuccess('success', [
'unread' => $unread,
]);
@ -331,7 +341,61 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/download 09. 文件下载
* @api {get} api/dialog/msg/detail 09. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__detail
*
* @apiParam {Number} msg_id 消息ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__detail()
{
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
//
$dialogMsg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($dialogMsg)) {
return Base::retError("文件不存在");
}
$data = $dialogMsg->toArray();
//
if ($data['type'] == 'file') {
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
$filePath = public_path($msg['path']);
if (in_array($msg['ext'], $codeExt) && $msg['size'] < 2 * 1024 * 1024) {
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($msg['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($msg['ext'], $localExt)) {
$url = Base::fillUrl($msg['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $msg['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
}
//
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/dialog/msg/download 10. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -363,9 +427,9 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/withdraw 聊天消息撤回
* @api {get} api/dialog/msg/withdraw 11. 聊天消息撤回
*
* @apiDescription 需要token身份
* @apiDescription 消息撤回限制24小时内需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__withdraw
@ -375,43 +439,16 @@ class DialogController extends AbstractController
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*
* @return array
*/
public function msg__withdraw(): array
public function msg__withdraw()
{
$user = User::auth();
$msg_id = intval(Request::input("msg_id"));
$msg = WebSocketDialogMsg::whereId($msg_id)->whereUserid($user->userid)->first();
if (empty($msg)) {
return Base::retError("此消息不可撤回");
return Base::retError("消息不存在或已被删除");
}
$send_dt = Carbon::parse($msg->created_at)->addMinutes(5);
if ($send_dt->lt(Carbon::now()))
return Base::retError("已超过5分钟此消息不能撤回");
// 删除文件、图片
if ($msg->type == WebSocketDialogMsg::MSG_TYPE_FILE) {
if (is_array($msg->msg)) {
// 删除本体
if (!empty($msg->msg["file"]))
@unlink($msg->msg["file"]);
// 删除缩略图
if (!empty($msg->msg["thumb"]))
@unlink($msg->msg["thumb"]);
}
}
// 直接删除消息
$msg->delete();
/* 原始需求:消息直接删除,无需提示 */
// 发送撤回指令
// WebSocketDialogMsg::sendMsg($msg->dialog_id, 'withdraw', [
// "msg_id" => $msg->id, // 被撤回的消息Id
// ], $user->userid);
$msg->deleteMsg();
return Base::retSuccess("success");
}
}

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
@ -10,12 +9,14 @@ use App\Models\FileContent;
use App\Models\FileLink;
use App\Models\FileUser;
use App\Models\User;
use App\Models\WebSocketDialogMsg;
use App\Module\Base;
use App\Module\Ihttp;
use App\Tasks\BatchRemoveFileTask;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Support\Facades\DB;
use Request;
use Response;
/**
* @apiDefine file
@ -447,11 +448,11 @@ class FileController extends AbstractController
* @apiName content
*
* @apiParam {Number|String} id
* - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
* - Number: 文件ID需要登录
* - String: 链接码(不需要登录,用于预览)
* @apiParam {String} down 直接下载
* - no: 浏览(默认)
* - yes: 下载
* - yes: 下载office文件直接下载
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@ -689,6 +690,8 @@ class FileController extends AbstractController
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx' => "code",
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
'xmind' => "xmind",
'rp' => "axure",
default => "",
};
$file = File::createInstance([

View File

@ -1084,7 +1084,61 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedown 23. 下载任务文件
* @api {get} api/project/task/filedetail 23. 获取任务文件详情
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__filedetail
*
* @apiParam {Number} file_id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__filedetail()
{
User::auth();
//
$file_id = intval(Request::input('file_id'));
//
$file = ProjectTaskFile::find($file_id);
if (empty($file)) {
return Base::retError("文件不存在");
}
$data = $file->toArray();
$data['path'] = $file->getRawOriginal('path');
//
ProjectTask::userTask($file->task_id, true, true);
//
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$filePath = public_path($data['path']);
if (in_array($data['ext'], $codeExt) && $data['size'] < 2 * 1024 * 1024) {
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($data['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($data['ext'], $localExt)) {
$url = Base::fillUrl($data['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $data['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/project/task/filedown 24. 下载任务文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1118,7 +1172,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/add 24. 添加任务
* @api {post} api/project/task/add 25. 添加任务
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1189,7 +1243,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/addsub 25. 添加子任务
* @api {get} api/project/task/addsub 26. 添加子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1229,7 +1283,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/update 26. 修改任务、子任务
* @api {post} api/project/task/update 27. 修改任务、子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1274,72 +1328,6 @@ class ProjectController extends AbstractController
return Base::retSuccess('修改成功', $data);
}
/**
* @api {post} api/project/task/upload 27. 上传文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__upload
*
* @apiParam {Number} task_id 任务ID
* @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片二选一
* @apiParam {File} [files] post-文件对象(二选一)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__upload()
{
$user = User::auth();
//
$task_id = Base::getPostInt('task_id');
//
$task = ProjectTask::userTask($task_id, true, true);
//
$path = "uploads/task/" . $task->id . "/";
$image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename');
if ($image64) {
$data = Base::image64save([
"image64" => $image64,
"path" => $path,
"fileName" => $fileName,
]);
} else {
$data = Base::upload([
"file" => Request::file('files'),
"type" => 'file',
"path" => $path,
"fileName" => $fileName,
]);
}
//
if (Base::isError($data)) {
return Base::retError($data['msg']);
} else {
$fileData = $data['data'];
$file = ProjectTaskFile::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'name' => $fileData['name'],
'size' => $fileData['size'] * 1024,
'ext' => $fileData['ext'],
'path' => $fileData['path'],
'thumb' => Base::unFillUrl($fileData['thumb']),
'userid' => $user->userid,
]);
$file->save();
//
$file = ProjectTaskFile::find($file->id);
$task->addLog("上传文件:" . $file->name);
$task->pushMsg('upload', $file);
return Base::retSuccess("上传成功", $file);
}
}
/**
* @api {get} api/project/task/dialog 28. 创建/获取聊天室
*
@ -1599,7 +1587,7 @@ class ProjectController extends AbstractController
/**
* @api {get} api/project/flow/list 33. 工作流列表
*
* @apiDescription 需要token身份(限:项目负责人)
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName flow__list
@ -1615,13 +1603,8 @@ class ProjectController extends AbstractController
User::auth();
//
$project_id = intval(Request::input('project_id'));
$is_filter = intval(Request::input('is_filter', 0));
//
if ($is_filter > 0) {
$project = Project::userProject($project_id, null);
} else {
$project = Project::userProject($project_id, true, true);
}
$project = Project::userProject($project_id, true);
//
$list = ProjectFlow::with(['ProjectFlowItem'])->whereProjectId($project->id)->get();
return Base::retSuccess('success', $list);

View File

@ -30,9 +30,6 @@ class VerifyCsrfToken extends Middleware
// 修改任务
'api/project/task/update/',
// 上传任务问题
'api/project/task/upload/',
// 聊天发文件
'api/dialog/msg/sendfile/',

View File

@ -354,7 +354,7 @@ class Project extends AbstractModel
* 获取项目信息(用于判断会员是否存在项目内)
* @param int $project_id
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param null $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
* @param null|bool $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
* @return self
*/
public static function userProject($project_id, $archived = true, $mustOwner = null)

View File

@ -8,6 +8,7 @@ use App\Tasks\PushTask;
use App\Tasks\WebSocketDialogMsgTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\WebSocketDialogMsg
@ -21,11 +22,15 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
* @property int|null $send 发送数量
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int|mixed $percentage
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
@ -34,10 +39,14 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
* @mixin \Eloquent
*/
class WebSocketDialogMsg extends AbstractModel
{
use SoftDeletes;
protected $appends = [
'percentage',
];
@ -46,8 +55,13 @@ class WebSocketDialogMsg extends AbstractModel
'updated_at',
];
const MSG_TYPE_TEXT = "text";
const MSG_TYPE_FILE = "file";
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
}
/**
* 阅读占比
@ -127,6 +141,47 @@ class WebSocketDialogMsg extends AbstractModel
return true;
}
/**
* 删除消息
* @return void
*/
public function deleteMsg()
{
$send_dt = Carbon::parse($this->created_at)->addDay();
if ($send_dt->lt(Carbon::now())) {
throw new ApiException('已超过24小时此消息不能撤回');
}
AbstractModel::transaction(function() {
$deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
$this->delete();
//
$last_msg = null;
if ($this->webSocketDialog) {
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
$this->webSocketDialog->last_at = $last_msg->created_at;
$this->webSocketDialog->save();
}
//
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
PushTask::push([
'userid' => $userids,
'msg' => [
'type' => 'dialog',
'mode' => 'delete',
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $last_msg,
'update_read' => $deleteRead ? 1 : 0
],
]
]);
}
});
}
/**
* 发送消息
* @param int $dialog_id 会话ID 聊天室ID

View File

@ -2263,7 +2263,8 @@ class Base
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
'xmind', 'rp',
'xmind',
'rp',
];
break;
default:

View File

@ -204,7 +204,19 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function deleteUser($fd)
{
WebSocket::whereFd($fd)->delete();
$array = [];
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
/** @var WebSocket $item */
foreach ($list as $item) {
$item->delete();
if ($item->path && str_starts_with($item->path, "file/content/")) {
$array[$item->path] = $item->path;
}
}
});
foreach ($array as $path) {
$this->pushPath($path);
}
}
/**

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogMsgsAddDeletes 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', 'deleted_at')) {
$table->softDeletes();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
}

View File

@ -96,7 +96,7 @@ services:
fileview:
container_name: "dootask-fileview-${APP_ID}"
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC2"
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC3"
environment:
TZ: "Asia/Shanghai"
KK_CONTEXT_PATH: "/fileview"

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.7.53",
"version": "0.7.72",
"description": "DooTask is task management system.",
"main": "main.js",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.7.53",
"version": "0.7.72",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",
@ -64,7 +64,7 @@
"stylus-loader": "^6.2.0",
"tinymce": "^5.10.2",
"tui-calendar-hi": "^1.15.1-5",
"view-design-hi": "^4.7.0-8",
"view-design-hi": "^4.7.0-12",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.3",
"vue-emoji-picker": "^1.0.3",

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
define("ace/theme/dracula-dark",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-dracula-dark",t.cssText=".ace-dracula-dark .ace_gutter {background: #232323;color: rgb(144,145,148)}.ace-dracula-dark .ace_print-margin {width: 1px;background: #44475a}.ace-dracula-dark {background-color: #232323;color: #f8f8f2}.ace-dracula-dark .ace_cursor {color: #f8f8f0}.ace-dracula-dark .ace_marker-layer .ace_selection {background: #44475a}.ace-dracula-dark.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #232323;border-radius: 2px}.ace-dracula-dark .ace_marker-layer .ace_step {background: rgb(198, 219, 174)}.ace-dracula-dark .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #a29709}.ace-dracula-dark .ace_marker-layer .ace_active-line {background: #44475a}.ace-dracula-dark .ace_gutter-active-line {background-color: #44475a}.ace-dracula-dark .ace_marker-layer .ace_selected-word {box-shadow: 0px 0px 0px 1px #a29709;border-radius: 3px;}.ace-dracula-dark .ace_fold {background-color: #50fa7b;border-color: #f8f8f2}.ace-dracula-dark .ace_keyword {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_language {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_numeric {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character.ace_escape {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_other {color: #bd93f9}.ace-dracula-dark .ace_support.ace_function {color: #8be9fd}.ace-dracula-dark .ace_support.ace_constant {color: #6be5fd}.ace-dracula-dark .ace_support.ace_class {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_support.ace_type {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_storage {color: #ff79c6}.ace-dracula-dark .ace_storage.ace_type {font-style: italic;color: #8be9fd}.ace-dracula-dark .ace_invalid {color: #F8F8F0;background-color: #ff79c6}.ace-dracula-dark .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #bd93f9}.ace-dracula-dark .ace_string {color: #f1fa8c}.ace-dracula-dark .ace_comment {color: #6272a4}.ace-dracula-dark .ace_variable {color: #50fa7b}.ace-dracula-dark .ace_variable.ace_parameter {font-style: italic;color: #ffb86c}.ace-dracula-dark .ace_entity.ace_other.ace_attribute-name {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_function {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_tag {color: #ff79c6}.ace-dracula-dark .ace_invisible {color: #626680;}.ace-dracula-dark .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}",t.$selectionColorConflict=!0;var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() {
define("ace/theme/dracula-dark",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-dracula-dark",t.cssText=".ace-dracula-dark .ace_gutter {background: #000000;color: rgb(144,145,148)}.ace-dracula-dark .ace_print-margin {width: 1px;background: #44475a}.ace-dracula-dark {background-color: #000000;color: #f8f8f2}.ace-dracula-dark .ace_cursor {color: #f8f8f0}.ace-dracula-dark .ace_marker-layer .ace_selection {background: #44475a}.ace-dracula-dark.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #000000;border-radius: 2px}.ace-dracula-dark .ace_marker-layer .ace_step {background: rgb(198, 219, 174)}.ace-dracula-dark .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #a29709}.ace-dracula-dark .ace_marker-layer .ace_active-line {background: #44475a}.ace-dracula-dark .ace_gutter-active-line {background-color: #44475a}.ace-dracula-dark .ace_marker-layer .ace_selected-word {box-shadow: 0px 0px 0px 1px #a29709;border-radius: 3px;}.ace-dracula-dark .ace_fold {background-color: #50fa7b;border-color: #f8f8f2}.ace-dracula-dark .ace_keyword {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_language {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_numeric {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character.ace_escape {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_other {color: #bd93f9}.ace-dracula-dark .ace_support.ace_function {color: #8be9fd}.ace-dracula-dark .ace_support.ace_constant {color: #6be5fd}.ace-dracula-dark .ace_support.ace_class {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_support.ace_type {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_storage {color: #ff79c6}.ace-dracula-dark .ace_storage.ace_type {font-style: italic;color: #8be9fd}.ace-dracula-dark .ace_invalid {color: #F8F8F0;background-color: #ff79c6}.ace-dracula-dark .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #bd93f9}.ace-dracula-dark .ace_string {color: #f1fa8c}.ace-dracula-dark .ace_comment {color: #6272a4}.ace-dracula-dark .ace_variable {color: #50fa7b}.ace-dracula-dark .ace_variable.ace_parameter {font-style: italic;color: #ffb86c}.ace-dracula-dark .ace_entity.ace_other.ace_attribute-name {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_function {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_tag {color: #ff79c6}.ace-dracula-dark .ace_invisible {color: #626680;}.ace-dracula-dark .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}",t.$selectionColorConflict=!0;var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() {
window.require(["ace/theme/dracula-dark"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

1
public/js/build/159.js vendored Normal file
View File

@ -0,0 +1 @@
"use strict";(self.webpackChunkDooTask=self.webpackChunkDooTask||[]).push([[159],{26071:(e,t,i)=>{i.d(t,{Z:()=>r});var n=i(1519),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,".component-only-office[data-v-c9bf06c2]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.component-only-office .placeholder[data-v-c9bf06c2]{flex:1;height:100%;width:100%}.component-only-office .office-loading[data-v-c9bf06c2]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:2}",""]);const r=o},36159:(e,t,i)=>{i.r(t),i.d(t,{default:()=>u});var n=i(20629);function o(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function r(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?o(Object(i),!0).forEach((function(t){s(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function s(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const l={name:"OnlyOffice",props:{id:{type:String,default:function(){return"office_"+Math.round(1e4*Math.random())}},code:{type:String,default:""},value:{type:[Object,Array],default:function(){return{}}},readOnly:{type:Boolean,default:!1}},data:function(){return{loadIng:0,docEditor:null}},mounted:function(){},beforeDestroy:function(){null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null)},computed:r(r({},(0,n.rn)(["userToken","userInfo","themeIsDark"])),{},{fileType:function(){return this.getType(this.value.type)},fileName:function(){return this.value.name}}),watch:{"value.id":{handler:function(e){var t=this;e&&(this.loadIng++,$A.loadScript($A.apiUrl("../office/web-apps/apps/api/documents/api.js"),(function(e){t.loadIng--,null!==e?$A.modalAlert("组件加载失败!"):t.loadFile()})))},immediate:!0}},methods:{getType:function(e){switch(e){case"word":return"docx";case"excel":return"xlsx";case"ppt":return"pptx"}return e},loadFile:function(){var e=this;null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null);var t="zh";switch(this.getLanguage()){case"CN":case"TC":t="zh";break;default:t="en"}var i=this.code||this.value.id,n=$A.strExists(this.fileName,".")?this.fileName:this.fileName+"."+this.fileType,o={document:{fileType:this.fileType,key:this.fileType+"-"+i,title:n,url:"http://nginx/api/file/content/?id="+i+"&token="+this.userToken},editorConfig:{mode:"edit",lang:t,user:{id:this.userInfo.userid,name:this.userInfo.nickname},customization:{uiTheme:this.themeIsDark?"theme-dark":"theme-classic-light"},callbackUrl:"http://nginx/api/file/content/office?id="+i+"&token="+this.userToken}};if(/\/hideenOfficeTitle\//.test(window.navigator.userAgent)&&(o.document.title=" "),$A.leftExists(i,"msgFile_")?o.document.url="http://nginx/api/dialog/msg/download/?msg_id="+$A.leftDelete(i,"msgFile_")+"&token="+this.userToken:$A.leftExists(i,"taskFile_")&&(o.document.url="http://nginx/api/project/task/filedown/?file_id="+$A.leftDelete(i,"taskFile_")+"&token="+this.userToken),this.readOnly&&(o.editorConfig.mode="view",o.editorConfig.callbackUrl=null,!o.editorConfig.user.id)){var r=$A.getStorageInt("viewer");r||(r=$A.randNum(1e3,99999),$A.setStorage("viewer",r)),o.editorConfig.user.id="viewer_"+r,o.editorConfig.user.name="Viewer_"+r}this.$nextTick((function(){e.docEditor=new DocsAPI.DocEditor(e.id,o)}))}}};var a=i(93379),c=i.n(a),d=i(26071),f={insert:"head",singleton:!1};c()(d.Z,f);d.Z.locals;const u=(0,i(51900).Z)(l,(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{staticClass:"component-only-office"},[i("div",{staticClass:"placeholder",attrs:{id:this.id}}),e._v(" "),e.loadIng>0?i("div",{staticClass:"office-loading"},[i("Loading")],1):e._e()])}),[],!1,null,"c9bf06c2",null).exports}}]);

File diff suppressed because one or more lines are too long

2
public/js/build/196.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/build/26.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/build/264.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/build/405.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/build/428.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/build/43.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
"use strict";(self.webpackChunkDooTask=self.webpackChunkDooTask||[]).push([[766],{40345:(e,t,i)=>{i.d(t,{Z:()=>r});var n=i(1519),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,".component-only-office[data-v-ba382ddc]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.component-only-office .placeholder[data-v-ba382ddc]{flex:1;height:100%;width:100%}.component-only-office .office-loading[data-v-ba382ddc]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:2}",""]);const r=o},27766:(e,t,i)=>{i.r(t),i.d(t,{default:()=>f});var n=i(20629);function o(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function r(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?o(Object(i),!0).forEach((function(t){a(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const c={name:"OnlyOffice",props:{id:{type:String,default:function(){return"office_"+Math.round(1e4*Math.random())}},code:{type:String,default:""},value:{type:[Object,Array],default:function(){return{}}},readOnly:{type:Boolean,default:!1}},data:function(){return{loadIng:0,docEditor:null}},mounted:function(){},beforeDestroy:function(){null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null)},computed:r(r({},(0,n.rn)(["userToken","userInfo","themeIsDark"])),{},{fileType:function(){return this.getType(this.value.type)},fileName:function(){return this.value.name}}),watch:{"value.id":{handler:function(e){var t=this;e&&(this.loadIng++,$A.loadScript($A.apiUrl("../office/web-apps/apps/api/documents/api.js"),(function(e){t.loadIng--,null!==e?$A.modalAlert("组件加载失败!"):t.loadFile()})))},immediate:!0}},methods:{getType:function(e){switch(e){case"word":return"docx";case"excel":return"xlsx";case"ppt":return"pptx"}return""},loadFile:function(){var e=this;null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null);var t="zh";switch(this.getLanguage()){case"CN":case"TC":t="zh";break;default:t="en"}var i=this.code||this.value.id,n={document:{fileType:this.fileType,key:this.fileType+"-"+i,title:this.fileName+"."+this.fileType,url:"http://nginx/api/file/content/?id="+i+"&token="+this.userToken},editorConfig:{mode:"edit",lang:t,user:{id:this.userInfo.userid,name:this.userInfo.nickname},customization:{uiTheme:this.themeIsDark?"theme-dark":"theme-classic-light"},callbackUrl:"http://nginx/api/file/content/office?id="+i+"&token="+this.userToken}};if(this.readOnly&&(n.editorConfig.mode="view",n.editorConfig.callbackUrl=null,!n.editorConfig.user.id)){var o=$A.getStorageInt("viewer");o||(o=$A.randNum(1e3,99999),$A.setStorage("viewer",o)),n.editorConfig.user.id="viewer_"+o,n.editorConfig.user.name="Viewer_"+o}this.$nextTick((function(){e.docEditor=new DocsAPI.DocEditor(e.id,n)}))}}};var l=i(93379),s=i.n(l),d=i(40345),u={insert:"head",singleton:!1};s()(d.Z,u);d.Z.locals;const f=(0,i(51900).Z)(c,(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{staticClass:"component-only-office"},[i("div",{staticClass:"placeholder",attrs:{id:this.id}}),e._v(" "),e.loadIng>0?i("div",{staticClass:"office-loading"},[i("Loading")],1):e._e()])}),[],!1,null,"ba382ddc",null).exports}}]);

1
public/js/build/893.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,11 @@ import Language from './language/index'
import store from './store/index'
Vue.use(Vuex);
Vue.use(ViewUI);
Vue.use(ViewUI, {
modal: {
checkEscClose: true
}
});
Vue.use(VueRouter);
Vue.use(Language);

View File

@ -63,6 +63,7 @@
type: Boolean,
default: false
},
beforeClose: Function
},
data() {
@ -136,12 +137,25 @@
},
methods: {
mask () {
mask() {
if (this.maskClosable) {
this.close()
}
},
close() {
if (!this.beforeClose) {
return this.handleClose();
}
const before = this.beforeClose();
if (before && before.then) {
before.then(this.handleClose);
} else {
this.handleClose();
}
},
handleClose () {
this.$emit("input", false)
},
escClose(e) {

View File

@ -123,7 +123,7 @@ export default {
case 'ppt':
return 'pptx'
}
return '';
return type;
},
loadFile() {
@ -144,11 +144,12 @@ export default {
}
//
let fileKey = this.code || this.value.id;
let fileName = $A.strExists(this.fileName, '.') ? this.fileName : (this.fileName + '.' + this.fileType);
const config = {
"document": {
"fileType": this.fileType,
"key": this.fileType + '-' + fileKey,
"title": this.fileName + '.' + this.fileType,
"title": fileName,
"url": 'http://nginx/api/file/content/?id=' + fileKey + '&token=' + this.userToken,
},
"editorConfig": {
@ -164,6 +165,14 @@ export default {
"callbackUrl": 'http://nginx/api/file/content/office?id=' + fileKey + '&token=' + this.userToken,
}
};
if (/\/hideenOfficeTitle\//.test(window.navigator.userAgent)) {
config.document.title = " ";
}
if ($A.leftExists(fileKey, "msgFile_")) {
config.document.url = 'http://nginx/api/dialog/msg/download/?msg_id=' + $A.leftDelete(fileKey, "msgFile_") + '&token=' + this.userToken;
} else if ($A.leftExists(fileKey, "taskFile_")) {
config.document.url = 'http://nginx/api/project/task/filedown/?file_id=' + $A.leftDelete(fileKey, "taskFile_") + '&token=' + this.userToken;
}
if (this.readOnly) {
config.editorConfig.mode = "view";
config.editorConfig.callbackUrl = null;

View File

@ -1,22 +1,23 @@
<template>
<div v-if="ready" :class="['common-user', maxHiddenClass]">
<div :class="['common-user', maxHiddenClass]">
<Select
v-model="values"
ref="select"
v-model="selects"
:transfer="transfer"
:remote-method="searchUser"
:placeholder="placeholder"
:size="size"
:loading="loading"
:loading="loadIng > 0"
:loading-text="$L('加载中...')"
:default-label="value"
:default-event-object="true"
:multipleMax="multipleMax"
:multipleUncancelable="uncancelable"
:multiple-max="multipleMax"
:multiple-uncancelable="uncancelable"
:remote-method="searchUser"
@on-query-change="searchUser"
@on-open-change="openChange"
multiple
filterable
transfer-class-name="common-user-transfer"
@on-open-change="openChange"
@on-set-default-options="setDefaultOptions">
transfer-class-name="common-user-transfer">
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '')}}</div>
<slot name="option-prepend"></slot>
<Option
@ -33,7 +34,7 @@
</div>
</Option>
</Select>
<div v-if="!initialized" class="common-user-loading"><Loading/></div>
<div v-if="loadIng > 0" class="common-user-loading"><Loading/></div>
</div>
</template>
@ -87,36 +88,23 @@
},
data() {
return {
ready: false,
initialized: false,
loading: false,
openLoad: false,
values: [],
loadIng: 0,
selects: [],
list: [],
options: [],
searchKey: null,
searchHistory: [],
subscribe: null,
}
},
mounted() {
if ($A.isArray(this.value)) {
this.values = $A.cloneJSON(this.value);
} else {
this.$emit('input', this.value ? [this.value] : []);
}
this.$nextTick(() => {
this.ready = true;
});
this.subscribe = Store.subscribe('cacheUserActive', (data) => {
let index = this.list.findIndex(({userid}) => userid == data.userid);
if (index > -1) {
this.initialized = true;
this.$set(this.list, index, Object.assign({}, this.list[index], data));
}
let option = this.options.find(({value}) => value == data.userid);
if (option) {
this.$set(option, 'label', data.nickname)
this.$set(option, 'avatar', data.userimg)
this.handleSelectData();
}
});
},
@ -128,9 +116,9 @@
},
computed: {
maxHiddenClass() {
const {multipleMax, maxHiddenInput, values} = this;
const {multipleMax, maxHiddenInput, selects} = this;
if (multipleMax && maxHiddenInput) {
if (values.length >= multipleMax) {
if (selects.length >= multipleMax) {
return 'hidden-input'
}
}
@ -138,61 +126,62 @@
}
},
watch: {
value(val) {
this.values = val;
value: {
handler() {
this.valueChange()
},
immediate: true,
},
values(val) {
selects(val) {
this.$emit('input', val);
}
},
methods: {
openChange(show) {
if (show && !this.openLoad) {
this.openLoad = true;
if (this.list.length == this.values.length || this.list.length <= 1) {
this.$nextTick(this.searchUser);
searchUser(key) {
if (typeof key !== "string") key = "";
if (key == this.searchKey) return;
this.searchKey = key;
//
const history = this.searchHistory.find(item => item.key == key);
if (history) this.list = history.data;
//
if (!history) this.loadIng++;
setTimeout(() => {
if (this.searchKey != key) {
if (!history) this.loadIng--;
return;
}
}
},
setDefaultOptions(options) {
this.options = options;
options.forEach(({value, label}) => {
this.list.push({
userid: value,
nickname: label,
});
this.$store.dispatch("getUserBasic", {userid: value});
});
if (this.list.length == 0) {
this.initialized = true;
}
},
searchUser(query) {
if (query !== '') {
this.loading = true;
this.$store.dispatch("call", {
url: 'users/search',
data: {
keys: {
key: query || '',
key,
project_id: this.projectId,
no_project_id: this.noProjectId,
},
take: 30
},
}).then(({data}) => {
this.loading = false;
if (!history) this.loadIng--;
this.list = data;
//
const index = this.searchHistory.findIndex(item => item.key == key);
const tmpData = {
key,
data,
time: $A.Time()
};
if (index > -1) {
this.searchHistory.splice(index, 1, tmpData)
} else {
this.searchHistory.push(tmpData)
}
}).catch(({msg}) => {
this.loading = false;
if (!history) this.loadIng--;
this.list = [];
$A.messageWarning(msg);
});
} else {
this.list = [];
}
}, this.searchHistory.length > 0 ? 300 : 0)
},
isDisabled(userid) {
@ -200,6 +189,48 @@
return false;
}
return this.disabledChoice.includes(userid)
},
openChange(show) {
if (show) {
this.$nextTick(this.searchUser);
}
},
valueChange() {
if (this.selects == this.value) {
return
}
if ($A.isArray(this.value)) {
this.selects = $A.cloneJSON(this.value);
} else if (this.value) {
this.selects = [this.value]
} else {
this.selects = [];
}
this.selects.some(userid => {
if (!this.list.find(item => item.userid == userid)) {
this.list.push({userid, nickname: userid});
this.$store.dispatch("getUserBasic", {userid});
}
})
},
handleSelectData() {
this.__handleSelectTimeout && clearTimeout(this.__handleSelectTimeout);
this.__handleSelectTimeout = setTimeout(() => {
if (!this.$refs.select) {
return;
}
const list = this.$refs.select.getValue();
list && list.some(option => {
const data = this.list.find(({userid}) => userid == option.value)
if (data) {
this.$set(option, 'label', data.nickname)
this.$set(option, 'avatar', data.userimg)
}
})
}, 100);
}
}
};

View File

@ -13,15 +13,8 @@
<Input v-if="$Electron && cacheServerUrl" :value="$A.getDomain(cacheServerUrl)" prefix="ios-globe-outline" size="large" readonly clearable @on-clear="clearServerUrl"/>
<Input v-model="email" prefix="ios-mail-outline" :placeholder="$L('输入您的电子邮件')" size="large" @on-enter="onLogin" @on-blur="onBlur" />
<Input v-if="loginType=='login'"
v-model="password" prefix="ios-lock-outline" :placeholder="$L('输入您的密码')" type="password"
size="large"
@on-enter="onLogin" />
<Poptip v-else :content="$L('密码必须包含数字字母大小写或者特殊字符的组合长度在6~32位之间')" :transfer="true">
<Input v-model="password" prefix="ios-lock-outline" :placeholder="$L('输入您的密码')" type="password"
size="large"
@on-enter="onLogin" />
</Poptip>
<Input v-model="password" prefix="ios-lock-outline" :placeholder="$L('输入您的密码')" type="password" size="large" @on-enter="onLogin" />
<Input v-if="loginType=='reg'" v-model="password2" prefix="ios-lock-outline" :placeholder="$L('输入确认密码')" type="password" size="large" @on-enter="onLogin" />
<Input v-if="loginType=='reg' && needInvite" v-model="invite" class="login-code" :placeholder="$L('请输入注册邀请码')" type="text" size="large" @on-enter="onLogin"><span slot="prepend">&nbsp;{{$L('邀请码')}}&nbsp;</span></Input>
@ -252,7 +245,7 @@ export default {
}
if (this.loginType == 'reg') {
if (this.password != this.password2) {
$A.noticeError("确认密码输入不一致");
$A.messageWarning("确认密码输入不一致");
return;
}
}
@ -276,10 +269,7 @@ export default {
});
}).catch(({data, msg}) => {
this.loadIng--;
$A.noticeError({
desc: msg,
duration: 10
});
$A.modalError(msg);
if (data.code === 'need') {
this.reCode();
this.codeNeed = true;

View File

@ -301,10 +301,6 @@ export default {
//
document.addEventListener('keydown', this.shortcutEvent);
window.addEventListener('resize', this.innerHeightListener);
//
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', 0);
}
},
beforeDestroy() {
@ -455,6 +451,12 @@ export default {
}, 5000)
},
workReportShow(show) {
if (show) {
this.getReportUnread(0);
}
},
unreadTotal: {
handler(num) {
if (this.$Electron) {
@ -554,7 +556,6 @@ export default {
if (this.reportUnreadNumber > 0) {
this.reportTabs = "receive";
}
this.getReportUnread(0);
this.workReportShow = true;
return;
case 'clearCache':

View File

@ -1,84 +0,0 @@
<template>
<li
:id="'view_' + dialogMsg.id"
:class="{self:dialogMsg.userid === userId, 'history-tip': topId === dialogMsg.id}"
@mouseover="listHover(true)"
@mouseleave="listHover(false)">
<em v-if="topId === dialogMsg.id" class="history-text">{{$L('历史消息')}}</em>
<div class="dialog-avatar">
<UserAvatar :userid="dialogMsg.userid" :tooltip-disabled="dialogMsg.userid === userId" :size="30"/>
</div>
<DialogView :msg-data="dialogMsg" :dialog-type="dialogData.type"/>
<div class="dialog-action" v-show="showAction">
<Tooltip v-if="parseInt(dialogMsg.userid) === parseInt(userId)" :content="$L('撤回')" :placement="msgIndex === 0 ? 'bottom' : 'top'">
<Button type="text" icon="md-undo" @click="messageWithdraw"/>
</Tooltip>
</div>
</li>
</template>
<script>
import {mapState} from "vuex";
import DialogView from "./DialogView";
export default {
name: "DialogList",
components: {DialogView},
props: {
dialogMsg: {
type: Object,
default: {}
},
topId: {
type: Number,
default: 0
},
dialogData: {
type: Object,
default: {}
},
msgIndex: {
type: Number,
default: -1,
}
},
data() {
return {
showAction: false
}
},
computed: {
...mapState([
'userId',
]),
},
watch: {
},
methods: {
listHover(act) {
this.showAction = act === true;
},
messageWithdraw(){
this.$store.dispatch("call", {
url: 'dialog/msg/withdraw',
data: {
msg_id: this.dialogMsg.id
},
method: 'get',
}).then(({data, msg}) => {
// data
$A.messageSuccess("消息已撤回");
this.$store.dispatch("getDialogMsgs", this.dialogData.id);
// msg
}).catch(({msg}) => {
// msg
$A.messageError(msg);
});
}
}
}
</script>

View File

@ -1,31 +1,49 @@
<template>
<div class="dialog-view" :data-id="msgData.id">
<div :class="`dialog-view ${msgData.type}`" :data-id="msgData.id">
<!--文本-->
<div v-if="msgData.type === 'text'" class="dialog-content">
<pre class="no-dark-mode" v-html="textMsg(msgData.msg.text)"></pre>
</div>
<!--等待-->
<div v-else-if="msgData.type === 'loading'" class="dialog-content loading"><Loading/></div>
<!--文件-->
<div v-else-if="msgData.type === 'file'" :class="['dialog-content', msgData.msg.type]">
<div class="dialog-file" @click="downFile">
<img v-if="msgData.msg.type === 'img'" class="file-img" :style="imageStyle(msgData.msg)" :src="msgData.msg.thumb"/>
<div v-else class="file-box">
<img class="file-thumb" :src="msgData.msg.thumb"/>
<div class="file-info">
<div class="file-name">{{msgData.msg.name}}</div>
<div class="file-size">{{$A.bytesToSize(msgData.msg.size)}}</div>
<div class="dialog-head">
<!--详情-->
<div class="dialog-content">
<!--文本-->
<div v-if="msgData.type === 'text'" class="content-text">
<pre class="no-dark-mode">{{textMsg(msgData.msg.text)}}</pre>
</div>
<!--文件-->
<div v-else-if="msgData.type === 'file'" :class="`content-file ${msgData.msg.type}`">
<div class="dialog-file">
<img v-if="msgData.msg.type === 'img'" class="file-img" :style="imageStyle(msgData.msg)" :src="msgData.msg.thumb" @click="viewFile"/>
<div v-else class="file-box">
<img class="file-thumb" :src="msgData.msg.thumb"/>
<div class="file-info">
<div class="file-name">{{msgData.msg.name}}</div>
<div class="file-size">{{$A.bytesToSize(msgData.msg.size)}}</div>
</div>
</div>
</div>
</div>
<!--等待-->
<div v-else-if="msgData.type === 'loading'" class="content-loading">
<Loading/>
</div>
<!--未知-->
<div v-else class="content-unknown">{{$L("未知的消息类型")}}</div>
</div>
<!--菜单-->
<div v-if="showMenu" class="dialog-menu">
<div class="menu-icon">
<Icon v-if="msgData.userid == userId" @click="withdraw" type="md-undo" :title="$L('撤回')"/>
<template v-if="msgData.type === 'file'">
<Icon @click="viewFile" type="md-eye" :title="$L('查看')"/>
<Icon @click="downFile" type="md-arrow-round-down" :title="$L('下载')"/>
</template>
</div>
</div>
</div>
<!--未知-->
<div v-else class="dialog-content unknown">{{$L("未知的消息类型")}}</div>
<!--时间/阅读-->
<div v-if="msgData.created_at" class="dialog-foot">
<div class="time">{{$A.formatTime(msgData.created_at)}}</div>
<div class="time" :title="msgData.created_at">{{$A.formatTime(msgData.created_at)}}</div>
<Poptip
v-if="msgData.send > 1 || dialogType == 'group'"
class="percent"
@ -85,7 +103,7 @@ export default {
},
computed: {
...mapState(['userToken']),
...mapState(['userToken', 'userId']),
readList() {
return this.read_list.filter(({read_at}) => read_at)
@ -93,6 +111,10 @@ export default {
unreadList() {
return this.read_list.filter(({read_at}) => !read_at)
},
showMenu() {
return this.msgData.userid == this.userId || this.msgData.type === 'file'
}
},
@ -138,8 +160,7 @@ export default {
if (!text) {
return ""
}
text = text.trim().replace(/(\n\x20*){3,}/g, "<br/><br/>");
text = text.trim().replace(/\n/g, "<br/>");
text = text.trim().replace(/(\n\x20*){3,}/g, "\n\n");
return text;
},
@ -167,6 +188,48 @@ export default {
return {};
},
withdraw() {
$A.modalConfirm({
content: `确定撤回此信息吗?`,
okText: '撤回',
loading: true,
onOk: () => {
this.$store.dispatch("call", {
url: 'dialog/msg/withdraw',
data: {
msg_id: this.msgData.id
},
}).then(() => {
$A.messageSuccess("消息已撤回");
this.$store.dispatch("forgetDialogMsg", this.msgData.id);
this.$Modal.remove();
}).catch(({msg}) => {
$A.messageError(msg, 301);
this.$Modal.remove();
});
}
});
},
viewFile() {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: `${this.msgData.msg.name} (${$A.bytesToSize(this.msgData.msg.size)})`,
titleFixed: true,
name: 'file-msg-' + this.msgData.id,
path: "/single/file/msg/" + this.msgData.id,
force: false,
config: {
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
}
});
} else {
window.open($A.apiUrl(`../single/file/msg/${this.msgData.id}`))
}
},
downFile() {
$A.modalConfirm({
title: '下载文件',

View File

@ -35,14 +35,17 @@
<li v-if="dialogData.hasMorePages" class="history" @click="loadNextPage">{{$L('加载历史消息')}}</li>
<li v-else-if="dialogData.loading > 0 && dialogMsgList.length === 0" class="loading"><Loading/></li>
<li v-else-if="dialogMsgList.length === 0" class="nothing">{{$L('暂无消息')}}</li>
<DialogList
v-for="(item, index) in dialogMsgList"
:dialogMsg="item"
:topId="topId"
<li
v-for="item in dialogMsgList"
:id="'view_' + item.id"
:key="item.id"
:dialogData="dialogData"
:msgIndex="index"
/>
:class="{self:item.userid == userId, 'history-tip': topId == item.id}">
<em v-if="topId == item.id" class="history-text">{{$L('历史消息')}}</em>
<div class="dialog-avatar">
<UserAvatar :userid="item.userid" :tooltipDisabled="item.userid == userId" :size="30"/>
</div>
<DialogView :msg-data="item" :dialog-type="dialogData.type"/>
</li>
<li
v-for="item in tempMsgList"
:id="'tmp_' + item.id"
@ -109,12 +112,11 @@ import ScrollerY from "../../../components/ScrollerY";
import {mapState} from "vuex";
import DialogView from "./DialogView";
import DialogUpload from "./DialogUpload";
import DialogList from "./DialogList";
import {Store} from "le5le-store";
export default {
name: "DialogWrapper",
components: {DialogList, DialogUpload, DialogView, ScrollerY, DragInput},
components: {DialogUpload, DialogView, ScrollerY, DragInput},
props: {
dialogId: {
type: Number,
@ -214,12 +216,21 @@ export default {
watch: {
'$route': {
handler (route) {
if (route.query && route.query.sendmsg && this.msgText == '') {
if ($A.isJson(window.__sendDialogMsg) && window.__sendDialogMsg.time > $A.Time()) {
const {msgFile, msgText} = window.__sendDialogMsg;
window.__sendDialogMsg = null;
this.$nextTick(() => {
if ($A.isArray(msgFile) && msgFile.length > 0) {
this.sendFileMsg(msgFile);
} else if (msgText) {
this.sendMsg(msgText);
}
});
}
if (route.query && route.query._) {
let query = $A.cloneJSON(route.query);
delete query.sendmsg;
delete query._;
this.goForward({query}, true);
this.msgText = route.query.sendmsg;
this.$nextTick(this.sendMsg);
}
},
immediate: true
@ -280,24 +291,11 @@ export default {
this.msgText = '';
},
chatKeydown(e) {
if (e.keyCode === 13) {
if (e.shiftKey) {
return;
}
e.preventDefault();
this.sendMsg();
}
},
pasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
sendFileMsg(files) {
if (files.length > 0) {
this.pasteFile = [];
this.pasteItem = [];
postFiles.some(file => {
files.some(file => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ({target}) => {
@ -314,10 +312,28 @@ export default {
}
},
pasteSend() {
this.pasteFile.some(file => {
this.$refs.chatUpload.upload(file)
});
chatKeydown(e) {
if (e.keyCode === 13) {
if (e.shiftKey) {
return;
}
e.preventDefault();
this.sendMsg();
}
},
pasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
this.sendFileMsg(postFiles);
}
},
chatPasteDrag(e, type) {
this.dialogDrag = false;
this.pasteDrag(e, type);
},
chatDragOver(show, e) {
@ -336,29 +352,10 @@ export default {
}
},
chatPasteDrag(e, type) {
this.dialogDrag = false;
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
this.pasteFile = [];
this.pasteItem = [];
postFiles.some(file => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ({target}) => {
this.pasteFile.push(file)
this.pasteItem.push({
type: $A.getMiddle(file.type, null, '/'),
name: file.name,
size: file.size,
result: target.result
})
this.pasteShow = true
}
});
}
pasteSend() {
this.pasteFile.some(file => {
this.$refs.chatUpload.upload(file)
});
},
chatFile(type, file) {

View File

@ -112,6 +112,7 @@ export default {
document.addEventListener('keydown', this.keySave);
window.addEventListener('message', this.handleMessage)
},
beforeDestroy() {
document.removeEventListener('keydown', this.keySave);
window.removeEventListener('message', this.handleMessage)
@ -130,6 +131,18 @@ export default {
deep: true,
},
value: {
handler(val) {
if (val) {
this.ready = true;
this.editUser = [this.userId];
} else {
this.fileContent[this.fileId] = this.contentDetail;
}
},
immediate: true,
},
wsMsg: {
handler(info) {
const {type, data} = info;
@ -158,21 +171,15 @@ export default {
deep: true,
},
value: {
handler(val) {
if (val) {
this.ready = true;
this.editUser = [this.userId];
} else {
this.fileContent[this.fileId] = this.contentDetail;
}
},
immediate: true,
wsOpenNum() {
if (this.$isSubElectron) {
this.$store.dispatch("websocketPath", "file/content/" + this.fileId);
}
},
},
computed: {
...mapState(['fileContent', 'wsMsg', 'userId']),
...mapState(['fileContent', 'wsMsg', 'userId', 'wsOpenNum']),
equalContent() {
return this.contentBak == $A.jsonStringify(this.contentDetail);
@ -246,6 +253,10 @@ export default {
this.loadContent--;
this.contentDetail = data.content;
this.updateBak();
//
if (this.$isSubElectron) {
this.$store.dispatch("websocketConnection")
}
}).catch(({msg}) => {
$A.modalError(msg);
this.loadIng--;

View File

@ -35,7 +35,7 @@
</ETooltip>
</li>
<li :class="['project-icon', searchText!='' ? 'active' : '']">
<Tooltip :always="searchAlways" @on-popper-show="searchFocus" theme="light">
<Tooltip :always="searchText!=''" @on-popper-show="searchFocus" theme="light" :rawIndex="10">
<Icon class="menu-icon" type="ios-search" @click="searchFocus" />
<div slot="content">
<Input v-model="searchText" ref="searchInput" :placeholder="$L('名称、描述...')" class="search-input" clearable/>
@ -69,23 +69,17 @@
</li>
</ul>
</div>
<div class="project-subbox">
<div class="project-subbox clearfix">
<div class="project-subtitle">{{projectData.desc}}</div>
<div class="project-switch">
<div v-if="flowList && flowList.length > 0" class="project-select">
<div class="title">{{$L('进度')}}</div>
<Select
v-model="flowId"
style="width:100%"
:placeholder="this.$L('全部')"
>
<Option value="0">{{this.$L('全部')}}</Option>
<Option v-for="item in flowList" :value="item.id" :key="item.id">{{ item.name }}</Option>
</Select>
</div>
<div v-if="completedCount > 0" class="project-checkbox">
<Checkbox :value="projectParameter('completedTask')" @on-change="toggleCompleted">{{$L('显示已完成')}}</Checkbox>
</div>
<div v-if="flowList.length > 0" class="project-select">
<Cascader :data="flowData" @on-change="flowChange" transfer-class-name="project-list-flow-cascader" transfer>
<span :class="`project-flow ${flowInfo.status}`">{{ flowTitle }}</span>
</Cascader>
</div>
<div :class="['project-switch-button', !projectParameter('card') ? 'menu' : '']" @click="$store.dispatch('toggleProjectParameter', 'card')">
<div><i class="taskfont">&#xe60c;</i></div>
<div><i class="taskfont">&#xe66a;</i></div>
@ -343,7 +337,7 @@
:mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('项目成员')">
<UserInput v-if="userShow" v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/>
<UserInput v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
@ -405,7 +399,7 @@
:mask-closable="false">
<Form :model="transferData" label-width="auto" @submit.native.prevent>
<FormItem prop="owner_userid" :label="$L('项目负责人')">
<UserInput v-if="transferShow" v-model="transferData.owner_userid" :multiple-max="1" :placeholder="$L('选择项目负责人')"/>
<UserInput v-model="transferData.owner_userid" :multiple-max="1" :placeholder="$L('选择项目负责人')"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
@ -418,8 +412,9 @@
<DrawerOverlay
v-model="workflowShow"
placement="right"
:beforeClose="workflowBeforeClose"
:size="1280">
<ProjectWorkflow v-if="workflowShow" :project-id="projectId"/>
<ProjectWorkflow ref="workflow" v-if="workflowShow" :project-id="projectId"/>
</DrawerOverlay>
<!--查看项目动态-->
@ -506,8 +501,9 @@ export default {
archivedTaskShow: false,
projectDialogSubscribe: null,
flowInfo: {},
flowList: [],
flowId: 0
}
},
@ -550,17 +546,6 @@ export default {
...mapGetters(['projectData', 'projectParameter', 'transforTasks']),
searchAlways() {
return !(!this.searchText
|| this.settingShow
|| this.userShow
|| this.inviteShow
|| this.transferShow
|| this.workflowShow
|| this.logShow
|| this.archivedTaskShow);
},
userWaitRemove() {
const {userids, useridbak} = this.userData;
if (!userids) {
@ -582,23 +567,21 @@ export default {
},
panelTask() {
const {searchText,flowId} = this;
const {searchText, flowInfo} = this;
return function (list) {
if (!this.projectParameter('completedTask')) {
list = list.filter(({complete_at}) => {
return !complete_at;
});
}
if (flowInfo.value > 0) {
list = list.filter(({flow_item_id}) => flow_item_id === flowInfo.value);
}
if (searchText) {
list = list.filter(({name, desc}) => {
return $A.strExists(name, searchText) || $A.strExists(desc, searchText);
});
}
if(flowId > 0){
list = list.filter(({flow_item_id}) => {
return flow_item_id === flowId;
});
}
return list;
}
},
@ -615,8 +598,18 @@ export default {
return this.windowWidth > 1200 ? 8 : 3;
},
allTask() {
const {cacheTasks, projectId} = this;
return cacheTasks.filter(task => {
if (task.archived_at) {
return false;
}
return task.project_id == projectId
})
},
columnList() {
const {projectId, cacheColumns, cacheTasks} = this;
const {projectId, cacheColumns, allTask} = this;
const list = cacheColumns.filter(({project_id}) => {
return project_id == projectId
}).sort((a, b) => {
@ -626,15 +619,12 @@ export default {
return a.id - b.id;
});
list.forEach((column) => {
column.tasks = this.transforTasks(cacheTasks.filter(task => {
if (task.archived_at) {
return false;
}
column.tasks = this.transforTasks(allTask.filter(task => {
return task.column_id == column.id;
})).sort((a, b) => {
let at1 = $A.Date(a.complete_at),
at2 = $A.Date(b.complete_at);
if(at1 || at2){
if (at1 || at2) {
return at1 - at2;
}
if (a.sort != b.sort) {
@ -647,8 +637,8 @@ export default {
},
myList() {
const {cacheTasks, taskCompleteTemps, sortField, sortType} = this;
let array = cacheTasks.filter(task => this.myFilter(task));
const {allTask, taskCompleteTemps, sortField, sortType} = this;
let array = allTask.filter(task => this.myFilter(task));
let tmps = taskCompleteTemps.filter(task => this.myFilter(task, false));
if (tmps.length > 0) {
array = $A.cloneJSON(array)
@ -670,8 +660,8 @@ export default {
},
helpList() {
const {cacheTasks, taskCompleteTemps, sortField, sortType} = this;
let array = cacheTasks.filter(task => this.helpFilter(task));
const {allTask, taskCompleteTemps, sortField, sortType} = this;
let array = allTask.filter(task => this.helpFilter(task));
let tmps = taskCompleteTemps.filter(task => this.helpFilter(task, false));
if (tmps.length > 0) {
array = $A.cloneJSON(array)
@ -693,12 +683,12 @@ export default {
},
unList() {
const {projectId, cacheTasks, searchText, sortField, sortType, flowId} = this;
const array = cacheTasks.filter(task => {
if (task.archived_at) {
const {allTask, searchText, sortField, sortType, flowInfo} = this;
const array = allTask.filter(task => {
if (task.parent_id > 0) {
return false;
}
if (task.project_id != projectId || task.parent_id > 0) {
if (flowInfo.value > 0 && task.flow_item_id !== flowInfo.value) {
return false;
}
if (searchText) {
@ -706,9 +696,6 @@ export default {
return false;
}
}
if(task.flow_item_id !== flowId && flowId > 0){
return false;
}
return !task.complete_at;
});
return array.sort((a, b) => {
@ -727,12 +714,12 @@ export default {
},
completedList() {
const {projectId, cacheTasks, searchText, flowId} = this;
const array = cacheTasks.filter(task => {
if (task.archived_at) {
const {allTask, searchText, flowInfo} = this;
const array = allTask.filter(task => {
if (task.parent_id > 0) {
return false;
}
if (task.project_id != projectId || task.parent_id > 0) {
if (flowInfo.value > 0 && task.flow_item_id !== flowInfo.value) {
return false;
}
if (searchText) {
@ -740,9 +727,6 @@ export default {
return false;
}
}
if(task.flow_item_id !== flowId && flowId > 0){
return false;
}
return task.complete_at;
});
return array.sort((a, b) => {
@ -753,17 +737,55 @@ export default {
},
completedCount() {
const {projectId, cacheTasks} = this;
return cacheTasks.filter(task => {
if (task.archived_at) {
return false;
}
if (task.project_id != projectId || task.parent_id > 0) {
const {allTask} = this;
return allTask.filter(task => {
if (task.parent_id > 0) {
return false;
}
return task.complete_at;
}).length;
},
flowTitle() {
const {flowInfo, allTask} = this;
if (flowInfo.value) {
return flowInfo.label;
}
return `${this.$L('全部')} (${allTask.length})`
},
flowData() {
const {flowList, allTask} = this;
let list = [{
value: 0,
label: `${this.$L('全部')} (${allTask.length})`,
children: []
}];
let flows = flowList.map(item1 => {
return {
value: item1.id,
label: item1.name,
status: item1.status,
children: item1.project_flow_item.map(item2 => {
let length = allTask.filter(task => {
return task.flow_item_id == item2.id;
}).length
return {
value: item2.id,
label: `${item2.name} (${length})`,
status: item2.status,
class: item2.status
}
})
}
});
if (flows.length === 1) {
list.push(...flows[0].children)
} else if (flows.length > 0) {
list.push(...flows)
}
return list
},
},
watch: {
@ -772,10 +794,11 @@ export default {
},
projectId: {
handler(val) {
if (val) {
if (val > 0) {
this.getFlowData();
}
},
immediate: true,
},
},
@ -827,10 +850,7 @@ export default {
sort = -1;
upTask.push(...item.task.map(id => {
sort++;
upTask.push(...this.cacheTasks.filter(task => {
if (task.archived_at) {
return false;
}
upTask.push(...this.allTask.filter(task => {
return task.parent_id == id
}).map(({id}) => {
return {
@ -1168,21 +1188,20 @@ export default {
taskIsHidden(task) {
const {name, desc, complete_at} = task;
const {searchText,flowId} = this;
const {searchText, flowInfo} = this;
if (!this.projectParameter('completedTask')) {
if (complete_at) {
return true;
}
}
if (flowInfo.value > 0 && task.flow_item_id !== flowInfo.value) {
return true;
}
if (searchText) {
if (!($A.strExists(name, searchText) || $A.strExists(desc, searchText))) {
return true;
}
}
if(task.flow_item_id !== flowId && flowId > 0){
return true;
}
return false;
},
@ -1210,6 +1229,23 @@ export default {
});
},
getFlowData() {
this.$store.dispatch("call", {
url: 'project/flow/list',
data: {
project_id: this.projectId,
},
}).then(({data}) => {
this.flowList = data;
}).catch(() => {
this.flowList = [];
});
},
flowChange(value, data) {
this.flowInfo = data.pop();
},
inviteCopy() {
if (!this.inviteData.url) {
return;
@ -1230,34 +1266,46 @@ export default {
this.$store.dispatch('toggleProjectParameter', 'completedTask');
},
workflowBeforeClose() {
return new Promise(resolve => {
if (!this.$refs.workflow.existDiff()) {
resolve()
return
}
$A.modalConfirm({
content: '设置尚未保存,是否放弃修改?',
cancelText: '放弃',
okText: '保存',
onCancel: () => {
resolve()
},
onOk: () => {
this.$refs.workflow.saveAll()
resolve()
}
});
})
},
myFilter(task, chackCompleted = true) {
if (task.archived_at) {
return false;
}
if (task.project_id != this.projectId) {
return false;
}
if (!this.projectParameter('completedTask') && chackCompleted === true) {
if (task.complete_at) {
return false;
}
}
if (this.flowInfo.value > 0 && task.flow_item_id !== this.flowInfo.value) {
return false;
}
if (this.searchText) {
if (!$A.strExists(task.name, this.searchText) && !$A.strExists(task.desc, this.searchText)) {
return false;
}
}
if(task.flow_item_id !== this.flowId && this.flowId > 0){
return false;
}
return task.owner;
},
helpFilter(task, chackCompleted = true) {
if (task.archived_at) {
return false;
}
if (task.project_id != this.projectId || task.parent_id > 0) {
if (task.parent_id > 0) {
return false;
}
if (!this.projectParameter('completedTask') && chackCompleted === true) {
@ -1265,37 +1313,20 @@ export default {
return false;
}
}
if (this.flowInfo.value > 0 && task.flow_item_id !== this.flowInfo.value) {
return false;
}
if (this.searchText) {
if (!$A.strExists(task.name, this.searchText) && !$A.strExists(task.desc, this.searchText)) {
return false;
}
}
if(task.flow_item_id !== this.flowId && this.flowId > 0){
return false;
}
return task.task_user && task.task_user.find(({userid, owner}) => userid == this.userId && owner == 0);
},
expiresFormat(date) {
return $A.countDownFormat(date, this.nowTime)
},
getFlowData() {
this.$store.dispatch("call", {
url: 'project/flow/list',
data: {
project_id: this.projectId,
is_filter: 1
},
}).then(({data}) => {
let flowList = data.map(item => {
return item.project_flow_item;
});
this.flowList = flowList[0];
}).catch(({msg}) => {
this.flowList = [];
return false;
});
},
}
}
</script>

View File

@ -143,7 +143,7 @@
:mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('状态负责人')">
<UserInput v-if="userShow" v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择状态负责人')"/>
<UserInput v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择状态负责人')"/>
</FormItem>
<FormItem prop="usertype" :label="$L('流转模式')">
<RadioGroup v-model="userData.usertype">
@ -261,6 +261,12 @@ export default {
return JSON.stringify(project_flow_item) != project_flow_bak
},
existDiff() {
return !!this.list.find(data => {
return this.contrast(data.project_flow_item, data.project_flow_bak)
});
},
onCreate() {
let id = -1 * $A.randNum(1000, 10000);
this.list.push({
@ -468,6 +474,14 @@ export default {
$A.modalError(msg);
});
},
saveAll() {
this.list.some(data => {
if (this.contrast(data.project_flow_item, data.project_flow_bak)) {
this.onSave(data)
}
});
},
}
}
</script>

View File

@ -39,7 +39,6 @@
<Col span="22">
<div class="report-users">
<UserInput
v-if="userInputShow"
v-model="reportData.receive"
:disabledChoice="[userId]"
:placeholder="$L('选择接收人')"
@ -95,7 +94,6 @@ export default {
offset: 0 // -1
},
disabledType: false,
userInputShow: true,
prevCycleText: "",
nextCycleText: "",
};
@ -105,16 +103,10 @@ export default {
if (this.id > 0) {
this.getDetail(val);
}else{
this.userInputShow = false;
this.reportData.offset = 0;
this.reportData.type = "weekly";
this.reportData.receive = [];
this.getTemplate();
setTimeout(() => {
//
// UserInput
this.userInputShow = true;
}, 50)
}
},
},
@ -140,7 +132,7 @@ export default {
title: '覆盖提交',
content: '你已提交过此日期的报告,是否覆盖提交?',
loading: true,
zIndex: 2000,
append: this.$el,
onOk: () => {
this.doSubmit();
}
@ -217,7 +209,6 @@ export default {
},
getDetail(reportId) {
this.userInputShow = false;
this.$store.dispatch("call", {
url: 'report/detail',
data: {
@ -231,12 +222,10 @@ export default {
this.reportData.type = data.type_val;
this.reportData.id = reportId;
this.disabledType = true;
this.userInputShow = true;
// msg
}).catch(({msg}) => {
// msg
$A.messageError(msg);
this.userInputShow = true;
});
},
@ -259,18 +248,15 @@ export default {
//
getLastSubmitter() {
this.userInputShow = false;
this.$store.dispatch("call", {
url: 'report/last_submitter',
}).then(({data, msg}) => {
// data
this.reportData.receive = data;
this.userInputShow = true;
// msg
}).catch(({msg}) => {
// msg
$A.messageError(msg);
this.userInputShow = true;
});
},

View File

@ -7,6 +7,7 @@
:clearable="false"
:placeholder="$L('请选择项目')"
:load-data="cascaderLoadData"
@on-change="cascaderChange"
@on-input-change="cascaderInputChange"
@on-visible-change="cascaderShow=!cascaderShow"
filterable/>
@ -81,8 +82,7 @@
v-model="addData.owner"
:multiple-max="10"
:placeholder="$L('选择任务负责人')"
:project-id="addData.project_id"
:transfer="false"/>
:project-id="addData.project_id"/>
<div v-if="showAddAssist" class="task-add-assist">
<Checkbox v-model="addData.add_assist" :true-value="1" :false-value="0">{{$L('加入任务协助人员列表')}}</Checkbox>
<ETooltip :content="$L('你不是任务负责人时建议加入任务协助人员列表')">
@ -271,18 +271,22 @@ export default {
}
},
'addData.project_id'(id) {
$A.setStorage("cacheAddTaskProjectId", id);
if (id > 0) {
$A.setStorage("cacheAddTaskProjectId", id);
}
},
'addData.column_id'(id) {
const {project_id, column_id} = this.addData;
const {project_id} = this.addData;
this.$nextTick(() => {
if (project_id && column_id) {
this.$set(this.addData, 'cascader', [project_id, column_id]);
if (project_id && id) {
this.$set(this.addData, 'cascader', [project_id, id]);
} else {
this.$set(this.addData, 'cascader', []);
}
})
$A.setStorage("cacheAddTaskColumnId", id);
if (id > 0) {
$A.setStorage("cacheAddTaskColumnId", id);
}
}
},
methods: {
@ -427,6 +431,10 @@ export default {
});
},
cascaderChange(value) {
value[1] && this.$set(this.addData, 'column_id', value[1])
},
cascaderInputChange(key) {
this.cascaderValue = key || "";
//
@ -465,10 +473,6 @@ export default {
return;
}
this.loadIng++;
//
if ( this.addData.cascader.length > 0 ) {
this.addData.column_id = this.addData.cascader[1];
}
this.$store.dispatch("taskAdd", this.addData).then(({msg}) => {
this.loadIng--;
$A.messageSuccess(msg);

View File

@ -45,12 +45,10 @@
:width="240"
placement="bottom"
@on-popper-show="openOwner"
@on-popper-hide="ownerShow=false"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-if="ownerShow"
v-model="ownerData.owner_userid"
:multiple-max="1"
:project-id="taskDetail.project_id"
@ -66,7 +64,12 @@
</Poptip>
</li>
<!--主任务-->
<div v-else-if="ready" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}">
<div
v-else-if="ready"
:class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}"
@drop.prevent="taskPasteDrag($event, 'drag')"
@dragover.prevent="taskDragOver(true, $event)"
@dragleave.prevent="taskDragOver(false, $event)">
<div v-show="taskDetail.id > 0" class="task-info">
<div class="head">
<TaskMenu
@ -188,12 +191,10 @@
class="item-content user"
placement="bottom"
@on-popper-show="openOwner"
@on-popper-hide="ownerShow=false"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-if="ownerShow"
v-model="ownerData.owner_userid"
:multiple-max="10"
:project-id="taskDetail.project_id"
@ -218,12 +219,10 @@
class="item-content user"
placement="bottom"
@on-popper-show="openAssist"
@on-popper-hide="assistShow=false"
@on-ok="onAssist"
transfer>
<div slot="content">
<UserInput
v-if="assistShow"
v-model="assistData.assist_userid"
:multiple-max="10"
:project-id="taskDetail.project_id"
@ -274,18 +273,22 @@
<li v-for="file in fileList">
<img v-if="file.id" class="file-ext" :src="file.thumb"/>
<Loading v-else class="file-load"/>
<div class="file-name" @click="downFile(file)">{{file.name}}</div>
<div class="file-name">{{file.name}}</div>
<div class="file-size">{{$A.bytesToSize(file.size)}}</div>
<EPopover v-model="file._deling" class="file-delete">
<div class="task-detail-delete-file-popover">
<p>{{$L('你确定要删除这个文件吗?')}}</p>
<div class="buttons">
<Button size="small" type="text" @click="file._deling=false">{{$L('取消')}}</Button>
<Button size="small" type="primary" @click="deleteFile(file)">{{$L('确定')}}</Button>
<div class="file-menu" :class="{show:file._show_menu}">
<Icon @click="viewFile(file)" type="md-eye" />
<Icon @click="downFile(file)" type="md-arrow-round-down" />
<EPopover v-model="file._show_menu" class="file-delete">
<div class="task-detail-delete-file-popover">
<p>{{$L('你确定要删除这个文件吗?')}}</p>
<div class="buttons">
<Button size="small" type="text" @click="file._show_menu=false">{{$L('取消')}}</Button>
<Button size="small" type="primary" @click="deleteFile(file)">{{$L('确定')}}</Button>
</div>
</div>
</div>
<i slot="reference" :class="['taskfont', file._deling ? 'deling' : '']">&#xe6ea;</i>
</EPopover>
<i slot="reference" class="taskfont del">&#xe6ea;</i>
</EPopover>
</div>
</li>
</ul>
<ul class="item-content">
@ -342,7 +345,7 @@
</EDropdown>
</div>
</div>
<TaskUpload ref="upload" class="upload"/>
<TaskUpload ref="upload" class="upload" @on-select-file="onSelectFile"/>
</div>
<div v-show="taskDetail.id > 0" class="task-dialog" :style="dialogStyle">
<template v-if="hasOpenDialog">
@ -377,7 +380,7 @@
<div v-else class="no-dialog">
<div class="no-tip">{{$L('暂无消息')}}</div>
<div class="no-input">
<Input
<DragInput
class="dialog-input"
v-model="msgText"
type="textarea"
@ -386,8 +389,9 @@
:autosize="{ minRows: 1, maxRows: 3 }"
:maxlength="255"
:placeholder="$L('输入消息...')"
@on-keydown="msgKeydown"/>
<div class="no-send" @click="openSend">
@on-keydown="msgKeydown"
@on-input-paste="msgPasteDrag"/>
<div class="no-send" @click="msgDialog">
<Loading v-if="sendLoad > 0"/>
<Icon v-else type="md-send" />
</div>
@ -396,6 +400,9 @@
</div>
</div>
<div v-if="!taskDetail.id" class="task-load"><Loading/></div>
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
</div>
</div>
</template>
@ -409,10 +416,11 @@ import DialogWrapper from "./DialogWrapper";
import ProjectLog from "./ProjectLog";
import {Store} from "le5le-store";
import TaskMenu from "./TaskMenu";
import DragInput from "../../../components/DragInput";
export default {
name: "TaskDetail",
components: {TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
components: {DragInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
props: {
taskId: {
type: Number,
@ -434,14 +442,12 @@ export default {
taskDetail: {},
ownerShow: false,
ownerData: {},
ownerLoad: 0,
receiveShow: false,
assistForce: false,
assistShow: false,
assistData: {},
assistLoad: 0,
@ -461,6 +467,7 @@ export default {
innerHeight: Math.min(1100, window.innerHeight),
msgText: '',
msgFile: [],
navActive: 'dialog',
logLoadIng: false,
@ -489,6 +496,7 @@ export default {
toolbar: 'uploadImages | uploadFiles | bold italic underline forecolor backcolor | codesample | preview screenload'
},
dialogDrag: false,
receiveTaskSubscribe: null,
}
},
@ -524,6 +532,8 @@ export default {
'taskContents',
'taskFiles',
'taskPriority',
'windowMax768'
]),
projectName() {
@ -582,7 +592,7 @@ export default {
},
hasOpenDialog() {
return this.taskDetail.dialog_id > 0 && !this.$store.state.windowMax768;
return this.taskDetail.dialog_id > 0 && !this.windowMax768;
},
dialogStyle() {
@ -779,7 +789,6 @@ export default {
const list = this.getOwner.map(({userid}) => userid)
this.$set(this.taskDetail, 'owner_userid', list)
this.$set(this.ownerData, 'owner_userid', list)
this.ownerShow = true;
},
onOwner(pick) {
@ -815,13 +824,11 @@ export default {
this.$store.dispatch("taskUpdate", data).then(({msg}) => {
$A.messageSuccess(msg);
this.ownerLoad--;
this.ownerShow = false;
this.receiveShow = false;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
}).catch(({msg}) => {
$A.modalError(msg);
this.ownerLoad--;
this.ownerShow = false;
this.receiveShow = false;
})
},
@ -831,7 +838,6 @@ export default {
this.$set(this.taskDetail, 'assist_userid', list)
this.$set(this.assistData, 'assist_userid', list);
this.$set(this.assistData, 'disabled', this.getOwner.map(({userid}) => userid))
this.assistShow = true;
},
onAssist() {
@ -847,12 +853,10 @@ export default {
}).then(({msg}) => {
$A.messageSuccess(msg);
this.assistLoad--;
this.assistShow = false;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
}).catch(({msg}) => {
$A.modalError(msg);
this.assistLoad--;
this.assistShow = false;
})
},
@ -988,14 +992,13 @@ export default {
return;
}
e.preventDefault();
this.msgDialog();
if (this.msgText) {
this.msgDialog();
}
}
},
msgDialog() {
if (!this.msgText) {
return;
}
if (this.sendLoad > 0) {
return;
}
@ -1011,18 +1014,26 @@ export default {
this.$store.dispatch("getDialogOne", data.dialog_id).then(() => {
this.sendLoad--;
if ($A.isSubElectron) {
this.resizeDialog();
this.resizeDialog().then(() => {
this.sendDialogMsg();
});
} else {
this.$nextTick(() => {
if (this.$store.state.windowMax768) {
this.goForward({path: '/manage/messenger', query: {sendmsg: this.msgText}});
if (this.windowMax768) {
window.__sendDialogMsg = {
time: $A.Time() + 10,
msgText: this.msgText,
msgFile: this.msgFile
};
this.msgFile = [];
this.msgText = "";
this.goForward({path: '/manage/messenger', query: {_: $A.randomString(6)}});
$A.setStorage("messenger::dialogId", data.dialog_id)
this.$store.state.dialogOpenId = data.dialog_id;
this.$store.dispatch('openTask', 0);
} else {
this.$refs.dialog.sendMsg(this.msgText);
this.sendDialogMsg();
}
this.msgText = "";
});
}
}).catch(({msg}) => {
@ -1035,39 +1046,53 @@ export default {
});
},
openSend() {
if (this.sendLoad > 0) {
return;
sendDialogMsg() {
if (this.msgFile.length > 0) {
this.$refs.dialog.sendFileMsg(this.msgFile);
} else if (this.msgText) {
this.$refs.dialog.sendMsg(this.msgText);
}
this.sendLoad++;
//
this.$store.dispatch("call", {
url: 'project/task/dialog',
data: {
task_id: this.taskDetail.id,
},
}).then(({data}) => {
this.sendLoad--;
this.$store.dispatch("saveTask", data);
this.$store.dispatch("getDialogOne", data.dialog_id).catch(() => {})
if ($A.isSubElectron) {
this.resizeDialog();
} else {
this.$nextTick(() => {
this.goForward({path: '/manage/messenger', query: {sendmsg: this.msgText}});
$A.setStorage("messenger::dialogId", data.dialog_id)
this.$store.state.dialogOpenId = data.dialog_id;
this.$store.dispatch('openTask', 0);
});
this.msgFile = [];
this.msgText = "";
},
msgPasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
this.msgFile = Array.prototype.slice.call(files);
if (this.msgFile.length > 0) {
e.preventDefault();
this.msgDialog()
}
},
taskPasteDrag(e, type) {
this.dialogDrag = false;
this.msgPasteDrag(e, type);
},
taskDragOver(show, e) {
let random = (this.__dialogDrag = $A.randomString(8));
if (!show) {
setTimeout(() => {
if (random === this.__dialogDrag) {
this.dialogDrag = show;
}
}, 150);
} else {
if (e.dataTransfer.effectAllowed === 'move') {
return;
}
}).catch(({msg}) => {
this.sendLoad--;
$A.modalError(msg);
});
this.dialogDrag = true;
}
},
onSelectFile(file) {
this.msgFile = [file];
this.msgDialog()
},
deleteFile(file) {
this.$set(file, '_deling', false);
this.$set(file, '_show_menu', false);
this.$store.dispatch("forgetTaskFile", file.id)
//
this.$store.dispatch("call", {
@ -1083,9 +1108,7 @@ export default {
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el.handleClick()
}
el && el.handleClick()
},
openNewWin() {
@ -1112,25 +1135,43 @@ export default {
},
resizeDialog() {
this.$Electron.ipcRenderer.sendSync('windowSize', {
width: Math.max(1100, window.innerWidth),
height: Math.max(720, window.innerHeight),
minWidth: 800,
minHeight: 600,
autoZoom: true,
});
if (this.msgText) {
return new Promise(resolve => {
this.$Electron.ipcRenderer.sendSync('windowSize', {
width: Math.max(1100, window.innerWidth),
height: Math.max(720, window.innerHeight),
minWidth: 800,
minHeight: 600,
autoZoom: true,
});
let num = 0;
let interval = setInterval(() => {
num++;
if (this.$refs.dialog || num > 20) {
clearInterval(interval);
if (this.$refs.dialog) {
this.$refs.dialog.sendMsg(this.msgText);
this.msgText = "";
resolve()
}
}
}, 100);
})
},
viewFile(file) {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: `${file.name} (${$A.bytesToSize(file.size)})`,
titleFixed: true,
name: 'file-task-' + file.id,
path: "/single/file/task/" + file.id,
force: false,
config: {
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
}
});
} else {
window.open($A.apiUrl(`../single/file/task/${file.id}`))
}
},

View File

@ -2,23 +2,18 @@
<Upload
name="files"
ref="upload"
:action="actionUrl"
:headers="headers"
:data="params"
action=""
multiple
:format="uploadFormat"
:show-upload-list="false"
:max-size="maxSize"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize">
:on-exceeded-size="handleMaxSize"
:before-upload="handleBeforeUpload">
</Upload>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'TaskUpload',
props: {
@ -31,54 +26,10 @@ export default {
data() {
return {
uploadFormat: ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'],
actionUrl: $A.apiUrl('project/task/upload'),
}
},
computed: {
...mapState(['userToken', 'taskId', 'taskFiles']),
headers() {
return {
fd: $A.getStorageString("userWsFd"),
token: this.userToken,
}
},
params() {
return {
task_id: this.taskId,
}
},
},
methods: {
handleProgress(event, file) {
//
if (typeof file.tempId === "undefined") {
file.tempId = $A.randomString(8);
file.task_id = this.taskId;
this.taskFiles.push(file);
}
},
handleSuccess({ret, data, msg}, file) {
//
let index = this.taskFiles.findIndex(({tempId}) => tempId == file.tempId);
if (index > -1) {
this.taskFiles.splice(index, 1);
}
if (ret === 1) {
this.taskFiles.push(data);
} else {
this.$refs.upload.fileList.pop();
$A.modalWarning({
title: '发送失败',
content: '文件 ' + file.name + ' 发送失败,' + msg
});
}
},
handleFormatError(file) {
//
$A.modalWarning({
@ -95,15 +46,16 @@ export default {
});
},
handleBeforeUpload(file) {
//
this.$emit("on-select-file", file)
return false;
},
handleClick() {
//
this.$refs.upload.handleClick()
},
upload(file) {
//file
this.$refs.upload.upload(file);
},
}
}
</script>

View File

@ -62,10 +62,12 @@
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>
</div>
<ETooltip :content="item.end_at" placement="right">
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>
</div>
</ETooltip>
</li>
</ul>
</template>

View File

@ -445,7 +445,8 @@ export default {
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
'xmind', 'rp',
'xmind',
'rp',
],
uploadAccept: '',
maxSize: 204800,
@ -831,8 +832,8 @@ export default {
})
},
openFile(item) {
if (this.contextMenuVisible) {
openFile(item, checkMenuVisible = true) {
if (checkMenuVisible && this.contextMenuVisible) {
return;
}
if (this.fileList.findIndex((file) => file._edit === true) > -1) {
@ -858,8 +859,9 @@ export default {
openSingle(item) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: item.name,
title: this.formatName(item),
titleFixed: true,
userAgent: "/hideenOfficeTitle/",
name: 'file-' + item.id,
path: "/single/file/" + item.id,
force: false, //
@ -902,7 +904,7 @@ export default {
dropFile(item, command) {
switch (command) {
case 'open':
this.openFile(item);
this.openFile(item, false);
break;
case 'rename':
@ -987,7 +989,6 @@ export default {
case 'download':
if (!item.ext) {
$A.modalError("此文件不支持下载");
return;
}
$A.modalConfirm({

View File

@ -0,0 +1,151 @@
<template>
<div class="single-file-msg">
<PageTitle :title="title"/>
<Loading v-if="loadIng > 0"/>
<template v-else>
<AceEditor v-if="isCode" v-model="codeContent" :ext="codeExt" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isOffice" v-model="officeContent" :code="officeCode" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
</template>
<style lang="scss" scoped>
.single-file-msg {
display: flex;
align-items: center;
.preview-iframe,
.ace_editor,
.no-support {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
margin: 0;
outline: 0;
padding: 0;
}
.preview-iframe {
background: 0 0;
float: none;
max-width: none;
}
.view-editor,
.no-support {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
<style lang="scss">
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
export default {
components: {OnlyOffice, AceEditor},
data() {
return {
loadIng: 0,
msgDetail: {},
}
},
mounted() {
//
},
watch: {
'$route': {
handler() {
this.getInfo();
},
immediate: true
},
},
computed: {
title() {
const {msg} = this.msgDetail;
if (msg && msg.name) {
return msg.name;
}
return "Loading..."
},
isCode() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.msgDetail.content;
}
return '';
},
codeExt() {
if (this.isCode) {
return this.msgDetail.msg.ext;
}
return 'txt'
},
isOffice() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 2;
},
officeContent() {
return {
id: this.isOffice ? this.msgDetail.id : 0,
type: this.msgDetail.msg.ext,
name: this.title
}
},
officeCode() {
if (this.isOffice) {
return "msgFile_" + this.msgDetail.id;
}
return ''
},
isPreview() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 3;
},
previewUrl() {
if (this.isPreview) {
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.msgDetail.url))
}
return ''
}
},
methods: {
getInfo() {
let msg_id = $A.runNum(this.$route.params.id);
if (msg_id <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'dialog/msg/detail',
data: {
msg_id,
},
}).then(({data}) => {
this.loadIng--;
this.msgDetail = data;
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({
content: msg,
onOk: () => {
if (this.$Electron) {
window.close();
}
}
});
});
}
}
}
</script>

View File

@ -0,0 +1,151 @@
<template>
<div class="single-file-task">
<PageTitle :title="title"/>
<Loading v-if="loadIng > 0"/>
<template v-else>
<AceEditor v-if="isCode" v-model="codeContent" :ext="codeExt" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isOffice" v-model="officeContent" :code="officeCode" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
</template>
<style lang="scss" scoped>
.single-file-task {
display: flex;
align-items: center;
.preview-iframe,
.ace_editor,
.no-support {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
margin: 0;
outline: 0;
padding: 0;
}
.preview-iframe {
background: 0 0;
float: none;
max-width: none;
}
.view-editor,
.no-support {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
<style lang="scss">
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
export default {
components: {OnlyOffice, AceEditor},
data() {
return {
loadIng: 0,
fileDetail: {},
}
},
mounted() {
//
},
watch: {
'$route': {
handler() {
this.getInfo();
},
immediate: true
},
},
computed: {
title() {
const {name} = this.fileDetail;
if (name) {
return name;
}
return "Loading..."
},
isCode() {
return this.fileDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.fileDetail.content;
}
return '';
},
codeExt() {
if (this.isCode) {
return this.fileDetail.ext;
}
return 'txt'
},
isOffice() {
return this.fileDetail.file_mode == 2;
},
officeContent() {
return {
id: this.isOffice ? this.fileDetail.id : 0,
type: this.fileDetail.ext,
name: this.title
}
},
officeCode() {
if (this.isOffice) {
return "taskFile_" + this.fileDetail.id;
}
return ''
},
isPreview() {
return this.fileDetail.file_mode == 3;
},
previewUrl() {
if (this.isPreview) {
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.url))
}
return ''
}
},
methods: {
getInfo() {
let file_id = $A.runNum(this.$route.params.id);
if (file_id <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'project/task/filedetail',
data: {
file_id,
},
}).then(({data}) => {
this.loadIng--;
this.fileDetail = data;
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({
content: msg,
onOk: () => {
if (this.$Electron) {
window.close();
}
}
});
});
}
}
}
</script>

View File

@ -38,18 +38,25 @@
</style>
<script>
import TaskDetail from "../manage/components/TaskDetail";
import {mapState} from "vuex";
export default {
components: {TaskDetail},
data() {
return {
loadIng: 0,
taskInfo: {},
taskId: 0,
}
},
mounted() {
//
},
computed: {
...mapState(['cacheTasks']),
taskInfo() {
return this.cacheTasks.find(({id}) => id === this.taskId) || {}
}
},
watch: {
'$route': {
handler() {
@ -60,20 +67,19 @@ export default {
},
methods: {
getInfo() {
let task_id = $A.runNum(this.$route.params.id);
if (task_id <= 0) {
this.taskId = $A.runNum(this.$route.params.id);
if (this.taskId <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("getTaskOne", {
task_id,
task_id: this.taskId,
archived: 'all'
}).then(({data}) => {
}).then(() => {
this.loadIng--;
this.taskInfo = data;
this.$store.dispatch("getTaskContent", task_id);
this.$store.dispatch("getTaskFiles", task_id);
this.$store.dispatch("getTaskForParent", task_id).catch(() => {})
this.$store.dispatch("getTaskContent", this.taskId);
this.$store.dispatch("getTaskFiles", this.taskId);
this.$store.dispatch("getTaskForParent", this.taskId).catch(() => {})
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({

View File

@ -75,6 +75,16 @@ export default [
},
]
},
{
name: 'single-file-msg',
path: '/single/file/msg/:id',
component: () => import('./pages/single/fileMsg.vue'),
},
{
name: 'single-file-task',
path: '/single/file/task/:id',
component: () => import('./pages/single/fileTask.vue'),
},
{
name: 'single-file',
path: '/single/file/:id',

View File

@ -273,11 +273,11 @@ export default {
dispatch("call", {
url: 'users/basic',
data: {
userid: array.map(({userid}) => userid)
userid: [...new Set(array.map(({userid}) => userid))]
},
}).then(result => {
time = $A.Time();
array.forEach((value) => {
array.forEach(value => {
let data = result.data.find(({userid}) => userid == value.userid) || Object.assign(value, {email: ""});
data._time = time;
dispatch("saveUserBasic", data);
@ -1835,6 +1835,23 @@ export default {
}
},
/**
* 忘记消息数据
* @param state
* @param msg_id
*/
forgetDialogMsg({state}, msg_id) {
$A.execMainDispatch("forgetDialogMsg", msg_id)
//
let ids = $A.isArray(msg_id) ? msg_id : [msg_id];
ids.some(id => {
let index = state.dialogMsgs.findIndex(item => item.id == id);
if (index > -1) {
state.dialogMsgs.splice(index, 1);
}
})
},
/**
* 获取会话消息
* @param state
@ -2034,24 +2051,49 @@ export default {
(function (msg) {
const {mode, data} = msg;
const {dialog_id} = data;
if (["add", "chat"].includes(mode) && !state.dialogMsgs.find(({id}) => id == data.id)) {
// 新增任务消息数量
dispatch("increaseTaskMsgNum", dialog_id);
if (mode === "chat") {
return;
}
let dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
// 更新对话列表
if (dialog) {
// 新增未读数
dialog.unread++;
}
Store.set('dialogMsgPush', data);
switch (mode) {
case 'delete':
// 删除消息
dispatch("forgetDialogMsg", data.id)
//
let dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
if (dialog) {
// 更新最后消息
dialog.last_at = data.last_msg && data.last_msg.created_at;
dialog.last_msg = data.last_msg;
if (data.update_read) {
// 更新未读数量
dispatch("call", {
url: 'dialog/msg/unread',
dialog_id: data.dialog_id
}).then(result => {
dialog.unread = result.data.unread
}).catch(() => {});
}
}
break;
case 'add':
case 'chat':
if (!state.dialogMsgs.find(({id}) => id == data.id)) {
// 新增任务消息数量
dispatch("increaseTaskMsgNum", dialog_id);
if (mode === "chat") {
return;
}
let dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
// 更新对话列表
if (dialog) {
// 新增未读数
dialog.unread++;
}
Store.set('dialogMsgPush', data);
}
// 更新消息列表
dispatch("saveDialogMsg", data)
// 更新最后消息
dispatch("updateDialogLastMsg", data);
break;
}
// 更新消息列表
dispatch("saveDialogMsg", data)
// 更新最后消息
dispatch("updateDialogLastMsg", data);
})(msgDetail);
break;

View File

@ -147,8 +147,7 @@
}
img {
width: 100%;
height: auto;
max-width: 100%;
}
}
}

View File

@ -140,8 +140,10 @@ body.dark-mode-reverse {
> li {
.dialog-view {
.dialog-content {
color: #ffffff;
background-color: #e1e1e1;
.content-text {
color: #ffffff;
}
}
}
&.self {

View File

@ -8,6 +8,7 @@
flex-direction: column;
background-color: #ffffff;
z-index: 1;
.dialog-title {
display: flex;
flex-direction: column;
@ -15,6 +16,7 @@
padding: 0 30px;
height: 68px;
position: relative;
&:before {
content: "";
position: absolute;
@ -24,6 +26,7 @@
height: 1px;
background-color: #f4f5f5;
}
&.completed {
&:after {
content: "\f373";
@ -39,18 +42,23 @@
z-index: 1;
}
}
.main-title {
display: flex;
align-items: center;
line-height: 22px;
max-width: 100%;
.ivu-tag {
flex-shrink: 0;
margin: 0 6px 0 0;
padding: 0 5px;
&.ivu-tag-success {
padding: 0 6px;
}
}
.ivu-icon {
font-size: 18px;
margin-right: 6px;
@ -59,6 +67,7 @@
color: $primary-color;
}
}
> h2 {
font-size: 17px;
font-weight: 600;
@ -66,6 +75,7 @@
text-overflow: ellipsis;
white-space: nowrap;
}
> em {
font-style: normal;
font-size: 17px;
@ -73,24 +83,29 @@
padding-left: 6px;
}
}
.sub-title {
flex-shrink: 0;
font-size: 12px;
line-height: 20px;
color: #aaaaaa;
&.pointer {
cursor: pointer;
&:hover {
color: #888888;
}
}
}
}
.dialog-scroller {
position: relative;
flex: 1;
padding: 0 32px;
overflow: auto;
.dialog-list {
> ul {
> li {
@ -99,9 +114,11 @@
align-items: flex-end;
list-style: none;
margin-bottom: 16px;
&:first-child {
margin-top: 16px;
}
.dialog-avatar {
position: relative;
margin-bottom: 20px;
@ -109,105 +126,175 @@
width: 30px;
height: 30px;
}
.dialog-view {
max-width: 70%;
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 0 8px;
.dialog-content {
color: #333333;
background-color: #F4F5F7;
padding: 8px;
min-width: 32px;
border-radius: 6px 6px 6px 0;
.dialog-file {
cursor: pointer;
}
> pre {
display: block;
margin: 0;
padding: 0;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-word;
}
&.loading {
display: flex;
.common-loading {
width: 20px;
height: 20px;
margin: 4px;
position: relative;
&.text {
max-width: 70%;
}
&:hover {
.dialog-head {
.dialog-menu {
opacity: 1;
}
}
&.file {
display: inline-block;
.file-box {
background-color: #ffffff;
display: flex;
align-items: center;
padding: 10px 14px;
border-radius: 3px;
width: 220px;
.file-thumb {
width: 36px;
}
.file-info {
margin-left: 12px;
display: flex;
flex-direction: column;
justify-content: center;
.file-name {
color: #333333;
font-size: 14px;
line-height: 18px;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.file-size {
padding-top: 4px;
color: #666666;
font-size: 14px;
}
}
}
}
&.img {
padding: 0;
display: flex;
max-width: 220px;
max-height: 220px;
border-radius: 6px;
background-color: transparent;
overflow: hidden;
.file-img {
display: flex;
}
}
&.unknown {
text-decoration: underline;
}
}
.dialog-head {
display: flex;
align-items: flex-start;
.dialog-content {
background-color: #F4F5F7;
padding: 8px;
min-width: 32px;
border-radius: 6px 6px 6px 0;
display: flex;
align-items: flex-start;
.content-text {
color: #333333;
> pre {
display: block;
margin: 0;
padding: 0;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-word;
}
}
.content-file {
&.file {
display: inline-block;
.file-box {
background-color: #ffffff;
display: flex;
align-items: center;
padding: 10px 14px;
border-radius: 3px;
width: 220px;
.file-thumb {
width: 36px;
}
.file-info {
margin-left: 12px;
display: flex;
flex-direction: column;
justify-content: center;
.file-name {
color: #333333;
font-size: 14px;
line-height: 18px;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.file-size {
padding-top: 4px;
color: #666666;
font-size: 14px;
}
}
}
}
&.img {
padding: 0;
display: flex;
max-width: 220px;
max-height: 220px;
border-radius: 6px;
background-color: transparent;
overflow: hidden;
.file-img {
display: flex;
cursor: pointer;
}
}
}
.content-loading {
display: flex;
.common-loading {
width: 20px;
height: 20px;
margin: 4px;
}
}
.content-unknown {
text-decoration: underline;
}
}
.dialog-menu {
opacity: 0;
margin-left: 6px;
transition: all 0.3s;
.menu-icon {
display: flex;
align-items: center;
border-radius: 4px;
border: 1px solid #ddd;
background-color: #ffffff;
> i {
flex: 1;
display: inline-block;
padding: 4px 6px;
color: #999;
font-size: 13px;
cursor: pointer;
& + i {
border-left: 1px solid #ddd;
}
&:hover {
color: #777;
}
}
}
}
}
.dialog-foot {
display: flex;
align-items: center;
padding-top: 4px;
height: 21px;
line-height: 1;
.common-loading {
margin: 0 2px;
width: 10px;
height: 10px;
}
.time {
color: #bbbbbb;
font-size: 12px;
}
.done {
display: none;
margin-left: 4px;
@ -215,6 +302,7 @@
font-size: 12px;
color: $primary-color;
}
.percent {
display: none;
margin-left: 4px;
@ -222,6 +310,7 @@
}
}
}
.dialog-action {
align-self: flex-start;
display: flex;
@ -233,6 +322,7 @@
}
}
&.history {
cursor: pointer;
justify-content: center;
@ -241,13 +331,16 @@
margin: 12px 0;
opacity: 0.6;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
&.history-tip {
position: relative;
padding-top: 60px;
.history-text {
font-style: normal;
position: absolute;
@ -263,15 +356,18 @@
transform: translateX(-50%);
}
}
&.loading {
padding: 12px 0;
justify-content: center;
.common-loading {
margin: 0;
width: 18px;
height: 18px;
}
}
&.nothing {
position: absolute;
top: 50%;
@ -279,32 +375,54 @@
transform: translate(-50%, -50%);
color: #999999;
}
&.bottom {
height: 0;
margin: 0;
padding: 0;
}
&.self {
flex-direction: row-reverse;
.dialog-view {
align-items: flex-end;
margin: 0 8px 0 0;
.dialog-content {
color: #ffffff;
background-color: $primary-color;
border-radius: 6px 6px 0 6px;
&.file {
background-color: #F4F5F7;
.dialog-head {
flex-direction: row-reverse;
.dialog-content {
background-color: $primary-color;
border-radius: 6px 6px 0 6px;
.content-text {
color: #ffffff;
}
.content-file {
&.file {
background-color: #F4F5F7;
}
&.img {
border-radius: 6px;
background-color: transparent;
}
}
}
&.img {
border-radius: 6px;
background-color: transparent;
.dialog-menu {
margin-left: 0;
margin-right: 6px;
}
}
.dialog-foot {
.done {
display: inline-block;
}
.percent {
display: inline-block;
}
@ -315,6 +433,7 @@
}
}
}
.dialog-footer {
display: flex;
flex-direction: column;
@ -322,6 +441,7 @@
padding: 0 28px;
margin-bottom: 20px;
position: relative;
.dialog-newmsg {
display: none;
height: 30px;
@ -336,19 +456,23 @@
cursor: pointer;
z-index: 2;;
}
.dialog-input {
background-color: #F4F5F7;
padding: 10px 52px 10px 12px;
border-radius: 10px;
.ivu-input {
border: 0;
resize: none;
background-color: transparent;
&:focus {
box-shadow: none;
}
}
}
.dialog-send {
position: absolute;
top: 0;
@ -360,19 +484,23 @@
align-items: center;
justify-content: center;
}
.chat-upload {
display: none;
width: 0;
height: 0;
overflow: hidden;
}
&.newmsg {
margin-top: -50px;
.dialog-newmsg {
display: block;
}
}
}
.drag-over {
position: absolute;
top: 0;
@ -384,6 +512,7 @@
display: flex;
align-items: center;
justify-content: center;
&:before {
content: "";
position: absolute;
@ -394,6 +523,7 @@
border: 2px dashed #7b7b7b;
border-radius: 12px;
}
.drag-text {
padding: 12px;
font-size: 18px;
@ -401,23 +531,29 @@
}
}
}
.dialog-wrapper-read-poptip-content {
display: flex;
position: relative;
.read,
.unread {
flex: 1;
max-height: 300px;
overflow: auto;
> li {
list-style: none;
margin-bottom: 12px;
.common-avatar {
width: 100%;
}
&:last-child {
margin-bottom: 6px;
}
&.read-title {
> em {
font-size: 18px;
@ -428,11 +564,13 @@
}
}
}
.unread {
> li {
padding-left: 16px;
}
}
&:before {
content: "";
position: absolute;
@ -446,10 +584,12 @@
.dialog-wrapper-paste {
margin-top: -4px;
img {
max-width: 100%;
max-height: 1000px;
}
> div,
> img {
display: flex;
@ -464,6 +604,7 @@
.dialog-footer {
padding: 0 20px;
margin-bottom: 16px;
.dialog-send {
right: 20px;
}

View File

@ -114,16 +114,15 @@
}
.project-subbox {
width: 100%;
display: flex;
justify-content: space-between;
.project-subtitle {
flex: 1;
float: left;
color: #999999;
line-height: 24px;
padding: 3px 0;
}
.project-switch {
margin-left: 80px;
flex-shrink: 0;
float: right;
margin-left: 32px;
display: flex;
align-items: flex-end;
.project-checkbox {
@ -136,14 +135,39 @@
box-shadow: none;
}
}
.project-select{
.project-select {
display: flex;
align-items: center;
margin-right: 14px;
opacity: 0.9;
z-index: 1000;
.title{
width:50px;
height: 30px;
.project-flow {
font-size: 13px;
height: 28px;
line-height: 26px;
padding: 0 8px;
border-radius: 4px;
background: #f7f7f7;
border: 1px solid #e8eaec;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
}
}
.project-switch-button {
@ -206,7 +230,7 @@
.project-column {
display: flex;
height: 100%;
padding-top: 18px;
padding-top: 15px;
overflow-x: auto;
overflow-y: hidden;
> ul {
@ -932,6 +956,42 @@
}
}
.project-list-flow-cascader {
.ivu-cascader-menu-item {
color: $primary-text-color !important;
&.start {
color: #595959 !important;
}
&.progress {
color: #0171c2 !important;
}
&.end {
color: #038a24 !important;
}
&:hover,
&.ivu-cascader-menu-item-active {
&.project-list-flow-cascader-item {
&.start {
background-color: rgba(38, 38, 38, 0.05);
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
}
}
}
}
}
@media (max-width: 768px) {
.project-list {
.project-head {
@ -957,16 +1017,6 @@
.project-switch {
margin-left: 0;
justify-content: flex-end;
.project-select{
display: flex;
align-items: center;
margin-right: 14px;
opacity: 0.9;
z-index: 1000;
.title{
width:50px;
}
}
}
}
}

View File

@ -6,7 +6,7 @@
min-height: 120px;
overflow: auto;
.task-info {
flex: 1;
flex: 3;
display: flex;
flex-direction: column;
position: relative;
@ -251,10 +251,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
&:hover {
color: $primary-color;
}
}
.file-size {
flex-shrink: 0;
@ -263,27 +259,38 @@
font-size: 12px;
color: #bbbbbb;
}
.file-delete {
.file-menu {
display: flex;
align-items: center;
opacity: 0;
transition: all 0.3s;
padding-left: 12px;
.taskfont {
display: none;
&.show {
opacity: 1;
}
i {
font-size: 14px;
color: #aaaaaa;
transition: color 0.3s;
cursor: pointer;
padding: 0 4px;
&:hover {
color: #ff0000;
color: #777777;
}
&.deling {
display: inline-block;
&.del {
font-size: 13px;
&:hover {
color: #ff0000;
}
}
}
}
&:hover {
.file-delete {
.taskfont {
display: inline-block;
}
.file-name {
color: $primary-title-color;
}
.file-menu {
opacity: 1;
}
}
}
@ -452,6 +459,7 @@
}
}
.task-dialog {
flex: 2;
flex-shrink: 0;
display: flex;
flex-direction: column;
@ -593,6 +601,33 @@
height: 32px;
}
}
.drag-over {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3;
background-color: rgba(255, 255, 255, 0.78);
display: flex;
align-items: center;
justify-content: center;
&:before {
content: "";
position: absolute;
top: 16px;
left: 16px;
right: 16px;
bottom: 16px;
border: 2px dashed #7b7b7b;
border-radius: 12px;
}
.drag-text {
padding: 12px;
font-size: 18px;
color: #666666;
}
}
&.open-dialog {
flex-direction: row;

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
define("ace/theme/dracula-dark",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-dracula-dark",t.cssText=".ace-dracula-dark .ace_gutter {background: #232323;color: rgb(144,145,148)}.ace-dracula-dark .ace_print-margin {width: 1px;background: #44475a}.ace-dracula-dark {background-color: #232323;color: #f8f8f2}.ace-dracula-dark .ace_cursor {color: #f8f8f0}.ace-dracula-dark .ace_marker-layer .ace_selection {background: #44475a}.ace-dracula-dark.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #232323;border-radius: 2px}.ace-dracula-dark .ace_marker-layer .ace_step {background: rgb(198, 219, 174)}.ace-dracula-dark .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #a29709}.ace-dracula-dark .ace_marker-layer .ace_active-line {background: #44475a}.ace-dracula-dark .ace_gutter-active-line {background-color: #44475a}.ace-dracula-dark .ace_marker-layer .ace_selected-word {box-shadow: 0px 0px 0px 1px #a29709;border-radius: 3px;}.ace-dracula-dark .ace_fold {background-color: #50fa7b;border-color: #f8f8f2}.ace-dracula-dark .ace_keyword {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_language {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_numeric {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character.ace_escape {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_other {color: #bd93f9}.ace-dracula-dark .ace_support.ace_function {color: #8be9fd}.ace-dracula-dark .ace_support.ace_constant {color: #6be5fd}.ace-dracula-dark .ace_support.ace_class {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_support.ace_type {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_storage {color: #ff79c6}.ace-dracula-dark .ace_storage.ace_type {font-style: italic;color: #8be9fd}.ace-dracula-dark .ace_invalid {color: #F8F8F0;background-color: #ff79c6}.ace-dracula-dark .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #bd93f9}.ace-dracula-dark .ace_string {color: #f1fa8c}.ace-dracula-dark .ace_comment {color: #6272a4}.ace-dracula-dark .ace_variable {color: #50fa7b}.ace-dracula-dark .ace_variable.ace_parameter {font-style: italic;color: #ffb86c}.ace-dracula-dark .ace_entity.ace_other.ace_attribute-name {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_function {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_tag {color: #ff79c6}.ace-dracula-dark .ace_invisible {color: #626680;}.ace-dracula-dark .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}",t.$selectionColorConflict=!0;var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() {
define("ace/theme/dracula-dark",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-dracula-dark",t.cssText=".ace-dracula-dark .ace_gutter {background: #000000;color: rgb(144,145,148)}.ace-dracula-dark .ace_print-margin {width: 1px;background: #44475a}.ace-dracula-dark {background-color: #000000;color: #f8f8f2}.ace-dracula-dark .ace_cursor {color: #f8f8f0}.ace-dracula-dark .ace_marker-layer .ace_selection {background: #44475a}.ace-dracula-dark.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #000000;border-radius: 2px}.ace-dracula-dark .ace_marker-layer .ace_step {background: rgb(198, 219, 174)}.ace-dracula-dark .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #a29709}.ace-dracula-dark .ace_marker-layer .ace_active-line {background: #44475a}.ace-dracula-dark .ace_gutter-active-line {background-color: #44475a}.ace-dracula-dark .ace_marker-layer .ace_selected-word {box-shadow: 0px 0px 0px 1px #a29709;border-radius: 3px;}.ace-dracula-dark .ace_fold {background-color: #50fa7b;border-color: #f8f8f2}.ace-dracula-dark .ace_keyword {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_language {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_numeric {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character {color: #bd93f9}.ace-dracula-dark .ace_constant.ace_character.ace_escape {color: #ff79c6}.ace-dracula-dark .ace_constant.ace_other {color: #bd93f9}.ace-dracula-dark .ace_support.ace_function {color: #8be9fd}.ace-dracula-dark .ace_support.ace_constant {color: #6be5fd}.ace-dracula-dark .ace_support.ace_class {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_support.ace_type {font-style: italic;color: #66d9ef}.ace-dracula-dark .ace_storage {color: #ff79c6}.ace-dracula-dark .ace_storage.ace_type {font-style: italic;color: #8be9fd}.ace-dracula-dark .ace_invalid {color: #F8F8F0;background-color: #ff79c6}.ace-dracula-dark .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #bd93f9}.ace-dracula-dark .ace_string {color: #f1fa8c}.ace-dracula-dark .ace_comment {color: #6272a4}.ace-dracula-dark .ace_variable {color: #50fa7b}.ace-dracula-dark .ace_variable.ace_parameter {font-style: italic;color: #ffb86c}.ace-dracula-dark .ace_entity.ace_other.ace_attribute-name {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_function {color: #50fa7b}.ace-dracula-dark .ace_entity.ace_name.ace_tag {color: #ff79c6}.ace-dracula-dark .ace_invisible {color: #626680;}.ace-dracula-dark .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}",t.$selectionColorConflict=!0;var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() {
window.require(["ace/theme/dracula-dark"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;