Compare commits
No commits in common. "master" and "v0.7.73" have entirely different histories.
2
.github/workflows/electron.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
exclude_types: other,chore
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@latest
|
||||
|
1
.gitignore
vendored
@ -19,6 +19,7 @@ Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
test.*
|
||||
composer.lock
|
||||
package-lock.json
|
||||
laravels-timer-process.pid
|
||||
.DS_Store
|
||||
|
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "resources/drawio"]
|
||||
path = resources/drawio
|
||||
url = https://github.com/jgraph/drawio.git
|
@ -21,9 +21,9 @@ Group No.: `546574618`
|
||||
# 1、Clone the repository
|
||||
|
||||
# Clone projects on github
|
||||
git clone --depth=1 https://github.com/kuaifan/dootask.git
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
# Or you can use gitee
|
||||
git clone --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、Enter directory
|
||||
cd dootask
|
||||
|
@ -21,9 +21,9 @@
|
||||
# 1、克隆项目到您的本地或服务器
|
||||
|
||||
# 通过github克隆项目
|
||||
git clone --depth=1 https://github.com/kuaifan/dootask.git
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
# 或者你也可以使用gitee
|
||||
git clone --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、进入目录
|
||||
cd dootask
|
||||
|
@ -2,16 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskFile;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
@ -41,10 +38,9 @@ class DialogController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at'])
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('u.userid', $user->userid)
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('web_socket_dialogs.last_at')
|
||||
->paginate(Base::getPaginate(200, 100));
|
||||
$list->transform(function (WebSocketDialog $item) use ($user) {
|
||||
@ -192,7 +188,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/msg/sendtext 06. 发送消息
|
||||
* @api {get} api/dialog/msg/sendtext 06. 发送消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@ -208,7 +204,6 @@ class DialogController extends AbstractController
|
||||
*/
|
||||
public function msg__sendtext()
|
||||
{
|
||||
Base::checkClientVersion('0.8.1');
|
||||
$user = User::auth();
|
||||
//
|
||||
$chat_nickname = Base::settingFind('system', 'chat_nickname');
|
||||
@ -219,8 +214,8 @@ class DialogController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
$dialog_id = Base::getPostInt('dialog_id');
|
||||
$text = trim(Base::getPostValue('text'));
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
$text = trim(Request::input('text'));
|
||||
//
|
||||
if (mb_strlen($text) < 1) {
|
||||
return Base::retError('消息内容不能为空');
|
||||
@ -230,21 +225,11 @@ class DialogController extends AbstractController
|
||||
//
|
||||
WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
if (mb_strlen($text) > 2000) {
|
||||
$array = mb_str_split($text, 2000);
|
||||
} else {
|
||||
$array = [$text];
|
||||
}
|
||||
$msg = [
|
||||
'text' => $text
|
||||
];
|
||||
//
|
||||
$list = [];
|
||||
foreach ($array as $item) {
|
||||
$res = WebSocketDialogMsg::sendMsg($dialog_id, 'text', ['text' => $item], $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$list[] = $res['data'];
|
||||
}
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('发送成功', $list);
|
||||
return WebSocketDialogMsg::sendMsg($dialog_id, 'text', $msg, $user->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,7 +257,7 @@ class DialogController extends AbstractController
|
||||
//
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
$path = "uploads/chat/" . $user->userid . "/";
|
||||
$image64 = Base::getPostValue('image64');
|
||||
$fileName = Base::getPostValue('filename');
|
||||
if ($image64) {
|
||||
@ -284,7 +269,7 @@ class DialogController extends AbstractController
|
||||
} else {
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
"type" => 'file',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
]);
|
||||
@ -364,9 +349,6 @@ class DialogController extends AbstractController
|
||||
* @apiName msg__detail
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -377,30 +359,39 @@ class DialogController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$msg_id = intval(Request::input('msg_id'));
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
$dialogMsg = WebSocketDialogMsg::whereId($msg_id)->first();
|
||||
if (empty($dialogMsg)) {
|
||||
return Base::retError("文件不存在");
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $dialogMsg->id,
|
||||
'update_at' => Carbon::parse($dialogMsg->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$data = $dialogMsg->toArray();
|
||||
//
|
||||
if ($data['type'] == 'file') {
|
||||
$codeExt = ['txt'];
|
||||
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
|
||||
$msg = File::formatFileData($msg);
|
||||
$data['content'] = $msg['content'];
|
||||
$data['file_mode'] = $msg['file_mode'];
|
||||
$filePath = public_path($msg['path']);
|
||||
if (in_array($msg['ext'], $codeExt) && $msg['size'] < 2 * 1024 * 1024) {
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = file_get_contents($filePath);
|
||||
$data['file_mode'] = 1;
|
||||
} elseif (in_array($msg['ext'], $officeExt)) {
|
||||
// office预览
|
||||
$data['file_mode'] = 2;
|
||||
} else {
|
||||
// 其他预览
|
||||
if (in_array($msg['ext'], $localExt)) {
|
||||
$url = Base::fillUrl($msg['path']);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $msg['path'];
|
||||
}
|
||||
$data['url'] = base64_encode($url);
|
||||
$data['file_mode'] = 3;
|
||||
}
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
return Base::retSuccess("success", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -460,31 +451,4 @@ class DialogController extends AbstractController
|
||||
$msg->deleteMsg();
|
||||
return Base::retSuccess("success");
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/top 12. 会话置顶
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName top
|
||||
*
|
||||
* @apiParam {Number} dialog_id 会话ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function top()
|
||||
{
|
||||
$user = User::auth();
|
||||
$dialogId = intval(Request::input('dialog_id'));
|
||||
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
|
||||
if (!$dialogUser) {
|
||||
return Base::retError("会话不存在");
|
||||
}
|
||||
$dialogUser->top_at = $dialogUser->top_at ? null : Carbon::now();
|
||||
$dialogUser->save();
|
||||
return Base::retSuccess("success", $dialogId);
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,12 @@ use App\Models\FileContent;
|
||||
use App\Models\FileLink;
|
||||
use App\Models\FileUser;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* @apiDefine file
|
||||
@ -206,30 +207,21 @@ class FileController extends AbstractController
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'drawio',
|
||||
'flow',
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
])) {
|
||||
return Base::retError('类型错误');
|
||||
}
|
||||
$ext = str_replace([
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'drawio',
|
||||
$ext = '';
|
||||
if (in_array($type, [
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
], [
|
||||
'',
|
||||
'md',
|
||||
'mind',
|
||||
'drawio',
|
||||
'docx',
|
||||
'xlsx',
|
||||
'pptx',
|
||||
], $type);
|
||||
])) {
|
||||
$ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type);
|
||||
}
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
@ -302,19 +294,9 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$data = AbstractModel::transaction(function() use ($file) {
|
||||
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
|
||||
$file->size = $content?->size ?: 0;
|
||||
$file->save();
|
||||
if ($content) {
|
||||
$content = $content->toArray();
|
||||
$content['fid'] = $file->id;
|
||||
$content['userid'] = $file->userid;
|
||||
FileContent::createInstance($content)->save();
|
||||
}
|
||||
return File::find($file->id);
|
||||
});
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess('复制成功', $data);
|
||||
}
|
||||
@ -327,7 +309,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName move
|
||||
*
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Number} pid 移动到的文件夹ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@ -338,25 +320,15 @@ class FileController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$ids = Request::input('ids');
|
||||
$id = intval(Request::input('id'));
|
||||
$pid = intval(Request::input('pid'));
|
||||
//
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择移动的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能移动100个文件或文件夹');
|
||||
}
|
||||
if ($pid > 0) {
|
||||
File::permissionFind($pid, 1);
|
||||
}
|
||||
//
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($pid, $ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, 1000);
|
||||
//
|
||||
if ($pid > 0) {
|
||||
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$arr = [];
|
||||
$tid = $pid;
|
||||
while ($tid > 0) {
|
||||
@ -364,19 +336,14 @@ class FileController extends AbstractController
|
||||
$tid = intval(File::whereId($tid)->value('pid'));
|
||||
}
|
||||
if (in_array($id, $arr)) {
|
||||
throw new ApiException('移动位置错误');
|
||||
return Base::retError('位置错误');
|
||||
}
|
||||
}
|
||||
//
|
||||
$file->pid = $pid;
|
||||
$file->save();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
foreach ($files as $file) {
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
return Base::retSuccess('操作成功', $files);
|
||||
return Base::retSuccess('操作成功', $file);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -387,7 +354,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName remove
|
||||
*
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
* @apiParam {Number} id 文件ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -397,25 +364,12 @@ class FileController extends AbstractController
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$ids = Request::input('ids');
|
||||
$id = intval(Request::input('id'));
|
||||
//
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择删除的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能删除100个文件或文件夹');
|
||||
}
|
||||
//
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, 1000);
|
||||
$file->deleteFile();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('删除成功', $files);
|
||||
$file->deleteFile();
|
||||
return Base::retSuccess('删除成功', $file);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,9 +383,6 @@ class FileController extends AbstractController
|
||||
* @apiParam {Number|String} id
|
||||
* - Number: 文件ID(需要登录)
|
||||
* - String: 链接码(不需要登录,用于预览)
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
* @apiParam {String} down 直接下载
|
||||
* - no: 浏览(默认)
|
||||
* - yes: 下载(office文件直接下载)
|
||||
@ -444,7 +395,6 @@ class FileController extends AbstractController
|
||||
{
|
||||
$id = Request::input('id');
|
||||
$down = Request::input('down', 'no');
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
if (Base::isNumber($id)) {
|
||||
User::auth();
|
||||
@ -459,13 +409,6 @@ class FileController extends AbstractController
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
return FileContent::formatContent($file, $content?->content, $down == 'yes');
|
||||
}
|
||||
@ -488,7 +431,6 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function content__save()
|
||||
{
|
||||
Base::checkClientVersion('0.9.13');
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = Base::getPostInt('id');
|
||||
@ -502,11 +444,12 @@ class FileController extends AbstractController
|
||||
$isRep = false;
|
||||
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '"', $data['content']);
|
||||
$p = "uploads/files/document/" . $id . "/";
|
||||
Base::makeDir(public_path($p));
|
||||
$p.= md5($text) . "." . $matchs[1][$key];
|
||||
$r = file_put_contents(public_path($p), base64_decode($text));
|
||||
if ($r) {
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($p) . '"', $data['content']);
|
||||
$isRep = true;
|
||||
}
|
||||
}
|
||||
@ -516,41 +459,11 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['content'];
|
||||
$file->ext = $contentArray['type'] == 'md' ? 'md' : 'text';
|
||||
break;
|
||||
case '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('参数错误');
|
||||
}
|
||||
$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' => [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
],
|
||||
'content' => $content,
|
||||
'text' => $text,
|
||||
'size' => filesize($save),
|
||||
'size' => strlen($content),
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$content->save();
|
||||
@ -590,7 +503,7 @@ class FileController extends AbstractController
|
||||
if ($status === 2) {
|
||||
$parse = parse_url($url);
|
||||
$from = 'http://' . env('APP_IPPR') . '.3' . $parse['path'] . '?' . $parse['query'];
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym") . '/' . $file->id . '/' . $key;
|
||||
$path = 'uploads/office/' . date("Ym") . '/' . $file->id . '/' . $user->userid . '-' . $key;
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
$res = Ihttp::download($from, $save);
|
||||
@ -608,7 +521,6 @@ class FileController extends AbstractController
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->updated_at = Carbon::now();
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
@ -652,11 +564,10 @@ class FileController extends AbstractController
|
||||
}
|
||||
//
|
||||
$dirs = explode("/", $webkitRelativePath);
|
||||
AbstractModel::transaction(function() use ($user, $userid, $dirs, &$pid) {
|
||||
while (count($dirs) > 1) {
|
||||
$dirName = array_shift($dirs);
|
||||
if ($dirName) {
|
||||
$pushMsg = [];
|
||||
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$pushMsg) {
|
||||
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
|
||||
if (empty($dirRow)) {
|
||||
$dirRow = File::createInstance([
|
||||
@ -667,21 +578,19 @@ class FileController extends AbstractController
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
if ($dirRow->save()) {
|
||||
$pushMsg[] = File::find($dirRow->id);
|
||||
$tmpRow = File::find($dirRow->id);
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
}
|
||||
}
|
||||
if (empty($dirRow)) {
|
||||
throw new ApiException('创建文件夹失败');
|
||||
}
|
||||
$pid = $dirRow->id;
|
||||
}
|
||||
}
|
||||
});
|
||||
foreach ($pushMsg as $tmpRow) {
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
||||
$path = 'uploads/file/' . date("Ym") . '/u' . $user->userid . '/';
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
@ -694,9 +603,6 @@ class FileController extends AbstractController
|
||||
$data = $data['data'];
|
||||
//
|
||||
$type = match ($data['ext']) {
|
||||
'text', 'md', 'markdown' => 'document',
|
||||
'drawio' => 'drawio',
|
||||
'mind' => 'mind',
|
||||
'doc', 'docx' => "word",
|
||||
'xls', 'xlsx' => "excel",
|
||||
'ppt', 'pptx' => "ppt",
|
||||
@ -710,7 +616,7 @@ class FileController extends AbstractController
|
||||
'txt' => "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',
|
||||
'ocamlmakefile', 'make', 'md', 'markdown', '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',
|
||||
@ -721,9 +627,6 @@ class FileController extends AbstractController
|
||||
'rp' => "axure",
|
||||
default => "",
|
||||
};
|
||||
if ($data['ext'] == 'markdown') {
|
||||
$data['ext'] = 'md';
|
||||
}
|
||||
$file = File::createInstance([
|
||||
'pid' => $pid,
|
||||
'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
|
||||
@ -737,7 +640,6 @@ class FileController extends AbstractController
|
||||
$file->size = $data['size'] * 1024;
|
||||
$file->save();
|
||||
//
|
||||
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => [
|
||||
|
@ -4,7 +4,6 @@ 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;
|
||||
@ -17,13 +16,10 @@ 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
|
||||
@ -208,9 +204,6 @@ class ProjectController extends AbstractController
|
||||
* @apiParam {String} name 项目名称
|
||||
* @apiParam {String} [desc] 项目介绍
|
||||
* @apiParam {String} [columns] 列表,格式:列表名称1,列表名称2
|
||||
* @apiParam {String} [flow] 开启流程
|
||||
* - open: 开启
|
||||
* - close: 关闭(默认)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -222,7 +215,6 @@ class ProjectController extends AbstractController
|
||||
// 项目名称
|
||||
$name = trim(Request::input('name', ''));
|
||||
$desc = trim(Request::input('desc', ''));
|
||||
$flow = trim(Request::input('flow', 'close'));
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
@ -259,7 +251,7 @@ class ProjectController extends AbstractController
|
||||
'desc' => $desc,
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
|
||||
AbstractModel::transaction(function() use ($insertColumns, $project) {
|
||||
$project->save();
|
||||
ProjectUser::createInstance([
|
||||
'project_id' => $project->id,
|
||||
@ -276,10 +268,6 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
$project->dialog_id = $dialog->id;
|
||||
$project->save();
|
||||
//
|
||||
if ($flow == 'open') {
|
||||
$project->addFlow(Base::json2array('[{"id":"-10","name":"\u5f85\u5904\u7406","status":"start","turns":["-10","-11","-12","-13"],"usertype":"add","userlimit":"0","sort":"0"},{"id":"-11","name":"\u8fdb\u884c\u4e2d","status":"progress","turns":["-10","-11","-12","-13"],"usertype":"add","userlimit":"0","sort":"1"},{"id":"-12","name":"\u5df2\u5b8c\u6210","status":"end","turns":["-10","-11","-12","-13"],"usertype":"add","userlimit":"0","sort":"2"},{"id":"-13","name":"\u5df2\u53d6\u6d88","status":"end","turns":["-10","-11","-12","-13"],"usertype":"add","userlimit":"0","sort":"3"}]'));
|
||||
}
|
||||
});
|
||||
//
|
||||
$data = Project::find($project->id);
|
||||
@ -607,14 +595,13 @@ class ProjectController extends AbstractController
|
||||
if (!is_array($item['task'])) continue;
|
||||
$index = 0;
|
||||
foreach ($item['task'] as $task_id) {
|
||||
if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
|
||||
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
'sort' => $index
|
||||
])) {
|
||||
]);
|
||||
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
]);
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
@ -980,154 +967,6 @@ 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. 获取单个任务信息
|
||||
*
|
||||
@ -1247,15 +1086,12 @@ class ProjectController extends AbstractController
|
||||
/**
|
||||
* @api {get} api/project/task/filedetail 23. 获取任务文件详情
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__filedetail
|
||||
*
|
||||
* @apiParam {Number} file_id 文件ID
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -1266,32 +1102,45 @@ class ProjectController extends AbstractController
|
||||
User::auth();
|
||||
//
|
||||
$file_id = intval(Request::input('file_id'));
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
//
|
||||
$file = ProjectTaskFile::find($file_id);
|
||||
if (empty($file)) {
|
||||
return Base::retError("文件不存在");
|
||||
}
|
||||
//
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$data = $file->toArray();
|
||||
$data['path'] = $file->getRawOriginal('path');
|
||||
//
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
ProjectTask::userTask($file->task_id, true, true);
|
||||
//
|
||||
return Base::retSuccess('success', File::formatFileData($data));
|
||||
$codeExt = ['txt'];
|
||||
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
$filePath = public_path($data['path']);
|
||||
if (in_array($data['ext'], $codeExt) && $data['size'] < 2 * 1024 * 1024) {
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = file_get_contents($filePath);
|
||||
$data['file_mode'] = 1;
|
||||
} elseif (in_array($data['ext'], $officeExt)) {
|
||||
// office预览
|
||||
$data['file_mode'] = 2;
|
||||
} else {
|
||||
// 其他预览
|
||||
if (in_array($data['ext'], $localExt)) {
|
||||
$url = Base::fillUrl($data['path']);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $data['path'];
|
||||
}
|
||||
$data['url'] = base64_encode($url);
|
||||
$data['file_mode'] = 3;
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/filedown 24. 下载任务文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__filedown
|
||||
@ -1314,7 +1163,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
try {
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
ProjectTask::userTask($file->task_id, true, true);
|
||||
} catch (\Exception $e) {
|
||||
abort(403, $e->getMessage() ?: "This file not support download.");
|
||||
}
|
||||
@ -1792,7 +1641,95 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
//
|
||||
return Base::retSuccess('保存成功', $project->addFlow($flows));
|
||||
return AbstractModel::transaction(function() use ($project, $flows) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($project->id)->first();
|
||||
if (empty($projectFlow)) {
|
||||
$projectFlow = ProjectFlow::createInstance([
|
||||
'project_id' => $project->id,
|
||||
'name' => 'Default'
|
||||
]);
|
||||
if (!$projectFlow->save()) {
|
||||
throw new ApiException('工作流创建失败');
|
||||
}
|
||||
}
|
||||
//
|
||||
$ids = [];
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||
$usertype = trim($item['usertype']);
|
||||
$userlimit = intval($item['userlimit']);
|
||||
if ($usertype == 'replace' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
|
||||
}
|
||||
if ($usertype == 'merge' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
|
||||
}
|
||||
if ($userlimit && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
|
||||
}
|
||||
$flow = ProjectFlowItem::updateInsert([
|
||||
'id' => $id,
|
||||
'project_id' => $project->id,
|
||||
'flow_id' => $projectFlow->id,
|
||||
], [
|
||||
'name' => trim($item['name']),
|
||||
'status' => trim($item['status']),
|
||||
'sort' => intval($item['sort']),
|
||||
'turns' => $turns,
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
]);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
$idc[$id] = $flow->id;
|
||||
}
|
||||
if ($flow->status == 'start') {
|
||||
$hasStart = true;
|
||||
}
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
throw new ApiException('至少需要1个开始状态');
|
||||
}
|
||||
if (!$hasEnd) {
|
||||
throw new ApiException('至少需要1个结束状态');
|
||||
}
|
||||
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
//
|
||||
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($project->id)->find($projectFlow->id);
|
||||
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||
foreach ($projectFlow->projectFlowItem as $item) {
|
||||
$turns = $item->turns;
|
||||
foreach ($idc as $oid => $nid) {
|
||||
if (in_array($oid, $turns)) {
|
||||
$turns = array_diff($turns, [$oid]);
|
||||
$turns[] = $nid;
|
||||
}
|
||||
}
|
||||
if (!in_array($item->id, $turns)) {
|
||||
$turns[] = $item->id;
|
||||
}
|
||||
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||
sort($turns);
|
||||
$item->turns = $turns;
|
||||
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||
}
|
||||
return Base::retSuccess('保存成功', $projectFlow);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1878,31 +1815,4 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/top 37. 项目置顶
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName top
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function top()
|
||||
{
|
||||
$user = User::auth();
|
||||
$projectId = intval(Request::input('project_id'));
|
||||
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
|
||||
if (!$projectUser) {
|
||||
return Base::retError("项目不存在");
|
||||
}
|
||||
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
|
||||
$projectUser->save();
|
||||
return Base::retSuccess("success", $projectId);
|
||||
}
|
||||
}
|
||||
|
@ -101,16 +101,12 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/priority 03. 任务优先级
|
||||
* @api {post} api/system/priority 03. 获取优先级、保存优先级
|
||||
*
|
||||
* @apiDescription 获取任务优先级、保存任务优先级
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName priority
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,color,days,priority}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@ -150,54 +146,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/column/template 04. 创建项目模板
|
||||
*
|
||||
* @apiDescription 获取创建项目模板、保存创建项目模板
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName column__template
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,columns}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function column__template()
|
||||
{
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
User::auth('admin');
|
||||
$list = Base::getPostValue('list');
|
||||
$array = [];
|
||||
if (empty($list) || !is_array($list)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
foreach ($list AS $item) {
|
||||
if (empty($item['name']) || empty($item['columns'])) {
|
||||
continue;
|
||||
}
|
||||
$array[] = [
|
||||
'name' => $item['name'],
|
||||
'columns' => array_values(array_filter(array_unique(explode(",", $item['columns']))))
|
||||
];
|
||||
}
|
||||
if (empty($array)) {
|
||||
return Base::retError('参数为空');
|
||||
}
|
||||
$setting = Base::setting('columnTemplate', $array);
|
||||
} else {
|
||||
$setting = Base::setting('columnTemplate');
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 05. 获取终端详细信息
|
||||
* @api {get} api/system/get/info 04. 获取终端详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@ -226,7 +175,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ip 06. 获取IP地址
|
||||
* @api {get} api/system/get/ip 05. 获取IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@ -241,7 +190,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/cnip 07. 是否中国IP地址
|
||||
* @api {get} api/system/get/cnip 06. 是否中国IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@ -258,7 +207,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipgcj02 08. 获取IP地址经纬度
|
||||
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@ -275,7 +224,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipinfo 09. 获取IP地址详细信息
|
||||
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@ -292,7 +241,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/imgupload 10. 上传图片
|
||||
* @api {post} api/system/imgupload 09. 上传图片
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@ -315,7 +264,7 @@ class SystemController extends AbstractController
|
||||
if (!$scale[0] && !$scale[1]) {
|
||||
$scale = [2160, 4160, -1];
|
||||
}
|
||||
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
@ -342,7 +291,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/imgview 11. 浏览图片空间
|
||||
* @api {get} api/system/get/imgview 10. 浏览图片空间
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@ -360,7 +309,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$publicPath = "uploads/user/picture/" . User::userid() . "/";
|
||||
$publicPath = "uploads/picture/" . User::userid() . "/";
|
||||
$dirPath = public_path($publicPath);
|
||||
$dirs = $files = [];
|
||||
//
|
||||
@ -438,7 +387,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/fileupload 12. 上传文件
|
||||
* @api {post} api/system/fileupload 11. 上传文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@ -458,7 +407,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
|
@ -21,9 +21,6 @@ class VerifyCsrfToken extends Middleware
|
||||
// 保存任务优先级
|
||||
'api/system/priority/',
|
||||
|
||||
// 保存创建项目列表模板
|
||||
'api/system/column/template/',
|
||||
|
||||
// 添加任务
|
||||
'api/project/task/add/',
|
||||
|
||||
@ -33,9 +30,6 @@ class VerifyCsrfToken extends Middleware
|
||||
// 修改任务
|
||||
'api/project/task/update/',
|
||||
|
||||
// 聊天发文本
|
||||
'api/dialog/msg/sendtext/',
|
||||
|
||||
// 聊天发文件
|
||||
'api/dialog/msg/sendfile/',
|
||||
|
||||
|
@ -153,10 +153,9 @@ 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 = [], &$isInsert = true)
|
||||
public static function updateInsert($where, $update = [], $insert = [])
|
||||
{
|
||||
$row = static::where($where)->first();
|
||||
if (empty($row)) {
|
||||
@ -166,10 +165,8 @@ 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;
|
||||
|
@ -50,39 +50,6 @@ 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
|
||||
@ -272,77 +239,4 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class FileContent extends AbstractModel
|
||||
if (empty($content)) {
|
||||
$content = match ($file->type) {
|
||||
'document' => [
|
||||
"type" => $file->ext,
|
||||
"type" => "md",
|
||||
"content" => "",
|
||||
],
|
||||
default => json_decode('{}'),
|
||||
@ -68,21 +68,25 @@ class FileContent extends AbstractModel
|
||||
if ($download) {
|
||||
abort(403, "This file is empty.");
|
||||
}
|
||||
} else {
|
||||
$path = $content['url'];
|
||||
if ($file->ext) {
|
||||
$res = File::formatFileData([
|
||||
'path' => $path,
|
||||
'ext' => $file->ext,
|
||||
'size' => $file->size,
|
||||
'name' => $file->name,
|
||||
]);
|
||||
$content = $res['content'];
|
||||
} else {
|
||||
$content['preview'] = false;
|
||||
if ($file->ext) {
|
||||
$filePath = public_path($content['url']);
|
||||
if (in_array($file->type, ['txt', 'code']) && $file->size < 2 * 1024 * 1024) {
|
||||
// 支持编辑,限制2M内的文件
|
||||
$content['content'] = file_get_contents($filePath);
|
||||
} else {
|
||||
// 支持预览
|
||||
if (in_array($file->type, ['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;
|
||||
}
|
||||
}
|
||||
if ($download) {
|
||||
$filePath = public_path($path);
|
||||
if (isset($filePath)) {
|
||||
return Response::download($filePath, $name);
|
||||
} else {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
@ -113,7 +112,6 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->leftJoin('project_users', function ($leftJoin) use ($userid) {
|
||||
$leftJoin
|
||||
@ -137,7 +135,6 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->join('project_users', 'projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', $userid);
|
||||
@ -214,7 +211,7 @@ class Project extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
|
||||
return $this->projectUser->pluck('userid')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -353,114 +350,6 @@ class Project extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加工作流
|
||||
* @param $flows
|
||||
* @return mixed
|
||||
*/
|
||||
public function addFlow($flows)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($flows) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($this->id)->first();
|
||||
if (empty($projectFlow)) {
|
||||
$projectFlow = ProjectFlow::createInstance([
|
||||
'project_id' => $this->id,
|
||||
'name' => 'Default'
|
||||
]);
|
||||
if (!$projectFlow->save()) {
|
||||
throw new ApiException('工作流创建失败');
|
||||
}
|
||||
}
|
||||
//
|
||||
$ids = [];
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
$upTaskList = [];
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||
$usertype = trim($item['usertype']);
|
||||
$userlimit = intval($item['userlimit']);
|
||||
if ($usertype == 'replace' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
|
||||
}
|
||||
if ($usertype == 'merge' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
|
||||
}
|
||||
if ($userlimit && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
|
||||
}
|
||||
$flow = ProjectFlowItem::updateInsert([
|
||||
'id' => $id,
|
||||
'project_id' => $this->id,
|
||||
'flow_id' => $projectFlow->id,
|
||||
], [
|
||||
'name' => trim($item['name']),
|
||||
'status' => trim($item['status']),
|
||||
'sort' => intval($item['sort']),
|
||||
'turns' => $turns,
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
], [], $isInsert);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
$idc[$id] = $flow->id;
|
||||
}
|
||||
if ($flow->status == 'start') {
|
||||
$hasStart = true;
|
||||
}
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
if (!$isInsert) {
|
||||
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
throw new ApiException('至少需要1个开始状态');
|
||||
}
|
||||
if (!$hasEnd) {
|
||||
throw new ApiException('至少需要1个结束状态');
|
||||
}
|
||||
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
//
|
||||
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) {
|
||||
$turns = $item->turns;
|
||||
foreach ($idc as $oid => $nid) {
|
||||
if (in_array($oid, $turns)) {
|
||||
$turns = array_diff($turns, [$oid]);
|
||||
$turns[] = $nid;
|
||||
}
|
||||
}
|
||||
if (!in_array($item->id, $turns)) {
|
||||
$turns[] = $item->id;
|
||||
}
|
||||
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||
sort($turns);
|
||||
$item->turns = $turns;
|
||||
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||
}
|
||||
return $projectFlow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
|
@ -508,15 +508,13 @@ class ProjectTask extends AbstractModel
|
||||
{
|
||||
AbstractModel::transaction(function () use ($data, &$updateMarking) {
|
||||
// 判断版本
|
||||
Base::checkClientVersion('0.6.0');
|
||||
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) {
|
||||
throw new ApiException('当前版本过低');
|
||||
}
|
||||
// 主任务
|
||||
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
|
||||
// 工作流
|
||||
if (Arr::exists($data, 'flow_item_id')) {
|
||||
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
|
||||
if (!$isProjectOwner && !$this->isOwner()) {
|
||||
throw new ApiException('仅限项目或任务负责人修改任务状态');
|
||||
}
|
||||
if ($this->flow_item_id == $data['flow_item_id']) {
|
||||
throw new ApiException('任务状态未发生改变');
|
||||
}
|
||||
@ -537,7 +535,8 @@ class ProjectTask extends AbstractModel
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
|
||||
}
|
||||
if ($currentFlowItem->userlimit) {
|
||||
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
|
||||
if (!in_array(User::userid(), $currentFlowItem->userids)
|
||||
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
|
||||
}
|
||||
}
|
||||
@ -585,14 +584,6 @@ 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')) {
|
||||
@ -614,6 +605,7 @@ class ProjectTask extends AbstractModel
|
||||
$this->completeTask(null);
|
||||
}
|
||||
$updateMarking['is_update_project'] = true;
|
||||
return;
|
||||
}
|
||||
// 标题
|
||||
if (Arr::exists($data, 'name') && $this->name != $data['name']) {
|
||||
@ -672,7 +664,6 @@ class ProjectTask extends AbstractModel
|
||||
// 计划时间(原则:子任务时间在主任务时间内)
|
||||
if (Arr::exists($data, 'times')) {
|
||||
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)];
|
||||
$oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : '';
|
||||
$this->start_at = null;
|
||||
$this->end_at = null;
|
||||
$times = $data['times'];
|
||||
@ -736,10 +727,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
});
|
||||
}
|
||||
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$this->addLog("修改{任务}时间", [
|
||||
'change' => [$oldStringAt, $newStringAt]
|
||||
]);
|
||||
$this->addLog("修改{任务}时间");
|
||||
}
|
||||
// 以下紧顶级任务可修改
|
||||
if ($this->parent_id === 0) {
|
||||
@ -809,7 +797,6 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
// 优先级
|
||||
$p = false;
|
||||
$oldPName = $this->p_name;
|
||||
if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) {
|
||||
$this->p_level = intval($data['p_level']);
|
||||
$p = true;
|
||||
@ -823,9 +810,7 @@ class ProjectTask extends AbstractModel
|
||||
$p = true;
|
||||
}
|
||||
if ($p) {
|
||||
$this->addLog("修改{任务}优先级", [
|
||||
'change' => [$oldPName, $this->p_name]
|
||||
]);
|
||||
$this->addLog("修改{任务}优先级");
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
@ -866,7 +851,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
$userids = ProjectTaskUser::whereTaskId($this->id)->orderByDesc('owner')->orderByDesc('id')->pluck('userid')->toArray();
|
||||
$userids = $this->taskUser->pluck('userid')->toArray();
|
||||
$items = ProjectTask::with(['taskUser'])->where('parent_id', $this->id)->whereNull('archived_at')->get();
|
||||
foreach ($items as $item) {
|
||||
$userids = array_merge($userids, $item->taskUser->pluck('userid')->toArray());
|
||||
@ -902,44 +887,6 @@ class ProjectTask extends AbstractModel
|
||||
return $user->owner ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限版本
|
||||
* @param int $level 1-负责人,2-协助人/负责人,3-创建人/协助人/负责人
|
||||
* @return bool
|
||||
*/
|
||||
public function permission($level = 1)
|
||||
{
|
||||
if ($level >= 3 && $this->isCreater()) {
|
||||
return true;
|
||||
}
|
||||
if ($level >= 2 && $this->isAssister()) {
|
||||
return true;
|
||||
}
|
||||
return $this->isOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否创建者
|
||||
* @return bool
|
||||
*/
|
||||
public function isCreater()
|
||||
{
|
||||
return $this->userid == User::userid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否协助人员
|
||||
* @return bool
|
||||
*/
|
||||
public function isAssister()
|
||||
{
|
||||
$row = $this;
|
||||
while ($row->parent_id > 0) {
|
||||
$row = self::find($row->parent_id);
|
||||
}
|
||||
return ProjectTaskUser::whereTaskId($row->id)->whereUserid(User::userid())->whereOwner(0)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否负责人(或者是主任务的负责人)
|
||||
* @return bool
|
||||
@ -1007,16 +954,6 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function archivedTask($archived_at, $isAuto = false)
|
||||
{
|
||||
if (!$this->complete_at) {
|
||||
$flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name');
|
||||
if ($flowItems) {
|
||||
$flowItems = implode(",", array_values(array_unique($flowItems->toArray())));
|
||||
}
|
||||
if (empty($flowItems)) {
|
||||
$flowItems = "已完成";
|
||||
}
|
||||
throw new ApiException('仅限【' . $flowItems . '】状态的任务归档');
|
||||
}
|
||||
AbstractModel::transaction(function () use ($isAuto, $archived_at) {
|
||||
if ($archived_at === null) {
|
||||
// 取消归档
|
||||
@ -1169,11 +1106,11 @@ class ProjectTask extends AbstractModel
|
||||
* 获取任务(会员有任务权限 或 会员存在项目内)
|
||||
* @param int $task_id
|
||||
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
|
||||
* @param int|bool $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人(子任务时如果是主任务负责人也可以)
|
||||
* @param array $with
|
||||
* @return self
|
||||
*/
|
||||
public static function userTask($task_id, $archived = true, $permission = 0, $with = [])
|
||||
public static function userTask($task_id, $archived = true, $mustOwner = 0, $with = [])
|
||||
{
|
||||
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
|
||||
//
|
||||
@ -1199,11 +1136,11 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($permission === 2) {
|
||||
$permission = $task->hasOwner() ? 1 : 0;
|
||||
if ($mustOwner === 2) {
|
||||
$mustOwner = $task->hasOwner() ? 1 : 0;
|
||||
}
|
||||
if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
|
||||
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
|
||||
if (($mustOwner === 1 || $mustOwner === true) && !$task->isOwner() && !$project->owner) {
|
||||
throw new ApiException('仅限项目或任务负责人操作');
|
||||
}
|
||||
//
|
||||
return $task;
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?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
|
||||
{
|
||||
|
||||
}
|
@ -11,7 +11,6 @@ use App\Module\Base;
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $owner 是否负责人
|
||||
* @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,7 +21,6 @@ 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
|
||||
|
@ -181,7 +181,7 @@ class User extends AbstractModel
|
||||
public static function reg($email, $password, $other = [])
|
||||
{
|
||||
//邮箱
|
||||
if (!Base::isEmail($email)) {
|
||||
if (!Base::isMail($email)) {
|
||||
throw new ApiException('请输入正确的邮箱地址');
|
||||
}
|
||||
if (User::email2userid($email) > 0) {
|
||||
|
@ -70,7 +70,11 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
public function getPercentageAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['percentage'])) {
|
||||
$this->generatePercentage();
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
$this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
}
|
||||
return $this->appendattrs['percentage'];
|
||||
}
|
||||
@ -94,22 +98,6 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取占比
|
||||
* @param bool $increment 是否新增阅读数
|
||||
* @return int
|
||||
*/
|
||||
public function generatePercentage($increment = false) {
|
||||
if ($increment) {
|
||||
$this->increment('read');
|
||||
}
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
return $this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
return $this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已送达 同时 告诉发送人已送达
|
||||
* @param $userid
|
||||
@ -139,17 +127,13 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (!$msgRead->read_at) {
|
||||
$msgRead->read_at = Carbon::now();
|
||||
$msgRead->save();
|
||||
$this->generatePercentage(true);
|
||||
$this->increment('read');
|
||||
PushTask::push([
|
||||
'userid' => $this->userid,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'readed',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'read' => $this->read,
|
||||
'percentage' => $this->percentage,
|
||||
],
|
||||
'mode' => 'update',
|
||||
'data' => $this->toArray(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ namespace App\Models;
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @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,7 +16,6 @@ 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
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Tmp;
|
||||
use Cache;
|
||||
@ -88,18 +87,6 @@ class Base
|
||||
return $_A["__static_client_version"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端版本
|
||||
* @param string $min 最小版本
|
||||
* @return void
|
||||
*/
|
||||
public static function checkClientVersion($min)
|
||||
{
|
||||
if (version_compare(Base::getClientVersion(), $min, '<')) {
|
||||
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否域名格式
|
||||
* @param $domain
|
||||
@ -342,15 +329,19 @@ class Base
|
||||
{
|
||||
if (strtolower($charset) == 'utf-8') {
|
||||
if (Base::getStrlen($string) <= $length) return $string;
|
||||
$strcut = Base::utf8Substr($string, $length, $start);
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = Base::utf8Substr($strcut, $length, $start);
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
return $strcut . $dot;
|
||||
} else {
|
||||
$length = $length * 2;
|
||||
if (strlen($string) <= $length) return $string;
|
||||
$string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
|
||||
}
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
}
|
||||
return $strcut . $dot;
|
||||
}
|
||||
@ -808,31 +799,6 @@ 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
|
||||
@ -988,7 +954,7 @@ class Base
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return int
|
||||
*/
|
||||
public static function isEmail($str)
|
||||
public static function isMail($str)
|
||||
{
|
||||
$RegExp = '/^[a-z0-9][a-z\.0-9-_]+@[a-z0-9_-]+(?:\.[a-z]{0,3}\.[a-z]{0,2}|\.[a-z]{0,3}|\.[a-z]{0,2})$/i';
|
||||
return preg_match($RegExp, $str);
|
||||
@ -2270,7 +2236,7 @@ class Base
|
||||
$type = ['zip'];
|
||||
break;
|
||||
case 'file':
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'];
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'];
|
||||
break;
|
||||
case 'firmware':
|
||||
$type = ['img', 'tar', 'bin'];
|
||||
@ -2280,9 +2246,6 @@ class Base
|
||||
break;
|
||||
case 'more':
|
||||
$type = [
|
||||
'text', 'md', 'markdown',
|
||||
'drawio',
|
||||
'mind',
|
||||
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
|
||||
@ -2293,7 +2256,7 @@ class Base
|
||||
'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',
|
||||
'ocamlmakefile', 'make', 'md', 'markdown', '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',
|
||||
@ -2423,37 +2386,6 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件移动
|
||||
* @param array $uploadResult
|
||||
* @param string $newPath "/" 结尾
|
||||
* @return array
|
||||
*/
|
||||
public static function uploadMove($uploadResult, $newPath)
|
||||
{
|
||||
if (str_ends_with($newPath, "/") && file_exists($uploadResult['file'])) {
|
||||
Base::makeDir(public_path($newPath));
|
||||
$oldPath = dirname($uploadResult['path']) . "/";
|
||||
$newFile = str_replace($oldPath, $newPath, $uploadResult['file']);
|
||||
if (rename($uploadResult['file'], $newFile)) {
|
||||
$oldUrl = $uploadResult['url'];
|
||||
$uploadResult['file'] = $newFile;
|
||||
$uploadResult['path'] = str_replace($oldPath, $newPath, $uploadResult['path']);
|
||||
$uploadResult['url'] = str_replace($oldPath, $newPath, $uploadResult['url']);
|
||||
if ($uploadResult['thumb'] == $oldUrl) {
|
||||
$uploadResult['thumb'] = $uploadResult['url'];
|
||||
} elseif ($uploadResult['thumb']) {
|
||||
$oldThumb = substr($uploadResult['thumb'], strpos($uploadResult['thumb'], $newPath));
|
||||
$newThumb = str_replace($oldPath, $newPath, $oldThumb);
|
||||
if (file_exists(public_path($oldThumb)) && rename(public_path($oldThumb), public_path($newThumb))) {
|
||||
$uploadResult['thumb'] = str_replace($oldPath, $newPath, $uploadResult['thumb']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uploadResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缩略图
|
||||
* @param string $src_img 源图绝对完整地址{带文件名及后缀名}
|
||||
@ -2963,19 +2895,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
@ -1,144 +0,0 @@
|
||||
<?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) . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use Maatwebsite\Excel\Concerns\ToArray;
|
||||
|
||||
class BillImport implements ToArray
|
||||
{
|
||||
public function Array(Array $tables)
|
||||
{
|
||||
return $tables;
|
||||
}
|
||||
|
||||
}
|
@ -127,11 +127,9 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
case 'readMsg':
|
||||
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
|
||||
$userid = $this->getUserid($frame->fd);
|
||||
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
|
||||
/** @var WebSocketDialogMsg $item */
|
||||
foreach ($list as $item) {
|
||||
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
|
||||
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
|
||||
$item->readSuccess($userid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
|
43
cmd
@ -13,7 +13,6 @@ Error="${Red}[错误]${Font}"
|
||||
|
||||
cur_path="$(pwd)"
|
||||
cur_arg=$@
|
||||
COMPOSE="docker-compose"
|
||||
|
||||
judge() {
|
||||
if [[ 0 -eq $? ]]; then
|
||||
@ -56,24 +55,15 @@ check_docker() {
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
docker-compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
docker compose version &> /dev/null
|
||||
docker-compose --version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
COMPOSE="docker compose"
|
||||
fi
|
||||
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
|
||||
$COMPOSE version
|
||||
echo -e "${Error} ${RedBG} Docker-compose 版本过低,请升级至v2+!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_node() {
|
||||
npm --version &> /dev/null
|
||||
npm --version > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装nodejs!${Font}"
|
||||
exit 1
|
||||
@ -81,7 +71,7 @@ check_node() {
|
||||
}
|
||||
|
||||
docker_name() {
|
||||
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
}
|
||||
|
||||
run_compile() {
|
||||
@ -265,19 +255,16 @@ if [ $# -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
# 初始化文件
|
||||
if [[ -n "$(arg_get relock)" ]]; then
|
||||
rm -rf node_modules
|
||||
rm -rf package-lock.json
|
||||
rm -rf vendor
|
||||
rm -rf composer.lock
|
||||
fi
|
||||
rm -rf package-lock.json
|
||||
mkdir -p "${cur_path}/docker/log/supervisor"
|
||||
mkdir -p "${cur_path}/docker/mysql/data"
|
||||
chmod -R 775 "${cur_path}/docker/log/supervisor"
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
# 启动容器
|
||||
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
|
||||
$COMPOSE up php -d
|
||||
docker-compose up -d
|
||||
docker-compose restart php
|
||||
# 安装composer依赖
|
||||
run_exec php "composer install"
|
||||
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
|
||||
@ -305,8 +292,8 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "php artisan migrate --seed"
|
||||
# 设置初始化密码
|
||||
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
|
||||
$COMPOSE up -d
|
||||
supervisorctl_restart php
|
||||
docker-compose stop
|
||||
docker-compose start
|
||||
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
echo -e "$res"
|
||||
@ -319,7 +306,7 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "composer update"
|
||||
run_exec php "php artisan migrate"
|
||||
supervisorctl_restart php
|
||||
$COMPOSE up -d
|
||||
docker-compose up -d
|
||||
elif [[ "$1" == "uninstall" ]]; then
|
||||
shift 1
|
||||
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
|
||||
@ -333,7 +320,7 @@ if [ $# -gt 0 ]; then
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
$COMPOSE down
|
||||
docker-compose down
|
||||
rm -rf "./docker/mysql/data"
|
||||
rm -rf "./docker/log/supervisor"
|
||||
find "./storage/logs" -name "*.log" | xargs rm -rf
|
||||
@ -346,7 +333,7 @@ if [ $# -gt 0 ]; then
|
||||
elif [[ "$1" == "port" ]]; then
|
||||
shift 1
|
||||
env_set APP_PORT "$1"
|
||||
$COMPOSE up -d
|
||||
docker-compose up -d
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
elif [[ "$1" == "repassword" ]]; then
|
||||
@ -419,11 +406,11 @@ if [ $# -gt 0 ]; then
|
||||
e="./vendor/bin/phpunit $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "restart" ]]; then
|
||||
shift 1
|
||||
$COMPOSE stop "$@"
|
||||
$COMPOSE start "$@"
|
||||
docker-compose stop "$@"
|
||||
docker-compose start "$@"
|
||||
else
|
||||
$COMPOSE "$@"
|
||||
docker-compose "$@"
|
||||
fi
|
||||
else
|
||||
$COMPOSE ps
|
||||
docker-compose ps
|
||||
fi
|
||||
|
9554
composer.lock
generated
@ -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();
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class InsertSettingColumnTemplate extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$array = \App\Module\Base::setting('columnTemplate');
|
||||
if (empty($array)) {
|
||||
\App\Module\Base::setting('columnTemplate', [
|
||||
[
|
||||
'name' => '软件开发',
|
||||
'columns' => ['产品规划', '前端开发', '后端开发', '测试', '发布', '其他'],
|
||||
],
|
||||
[
|
||||
'name' => '产品开发',
|
||||
'columns' => ['产品计划', '正在设计', '正在研发', '测试', '准备发布', '发布成功'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WebSocketDialogUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('userid')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateType extends Migration
|
||||
{
|
||||
/**
|
||||
* 更改流程图文件类型
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereType('flow')->update([
|
||||
'type' => 'drawio'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereType('drawio')->update([
|
||||
'type' => 'flow'
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateExt extends Migration
|
||||
{
|
||||
/**
|
||||
* 更新后缀
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
|
||||
/** @var File $file */
|
||||
foreach ($files as $file) {
|
||||
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$contentArray = Base::json2array($fileContent?->content);
|
||||
$contentString = '';
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$file->ext = $contentArray['type'] ?: 'md';
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
case 'drawio':
|
||||
$file->ext = 'drawio';
|
||||
$contentString = $contentArray['xml'];
|
||||
break;
|
||||
case 'mind':
|
||||
$file->ext = 'mind';
|
||||
$contentString = $fileContent?->content;
|
||||
break;
|
||||
}
|
||||
$file->save();
|
||||
//
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
$content = [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
];
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => $fileContent?->text,
|
||||
'size' => $file->size,
|
||||
'userid' => $file->userid,
|
||||
]);
|
||||
$content->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereIn('ext', ['mind', 'drawio', 'md'])->update([
|
||||
'ext' => ''
|
||||
]);
|
||||
// ... 退回去意义不大,文件内容不做回滚操作
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ProjectUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('owner')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?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) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
@ -599,5 +599,7 @@ curl -O https://task.hitosea.com/uploads/files/3/202105/ba786dfc2f4c2fe916880474
|
||||
'deleted_at' => NULL,
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,6 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class FilesTableSeeder extends Seeder
|
||||
@ -207,7 +203,7 @@ class FilesTableSeeder extends Seeder
|
||||
'pid' => 0,
|
||||
'cid' => 0,
|
||||
'name' => '流程图',
|
||||
'type' => 'drawio',
|
||||
'type' => 'flow',
|
||||
'ext' => '',
|
||||
'size' => 5418,
|
||||
'userid' => 1,
|
||||
@ -284,47 +280,5 @@ class FilesTableSeeder extends Seeder
|
||||
));
|
||||
|
||||
|
||||
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
|
||||
/** @var File $file */
|
||||
foreach ($files as $file) {
|
||||
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$contentArray = Base::json2array($fileContent?->content);
|
||||
$contentString = '';
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$file->ext = $contentArray['type'] ?: 'md';
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
case 'drawio':
|
||||
$file->ext = 'drawio';
|
||||
$contentString = $contentArray['xml'];
|
||||
break;
|
||||
case 'mind':
|
||||
$file->ext = 'mind';
|
||||
$contentString = $fileContent?->content;
|
||||
break;
|
||||
}
|
||||
$file->save();
|
||||
//
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
$content = [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
];
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => $fileContent?->text,
|
||||
'size' => $file->size,
|
||||
'userid' => $file->userid,
|
||||
]);
|
||||
$content->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,14 @@ class SettingsTableSeeder extends Seeder
|
||||
{
|
||||
|
||||
|
||||
if (\DB::table('settings')->where('name', 'system')->count() > 0) {
|
||||
if (\DB::table('settings')->count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
\DB::table('settings')->insert(array (
|
||||
0 =>
|
||||
array (
|
||||
'id' => 1,
|
||||
'name' => 'system',
|
||||
'desc' => '',
|
||||
'setting' => '{"reg":"open","project_invite":"open","login_code":"auto"}',
|
||||
@ -31,6 +32,7 @@ class SettingsTableSeeder extends Seeder
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'id' => 2,
|
||||
'name' => 'priority',
|
||||
'desc' => '',
|
||||
'setting' => '[{"name":"\\u91cd\\u8981\\u4e14\\u7d27\\u6025","color":"#ED4014","days":1,"priority":1},{"name":"\\u91cd\\u8981\\u4e0d\\u7d27\\u6025","color":"#F16B62","days":3,"priority":2},{"name":"\\u7d27\\u6025\\u4e0d\\u91cd\\u8981","color":"#19C919","days":5,"priority":3},{"name":"\\u4e0d\\u91cd\\u8981\\u4e0d\\u7d27\\u6025","color":"#2D8CF0","days":0,"priority":4}]',
|
||||
|
@ -45,8 +45,6 @@ services:
|
||||
- php
|
||||
- office
|
||||
- fileview
|
||||
- drawio-webapp
|
||||
- drawio-export
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
@ -82,16 +80,13 @@ services:
|
||||
|
||||
office:
|
||||
container_name: "dootask-office-${APP_ID}"
|
||||
image: "onlyoffice/documentserver:7.0.0.132"
|
||||
image: "onlyoffice/documentserver:6.4.2.6"
|
||||
volumes:
|
||||
- ./docker/office/data:/var/www/onlyoffice/Data
|
||||
- ./docker/office/logs:/var/log/onlyoffice
|
||||
- ./docker/office/resources/documenteditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/documenteditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/presentationeditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/presentationeditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/spreadsheeteditor/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/spreadsheeteditor/main/resources/css/app.css
|
||||
- ./docker/office/resources/documenteditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/documenteditor/mobile/css/app.css
|
||||
- ./docker/office/resources/presentationeditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/presentationeditor/mobile/css/app.css
|
||||
- ./docker/office/resources/spreadsheeteditor/mobile/css/app.css:/var/www/onlyoffice/documentserver/web-apps/apps/spreadsheeteditor/mobile/css/app.css
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
networks:
|
||||
@ -110,36 +105,6 @@ services:
|
||||
ipv4_address: "${APP_IPPR}.7"
|
||||
restart: unless-stopped
|
||||
|
||||
drawio-webapp:
|
||||
container_name: "dootask-drawio-webapp-${APP_ID}"
|
||||
image: "jgraph/drawio:16.6.1"
|
||||
volumes:
|
||||
- ./docker/drawio/webapp/index.html:/usr/local/tomcat/webapps/draw/index.html
|
||||
- ./docker/drawio/webapp/stencils:/usr/local/tomcat/webapps/draw/stencils
|
||||
- ./docker/drawio/webapp/js/app.min.js:/usr/local/tomcat/webapps/draw/js/app.min.js
|
||||
- ./docker/drawio/webapp/js/croppie/croppie.min.css:/usr/local/tomcat/webapps/draw/js/croppie/croppie.min.css
|
||||
- ./docker/drawio/webapp/js/diagramly/ElectronApp.js:/usr/local/tomcat/webapps/draw/js/diagramly/ElectronApp.js
|
||||
networks:
|
||||
extnetwork:
|
||||
ipv4_address: "${APP_IPPR}.8"
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
depends_on:
|
||||
- drawio-export
|
||||
restart: unless-stopped
|
||||
|
||||
drawio-export:
|
||||
container_name: "dootask-drawio-export-${APP_ID}"
|
||||
image: "jgraph/export-server"
|
||||
networks:
|
||||
extnetwork:
|
||||
ipv4_address: "${APP_IPPR}.9"
|
||||
environment:
|
||||
TZ: "Asia/Shanghai"
|
||||
volumes:
|
||||
- ./docker/drawio/export/fonts:/usr/share/fonts/drawio
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
extnetwork:
|
||||
name: "dootask-networks-${APP_ID}"
|
||||
|
1
docker/drawio/export/fonts/.gitignore
vendored
@ -1 +0,0 @@
|
||||
|
@ -1,17 +0,0 @@
|
||||
# Change
|
||||
|
||||
## js/diagramly/ElectronApp.js
|
||||
|
||||
- 隐藏文件中的无用菜单
|
||||
|
||||
## js/app.min.js
|
||||
|
||||
- 隐藏帮助菜单
|
||||
- 取消未保存关闭窗口提示
|
||||
- `EmbedFile.prototype.getTitle=...` 改 `EmbedFile.prototype.getTitle=function(){return this.desc.title||(urlParams.title?decodeURIComponent(urlParams.title):"")}`
|
||||
- `c.insertTemplateEnabled&&!c.isOffline()&&this.addMenuItems(b,["insertTemplate"],d)` 改 `c.insertTemplateEnabled&&this.addMenuItems(b,["insertTemplate"],d)`
|
||||
- `390:270` 改 `390:285`
|
||||
|
||||
## index.html
|
||||
|
||||
- 隐藏加载中的提示
|
@ -1,477 +0,0 @@
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5" ><![endif]-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Flowchart Maker & Online Diagram Software</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="Description" content="diagrams.net is free online diagram software for making flowcharts, process diagrams, org charts, UML, ER and network diagrams">
|
||||
<meta name="Keywords" content="diagram, online, flow chart, flowchart maker, uml, erd">
|
||||
<meta itemprop="name" content="diagrams.net - free flowchart maker and diagrams online">
|
||||
<meta itemprop="description" content="diagrams.net is a free online diagramming application and flowchart maker . You can use it to create UML, entity relationship,
|
||||
org charts, BPMN and BPM, database schema and networks. Also possible are telecommunication network, workflow, flowcharts, maps overlays and GIS, electronic
|
||||
circuit and social network diagrams.">
|
||||
<meta itemprop="image" content="https://lh4.googleusercontent.com/-cLKEldMbT_E/Tx8qXDuw6eI/AAAAAAAAAAs/Ke0pnlk8Gpg/w500-h344-k/BPMN%2Bdiagram%2Brc2f.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="msapplication-config" content="images/browserconfig.xml">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#d89000">
|
||||
<script type="text/javascript">
|
||||
window.EXPORT_URL = window.location.origin + "/drawio/export/";
|
||||
window.DRAWIO_LIGHTBOX_URL = window.location.origin + "/drawio/webapp";
|
||||
|
||||
/**
|
||||
* URL Parameters and protocol description are here:
|
||||
*
|
||||
* https://desk.draw.io/support/solutions/articles/16000042546-what-url-parameters-are-supported
|
||||
*
|
||||
* Parameters for developers:
|
||||
*
|
||||
* - dev=1: For developers only
|
||||
* - test=1: For developers only
|
||||
* - export=URL for export: For developers only
|
||||
* - ignoremime=1: For developers only (see DriveClient.js). Use Cmd-S to override mime.
|
||||
* - createindex=1: For developers only (see etc/build/README)
|
||||
* - filesupport=0: For developers only (see Editor.js in core)
|
||||
* - savesidebar=1: For developers only (see Sidebar.js)
|
||||
* - pages=1: For developers only (see Pages.js)
|
||||
* - lic=email: For developers only (see LicenseServlet.java)
|
||||
* --
|
||||
* - networkshapes=1: For testing network shapes (temporary)
|
||||
*/
|
||||
var urlParams = (function()
|
||||
{
|
||||
var result = new Object();
|
||||
var params = window.location.search.slice(1).split('&');
|
||||
|
||||
for (var i = 0; i < params.length; i++)
|
||||
{
|
||||
idx = params[i].indexOf('=');
|
||||
|
||||
if (idx > 0)
|
||||
{
|
||||
result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
})();
|
||||
|
||||
// Forces CDN caches by passing URL parameters via URL hash
|
||||
if (window.location.hash != null && window.location.hash.substring(0, 2) == '#P')
|
||||
{
|
||||
try
|
||||
{
|
||||
urlParams = JSON.parse(decodeURIComponent(window.location.hash.substring(2)));
|
||||
|
||||
if (urlParams.hash != null)
|
||||
{
|
||||
window.location.hash = urlParams.hash;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Global variable for desktop
|
||||
var mxIsElectron = window && window.process && window.process.type;
|
||||
|
||||
// Redirects page if required
|
||||
if (urlParams['dev'] != '1')
|
||||
{
|
||||
(function()
|
||||
{
|
||||
var proto = window.location.protocol;
|
||||
|
||||
if (!mxIsElectron)
|
||||
{
|
||||
var host = window.location.host;
|
||||
|
||||
// Redirects apex, drive and rt to www
|
||||
if (host === 'draw.io' || host === 'rt.draw.io' || host === 'drive.draw.io')
|
||||
{
|
||||
host = 'www.draw.io';
|
||||
}
|
||||
|
||||
var href = proto + '//' + host + window.location.href.substring(
|
||||
window.location.protocol.length +
|
||||
window.location.host.length + 2);
|
||||
|
||||
// Redirects if href changes
|
||||
if (href != window.location.href)
|
||||
{
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds meta tag to the page.
|
||||
*/
|
||||
function mxmeta(name, content, httpEquiv)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = document.createElement('meta');
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
s.setAttribute('name', name);
|
||||
}
|
||||
|
||||
s.setAttribute('content', content);
|
||||
|
||||
if (httpEquiv != null)
|
||||
{
|
||||
s.setAttribute('http-equiv', httpEquiv);
|
||||
}
|
||||
|
||||
var t = document.getElementsByTagName('meta')[0];
|
||||
t.parentNode.insertBefore(s, t);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously adds scripts to the page.
|
||||
*/
|
||||
function mxscript(src, onLoad, id, dataAppKey, noWrite)
|
||||
{
|
||||
var defer = onLoad == null && !noWrite;
|
||||
|
||||
if ((urlParams['dev'] != '1' && typeof document.createElement('canvas').getContext === "function") ||
|
||||
onLoad != null || noWrite)
|
||||
{
|
||||
var s = document.createElement('script');
|
||||
s.setAttribute('type', 'text/javascript');
|
||||
s.setAttribute('defer', 'true');
|
||||
s.setAttribute('src', src);
|
||||
|
||||
if (id != null)
|
||||
{
|
||||
s.setAttribute('id', id);
|
||||
}
|
||||
|
||||
if (dataAppKey != null)
|
||||
{
|
||||
s.setAttribute('data-app-key', dataAppKey);
|
||||
}
|
||||
|
||||
if (onLoad != null)
|
||||
{
|
||||
var r = false;
|
||||
|
||||
s.onload = s.onreadystatechange = function()
|
||||
{
|
||||
if (!r && (!this.readyState || this.readyState == 'complete'))
|
||||
{
|
||||
r = true;
|
||||
onLoad();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var t = document.getElementsByTagName('script')[0];
|
||||
|
||||
if (t != null)
|
||||
{
|
||||
t.parentNode.insertBefore(s, t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
document.write('<script src="' + src + '"' + ((id != null) ? ' id="' + id +'" ' : '') +
|
||||
((dataAppKey != null) ? ' data-app-key="' + dataAppKey +'" ' : '') + '></scr' + 'ipt>');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously adds scripts to the page.
|
||||
*/
|
||||
function mxinclude(src)
|
||||
{
|
||||
var g = document.createElement('script');
|
||||
g.type = 'text/javascript';
|
||||
g.async = true;
|
||||
g.src = src;
|
||||
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(g, s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds meta tags with application name (depends on offline URL parameter)
|
||||
*/
|
||||
(function()
|
||||
{
|
||||
var name = 'diagrams.net';
|
||||
mxmeta('apple-mobile-web-app-title', name);
|
||||
mxmeta('application-name', name);
|
||||
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxmeta(null, 'default-src \'self\' \'unsafe-inline\'; connect-src \'self\' https://*.draw.io https://fonts.googleapis.com https://fonts.gstatic.com; img-src * data:; media-src *; font-src *; style-src-elem \'self\' \'unsafe-inline\' https://fonts.googleapis.com', 'Content-Security-Policy');
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks for local storage
|
||||
var isLocalStorage = false;
|
||||
|
||||
try
|
||||
{
|
||||
isLocalStorage = urlParams['local'] != '1' && typeof(localStorage) != 'undefined';
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var mxScriptsLoaded = false, mxWinLoaded = false;
|
||||
|
||||
function checkAllLoaded()
|
||||
{
|
||||
if (mxScriptsLoaded && mxWinLoaded)
|
||||
{
|
||||
App.main();
|
||||
}
|
||||
};
|
||||
|
||||
var t0 = new Date();
|
||||
|
||||
// Changes paths for local development environment
|
||||
if (urlParams['dev'] == '1')
|
||||
{
|
||||
// Used to request grapheditor/mxgraph sources in dev mode
|
||||
var mxDevUrl = document.location.protocol + '//devhost.jgraph.com/drawio/src/main';
|
||||
|
||||
// Used to request draw.io sources in dev mode
|
||||
var drawDevUrl = document.location.protocol + '//devhost.jgraph.com/drawio/src/main/webapp/';
|
||||
var geBasePath = drawDevUrl + '/js/grapheditor';
|
||||
var mxBasePath = mxDevUrl + '/mxgraph';
|
||||
|
||||
if (document.location.protocol == 'file:')
|
||||
{
|
||||
geBasePath = './js/grapheditor';
|
||||
mxBasePath = './mxgraph';
|
||||
drawDevUrl = './';
|
||||
|
||||
// Forces includes for dev environment in node.js
|
||||
mxForceIncludes = true;
|
||||
}
|
||||
|
||||
mxscript(drawDevUrl + 'js/PreConfig.js');
|
||||
mxscript(drawDevUrl + 'js/diagramly/Init.js');
|
||||
mxscript(geBasePath + '/Init.js');
|
||||
mxscript(mxBasePath + '/mxClient.js');
|
||||
|
||||
// Adds all JS code that depends on mxClient. This indirection via Devel.js is
|
||||
// required in some browsers to make sure mxClient.js (and the files that it
|
||||
// loads asynchronously) are available when the code loaded in Devel.js runs.
|
||||
mxscript(drawDevUrl + 'js/diagramly/Devel.js');
|
||||
|
||||
// Electron
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxscript('js/diagramly/DesktopLibrary.js');
|
||||
mxscript('js/diagramly/ElectronApp.js');
|
||||
}
|
||||
|
||||
mxscript(drawDevUrl + 'js/PostConfig.js');
|
||||
}
|
||||
else
|
||||
{
|
||||
(function()
|
||||
{
|
||||
var hostName = window.location.hostname;
|
||||
|
||||
// Supported domains are *.draw.io and the packaged version in Quip
|
||||
var supportedDomain = (hostName.substring(hostName.length - 8, hostName.length) === '.draw.io') ||
|
||||
(hostName.substring(hostName.length - 13, hostName.length) === '.diagrams.net');
|
||||
|
||||
function loadAppJS()
|
||||
{
|
||||
mxscript('js/app.min.js', function()
|
||||
{
|
||||
mxScriptsLoaded = true;
|
||||
checkAllLoaded();
|
||||
|
||||
// Electron
|
||||
if (mxIsElectron)
|
||||
{
|
||||
mxscript('js/diagramly/DesktopLibrary.js', function()
|
||||
{
|
||||
mxscript('js/diagramly/ElectronApp.js', function()
|
||||
{
|
||||
mxscript('js/extensions.min.js', function()
|
||||
{
|
||||
mxscript('js/stencils.min.js', function()
|
||||
{
|
||||
mxscript('js/shapes-14-6-5.min.js', function()
|
||||
{
|
||||
mxscript('js/PostConfig.js');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (!supportedDomain)
|
||||
{
|
||||
mxscript('js/PostConfig.js');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!supportedDomain || mxIsElectron)
|
||||
{
|
||||
mxscript('js/PreConfig.js', loadAppJS);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadAppJS();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// Adds basic error handling
|
||||
window.onerror = function()
|
||||
{
|
||||
var status = document.getElementById('geStatus');
|
||||
|
||||
if (status != null)
|
||||
{
|
||||
status.innerHTML = 'Page could not be loaded. Please try refreshing.';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/plgmlhohecdddhbmmkncjdmlhcmaachm">
|
||||
<link rel="stylesheet" type="text/css" href="js/croppie/croppie.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="styles/grapheditor.css">
|
||||
<link rel="preconnect" href="https://storage.googleapis.com">
|
||||
<link rel="canonical" href="https://app.diagrams.net">
|
||||
<link rel="manifest" href="images/manifest.json">
|
||||
<style type="text/css">
|
||||
body { overflow:hidden; }
|
||||
div.picker { z-index: 10007; }
|
||||
.geSidebarContainer .geTitle input {
|
||||
font-size:8pt;
|
||||
color:#606060;
|
||||
}
|
||||
.geBlock {
|
||||
display: none;
|
||||
z-index:-3;
|
||||
margin:100px;
|
||||
margin-top:40px;
|
||||
margin-bottom:30px;
|
||||
padding:20px;
|
||||
text-align:center;
|
||||
min-width:50%;
|
||||
}
|
||||
.geBlock h1, .geBlock h2 {
|
||||
margin-top:0px;
|
||||
padding-top:0px;
|
||||
}
|
||||
.geEditor *:not(.geScrollable)::-webkit-scrollbar {
|
||||
width:14px;
|
||||
height:14px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-track {
|
||||
background-clip:padding-box;
|
||||
border:solid transparent;
|
||||
border-width:1px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-corner {
|
||||
background-color:transparent;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-thumb {
|
||||
background-color:rgba(0,0,0,.1);
|
||||
background-clip:padding-box;
|
||||
border:solid transparent;
|
||||
border-radius:10px;
|
||||
}
|
||||
.geEditor ::-webkit-scrollbar-thumb:hover {
|
||||
background-color:rgba(0,0,0,.4);
|
||||
}
|
||||
.geTemplate {
|
||||
border:1px solid transparent;
|
||||
display:inline-block;
|
||||
_display:inline;
|
||||
vertical-align:top;
|
||||
border-radius:3px;
|
||||
overflow:hidden;
|
||||
font-size:14pt;
|
||||
cursor:pointer;
|
||||
margin:5px;
|
||||
}
|
||||
.geDialog h2 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
<!-- Workaround for binary XHR in IE 9/10, see App.loadUrl -->
|
||||
<!--[if (IE 9)|(IE 10)]><!-->
|
||||
<script type="text/vbscript">
|
||||
Function mxUtilsBinaryToArray(Binary)
|
||||
Dim i
|
||||
ReDim byteArray(LenB(Binary))
|
||||
For i = 1 To LenB(Binary)
|
||||
byteArray(i-1) = AscB(MidB(Binary, i, 1))
|
||||
Next
|
||||
mxUtilsBinaryToArray = byteArray
|
||||
End Function
|
||||
</script>
|
||||
<!--<![endif]-->
|
||||
</head>
|
||||
<body class="geEditor">
|
||||
<div id="geInfo">
|
||||
<div class="geBlock">
|
||||
<h1>Flowchart Maker and Online Diagram Software</h1>
|
||||
<p>
|
||||
diagrams.net (formerly draw.io) is free online diagram software. You can use it as a flowchart maker, network diagram software, to create UML online, as an ER diagram tool,
|
||||
to design database schema, to build BPMN online, as a circuit diagram maker, and more. draw.io can import .vsdx, Gliffy™ and Lucidchart™ files .
|
||||
</p>
|
||||
<h2 id="geStatus">Loading...</h2>
|
||||
<p>
|
||||
Please ensure JavaScript is enabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
if (navigator.userAgent != null && navigator.userAgent.toLowerCase().
|
||||
indexOf(' electron/') >= 0 && typeof process !== 'undefined' && process.versions.electron < 5)
|
||||
{
|
||||
// Redirects old Electron app to latest version
|
||||
var div = document.getElementById('geInfo');
|
||||
|
||||
if (div != null)
|
||||
{
|
||||
div.innerHTML = '<center><h2>You are using an out of date version of this app.<br>Please download the latest version ' +
|
||||
'<a href="https://github.com/jgraph/drawio-desktop/releases/latest" target="_blank">here</a>.</h2></center>';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (urlParams['dev'] != '1' && typeof document.createElement('canvas').getContext === "function")
|
||||
{
|
||||
window.addEventListener('load', function()
|
||||
{
|
||||
mxWinLoaded = true;
|
||||
checkAllLoaded();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
App.main();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
12282
docker/drawio/webapp/js/app.min.js
vendored
@ -1 +0,0 @@
|
||||
.croppie-container{width:100%;height:100%}.croppie-container .cr-image{z-index:-1;position:absolute;top:0;left:0;transform-origin:0 0;max-height:none;max-width:none}.croppie-container .cr-boundary{position:relative;overflow:hidden;margin:0 auto;z-index:1;width:100%;height:100%}.croppie-container .cr-resizer,.croppie-container .cr-viewport{position:absolute;border:2px solid #fff;margin:auto;top:0;bottom:0;right:0;left:0;box-shadow:0 0 2000px 2000px rgba(0,0,0,.5);z-index:0}.croppie-container .cr-resizer{z-index:2;box-shadow:none;pointer-events:none}.croppie-container .cr-resizer-horisontal,.croppie-container .cr-resizer-vertical{position:absolute;pointer-events:all}.croppie-container .cr-resizer-horisontal::after,.croppie-container .cr-resizer-vertical::after{display:block;position:absolute;box-sizing:border-box;border:1px solid #000;background:#fff;width:10px;height:10px;content:''}.croppie-container .cr-resizer-vertical{bottom:-5px;cursor:row-resize;width:100%;height:10px}.croppie-container .cr-resizer-vertical::after{left:50%;margin-left:-5px}.croppie-container .cr-resizer-horisontal{right:-5px;cursor:col-resize;width:10px;height:100%}.croppie-container .cr-resizer-horisontal::after{top:50%;margin-top:-5px}.croppie-container .cr-original-image{display:none}.croppie-container .cr-vp-circle{border-radius:50%}.croppie-container .cr-overlay{z-index:1;position:absolute;cursor:move;touch-action:none}.croppie-container .cr-slider-wrap{width:75%;margin:15px auto;text-align:center}.croppie-result{position:relative;overflow:hidden}.croppie-result img{position:absolute}.croppie-container .cr-image,.croppie-container .cr-overlay,.croppie-container .cr-viewport{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}.cr-slider{-webkit-appearance:none;width:300px;max-width:100%;padding-top:8px;padding-bottom:8px;background-color:transparent}.cr-slider::-webkit-slider-runnable-track{width:100%;height:3px;background:rgba(0,0,0,.5);border:0;border-radius:3px}.cr-slider::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:-6px}.cr-slider:focus{outline:0}.cr-slider::-moz-range-track{width:100%;height:3px;background:rgba(0,0,0,.5);border:0;border-radius:3px}.cr-slider::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:-6px}.cr-slider:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.cr-slider::-ms-track{width:100%;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}.cr-slider::-ms-fill-lower{background:rgba(0,0,0,.5);border-radius:10px}.cr-slider::-ms-fill-upper{background:rgba(0,0,0,.5);border-radius:10px}.cr-slider::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#ddd;margin-top:1px}.cr-slider:focus::-ms-fill-lower{background:rgba(0,0,0,.5)}.cr-slider:focus::-ms-fill-upper{background:rgba(0,0,0,.5)}.cr-rotate-controls{position:absolute;bottom:5px;left:5px;z-index:1}.cr-rotate-controls button{border:0;background:0 0}.cr-rotate-controls i:before{display:inline-block;font-style:normal;font-weight:900;font-size:22px}.cr-rotate-l i:before{content:'↺'}.cr-rotate-r i:before{content:'↻'}
|
2206
docker/drawio/webapp/js/diagramly/ElectronApp.js
vendored
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 4.4 KiB |
@ -18,6 +18,14 @@ upstream service {
|
||||
server php:20000 weight=5 max_fails=3 fail_timeout=30s;
|
||||
keepalive 16;
|
||||
}
|
||||
upstream office {
|
||||
server office weight=5 max_fails=3 fail_timeout=30s;
|
||||
keepalive 16;
|
||||
}
|
||||
upstream fileview {
|
||||
server fileview:8012 weight=5 max_fails=3 fail_timeout=30s;
|
||||
keepalive 16;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
@ -77,23 +85,6 @@ server {
|
||||
proxy_pass http://service;
|
||||
}
|
||||
|
||||
location /fileview {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Real-PORT $remote_port;
|
||||
proxy_set_header X-Forwarded-Host $the_host;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Scheme $scheme;
|
||||
proxy_set_header Server-Protocol $server_protocol;
|
||||
proxy_set_header Server-Name $server_name;
|
||||
proxy_set_header Server-Addr $server_addr;
|
||||
proxy_set_header Server-Port $server_port;
|
||||
proxy_pass http://fileview:8012;
|
||||
}
|
||||
|
||||
location /office/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@ -112,11 +103,12 @@ server {
|
||||
proxy_pass http://office/;
|
||||
}
|
||||
|
||||
location /drawio/webapp/ {
|
||||
location /fileview {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Real-PORT $remote_port;
|
||||
proxy_set_header X-Forwarded-Host $the_host/drawio/webapp;
|
||||
proxy_set_header X-Forwarded-Host $the_host;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
@ -125,27 +117,7 @@ server {
|
||||
proxy_set_header Server-Name $server_name;
|
||||
proxy_set_header Server-Addr $server_addr;
|
||||
proxy_set_header Server-Port $server_port;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_pass http://drawio-webapp:8080/;
|
||||
}
|
||||
|
||||
location /drawio/export/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Real-PORT $remote_port;
|
||||
proxy_set_header X-Forwarded-Host $the_host/drawio/export;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Scheme $scheme;
|
||||
proxy_set_header Server-Protocol $server_protocol;
|
||||
proxy_set_header Server-Name $server_name;
|
||||
proxy_set_header Server-Addr $server_addr;
|
||||
proxy_set_header Server-Port $server_port;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_pass http://drawio-export:8000/;
|
||||
proxy_pass http://fileview;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ command=php bin/laravels start -i
|
||||
numprocs=1
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=100
|
||||
startretries=3
|
||||
user=root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)s.log
|
||||
|
29
electron/build.js
vendored
@ -15,48 +15,25 @@ const packageFile = path.resolve(__dirname, "package.json");
|
||||
const packageBakFile = path.resolve(__dirname, "package-bak.json");
|
||||
const platform = ["build-mac", "build-mac-arm", "build-win"];
|
||||
|
||||
// 克隆 Drawio
|
||||
function cloneDrawio(systemInfo) {
|
||||
child_process.spawnSync("git", ["submodule", "update", "--quiet", "--init", "--depth=1"], {stdio: "inherit"});
|
||||
const drawioSrcDir = path.resolve(__dirname, "../resources/drawio/src/main/webapp");
|
||||
const drawioCoverDir = path.resolve(__dirname, "../docker/drawio/webapp");
|
||||
const drawioDestDir = path.resolve(electronDir, "drawio/webapp");
|
||||
fse.copySync(drawioSrcDir, drawioDestDir)
|
||||
fse.copySync(drawioCoverDir, drawioDestDir)
|
||||
//
|
||||
const preConfigFile = path.resolve(drawioDestDir, "js/PreConfig.js");
|
||||
if (!fse.existsSync(preConfigFile)) {
|
||||
console.log("clone drawio error!");
|
||||
process.exit()
|
||||
}
|
||||
let preConfigString = fs.readFileSync(preConfigFile, 'utf8');
|
||||
preConfigString += "\nwindow.systemInfo = " + JSON.stringify(systemInfo) + ";\n";
|
||||
preConfigString += fs.readFileSync(path.resolve(__dirname, "drawio.js"), 'utf8');
|
||||
fs.writeFileSync(preConfigFile, preConfigString, 'utf8');
|
||||
}
|
||||
|
||||
// 生成配置、编译应用
|
||||
function startBuild(data, publish) {
|
||||
// information
|
||||
console.log("Name: " + data.name);
|
||||
console.log("AppId: " + data.id);
|
||||
console.log("Version: " + config.version);
|
||||
// config.js
|
||||
let systemInfo = {
|
||||
title: data.name,
|
||||
version: config.version,
|
||||
origin: "./",
|
||||
apiUrl: utils.formatUrl(data.url) + "api/",
|
||||
}
|
||||
// drawio
|
||||
cloneDrawio(systemInfo)
|
||||
// config.js
|
||||
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo), 'utf8');
|
||||
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo, null, 2), 'utf8');
|
||||
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
|
||||
fs.writeFileSync(devloadCachePath, "", 'utf8');
|
||||
// index.html
|
||||
let indexFile = path.resolve(electronDir, "index.html");
|
||||
let indexString = fs.readFileSync(indexFile, 'utf8');
|
||||
indexString = indexString.replace(/<title>(.*?)<\/title>/g, `<title>${data.name}</title>`);
|
||||
indexString = indexString.replace(`<title></title>`, `<title>${data.name}</title>`);
|
||||
fs.writeFileSync(indexFile, indexString, 'utf8');
|
||||
// package.json Backup
|
||||
fse.copySync(packageFile, packageBakFile)
|
||||
|
11
electron/drawio.js
vendored
@ -1,11 +0,0 @@
|
||||
window.utilsStorage={getStorageString(key,def=''){let value=this.storage(key);return typeof value==="string"||typeof value==="number"?value:def},storage(key,value){if(!key){return}let keyName='__state__';if(key.substring(0,5)==='cache'){keyName='__state:'+key+'__'}if(typeof value==='undefined'){return this.loadFromlLocal(key,'',keyName)}else{this.savaToLocal(key,value,keyName)}},savaToLocal(key,value,keyName){try{if(typeof keyName==='undefined')keyName='__seller__';let seller=window.localStorage[keyName];if(!seller){seller={}}else{seller=JSON.parse(seller)}seller[key]=value;window.localStorage[keyName]=JSON.stringify(seller)}catch(e){}},loadFromlLocal(key,def,keyName){try{if(typeof keyName==='undefined')keyName='__seller__';let seller=window.localStorage[keyName];if(!seller){return def}seller=JSON.parse(seller);if(!seller||typeof seller[key]==='undefined'){return def}return seller[key]}catch(e){return def}},}
|
||||
|
||||
window.cacheServerUrl = window.utilsStorage.getStorageString("cacheServerUrl")
|
||||
if (window.cacheServerUrl) {
|
||||
window.systemInfo.apiUrl = window.cacheServerUrl
|
||||
}
|
||||
|
||||
window.EXPORT_URL = window.systemInfo.apiUrl + "../drawio/export/";
|
||||
window.DRAWIO_LIGHTBOX_URL = window.systemInfo.apiUrl + "../drawio/webapp";
|
||||
while (window.EXPORT_URL.indexOf("/../") !== -1) {window.EXPORT_URL = window.EXPORT_URL.replace(/\/(((?!\/).)*)\/\.\.\//, "/")}
|
||||
while (window.DRAWIO_LIGHTBOX_URL.indexOf("/../") !== -1) {window.DRAWIO_LIGHTBOX_URL = window.DRAWIO_LIGHTBOX_URL.replace(/\/(((?!\/).)*)\/\.\.\//, "/")}
|
67
electron/electron-preload.js
vendored
@ -1,67 +0,0 @@
|
||||
const {
|
||||
contextBridge,
|
||||
ipcRenderer
|
||||
} = require("electron");
|
||||
|
||||
let reqId = 1;
|
||||
let reqInfo = {};
|
||||
let fileChangedListeners = {};
|
||||
|
||||
ipcRenderer.on('mainResp', (event, resp) => {
|
||||
let callbacks = reqInfo[resp.reqId];
|
||||
|
||||
if (resp.error) {
|
||||
callbacks.error(resp.msg, resp.e);
|
||||
} else {
|
||||
callbacks.callback(resp.data);
|
||||
}
|
||||
|
||||
delete reqInfo[resp.reqId];
|
||||
});
|
||||
|
||||
ipcRenderer.on('fileChanged', (event, resp) => {
|
||||
let listener = fileChangedListeners[resp.path];
|
||||
|
||||
if (listener) {
|
||||
listener(resp.curr, resp.prev);
|
||||
}
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron', {
|
||||
request: (msg, callback, error) => {
|
||||
msg.reqId = reqId++;
|
||||
reqInfo[msg.reqId] = {callback: callback, error: error};
|
||||
|
||||
if (msg.action == 'watchFile') {
|
||||
fileChangedListeners[msg.path] = msg.listener;
|
||||
delete msg.listener;
|
||||
}
|
||||
|
||||
ipcRenderer.send('rendererReq', msg);
|
||||
},
|
||||
registerMsgListener: (action, callback) => {
|
||||
ipcRenderer.on(action, (event, args) => {
|
||||
callback(args);
|
||||
});
|
||||
},
|
||||
listenOnce: (action, callback) => {
|
||||
ipcRenderer.once(action, (event, args) => {
|
||||
callback(args);
|
||||
});
|
||||
},
|
||||
sendMessage: (action, args) => {
|
||||
ipcRenderer.send(action, args);
|
||||
},
|
||||
sendSyncMessage: (action, args) => {
|
||||
ipcRenderer.sendSync(action, args);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'process', {
|
||||
type: process.type,
|
||||
versions: process.versions
|
||||
}
|
||||
);
|
1199
electron/electron.js
vendored
423
electron/main.js
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
const fs = require('fs')
|
||||
const fse = require('fs-extra')
|
||||
const os = require("os");
|
||||
const path = require('path')
|
||||
const XLSX = require('xlsx');
|
||||
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
|
||||
const utils = require('./utils');
|
||||
const config = require('./package.json');
|
||||
const log = require("electron-log");
|
||||
|
||||
let mainWindow = null,
|
||||
subWindow = [],
|
||||
willQuitApp = false,
|
||||
inheritClose = false,
|
||||
devloadUrl = "",
|
||||
devloadCachePath = path.resolve(__dirname, ".devload"),
|
||||
downloadList = [],
|
||||
downloadCacheFile = path.join(app.getPath('cache'), config.name, '.downloadCache');
|
||||
|
||||
if (fs.existsSync(devloadCachePath)) {
|
||||
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
|
||||
}
|
||||
|
||||
if (fs.existsSync(downloadCacheFile)) {
|
||||
downloadList = utils.jsonParse(fs.readFileSync(downloadCacheFile, 'utf8'), [])
|
||||
} else {
|
||||
fse.ensureDirSync(path.join(app.getPath('cache'), config.name))
|
||||
}
|
||||
|
||||
function downloadUpdate(item) {
|
||||
const chain = item.getURLChain()
|
||||
if (chain.length == 0) {
|
||||
return
|
||||
}
|
||||
let currentState = item.getState()
|
||||
if (currentState == "progressing" && item.isPaused()) {
|
||||
currentState = "paused"
|
||||
}
|
||||
//
|
||||
const downloadItem = downloadList.find(item => item.url == chain[0])
|
||||
if (downloadItem && downloadItem.state != currentState) {
|
||||
downloadItem.state = currentState;
|
||||
downloadItem.result = {
|
||||
url: item.getURL(),
|
||||
name: item.getFilename(),
|
||||
savePath: item.getSavePath(),
|
||||
mimeType: item.getMimeType(),
|
||||
totalBytes: item.getTotalBytes(),
|
||||
chain,
|
||||
};
|
||||
fs.writeFileSync(downloadCacheFile, utils.jsonStringify(downloadList), 'utf8');
|
||||
//
|
||||
if (currentState == 'completed') {
|
||||
mainWindow.webContents.send("downloadDone", downloadItem)
|
||||
log.info("下载完成", downloadItem)
|
||||
} else {
|
||||
mainWindow.webContents.send("downloadUpdate", downloadItem)
|
||||
log.info("下载更新", downloadItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
center: true,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
})
|
||||
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + " MainTaskWindow/" + process.platform + "/" + os.arch() + "/1.0");
|
||||
|
||||
if (devloadUrl) {
|
||||
mainWindow.loadURL(devloadUrl).then(r => {
|
||||
|
||||
})
|
||||
} else {
|
||||
mainWindow.loadFile('./public/index.html').then(r => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
mainWindow.on('page-title-updated', (event, title) => {
|
||||
if (title == "index.html") {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('close', (e) => {
|
||||
if (!willQuitApp) {
|
||||
e.preventDefault();
|
||||
if (inheritClose) {
|
||||
mainWindow.webContents.send("windowClose", {})
|
||||
} else {
|
||||
if (process.platform === 'darwin') {
|
||||
app.hide();
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('will-download', (event, item) => {
|
||||
item.setSavePath(path.join(app.getPath('cache'), config.name, item.getFilename()));
|
||||
item.on('updated', () => {
|
||||
downloadUpdate(item)
|
||||
})
|
||||
item.on('done', () => {
|
||||
downloadUpdate(item)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createSubWindow(args) {
|
||||
if (!args) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof args !== "object") {
|
||||
args = {
|
||||
path: args,
|
||||
config: {},
|
||||
}
|
||||
}
|
||||
|
||||
let name = args.name || "auto_" + utils.randomString(6);
|
||||
let item = subWindow.find(item => item.name == name);
|
||||
let browser = item ? item.browser : null;
|
||||
if (browser) {
|
||||
browser.focus();
|
||||
if (args.force === false) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let config = args.config || {};
|
||||
if (typeof args.title !== "undefined") {
|
||||
config.title = args.title;
|
||||
}
|
||||
browser = new BrowserWindow(Object.assign({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
center: true,
|
||||
parent: mainWindow,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
devTools: args.devTools !== false,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
}, config))
|
||||
browser.on('page-title-updated', (event, title) => {
|
||||
if (title == "index.html" || args.titleFixed === true) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
browser.on('close', () => {
|
||||
let index = subWindow.findIndex(item => item.name == name);
|
||||
if (index > -1) {
|
||||
subWindow.splice(index, 1)
|
||||
}
|
||||
})
|
||||
subWindow.push({ name, browser })
|
||||
}
|
||||
browser.webContents.setUserAgent(browser.webContents.getUserAgent() + " SubTaskWindow/" + process.platform + "/" + os.arch() + "/1.0" + (args.userAgent ? (" " + args.userAgent) : ""));
|
||||
|
||||
if (devloadUrl) {
|
||||
browser.loadURL(devloadUrl + '#' + (args.hash || args.path)).then(r => {
|
||||
|
||||
})
|
||||
} else {
|
||||
browser.loadFile('./public/index.html', {
|
||||
hash: args.hash || args.path
|
||||
}).then(r => {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createMainWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('before-quit', () => {
|
||||
willQuitApp = true
|
||||
})
|
||||
|
||||
/**
|
||||
* 继承关闭窗口事件
|
||||
*/
|
||||
ipcMain.on('inheritClose', (event) => {
|
||||
inheritClose = true
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param args {url}
|
||||
*/
|
||||
ipcMain.on('downloadFile', (event, args) => {
|
||||
event.returnValue = "ok"
|
||||
//
|
||||
let appendJson = {state: "progressing", startTime: utils.Time()}
|
||||
let downloadItem = downloadList.find(({url}) => url == args.url)
|
||||
if (downloadItem) {
|
||||
switch (downloadItem.state) {
|
||||
case "completed":
|
||||
if (fs.existsSync(downloadItem.result.savePath)) { // 下载完成,文件存在
|
||||
log.info("下载已完成", downloadItem)
|
||||
mainWindow.webContents.send("downloadDone", downloadItem)
|
||||
return
|
||||
}
|
||||
break;
|
||||
case "progressing":
|
||||
if (downloadItem.startTime + 480 > utils.Time()) { // 下载中,未超时(超时时间8分钟)
|
||||
log.info("下载已存在", downloadItem)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
downloadItem = Object.assign(downloadItem, appendJson)
|
||||
} else {
|
||||
downloadList.push(downloadItem = Object.assign(args, appendJson))
|
||||
}
|
||||
fs.writeFileSync(downloadCacheFile, utils.jsonStringify(downloadList), 'utf8');
|
||||
mainWindow.webContents.downloadURL(downloadItem.url);
|
||||
log.info("下载开始", downloadItem)
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开文件
|
||||
* @param args {path}
|
||||
*/
|
||||
ipcMain.on('openFile', (event, args) => {
|
||||
utils.openFile(args.path)
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 退出客户端
|
||||
*/
|
||||
ipcMain.on('windowQuit', (event) => {
|
||||
event.returnValue = "ok"
|
||||
app.quit();
|
||||
})
|
||||
|
||||
/**
|
||||
* 创建路由窗口
|
||||
* @param args {path, ?}
|
||||
*/
|
||||
ipcMain.on('windowRouter', (event, args) => {
|
||||
createSubWindow(args)
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 隐藏窗口(mac隐藏,其他关闭)
|
||||
*/
|
||||
ipcMain.on('windowHidden', (event) => {
|
||||
if (process.platform === 'darwin') {
|
||||
app.hide();
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
ipcMain.on('windowClose', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
win.close()
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置窗口尺寸
|
||||
* @param args {width, height, autoZoom, minWidth, minHeight, maxWidth, maxHeight}
|
||||
*/
|
||||
ipcMain.on('windowSize', (event, args) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
if (args.width || args.height) {
|
||||
let [w, h] = win.getSize()
|
||||
const width = args.width || w
|
||||
const height = args.height || h
|
||||
win.setSize(width, height, args.animate === true)
|
||||
//
|
||||
if (args.autoZoom === true) {
|
||||
let move = false
|
||||
let [x, y] = win.getPosition()
|
||||
if (Math.abs(width - w) > 10) {
|
||||
move = true
|
||||
x -= (width - w) / 2
|
||||
}
|
||||
if (Math.abs(height - h) > 10) {
|
||||
move = true
|
||||
y -= (height - h) / 2
|
||||
}
|
||||
if (move) {
|
||||
win.setPosition(Math.max(0, Math.floor(x)), Math.max(0, Math.floor(y)))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.minWidth || args.minHeight) {
|
||||
win.setMinimumSize(args.minWidth || win.getMinimumSize()[0], args.minHeight || win.getMinimumSize()[1])
|
||||
}
|
||||
if (args.maxWidth || args.maxHeight) {
|
||||
win.setMaximumSize(args.maxWidth || win.getMaximumSize()[0], args.maxHeight || win.getMaximumSize()[1])
|
||||
}
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置窗口最小尺寸
|
||||
* @param args {minWidth, minHeight}
|
||||
*/
|
||||
ipcMain.on('windowMinSize', (event, args) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
win.setMinimumSize(args.minWidth || win.getMinimumSize()[0], args.minHeight || win.getMinimumSize()[1])
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置窗口最大尺寸
|
||||
* @param args {maxWidth, maxHeight}
|
||||
*/
|
||||
ipcMain.on('windowMaxSize', (event, args) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
win.setMaximumSize(args.maxWidth || win.getMaximumSize()[0], args.maxHeight || win.getMaximumSize()[1])
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 窗口居中
|
||||
*/
|
||||
ipcMain.on('windowCenter', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
win.center();
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 窗口最大化或恢复
|
||||
*/
|
||||
ipcMain.on('windowMax', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win.isMaximized()) {
|
||||
win.restore();
|
||||
} else {
|
||||
win.maximize();
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 给主窗口发送信息
|
||||
* @param args {channel, data}
|
||||
*/
|
||||
ipcMain.on('sendForwardMain', (event, args) => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send(args.channel, args.data)
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置Dock标记
|
||||
* @param args
|
||||
*/
|
||||
ipcMain.on('setDockBadge', (event, args) => {
|
||||
if(process.platform !== 'darwin'){
|
||||
// Mac only
|
||||
return;
|
||||
}
|
||||
if (utils.runNum(args) > 0) {
|
||||
app.dock.setBadge(String(args))
|
||||
} else {
|
||||
app.dock.setBadge("")
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存sheets
|
||||
*/
|
||||
ipcMain.on('saveSheet', (event, data, filename, opts) => {
|
||||
const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html".split("|");
|
||||
dialog.showSaveDialog({
|
||||
title: 'Save file as',
|
||||
defaultPath: filename,
|
||||
filters: [{
|
||||
name: "Spreadsheets",
|
||||
extensions: EXTENSIONS
|
||||
}]
|
||||
}).then(o => {
|
||||
XLSX.writeFile(data, o.filePath, opts);
|
||||
});
|
||||
event.returnValue = "ok"
|
||||
})
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "0.10.5",
|
||||
"version": "0.7.73",
|
||||
"description": "DooTask is task management system.",
|
||||
"main": "electron.js",
|
||||
"main": "main.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
@ -28,31 +28,30 @@
|
||||
"url": "https://github.com/kuaifan/dootask.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
||||
"@electron-forge/cli": "^6.0.0-beta.61",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.61",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.61",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.61",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.61",
|
||||
"dmg-license": "^1.0.10",
|
||||
"dotenv": "^16.0.0",
|
||||
"electron": "^17.0.1",
|
||||
"electron-builder": "^22.14.13"
|
||||
"dotenv": "^10.0.0",
|
||||
"electron": "^16.0.5",
|
||||
"electron-builder": "^22.14.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.26.0",
|
||||
"crc": "^3.8.0",
|
||||
"axios": "^0.24.0",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-log": "^4.4.6",
|
||||
"electron-log": "^4.4.3",
|
||||
"fs-extra": "^10.0.0",
|
||||
"pdf-lib": "^1.16.0"
|
||||
"xlsx": "^0.17.2"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.dootask.task",
|
||||
"artifactName": "${productName}-v${version}-${os}-${arch}.${ext}",
|
||||
"files": [
|
||||
"public/**/*",
|
||||
"electron-preload.js",
|
||||
"electron.js",
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"utils.js"
|
||||
],
|
||||
"mac": {
|
||||
|
14
electron/preload.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// preload.js
|
||||
|
||||
// All of the Node.js APIs are available in the preload process.
|
||||
// It has the same sandbox as a Chrome extension.
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency])
|
||||
}
|
||||
})
|
37
electron/utils.js
vendored
@ -1,5 +1,5 @@
|
||||
const fs = require("fs");
|
||||
const {shell, dialog} = require("electron");
|
||||
const {shell} = require("electron");
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
@ -269,39 +269,4 @@ module.exports = {
|
||||
}
|
||||
return Math.round(time / 1000)
|
||||
},
|
||||
|
||||
/**
|
||||
* 窗口关闭事件
|
||||
* @param event
|
||||
* @param app
|
||||
*/
|
||||
onBeforeUnload(event, app) {
|
||||
const sender = event.sender
|
||||
const contents = sender.webContents
|
||||
if (contents != null) {
|
||||
const destroy = () => {
|
||||
if (typeof app === "undefined") {
|
||||
sender.destroy()
|
||||
} else {
|
||||
if (process.platform === 'darwin') {
|
||||
app.hide()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
contents.executeJavaScript('if(typeof window.__onBeforeUnload === \'function\'){window.__onBeforeUnload()}', true).then(options => {
|
||||
if (this.isJson(options)) {
|
||||
let choice = dialog.showMessageBoxSync(sender, options)
|
||||
if (choice === 1) {
|
||||
contents.executeJavaScript('if(typeof window.__removeBeforeUnload === \'function\'){window.__removeBeforeUnload()}', true).catch(() => {});
|
||||
destroy()
|
||||
}
|
||||
} else if (options !== true) {
|
||||
destroy()
|
||||
}
|
||||
})
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
14
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "0.10.5",
|
||||
"version": "0.7.73",
|
||||
"description": "DooTask is task management system.",
|
||||
"scripts": {
|
||||
"start": "./cmd dev",
|
||||
@ -41,6 +41,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"echarts": "^5.2.2",
|
||||
"electron": "^16.0.5",
|
||||
"element-ui": "^2.15.6",
|
||||
"file-loader": "^6.2.0",
|
||||
"inquirer": "^8.2.0",
|
||||
@ -53,7 +54,7 @@
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"node-sass": "^6.0.1",
|
||||
"node-sass": "^4.11.0",
|
||||
"notification-koro1": "^1.1.1",
|
||||
"postcss": "^8.4.5",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
@ -61,9 +62,9 @@
|
||||
"sass-loader": "^12.4.0",
|
||||
"stylus": "^0.56.0",
|
||||
"stylus-loader": "^6.2.0",
|
||||
"tinymce": "^5.10.3",
|
||||
"tinymce": "^5.10.2",
|
||||
"tui-calendar-hi": "^1.15.1-5",
|
||||
"view-design-hi": "^4.7.0-16",
|
||||
"view-design-hi": "^4.7.0-12",
|
||||
"vue": "^2.6.14",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-emoji-picker": "^1.0.3",
|
||||
@ -74,7 +75,8 @@
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.2",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"xlsx": "^0.17.4"
|
||||
}
|
||||
}
|
||||
|