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
67 changed files with 1630 additions and 849 deletions
+75 -38
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");
}
}
+7 -4
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([
+60 -77
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);
-3
View File
@@ -30,9 +30,6 @@ class VerifyCsrfToken extends Middleware
// 修改任务
'api/project/task/update/',
// 上传任务问题
'api/project/task/upload/',
// 聊天发文件
'api/dialog/msg/sendfile/',
+1 -1
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)
+57 -2
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)
+2 -1
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:
+13 -1
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);
}
}
/**
@@ -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();
});
}
}
+1 -1
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"
+1 -1
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",
+2 -2
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",
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
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;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
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
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
-1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-1
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
View File
File diff suppressed because one or more lines are too long
+5 -1
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);
@@ -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) {
+11 -2
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;
+97 -66
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);
}
}
};
+4 -14
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;
+6 -5
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':
@@ -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>
@@ -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: '下载文件',
@@ -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) {
@@ -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--;
@@ -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>
@@ -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>
@@ -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;
});
},
@@ -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);
@@ -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}`))
}
},
@@ -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>
@@ -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>
+7 -6
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({
@@ -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>
@@ -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>
+16 -10
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({
+10
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',
+61 -19
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;
+1 -2
View File
@@ -147,8 +147,7 @@
}
img {
width: 100%;
height: auto;
max-width: 100%;
}
}
}
+3 -1
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 {
+226 -85
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;
}
+70 -20
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;
}
}
}
}
}
+50 -15
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
@@ -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;