Merge branch 'master' of github.com:kuaifan/dootask into develop

# Conflicts:
#	public/css/app.css
#	public/js/app.js
#	public/js/build/248.js
#	public/js/build/248.js.LICENSE.txt
#	public/js/build/501.js
#	public/js/build/510.js.LICENSE.txt
#	public/js/build/581.js.LICENSE.txt
#	public/js/build/601.js
#	public/js/build/712.js
#	public/js/build/747.js
#	resources/assets/js/pages/manage/file.vue
#	resources/assets/sass/pages/page-manage.scss
This commit is contained in:
kuaifan 2022-02-28 10:25:46 +08:00
commit ab388f1ef5
79 changed files with 2098 additions and 669 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Models\File;
use App\Models\ProjectTask;
use App\Models\ProjectTaskFile;
use App\Models\User;
@ -393,31 +394,13 @@ class DialogController extends AbstractController
$data = $dialogMsg->toArray();
//
if ($data['type'] == 'file') {
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'tif', 'tiff', 'mp3', 'wav', 'mp4', 'flv', 'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm'];
$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;
}
$msg = File::formatFileData($msg);
$data['content'] = $msg['content'];
$data['file_mode'] = $msg['file_mode'];
}
//
return Base::retSuccess("success", $data);
return Base::retSuccess('success', $data);
}
/**

View File

@ -348,9 +348,7 @@ class FileController extends AbstractController
return Base::retError('一次最多只能移动100个文件或文件夹');
}
if ($pid > 0) {
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
return Base::retError('参数错误');
}
File::permissionFind($pid, 1);
}
//
$files = [];
@ -518,40 +516,41 @@ class FileController extends AbstractController
}
}
//
$contentArray = Base::json2array($content);
switch ($file->type) {
case 'document':
$file->ext = $contentArray['type'] ?: 'md';
$contentArray = Base::json2array($content);
$contentString = $contentArray['content'];
$file->ext = $contentArray['type'] == 'md' ? 'md' : 'text';
break;
case 'drawio':
$file->ext = 'drawio';
$contentArray = Base::json2array($content);
$contentString = $contentArray['xml'];
$file->ext = 'drawio';
break;
case 'mind':
$contentString = $content;
$file->ext = 'mind';
break;
case 'code':
case 'txt':
$contentString = $content;
break;
default:
return Base::retError('参数错误');
}
if (isset($contentString)) {
$path = "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $id . "/" . md5($contentString);
$save = public_path($path);
Base::makeDir(dirname($save));
file_put_contents($save, $contentString);
$content = [
'type' => $file->ext,
'url' => $path
];
$size = filesize($save);
} else {
$size = strlen($content);
}
$path = "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $id . "/" . md5($contentString);
$save = public_path($path);
Base::makeDir(dirname($save));
file_put_contents($save, $contentString);
//
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => $content,
'content' => [
'type' => $file->ext,
'url' => $path
],
'text' => $text,
'size' => $size,
'size' => filesize($save),
'userid' => $user->userid,
]);
$content->save();

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\Project;
use App\Models\ProjectColumn;
use App\Models\ProjectFlow;
@ -16,10 +17,13 @@ use App\Models\ProjectUser;
use App\Models\User;
use App\Models\WebSocketDialog;
use App\Module\Base;
use App\Module\BillExport;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Madzipper;
use Request;
use Response;
use Session;
/**
* @apiDefine project
@ -603,13 +607,14 @@ class ProjectController extends AbstractController
if (!is_array($item['task'])) continue;
$index = 0;
foreach ($item['task'] as $task_id) {
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([
if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
'column_id' => $item['id'],
'sort' => $index
]);
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
'column_id' => $item['id'],
]);
])) {
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
'column_id' => $item['id'],
]);
}
$index++;
}
}
@ -975,6 +980,154 @@ class ProjectController extends AbstractController
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/project/task/export 18. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__export
*
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
* @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30']
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__export()
{
$user = User::auth('admin');
//
$userid = Base::arrayRetainInt(Request::input('userid'), true);
$time = Request::input('time');
if (empty($userid) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 20) {
return Base::retError('导出会员限制最多20个');
}
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
return Base::retError('时间选择错误');
}
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
return Base::retError('时间范围限制最大90天');
}
//
$headings = [];
$headings[] = '任务ID';
$headings[] = '任务标题';
$headings[] = '负责人';
$headings[] = '创建人';
$headings[] = '是否完成';
$headings[] = '完成时间';
$headings[] = '是否归档';
$headings[] = '归档时间';
$headings[] = '任务开始时间';
$headings[] = '任务结束时间';
$headings[] = '结束剩余';
$headings[] = '所属项目';
$headings[] = '父级任务ID';
$datas = [];
//
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
->where('project_task_users.owner', 1)
->whereIn('project_task_users.userid', $userid)
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay());
$builder->orderByDesc('project_tasks.id')->chunk(100, function($tasks) use (&$datas) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
if ($task->complete_at) {
$a = Carbon::parse($task->complete_at)->timestamp;
$b = Carbon::parse($task->end_at)->timestamp;
if ($b > $a) {
$endSurplus = Base::timeDiff($a, $b);
} else {
$endSurplus = "-" . Base::timeDiff($b, $a);
}
} else {
$endSurplus = '-';
}
$datas[] = [
$task->id,
Base::filterEmoji($task->name),
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
$task->complete_at ? '已完成' : '-',
$task->complete_at ?: '-',
$task->archived_at ? '已归档' : '-',
$task->archived_at ?: '-',
$task->start_at ?: '-',
$task->end_at ?: '-',
$endSurplus,
Base::filterEmoji($task->project?->name) ?: '-',
$task->parent_id ?: '-',
];
}
});
//
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
if (count($userid) > 1) {
$fileName .= "" . count($userid) . "位成员";
}
$fileName .= '任务统计_' . Base::time() . '.xls';
$filePath = "temp/task/export/" . date("Ym", Base::time());
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls'). ".zip";
$zipPath = storage_path($zipFile);
if (file_exists($zipPath)) {
Base::deleteDirAndFile($zipPath, true);
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Exception) { }
//
if (file_exists($zipPath)) {
$base64 = base64_encode(Base::array2string([
'file' => $zipFile,
]));
Session::put('task::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/project/task/down 18. 导出任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
*
* @apiSuccess {File} 文件下载
*/
public function task__down()
{
$userid = Session::get('task::export:userid');
if (empty($userid)) {
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
}
//
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
$file = $array['file'];
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return response()->download(storage_path($file));
}
/**
* @api {get} api/project/task/one 19. 获取单个任务信息
*
@ -1132,29 +1285,7 @@ class ProjectController extends AbstractController
//
ProjectTask::userTask($file->task_id, null);
//
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'tif', 'tiff', 'mp3', 'wav', 'mp4', 'flv', 'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm'];
$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);
return Base::retSuccess('success', File::formatFileData($data));
}
/**

View File

@ -153,9 +153,10 @@ class AbstractModel extends Model
* @param $where
* @param array $update 存在时更新的内容
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
* @param bool $isInsert 是否是插入数据
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
*/
public static function updateInsert($where, $update = [], $insert = [])
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
{
$row = static::where($where)->first();
if (empty($row)) {
@ -165,8 +166,10 @@ class AbstractModel extends Model
unset($array[$row->primaryKey]);
}
$row->updateInstance($array);
$isInsert = true;
} elseif ($update) {
$row->updateInstance($update);
$isInsert = false;
}
if (!$row->save()) {
return null;

View File

@ -50,6 +50,39 @@ class File extends AbstractModel
{
use SoftDeletes;
/**
* 文件文件
*/
const codeExt = [
'txt',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx'
];
/**
* office文件
*/
const officeExt = [
'doc', 'docx',
'xls', 'xlsx',
'ppt', 'pptx',
];
/**
* 本地媒体文件
*/
const localExt = [
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'tif', 'tiff',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
];
/**
* 是否有访问权限
* @param $userid
@ -239,4 +272,77 @@ class File extends AbstractModel
}
return $file;
}
/**
* 格式化内容数据
* @param array $data [path, size, ext, name]
* @return array
*/
public static function formatFileData(array $data)
{
$filePath = $data['path'];
$fileSize = $data['size'];
$fileExt = $data['ext'];
$fileDotExt = '.' . $fileExt;
$fileName = Base::rightDelete($data['name'], $fileDotExt) . $fileDotExt;
$publicPath = public_path($filePath);
//
switch ($fileExt) {
case 'md':
case 'text':
// 文本
$data['content'] = [
'type' => $fileExt,
'content' => file_get_contents($publicPath),
];
$data['file_mode'] = $fileExt;
break;
case 'drawio':
// 图表
$data['content'] = [
'xml' => file_get_contents($publicPath)
];
$data['file_mode'] = $fileExt;
break;
case 'mind':
// 思维导图
$data['content'] = Base::json2array(file_get_contents($publicPath));
$data['file_mode'] = $fileExt;
break;
default:
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
{
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($publicPath);
$data['file_mode'] = 'code';
}
elseif (in_array($fileExt, File::officeExt))
{
// office预览
$data['content'] = '';
$data['file_mode'] = 'office';
}
else
{
// 其他预览
if (in_array($fileExt, File::localExt)) {
$url = Base::fillUrl($filePath);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $filePath;
}
$data['content'] = [
'preview' => true,
'url' => base64_encode(Base::urlAddparameter($url, [
'fullfilename' => $fileName
])),
];
$data['file_mode'] = 'preview';
}
break;
}
return $data;
}
}

View File

@ -69,48 +69,20 @@ class FileContent extends AbstractModel
abort(403, "This file is empty.");
}
} else {
$content['preview'] = false;
$path = $content['url'];
if ($file->ext) {
$filePath = public_path($content['url']);
$fileType = $file->type;
if ($fileType == 'document')
{
// 文本
$content = [
'type' => $file->ext,
'content' => file_get_contents($filePath)
];
}
elseif ($fileType == 'drawio')
{
// 图表
$content = [
'xml' => file_get_contents($filePath)
];
}
elseif ($fileType == 'mind')
{
// 思维导图
$content = Base::json2array(file_get_contents($filePath));
}
elseif (in_array($fileType, ['txt', 'code']) && $file->size < 2 * 1024 * 1024)
{
// 其他文本和代码限制2M内的文件支持编辑
$content['content'] = file_get_contents($filePath);
}
else
{
// 支持预览
if (in_array($fileType, ['picture', 'image', 'tif', 'media'])) {
$url = Base::fillUrl($content['url']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
}
$content['url'] = base64_encode($url);
$content['preview'] = true;
}
$res = File::formatFileData([
'path' => $path,
'ext' => $file->ext,
'size' => $file->size,
'name' => $file->name,
]);
$content = $res['content'];
} else {
$content['preview'] = false;
}
if ($download) {
$filePath = public_path($path);
if (isset($filePath)) {
return Response::download($filePath, $name);
} else {

View File

@ -376,6 +376,7 @@ class Project extends AbstractModel
$idc = [];
$hasStart = false;
$hasEnd = false;
$upTaskList = [];
foreach ($flows as $item) {
$id = intval($item['id']);
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
@ -403,7 +404,7 @@ class Project extends AbstractModel
'userids' => $userids,
'usertype' => trim($item['usertype']),
'userlimit' => $userlimit,
]);
], [], $isInsert);
if ($flow) {
$ids[] = $flow->id;
if ($flow->id != $id) {
@ -415,6 +416,9 @@ class Project extends AbstractModel
if ($flow->status == 'end') {
$hasEnd = true;
}
if (!$isInsert) {
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
}
}
}
if (!$hasStart) {
@ -429,6 +433,12 @@ class Project extends AbstractModel
}
});
//
foreach ($upTaskList as $id => $value) {
ProjectTask::whereFlowItemId($id)->update([
'flow_item_name' => $value
]);
}
//
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
foreach ($projectFlow->projectFlowItem as $item) {

View File

@ -585,6 +585,14 @@ class ProjectTask extends AbstractModel
'flow' => $flowData,
'change' => [$currentFlowItem?->name, $newFlowItem->name]
]);
ProjectTaskFlowChange::createInstance([
'task_id' => $this->id,
'userid' => User::userid(),
'before_flow_item_id' => $flowData['flow_item_id'],
'before_flow_item_name' => $flowData['flow_item_name'],
'after_flow_item_id' => $this->flow_item_id,
'after_flow_item_name' => $this->flow_item_name,
])->save();
}
// 状态
if (Arr::exists($data, 'complete_at')) {

View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
/**
* App\Models\ProjectTaskFlowChange
*
* @property int $id
* @property int|null $task_id 任务ID
* @property int|null $userid 会员ID
* @property int|null $before_flow_item_id 变化前工作流状态ID
* @property string|null $before_flow_item_name (变化前)工作流状态名称
* @property int|null $after_flow_item_id 变化后工作流状态ID
* @property string|null $after_flow_item_name (变化后)工作流状态名称
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUserid($value)
* @mixin \Eloquent
*/
class ProjectTaskFlowChange extends AbstractModel
{
}

View File

@ -11,7 +11,7 @@ use App\Module\Base;
* @property int|null $project_id 项目ID
* @property int|null $userid 成员ID
* @property int|null $owner 是否负责人
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project
@ -22,6 +22,7 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
* @mixin \Eloquent

View File

@ -8,7 +8,7 @@ namespace App\Models;
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $userid 会员ID
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
* @property string|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
@ -17,6 +17,7 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
* @mixin \Eloquent

View File

@ -812,6 +812,31 @@ class Base
return $scheme.($_SERVER['HTTP_HOST'] ?? '');
}
/**
* 地址后拼接参数
* @param $url
* @param $parames
* @return mixed|string
*/
public static function urlAddparameter($url, $parames)
{
if ($parames && is_array($parames)) {
$array = [];
foreach ($parames as $key => $val) {
$array[] = $key . "=" . $val;
}
if ($array) {
$query = implode("&", $array);
if (str_contains($url, "?")) {
$url .= "&" . $query;
} else {
$url .= "?" . $query;
}
}
}
return $url;
}
/**
* 格式化内容图片地址
* @param $content
@ -2942,4 +2967,19 @@ class Base
$matrix = array_unique($matrix, SORT_REGULAR);
return array_merge($matrix);
}
/**
* 去除emoji表情
* @param $str
* @return string|string[]|null
*/
public static function filterEmoji($str)
{
return preg_replace_callback(
'/./u',
function (array $match) {
return strlen($match[0]) >= 4 ? '' : $match[0];
},
$str);
}
}

144
app/Module/BillExport.php Normal file
View File

@ -0,0 +1,144 @@
<?php
namespace App\Module;
use Excel;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
use Maatwebsite\Excel\Concerns\WithTitle;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Writer\Exception;
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
{
public $title;
public $headings = [];
public $data = [];
public $typeLists = [];
public $typeNumber = 0;
public function __construct($title, array $data)
{
$this->title = $title;
$this->data = $data;
}
public static function create($data = [], $title = "Sheet1") {
if (is_string($data)) {
list($title, $data) = [$data, $title];
}
if (!is_array($data)) {
$data = [];
}
return new BillExport($title, $data);
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setHeadings(array $headings) {
$this->headings = $headings;
return $this;
}
public function setData(array $data) {
$this->data = $data;
return $this;
}
public function setTypeList(array $typeList, $number = 0) {
$this->typeLists = $typeList;
$this->typeNumber = $number;
return $this;
}
public function store($fileName = '') {
if (empty($fileName)) {
$fileName = date("YmdHis") . '.xls';
}
try {
return Excel::store($this, $fileName);
} catch (Exception $e) {
return "导出错误:" . $e->getMessage();
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
return "导出错误:" . $e->getMessage();
}
}
public function download($fileName = '') {
if (empty($fileName)) {
$fileName = date("YmdHis") . '.xls';
}
try {
return Excel::download($this, $fileName);
} catch (Exception $e) {
return "导出错误:" . $e->getMessage();
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
return "导出错误:" . $e->getMessage();
}
}
/**
* 导出的文件标题
* @return string
*/
public function title(): string
{
return $this->title;
}
/**
* 标题行
* @return array
*/
public function headings(): array
{
return $this->headings;
}
/**
* 导出的内容
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return collect($this->data);
}
/**
* 设置单元格事件
* @return array
*/
public function registerEvents(): array
{
return [
AfterSheet::Class => function (AfterSheet $event) {
$count = count($this->data);
foreach ($this->typeLists AS $cell => $typeList) {
if ($cell && $typeList) {
$p = $this->headings ? 1 : 0;
for ($i = 1 + $p; $i <= max($count, $this->typeNumber) + $p; $i++) {
$validation = $event->sheet->getDelegate()->getCell($cell . $i)->getDataValidation();
$validation->setType(DataValidation::TYPE_LIST);
$validation->setErrorStyle(DataValidation::STYLE_WARNING);
$validation->setAllowBlank(false);
$validation->setShowDropDown(true);
$validation->setShowInputMessage(true);
$validation->setShowErrorMessage(true);
$validation->setErrorTitle('输入的值不合法');
$validation->setError('选择的值不在列表中,请选择列表中的值');
$validation->setPromptTitle('从列表中选择');
$validation->setPrompt('请选择下拉列表中的值');
$validation->setFormula1('"' . implode(',', $typeList) . '"');
}
}
}
}
];
}
}

16
app/Module/BillImport.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Module;
use Maatwebsite\Excel\Concerns\ToArray;
class BillImport implements ToArray
{
public function Array(Array $tables)
{
return $tables;
}
}

6
cmd
View File

@ -55,13 +55,13 @@ check_docker() {
echo -e "${Error} ${RedBG} 未安装 Docker${Font}"
exit 1
fi
docker-compose --version &> /dev/null
docker-compose version &> /dev/null
if [ $? -ne 0 ]; then
echo -e "${Error} ${RedBG} 未安装 Docker-compose${Font}"
exit 1
fi
if [[ -n `docker-compose --version | grep "docker-compose" | grep -E "\sv*1"` ]]; then
docker-compose --version
if [[ -n `docker-compose version | grep "docker-compose" | grep -E "\sv*1"` ]]; then
docker-compose version
echo -e "${Error} ${RedBG} Docker-compose 版本过低请升级至v2+${Font}"
exit 1
fi

View File

@ -17,7 +17,7 @@ class CreateProjectLogsTable extends Migration
$table->bigIncrements('id');
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('column_id')->nullable()->default(0)->comment('列表ID');
$table->bigInteger('task_id')->nullable()->default(0)->comment('项目ID');
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->string('detail', 500)->nullable()->default('')->comment('详细信息');
$table->timestamps();

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectTaskFlowChangesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_task_flow_changes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->bigInteger('before_item_id')->nullable()->default(0)->comment('变化前工作流状态ID');
$table->string('before_item_name', 50)->nullable()->default('')->comment('(变化前)工作流状态名称');
$table->bigInteger('after_item_id')->nullable()->default(0)->comment('变化后工作流状态ID');
$table->string('after_item_name', 50)->nullable()->default('')->comment('(变化后)工作流状态名称');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_task_flow_changes');
}
}

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class RenamePreProjectTaskFlowChangesItem extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_task_flow_changes', function (Blueprint $table) {
if (Schema::hasColumn('project_task_flow_changes', 'before_item_id')) {
$table->renameColumn('before_item_id', 'before_flow_item_id');
$table->renameColumn('before_item_name', 'before_flow_item_name');
$table->renameColumn('after_item_id', 'after_flow_item_id');
$table->renameColumn('after_item_name', 'after_flow_item_name');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_task_flow_changes', function (Blueprint $table) {
//
});
}
}

View File

@ -2,10 +2,10 @@
directory=/var/www
# 生产环境
#command=php bin/laravels start -i
command=php bin/laravels start -i
# 开发环境
command=./bin/inotify ./app
#command=./bin/inotify ./app
numprocs=1
autostart=true

View File

@ -72,7 +72,6 @@ function createMainWindow() {
preload: path.join(__dirname, 'electron-preload.js'),
webSecurity: true,
nodeIntegration: true,
nodeIntegrationInSubFrames: true,
contextIsolation: true,
nativeWindowOpen: true
}
@ -145,12 +144,11 @@ function createSubWindow(args) {
preload: path.join(__dirname, 'electron-preload.js'),
webSecurity: true,
nodeIntegration: true,
nodeIntegrationInSubFrames: true,
contextIsolation: true,
nativeWindowOpen: true
}, webPreferences),
}, config))
browser.on('page-title-updated', (event, title) => {
if (title == "index.html" || config.titleFixed === true) {
event.preventDefault()

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.9.61",
"version": "0.10.19",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",
@ -53,7 +53,7 @@
"less-loader": "^10.2.0",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"node-sass": "^4.11.0",
"node-sass": "^6.0.1",
"notification-koro1": "^1.1.1",
"postcss": "^8.4.5",
"resolve-url-loader": "^4.0.0",

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*!
* html2canvas 1.4.0 <https://html2canvas.hertzen.com>
* html2canvas 1.4.1 <https://html2canvas.hertzen.com>
* Copyright (c) 2022 Niklas von Hertzen <https://hertzen.com>
* Released under MIT License
*/

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@ -257,7 +257,7 @@
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 2.5.0 Built on 2021-12-21T09:44:51.866Z
* Version 2.5.1 Built on 2022-01-28T15:37:57.791Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@ -257,7 +257,7 @@
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 2.5.0 Built on 2021-12-21T09:44:51.866Z
* Version 2.5.1 Built on 2022-01-28T15:37:57.791Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*!
* clipboard.js v2.0.8
* clipboard.js v2.0.10
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,329 @@
/*!
* ====================================================
* Kity Minder Core - v1.4.50 - 2018-09-17
* https://github.com/fex-team/kityminder-core
* GitHub: https://github.com/fex-team/kityminder-core.git
* Copyright (c) 2018 Baidu FEX; Licensed BSD-3-Clause
* ====================================================
*/
/*!
* ====================================================
* kity - v2.0.4 - 2016-08-22
* https://github.com/fex-team/kity
* GitHub: https://github.com/fex-team/kity.git
* Copyright (c) 2016 Baidu FEX; Licensed BSD
* ====================================================
*/
/**
* @license
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @license
*
* Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/**
* @license
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* (c) Dean McNamee <dean@gmail.com>, 2013.
*
* https://github.com/deanm/omggif
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
* including animation and compression. It does not rely on any specific
* underlying system, so should run in the browser, Node, or Plask.
*/
/**
* @license
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2017 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2018 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2019 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* FPDF is released under a permissive license: there is no usage restriction.
* You may embed it freely in your application (commercial or not), with or
* without modifications.
*
* Reference: http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Joseph Myers does not specify a particular license for his work.
*
* Author: Joseph Myers
* Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js
*
* Modified by: Owen Leong
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
* Author: Owen Leong (@owenl131)
* Date: 15 Oct 2020
* References:
* https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
* https://github.com/foliojs/pdfkit/blob/master/lib/security.js
* http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Unicode Bidi Engine based on the work of Alex Shensis (@asthensis)
* MIT License
*/
/**
* @license
* jsPDF fileloading PlugIn
* Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com)
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF filters PlugIn
* Copyright (c) 2014 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF virtual FileSystem functionality
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* A class to parse color values
* @author Stoyan Stefanov <sstoo@gmail.com>
* {@link http://www.phpied.com/rgb-color-parser-in-javascript/}
* @license Use it if you like it
*/
/** ====================================================================
* @license
* jsPDF XMP metadata plugin
* Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/** @license
* Copyright (c) 2017 Dominik Homberger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://webpjs.appspot.com
WebPRiffParser dominikhlbg@gmail.com
*/
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 2.5.1 Built on 2022-01-28T15:37:57.791Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
* 2015-2021 yWorks GmbH, http://www.yworks.com
* 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
* 2016-2018 Aras Abbasi <aras.abbasi@gmail.com>
* 2010 Aaron Spike, https://github.com/acspike
* 2012 Willow Systems Corporation, https://github.com/willowsystems
* 2012 Pablo Hess, https://github.com/pablohess
* 2012 Florian Jenett, https://github.com/fjenett
* 2013 Warren Weckesser, https://github.com/warrenweckesser
* 2013 Youssef Beddad, https://github.com/lifof
* 2013 Lee Driscoll, https://github.com/lsdriscoll
* 2013 Stefan Slonevskiy, https://github.com/stefslon
* 2013 Jeremy Morel, https://github.com/jmorel
* 2013 Christoph Hartmann, https://github.com/chris-rock
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 James Makes, https://github.com/dollaruw
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Steven Spungin, https://github.com/Flamenco
* 2014 Kenneth Glassey, https://github.com/Gavvers
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributor(s):
* siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
* kim3er, mfo, alnorth, Flamenco
*/
/** @license
* Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
/*! @license DOMPurify 2.3.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.4/LICENSE */
/*! @license DOMPurify 2.3.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.6/LICENSE */

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,329 @@
/*!
* ====================================================
* Kity Minder Core - v1.4.50 - 2018-09-17
* https://github.com/fex-team/kityminder-core
* GitHub: https://github.com/fex-team/kityminder-core.git
* Copyright (c) 2018 Baidu FEX; Licensed BSD-3-Clause
* ====================================================
*/
/*!
* ====================================================
* kity - v2.0.4 - 2016-08-22
* https://github.com/fex-team/kity
* GitHub: https://github.com/fex-team/kity.git
* Copyright (c) 2016 Baidu FEX; Licensed BSD
* ====================================================
*/
/**
* @license
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @license
*
* Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/**
* @license
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* (c) Dean McNamee <dean@gmail.com>, 2013.
*
* https://github.com/deanm/omggif
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
* including animation and compression. It does not rely on any specific
* underlying system, so should run in the browser, Node, or Plask.
*/
/**
* @license
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2017 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2018 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2019 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* FPDF is released under a permissive license: there is no usage restriction.
* You may embed it freely in your application (commercial or not), with or
* without modifications.
*
* Reference: http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Joseph Myers does not specify a particular license for his work.
*
* Author: Joseph Myers
* Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js
*
* Modified by: Owen Leong
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
* Author: Owen Leong (@owenl131)
* Date: 15 Oct 2020
* References:
* https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
* https://github.com/foliojs/pdfkit/blob/master/lib/security.js
* http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Unicode Bidi Engine based on the work of Alex Shensis (@asthensis)
* MIT License
*/
/**
* @license
* jsPDF fileloading PlugIn
* Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com)
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF filters PlugIn
* Copyright (c) 2014 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF virtual FileSystem functionality
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* A class to parse color values
* @author Stoyan Stefanov <sstoo@gmail.com>
* {@link http://www.phpied.com/rgb-color-parser-in-javascript/}
* @license Use it if you like it
*/
/** ====================================================================
* @license
* jsPDF XMP metadata plugin
* Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/** @license
* Copyright (c) 2017 Dominik Homberger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://webpjs.appspot.com
WebPRiffParser dominikhlbg@gmail.com
*/
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 2.5.1 Built on 2022-01-28T15:37:57.791Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
* 2015-2021 yWorks GmbH, http://www.yworks.com
* 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
* 2016-2018 Aras Abbasi <aras.abbasi@gmail.com>
* 2010 Aaron Spike, https://github.com/acspike
* 2012 Willow Systems Corporation, https://github.com/willowsystems
* 2012 Pablo Hess, https://github.com/pablohess
* 2012 Florian Jenett, https://github.com/fjenett
* 2013 Warren Weckesser, https://github.com/warrenweckesser
* 2013 Youssef Beddad, https://github.com/lifof
* 2013 Lee Driscoll, https://github.com/lsdriscoll
* 2013 Stefan Slonevskiy, https://github.com/stefslon
* 2013 Jeremy Morel, https://github.com/jmorel
* 2013 Christoph Hartmann, https://github.com/chris-rock
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 James Makes, https://github.com/dollaruw
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Steven Spungin, https://github.com/Flamenco
* 2014 Kenneth Glassey, https://github.com/Gavvers
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributor(s):
* siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
* kim3er, mfo, alnorth, Flamenco
*/
/** @license
* Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -257,7 +257,7 @@
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 2.5.0 Built on 2021-12-21T09:44:51.866Z
* Version 2.5.1 Built on 2022-01-28T15:37:57.791Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF

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

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*!
* clipboard.js v2.0.8
* clipboard.js v2.0.10
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha

View File

@ -5,7 +5,7 @@
{{ $L('使用 SSO 登录') }}
</div>
<template v-if="showDown">
<div v-if="$Electron" class="common-right-bottom-link" @click="releasesNotification">
<div v-if="$Electron" class="common-right-bottom-link" @click="updateWinShow=true">
<Icon type="md-download"/>
{{ $L(repoTitle) }}
</div>
@ -14,20 +14,32 @@
{{ $L(repoTitle) }}
</a>
</template>
<Modal
v-model="updateWinShow"
:ok-text="$L('立即升级')"
:closable="false"
:mask-closable="false"
@on-ok="installApplication"
@on-cancel="repoStatus=2"
class-name="common-right-bottom-notification">
<div slot="header" class="notification-head">
<div class="notification-title">{{$L('发现新版本')}}</div>
<Tag color="volcano">{{repoReleases.tag_name}}</Tag>
</div>
<MarkdownPreview class="notification-body overlay-y" :initialValue="repoReleases.body"/>
</Modal>
</div>
</template>
<script>
import Vue from 'vue'
import MarkdownPreview from "./MDEditor/components/preview";
import axios from "axios";
Vue.component('MarkdownPreview', MarkdownPreview)
import {mapState} from "vuex";
import {Store} from "le5le-store";
export default {
name: 'RightBottom',
components: {MarkdownPreview},
data() {
return {
loadIng: 0,
@ -37,6 +49,7 @@ export default {
repoStatus: 0, // 0 12
repoReleases: {},
updateWinShow: false,
downloadResult: {},
subscribe: null,
@ -46,14 +59,15 @@ export default {
this.getReleases();
//
this.subscribe = Store.subscribe('releasesNotification', () => {
this.releasesNotification();
this.updateWinShow = true;
});
//
if (this.$Electron) {
this.$Electron.registerMsgListener('downloadDone', ({result}) => {
if (result.name == this.repoData.name && this.repoStatus !== 2) {
this.$store.state.clientNewVersion = this.repoReleases.tag_name
this.downloadResult = result;
this.releasesNotification()
this.updateWinShow = true;
}
})
}
@ -213,44 +227,6 @@ export default {
}
},
releasesNotification() {
const {tag_name, body} = this.repoReleases;
this.$store.state.clientNewVersion = tag_name
$A.modalConfirm({
okText: this.$L('立即更新'),
onOk: () => {
this.installApplication();
},
onCancel: () => {
this.repoStatus = 2;
},
render: (h) => {
return h('div', {
class: 'common-right-bottom-notification'
}, [
h('div', {
class: "notification-head"
}, [
h('div', {
class: "notification-title"
}, this.$L('发现新版本')),
h('Tag', {
props: {
color: 'volcano'
}
}, tag_name)
]),
h('MarkdownPreview', {
class: 'notification-body',
props: {
initialValue: body
}
}),
])
}
});
},
installApplication() {
if (!this.$Electron) {
return;

View File

@ -366,7 +366,11 @@
return
}
if ($A.Electron) {
$A.Electron.sendMessage('openExternal', url);
$A.Electron.request({action: 'openExternal', url}, () => {
// 成功
}, () => {
// 失败
});
} else {
window.open(url)
}
@ -422,6 +426,22 @@
}
if (typeof config === "string") config = {title:config};
let inputId = "modalInput_" + $A.randomString(6);
const onOk = () => {
if (typeof config.onOk === "function") {
if (config.onOk(config.value, () => {
$A.Modal.remove();
}) === true) {
$A.Modal.remove();
}
} else {
$A.Modal.remove();
}
};
const onCancel = () => {
if (typeof config.onCancel === "function") {
config.onCancel();
}
};
$A.Modal.confirm({
render: (h) => {
return h('div', [
@ -441,27 +461,16 @@
on: {
input: (val) => {
config.value = val;
},
'on-enter': (e) => {
$A(e.target).parents(".ivu-modal-body").find(".ivu-btn-primary").click();
}
}
})
])
},
onOk: () => {
if (typeof config.onOk === "function") {
if (config.onOk(config.value, () => {
$A.Modal.remove();
}) === true) {
$A.Modal.remove();
}
} else {
$A.Modal.remove();
}
},
onCancel: () => {
if (typeof config.onCancel === "function") {
config.onCancel();
}
},
onOk,
onCancel,
loading: true,
okText: $A.L(config.okText || '确定'),
cancelText: $A.L(config.cancelText || '取消'),

View File

@ -214,6 +214,7 @@ export default {
url: value + 'system/setting',
}).then(() => {
this.setServerUrl(value)
cb()
}).catch(({msg}) => {
$A.modalError(msg || "服务器地址无效", 301);
cb()

View File

@ -19,39 +19,84 @@
</div>
</div>
<DropdownMenu slot="list">
<DropdownItem
v-for="(item, key) in menu"
v-if="item.visible !== false"
:key="key"
:divided="!!item.divided"
:name="item.path">
{{$L(item.name)}}
<Badge v-if="item.path === 'version'" class="manage-menu-report-badge" :text="clientNewVersion"/>
<Badge v-if="item.path === 'workReport'" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
</DropdownItem>
<Dropdown placement="right-start" @on-click="setTheme">
<DropdownItem divided>
<div class="manage-menu-language">
{{$L('主题皮肤')}}
<Icon type="ios-arrow-forward"></Icon>
</div>
<template v-for="item in menu">
<!-- 团队管理 -->
<Dropdown
v-if="item.path === 'team'"
placement="right-start">
<DropdownItem divided>
<div class="manage-menu-flex">
{{$L(item.name)}}
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
<Icon v-else type="ios-arrow-forward"></Icon>
</div>
</DropdownItem>
<DropdownMenu slot="list">
<DropdownItem name="allUser">{{$L('团队管理')}}</DropdownItem>
<DropdownItem name="workReport">
<div class="manage-menu-flex">
{{$L('工作报告')}}
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
</div>
</DropdownItem>
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<!-- 主题皮肤 -->
<Dropdown
v-else-if="item.path === 'theme'"
placement="right-start"
@on-click="setTheme">
<DropdownItem divided>
<div class="manage-menu-flex">
{{$L(item.name)}}
<Icon type="ios-arrow-forward"></Icon>
</div>
</DropdownItem>
<DropdownMenu slot="list">
<DropdownItem
v-for="(item, key) in themeList"
:key="key"
:name="item.value"
:selected="themeMode === item.value">{{$L(item.name)}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<!-- 语言设置 -->
<Dropdown
v-else-if="item.path === 'language'"
placement="right-start"
@on-click="setLanguage">
<DropdownItem divided>
<div class="manage-menu-flex">
{{currentLanguage}}
<Icon type="ios-arrow-forward"></Icon>
</div>
</DropdownItem>
<DropdownMenu slot="list">
<DropdownItem
v-for="(item, key) in languageList"
:key="key"
:name="key"
:selected="getLanguage() === key">{{item}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<!-- 其他菜单 -->
<DropdownItem
v-else-if="item.visible !== false"
:divided="!!item.divided"
:name="item.path"
:style="item.style || {}">
{{$L(item.name)}}
<Badge
v-if="item.path === 'version'"
class="manage-menu-report-badge"
:text="clientNewVersion"/>
<Badge
v-else-if="item.path === 'workReport' && reportUnreadNumber > 0"
class="manage-menu-report-badge"
:count="reportUnreadNumber"/>
</DropdownItem>
<DropdownMenu slot="list">
<Dropdown-item v-for="(item, key) in themeList" :key="key" :name="item.value" :selected="themeMode === item.value">{{$L(item.name)}}</Dropdown-item>
</DropdownMenu>
</Dropdown>
<Dropdown placement="right-start" @on-click="setLanguage">
<DropdownItem divided>
<div class="manage-menu-language">
{{currentLanguage}}
<Icon type="ios-arrow-forward"></Icon>
</div>
</DropdownItem>
<DropdownMenu slot="list">
<Dropdown-item v-for="(item, key) in languageList" :key="key" :name="key" :selected="getLanguage() === key">{{item}}</Dropdown-item>
</DropdownMenu>
</Dropdown>
<DropdownItem divided name="signout" style="color:#f40">{{$L('退出登录')}}</DropdownItem>
</template>
</DropdownMenu>
</Dropdown>
<ul :class="overlayClass" @scroll="handleClickTopOperateOutside">
@ -184,6 +229,30 @@
<TaskAdd ref="addTask" v-model="addTaskShow"/>
</Modal>
<!--导出任务统计-->
<Modal
v-model="exportTaskShow"
:title="$L('导出任务统计')"
:mask-closable="false">
<Form ref="exportTask" :model="exportData" label-width="auto" @submit.native.prevent>
<FormItem :label="$L('导出会员')">
<UserInput v-model="exportData.userid" :multiple-max="20" :placeholder="$L('请选择会员')"/>
</FormItem>
<FormItem :label="$L('时间范围')">
<DatePicker
v-model="exportData.time"
type="daterange"
format="yyyy/MM/dd"
style="width:100%"
:placeholder="$L('请选择时间')"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="exportTaskShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="exportLoadIng > 0" @click="onExportTask">{{$L('导出')}}</Button>
</div>
</Modal>
<!--任务详情-->
<Modal
:value="taskId > 0"
@ -248,20 +317,27 @@
import { mapState, mapGetters } from 'vuex'
import TaskDetail from "./manage/components/TaskDetail";
import ProjectArchived from "./manage/components/ProjectArchived";
import notificationKoro from "notification-koro1";
import TeamManagement from "./manage/components/TeamManagement";
import ProjectManagement from "./manage/components/ProjectManagement";
import DrawerOverlay from "../components/DrawerOverlay";
import DragBallComponent from "../components/DragBallComponent";
import TaskAdd from "./manage/components/TaskAdd";
import Report from "./manage/components/Report";
import notificationKoro from "notification-koro1";
import {Store} from "le5le-store";
import UserInput from "../components/UserInput";
export default {
components: {
UserInput,
TaskAdd,
TaskDetail,
Report,
DragBallComponent, DrawerOverlay, ProjectManagement, TeamManagement, ProjectArchived, TaskDetail},
DragBallComponent,
DrawerOverlay,
ProjectManagement,
TeamManagement,
ProjectArchived},
data() {
return {
loadIng: 0,
@ -280,6 +356,13 @@ export default {
addTaskShow: false,
addTaskSubscribe: null,
exportTaskShow: false,
exportLoadIng: 0,
exportData: {
userid: [],
time: [],
},
dialogMsgSubscribe: null,
projectKeyValue: '',
@ -399,21 +482,37 @@ export default {
{path: 'personal', name: '个人设置'},
{path: 'password', name: '密码设置'},
{path: 'clearCache', name: '清除缓存'},
{path: 'system', name: '系统设置', divided: true},
{path: 'version', name: '更新版本', visible: !!this.clientNewVersion},
{path: 'workReport', name: '工作报告', divided: true},
{path: 'allUser', name: '团队管理'},
{path: 'allProject', name: '所有项目'},
{path: 'archivedProject', name: '已归档的项目'}
{path: 'allProject', name: '所有项目', divided: true},
{path: 'archivedProject', name: '已归档的项目'},
{path: 'team', name: '团队管理', divided: true},
{path: 'theme', name: '主题皮肤', divided: true},
{path: 'language', name: this.currentLanguage, divided: true},
{path: 'logout', name: '退出登录', style: {color: '#f40'}, divided: true},
]
} else {
return [
{path: 'personal', name: '个人设置'},
{path: 'password', name: '密码设置'},
{path: 'clearCache', name: '清除缓存'},
{path: 'version', name: '更新版本', divided: true, visible: !!this.clientNewVersion},
{path: 'workReport', name: '工作报告', divided: true},
{path: 'archivedProject', name: '已归档的项目'}
{path: 'archivedProject', name: '已归档的项目'},
{path: 'theme', name: '主题皮肤', divided: true},
{path: 'language', name: this.currentLanguage, divided: true},
{path: 'logout', name: '退出登录', style: {color: '#f40'}, divided: true},
]
}
},
@ -429,7 +528,7 @@ export default {
projectLists() {
const {projectKeyValue, cacheProjects} = this;
const data = cacheProjects.sort((a, b) => {
const data = $A.cloneJSON(cacheProjects).sort((a, b) => {
if (a.top_at || b.top_at) {
return $A.Date(b.top_at) - $A.Date(a.top_at);
}
@ -624,6 +723,9 @@ export default {
case 'archivedProject':
this.archivedProjectShow = true;
return;
case 'exportTask':
this.exportTaskShow = true;
return;
case 'workReport':
if (this.reportUnreadNumber > 0) {
this.reportTabs = "receive";
@ -641,7 +743,7 @@ export default {
window.location.reload()
});
return;
case 'signout':
case 'logout':
$A.modalConfirm({
title: '退出登录',
content: '你确定要登出系统?',
@ -805,11 +907,60 @@ export default {
this.$store.dispatch("call", {
url: 'report/unread',
}).then(({data}) => {
this.reportUnreadNumber = data.total ? data.total : 0;
this.reportUnreadNumber = data.total || 0;
}).catch(() => {});
}, typeof timeout === "number" ? timeout : 1000)
},
handleRightClick(event, item) {
this.handleClickTopOperateOutside();
this.topOperateItem = item;
this.$nextTick(() => {
const projectWrap = this.$refs.projectWrapper;
const projectBounding = projectWrap.getBoundingClientRect();
this.topOperateStyles = {
left: `${event.clientX - projectBounding.left}px`,
top: `${event.clientY - projectBounding.top}px`
};
this.topOperateVisible = true;
})
},
handleClickTopOperateOutside() {
this.topOperateVisible = false;
},
handleTopClick() {
this.$store.dispatch("call", {
url: 'project/top',
data: {
project_id: this.topOperateItem.id,
},
}).then(() => {
this.$store.dispatch("getProjects").catch(() => {});
}).catch(({msg}) => {
$A.modalError(msg, 301);
});
},
onExportTask() {
if (this.exportLoadIng > 0) {
return;
}
this.exportLoadIng++;
this.$store.dispatch("call", {
url: 'project/task/export',
data: this.exportData,
}).then(({data}) => {
this.exportLoadIng--;
this.exportTaskShow = false;
$A.downFile(data.url);
}).catch(({msg}) => {
this.exportLoadIng--;
$A.modalError(msg);
});
},
notificationInit() {
this.notificationClass = new notificationKoro(this.$L("打开通知成功"));
if (this.notificationClass.support) {
@ -870,37 +1021,6 @@ export default {
}
document.addEventListener(visibilityChangeEvent, visibilityChangeListener);
},
handleRightClick(event, item) {
this.handleClickTopOperateOutside();
this.topOperateItem = item;
this.$nextTick(() => {
const projectWrap = this.$refs.projectWrapper;
const projectBounding = projectWrap.getBoundingClientRect();
this.topOperateStyles = {
left: `${event.clientX - projectBounding.left}px`,
top: `${event.clientY - projectBounding.top}px`
};
this.topOperateVisible = true;
})
},
handleClickTopOperateOutside() {
this.topOperateVisible = false;
},
handleTopClick() {
this.$store.dispatch("call", {
url: 'project/top',
data: {
project_id: this.topOperateItem.id,
},
}).then(() => {
this.$store.dispatch("getProjects").catch(() => {});
}).catch(({msg}) => {
$A.modalError(msg, 301);
});
},
}
}
</script>

View File

@ -216,6 +216,7 @@ export default {
this.$Electron.sendMessage('windowRouter', {
name: 'file-msg-' + this.msgData.id,
path: "/single/file/msg/" + this.msgData.id,
userAgent: "/hideenOfficeTitle/",
force: false,
config: {
title: `${this.msgData.msg.name} (${$A.bytesToSize(this.msgData.msg.size)})`,

View File

@ -91,6 +91,7 @@
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
</div>
<!--拖动发送提示-->
<Modal
v-model="pasteShow"
:title="$L(pasteTitle)"

View File

@ -52,8 +52,8 @@
</template>
<Drawio v-else-if="file.type=='drawio'" ref="myFlow" v-model="contentDetail" :title="file.name" @saveData="handleClick('saveBefore')"/>
<Minder v-else-if="file.type=='mind'" ref="myMind" v-model="contentDetail" @saveData="handleClick('saveBefore')"/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" v-model="contentDetail" :ext="file.ext" @saveData="handleClick('saveBefore')"/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail" :documentKey="documentKey"/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" v-model="contentDetail.content" :ext="file.ext" @saveData="handleClick('saveBefore')"/>
</div>
</template>
<div v-if="contentLoad" class="content-load"><Loading/></div>

View File

@ -25,12 +25,12 @@
<div v-if="contentDetail" class="content-body">
<template v-if="file.type=='document'">
<MDPreview v-if="contentDetail.type=='md'" :initialValue="contentDetail.content"/>
<TEditor v-else v-model="contentDetail.content" height="100%" readOnly/>
<TEditor v-else :value="contentDetail.content" height="100%" readOnly/>
</template>
<Drawio v-else-if="file.type=='drawio'" ref="myFlow" v-model="contentDetail" :title="file.name" readOnly/>
<Minder v-else-if="file.type=='mind'" ref="myMind" v-model="contentDetail" readOnly/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail" :code="code" :documentKey="documentKey" readOnly/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" v-model="contentDetail.content" :ext="file.ext" readOnly/>
<Drawio v-else-if="file.type=='drawio'" ref="myFlow" :value="contentDetail" :title="file.name" readOnly/>
<Minder v-else-if="file.type=='mind'" ref="myMind" :value="contentDetail" readOnly/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" :value="contentDetail" :ext="file.ext" readOnly/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" :value="contentDetail" :code="code" :documentKey="documentKey" readOnly/>
</div>
</template>
<div v-if="contentLoad" class="content-load"><Loading/></div>

View File

@ -623,10 +623,8 @@ export default {
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) {
return at1 - at2;
if (a.complete_at || b.complete_at) {
return $A.Date(a.complete_at) - $A.Date(b.complete_at);
}
if (a.sort != b.sort) {
return a.sort - b.sort;

View File

@ -399,7 +399,10 @@
@on-input-paste="msgPasteDrag"/>
<div class="no-send" @click="msgDialog">
<Loading v-if="sendLoad > 0"/>
<Icon v-else type="md-send" />
<template v-else>
<Badge :count="taskDetail.msg_num"/>
<Icon type="md-send" />
</template>
</div>
</div>
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
@ -1164,6 +1167,7 @@ export default {
this.$Electron.sendMessage('windowRouter', {
name: 'file-task-' + file.id,
path: "/single/file/task/" + file.id,
userAgent: "/hideenOfficeTitle/",
force: false,
config: {
title: `${file.name} (${$A.bytesToSize(file.size)})`,

View File

@ -31,16 +31,13 @@
<span v-if="item.share && item.permission == 0" class="readonly">{{$L('只读')}}</span>
</li>
</ul>
<template v-if="shearFirst">
<Button :disabled="shearFirst.pid == pid" size="small" type="primary" @click="shearTo">
<div class="file-shear">
<span>{{ $L('粘贴') }}</span>
"<em>{{ shearFirst.name }}</em>"
<span v-if="shearIds.length > 1">{{ $L('') }}{{ shearIds.length }}{{ $L('个文件') }}</span>
</div>
</Button>
<Button type="primary" size="small" @click="clearShear">{{ $L('取消剪切') }}</Button>
</template>
<Button v-if="shearFirst" :disabled="shearFirst.pid == pid" size="small" type="primary" @click="shearTo">
<div class="file-shear">
<span>{{$L('粘贴')}}</span>
"<em>{{shearFirst.name}}</em>"
<span v-if="shearIds.length > 1">{{$L('')}}{{shearIds.length}}{{$L('个文件')}}</span>
</div>
</Button>
<template v-else-if="selectIds.length > 0">
<Button size="small" type="info" @click="handleContextClick('shearSelect')">
<Icon type="ios-cut" />
@ -60,73 +57,82 @@
</div>
</div>
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
<Table
:columns="columns"
:data="fileList"
:height="tableHeight"
:no-data-text="$L('没有任何文件')"
@on-cell-click="clickRow"
@on-contextmenu="handleContextMenu"
@on-select="handleTableSelect"
@on-select-cancel="handleTableSelect"
@on-select-all-cancel="handleTableSelect"
@on-select-all="handleTableSelect"
context-menu
stripe/>
</div>
<template v-else>
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
<i class="taskfont">&#xe60b;</i>
<p>{{$L('没有任何文件')}}</p>
<div
class="file-drag"
@drop.prevent="filePasteDrag($event, 'drag')"
@dragover.prevent="fileDragOver(true, $event)"
@dragleave.prevent="fileDragOver(false, $event)">
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
<Table
:columns="columns"
:data="fileList"
:height="tableHeight"
:no-data-text="$L('没有任何文件')"
@on-cell-click="clickRow"
@on-contextmenu="handleContextMenu"
@on-select="handleTableSelect"
@on-select-cancel="handleTableSelect"
@on-select-all-cancel="handleTableSelect"
@on-select-all="handleTableSelect"
context-menu
stripe/>
</div>
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
<ul class="clearfix">
<li
v-for="item in fileList"
:class="{
<template v-else>
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
<i class="taskfont">&#xe60b;</i>
<p>{{$L('没有任何文件')}}</p>
</div>
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
<ul class="clearfix">
<li
v-for="item in fileList"
:class="{
shear: shearIds.includes(item.id),
highlight: selectIds.includes(item.id),
}"
@contextmenu.prevent.stop="handleRightClick($event, item)"
@click="openFile(item)">
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
<Checkbox :value="selectIds.includes(item.id)"/>
</div>
<div class="file-menu" @click.stop="handleRightClick($event, item)">
<Icon type="ios-more" />
</div>
<div :class="`no-dark-mode-before file-icon ${item.type}`">
<template v-if="item.share">
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
</UserAvatar>
<div v-else class="share-icon no-dark-mode">
<i class="taskfont">&#xe757;</i>
</div>
</template>
<template v-else-if="isParentShare">
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
</UserAvatar>
</template>
</div>
<div v-if="item._edit" class="file-input">
<Input
:ref="'input_' + item.id"
v-model="item.newname"
size="small"
:disabled="!!item._load"
@on-blur="onBlur(item)"
@on-enter="onEnter(item)"/>
<div v-if="item._load" class="file-load"><Loading/></div>
</div>
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
</li>
</ul>
@contextmenu.prevent.stop="handleRightClick($event, item)"
@click="openFile(item)">
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
<Checkbox :value="selectIds.includes(item.id)"/>
</div>
<div class="file-menu" @click.stop="handleRightClick($event, item)">
<Icon type="ios-more" />
</div>
<div :class="`no-dark-mode-before file-icon ${item.type}`">
<template v-if="item.share">
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
</UserAvatar>
<div v-else class="share-icon no-dark-mode">
<i class="taskfont">&#xe757;</i>
</div>
</template>
<template v-else-if="isParentShare">
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
</UserAvatar>
</template>
</div>
<div v-if="item._edit" class="file-input">
<Input
:ref="'input_' + item.id"
v-model="item.newname"
size="small"
:disabled="!!item._load"
@on-blur="onBlur(item)"
@on-enter="onEnter(item)"/>
<div v-if="item._load" class="file-load"><Loading/></div>
</div>
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
</li>
</ul>
</div>
</template>
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
</div>
</template>
</div>
<div class="file-menu" :style="contextMenuStyles">
<Dropdown
@ -326,6 +332,21 @@
<FileContent v-else v-model="fileShow" :file="fileInfo"/>
</DrawerOverlay>
<!--拖动上传提示-->
<Modal
v-model="pasteShow"
:title="$L(pasteTitle)"
:cancel-text="$L('取消')"
:ok-text="$L('发送')"
:enter-ok="true"
@on-ok="pasteSend">
<div class="dialog-wrapper-paste">
<template v-for="item in pasteItem">
<img v-if="item.type == 'image'" :src="item.result"/>
<div v-else>{{$L('文件')}}: {{item.name}} ({{$A.bytesToSize(item.size)}})</div>
</template>
</div>
</Modal>
</div>
</template>
@ -460,6 +481,11 @@ export default {
shearIds: [],
selectIds: [],
dialogDrag: false,
pasteShow: false,
pasteFile: [],
pasteItem: [],
}
},
@ -540,6 +566,18 @@ export default {
const {navigator} = this;
return !!navigator.find(({share}) => share);
},
pasteTitle() {
const {pasteItem} = this;
let hasImage = pasteItem.find(({type}) => type == 'image')
let hasFile = pasteItem.find(({type}) => type != 'image')
if (hasImage && hasFile) {
return '上传文件/图片'
} else if (hasImage) {
return '上传图片'
}
return '上传文件'
}
},
watch: {
@ -868,7 +906,10 @@ export default {
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
}
},
webPreferences: {
nodeIntegrationInSubFrames: item.type === 'drawio'
},
});
},
@ -1281,8 +1322,59 @@ export default {
this.selectIds = [];
},
clearShear() {
this.shearIds = [];
/********************拖动上传部分************************/
pasteDragNext(e, type) {
let files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
files = Array.prototype.slice.call(files);
if (files.length > 0) {
e.preventDefault();
if (files.length > 0) {
this.pasteFile = [];
this.pasteItem = [];
files.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
}
});
}
}
},
filePasteDrag(e, type) {
this.dialogDrag = false;
this.pasteDragNext(e, type);
},
fileDragOver(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;
}
this.dialogDrag = true;
}
},
pasteSend() {
this.pasteFile.some(file => {
this.$refs.fileUpload.upload(file)
});
},
/********************文件上传部分************************/

View File

@ -3,9 +3,13 @@
<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" :documentKey="documentKey" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<MDPreview v-if="isType('md')" :initialValue="msgDetail.content.content"/>
<TEditor v-else-if="isType('text')" :value="msgDetail.content.content" height="100%" readOnly/>
<Drawio v-else-if="isType('drawio')" v-model="msgDetail.content" :title="msgDetail.msg.name" readOnly/>
<Minder v-else-if="isType('mind')" :value="msgDetail.content" readOnly/>
<AceEditor v-else-if="isType('code')" v-model="msgDetail.content" :ext="msgDetail.msg.ext" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isType('office')" v-model="officeContent" :code="officeCode" :documentKey="documentKey" readOnly/>
<iframe v-else-if="isType('preview')" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
@ -17,6 +21,8 @@
align-items: center;
.preview-iframe,
.ace_editor,
.markdown-preview-warp,
.teditor-wrapper,
.no-support {
position: absolute;
top: 0;
@ -42,13 +48,27 @@
}
</style>
<style lang="scss">
.single-file-msg {
.teditor-wrapper {
.teditor-box {
height: 100%;
}
}
}
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
import Vue from 'vue'
import Minder from '../../components/minder'
Vue.use(Minder)
const MDPreview = () => import('../../components/MDEditor/preview');
const TEditor = () => import('../../components/TEditor');
const AceEditor = () => import('../../components/AceEditor');
const OnlyOffice = () => import('../../components/OnlyOffice');
const Drawio = () => import('../../components/Drawio');
export default {
components: {OnlyOffice, AceEditor},
components: {AceEditor, TEditor, MDPreview, OnlyOffice, Drawio},
data() {
return {
loadIng: 0,
@ -80,47 +100,27 @@ export default {
return "Loading..."
},
isCode() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.msgDetail.content;
isType() {
const {msgDetail} = this;
return function (type) {
return msgDetail.type == 'file' && msgDetail.file_mode == type;
}
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,
id: this.msgDetail.id || 0,
type: this.msgDetail.msg.ext,
name: this.title,
}
},
officeCode() {
if (this.isOffice) {
return "msgFile_" + this.msgDetail.id;
}
return ''
return "msgFile_" + this.msgDetail.id;
},
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 ''
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.msgDetail.content.url))
}
},
methods: {

View File

@ -3,9 +3,13 @@
<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" :documentKey="documentKey" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<MDPreview v-if="isType('md')" :initialValue="fileDetail.content.content"/>
<TEditor v-else-if="isType('text')" :value="fileDetail.content.content" height="100%" readOnly/>
<Drawio v-else-if="isType('drawio')" v-model="fileDetail.content" :title="fileDetail.name" readOnly/>
<Minder v-else-if="isType('mind')" :value="fileDetail.content" readOnly/>
<AceEditor v-else-if="isType('code')" v-model="fileDetail.content" :ext="fileDetail.ext" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isType('office')" v-model="officeContent" :code="officeCode" :documentKey="documentKey" readOnly/>
<iframe v-else-if="isType('preview')" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
@ -17,6 +21,8 @@
align-items: center;
.preview-iframe,
.ace_editor,
.markdown-preview-warp,
.teditor-wrapper,
.no-support {
position: absolute;
top: 0;
@ -42,13 +48,27 @@
}
</style>
<style lang="scss">
.single-file-task {
.teditor-wrapper {
.teditor-box {
height: 100%;
}
}
}
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
import Vue from 'vue'
import Minder from '../../components/minder'
Vue.use(Minder)
const MDPreview = () => import('../../components/MDEditor/preview');
const TEditor = () => import('../../components/TEditor');
const AceEditor = () => import('../../components/AceEditor');
const OnlyOffice = () => import('../../components/OnlyOffice');
const Drawio = () => import('../../components/Drawio');
export default {
components: {OnlyOffice, AceEditor},
components: {AceEditor, TEditor, MDPreview, OnlyOffice, Drawio},
data() {
return {
loadIng: 0,
@ -80,47 +100,27 @@ export default {
return "Loading..."
},
isCode() {
return this.fileDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.fileDetail.content;
isType() {
const {fileDetail} = this;
return function (type) {
return fileDetail.file_mode == type;
}
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,
id: this.fileDetail.id || 0,
type: this.fileDetail.ext,
name: this.title,
}
},
officeCode() {
if (this.isOffice) {
return "taskFile_" + this.fileDetail.id;
}
return ''
return "taskFile_" + this.fileDetail.id;
},
isPreview() {
return this.fileDetail.file_mode == 3;
},
previewUrl() {
if (this.isPreview) {
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.url))
}
return ''
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.content.url))
}
},
methods: {

View File

@ -1628,6 +1628,7 @@ export default {
task_id: task_id
},
}).then(result => {
let task = state.cacheTasks.find(({id}) => id == task_id)
let {data} = result
data.turns.some(item => {
let index = state.taskFlowItems.findIndex(({id}) => id == item.id);
@ -1636,6 +1637,16 @@ export default {
} else {
state.taskFlowItems.push(item);
}
if (task
&& task.flow_item_id == item.id
&& task.flow_item_name != item.name) {
state.cacheTasks.filter(({flow_item_id})=> flow_item_id == item.id).some(task => {
dispatch("saveTask", {
id: task.id,
flow_item_name: `${item.status}|${item.name}`,
})
})
}
})
//
delete data.turns;

View File

@ -44,7 +44,7 @@
max-height: 210px;
overflow-x: hidden;
overflow-y: auto;
margin: 18px 0;
margin-bottom: 16px;
.markdown-preview {
margin: -20px -12px;
h2 {

View File

@ -78,6 +78,11 @@ body.dark-mode-reverse {
.project-list {
.project-head {
.project-titbox {
.project-title {
.top-text {
color: #000000;
}
}
.project-icons {
> li {
&.project-icon {

View File

@ -487,7 +487,7 @@
.dialog-send {
position: absolute;
top: 0;
right: 28px;
right: 14px;
bottom: 0;
font-size: 18px;
width: 46px;
@ -615,10 +615,6 @@
.dialog-footer {
padding: 0 20px;
margin-bottom: 16px;
.dialog-send {
right: 20px;
}
}
}
}

View File

@ -29,7 +29,7 @@
height: 22px;
}
}
.top-text{
.top-text {
width: 40px;
height: 24px;
border-radius:4px;
@ -37,7 +37,7 @@
background-color: #8BCF70;
color:#FFFFFF;
text-align: center;
margin-top: 4px;
margin-top: 3px;
padding-top: 2px
}
}
@ -478,9 +478,9 @@
padding: 0;
margin: 10px 0 0 0;
line-height: 20px;
word-break: break-all;
white-space: pre-wrap;
word-wrap: break-word;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.task-tags {

View File

@ -433,6 +433,7 @@
align-items: center;
margin-top: 6px;
height: 32px;
white-space: nowrap;
> i {
font-size: 14px;
padding-right: 8px;
@ -554,6 +555,12 @@
}
.no-send {
display: none;
.ivu-badge {
position: absolute;
transform: scale(0.6);
top: 5px;
left: 4px;
}
}
}
.drag-over {

View File

@ -90,24 +90,6 @@
}
}
}
.file-no {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: 5%;
opacity: 0.8;
> i {
font-size: 64px;
}
> p {
margin-top: 18px;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
}
.file-navigator {
display: flex;
align-items: center;
@ -254,192 +236,245 @@
}
}
}
.file-table {
.file-drag {
flex: 1;
cursor: default;
margin: 16px 32px 32px;
.ivu-table {
&:before {
display: none;
}
.ivu-table-tip {
opacity: 0.8;
span {
font-size: 14px;
font-weight: 500;
line-height: 1.8;
&:before {
display: block;
content: "\e60b";
font-family: "taskfont", "serif" !important;
font-size: 64px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
}
}
}
}
.file-nbox {
height: 0;
display: flex;
flex-direction: column;
position: relative;
.file-no {
flex: 1;
display: flex;
align-items: center;
position: relative;
&.shear {
opacity: 0.38;
justify-content: center;
flex-direction: column;
margin-bottom: 5%;
opacity: 0.8;
> i {
font-size: 64px;
}
.file-name {
flex: 1;
width: 0;
display: flex;
align-items: center;
position: relative;
margin-right: 46px;
&:before {
flex-shrink: 0;
content: "";
width: 22px;
height: 22px;
margin-right: 8px;
}
}
.permission {
padding-left: 6px;
font-size: 13px;
}
.taskfont {
color: #aaaaaa;
font-size: 16px;
margin: 0 3px;
> p {
margin-top: 18px;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
}
}
.file-list {
flex: 1;
padding: 0 20px 20px;
margin-top: 16px;
overflow: auto;
> ul {
margin-top: -12px;
> li {
list-style: none;
float: left;
margin: 12px;
width: 100px;
height: 110px;
position: relative;
border-radius: 5px;
.file-table {
flex: 1;
cursor: default;
margin: 16px 32px 32px;
.ivu-table {
&:before {
display: none;
}
.ivu-table-tip {
opacity: 0.8;
span {
font-size: 14px;
font-weight: 500;
line-height: 1.8;
&:before {
display: block;
content: "\e60b";
font-family: "taskfont", "serif" !important;
font-size: 64px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
}
}
}
}
.file-nbox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
cursor: pointer;
.file-input {
margin: 0 4px 4px;
position: relative;
input {
margin: 0;
padding: 1px 5px;
font-size: 13px;
}
.file-load {
position: absolute;
top: 0;
right: 6px;
bottom: 0;
display: flex;
.common-loading {
width: 10px;
height: 10px;
}
}
}
.file-name {
display: block;
width: 100%;
height: 20px;
line-height: 20px;
color: $primary-text-color;
font-size: 12px;
text-align: center;
margin-bottom: 5px;
padding: 0 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.file-check {
opacity: 0;
position: absolute;
top: 1px;
left: 4px;
transition: opacity 0.2s;
&.file-checked {
opacity: 1;
}
}
.file-menu {
opacity: 0;
position: absolute;
top: 2px;
right: 2px;
transition: opacity 0.2s;
display: flex;
.ivu-icon {
font-size: 16px;
color: #aaaaaa;
transition: color 0.2s;
padding: 2px 5px;
&:hover {
color: $primary-text-color;
}
}
}
.file-icon {
display: inline-block;
width: 64px;
height: 64px;
margin-top: 12px;
position: relative;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.share-icon,
.share-avatar {
position: absolute;
right: 0;
bottom: 0;
background-color: #9ACD7B;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0.9);
.taskfont {
font-size: 18px;
color: #ffffff;
}
}
}
position: relative;
&.shear {
opacity: 0.38;
}
&.highlight {
background-color: #f4f5f7;
}
&:hover {
background-color: #f4f5f7;
.file-menu,
.file-check {
opacity: 1;
.file-name {
flex: 1;
width: 0;
display: flex;
align-items: center;
position: relative;
margin-right: 46px;
&:before {
flex-shrink: 0;
content: "";
width: 22px;
height: 22px;
margin-right: 8px;
}
}
.permission {
padding-left: 6px;
font-size: 13px;
}
.taskfont {
color: #aaaaaa;
font-size: 16px;
margin: 0 3px;
}
}
}
.file-list {
flex: 1;
padding: 0 20px 20px;
margin-top: 16px;
overflow: auto;
> ul {
margin-top: -12px;
> li {
list-style: none;
float: left;
margin: 12px;
width: 100px;
height: 110px;
position: relative;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
cursor: pointer;
.file-input {
margin: 0 4px 4px;
position: relative;
input {
margin: 0;
padding: 1px 5px;
font-size: 13px;
}
.file-load {
position: absolute;
top: 0;
right: 6px;
bottom: 0;
display: flex;
.common-loading {
width: 10px;
height: 10px;
}
}
}
.file-name {
display: block;
width: 100%;
height: 20px;
line-height: 20px;
color: $primary-text-color;
font-size: 12px;
text-align: center;
margin-bottom: 5px;
padding: 0 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.file-check {
opacity: 0;
position: absolute;
top: 1px;
left: 4px;
transition: opacity 0.2s;
&.file-checked {
opacity: 1;
}
}
.file-menu {
opacity: 0;
position: absolute;
top: 2px;
right: 2px;
transition: opacity 0.2s;
display: flex;
.ivu-icon {
font-size: 16px;
color: #aaaaaa;
transition: color 0.2s;
padding: 2px 5px;
&:hover {
color: $primary-text-color;
}
}
}
.file-icon {
display: inline-block;
width: 64px;
height: 64px;
margin-top: 12px;
position: relative;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.share-icon,
.share-avatar {
position: absolute;
right: 0;
bottom: 0;
background-color: #9ACD7B;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0.9);
.taskfont {
font-size: 18px;
color: #ffffff;
}
}
}
&.shear {
opacity: 0.38;
}
&.highlight {
background-color: #f4f5f7;
}
&:hover {
background-color: #f4f5f7;
.file-menu,
.file-check {
opacity: 1;
}
}
}
}
}
.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;
margin: 16px 32px 32px;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2px dashed #7b7b7b;
border-radius: 12px;
}
.drag-text {
padding: 12px;
font-size: 18px;
color: #666666;
}
}
}
@ -620,8 +655,10 @@
.file-navigator {
margin: 0 24px 0;
}
.file-table {
margin: 16px 24px 24px;
.file-drag {
.file-table {
margin: 16px 24px 24px;
}
}
}
}

View File

@ -87,15 +87,7 @@
transform: scale(0.9);
vertical-align: top;
}
.manage-menu-language {
display: flex;
align-items: center;
justify-content: space-between;
.ivu-icon {
color: #666666;
}
}
.mannage-menu-skin {
.manage-menu-flex {
display: flex;
align-items: center;
justify-content: space-between;
@ -115,13 +107,13 @@
flex-shrink: 0;
display: flex;
align-items: center;
height: 30px;
height: 36px;
color: #6b6e72;
cursor: pointer;
position: relative;
width: 80%;
max-width: 100%;
margin: 8px auto;
margin: 5px auto;
padding: 0 4%;
border-radius: 4px;
> i {
@ -145,16 +137,14 @@
&.active {
background-color: #ffffff;
}
&.top {
background-color: #eeeff1;
}
&.menu-project {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0 0;
padding: 12px 0 0;
cursor: default;
width: 100%;
> ul {
width: 100%;
overflow: auto;
@ -163,7 +153,8 @@
flex-direction: column;
list-style: none;
cursor: pointer;
margin: 3px auto;
width: 80%;
margin: 2px auto;
border: 2px solid transparent;
.project-h1 {
position: relative;
@ -231,6 +222,11 @@
}
}
}
&.top {
.project-h1 {
background-color: #EEEFF1;
}
}
&.active {
.project-h1 {
background-color: #ffffff;
@ -248,11 +244,6 @@
display: block;
}
}
&.top {
.project-h1 {
background-color: #EEEFF1;
}
}
&.operate {
border-color: $primary-color;
}