Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9eaa575d1a | ||
|
996ed78a0e | ||
|
1759572b2e | ||
|
0a9f9eea90 | ||
|
223fb540b1 | ||
|
c5f1c95f7b | ||
|
3bbcbca926 | ||
|
63c1deb630 | ||
|
424e2428fe | ||
|
2fdef45156 | ||
|
4cd4550a36 | ||
|
16af625aef | ||
|
8e72794c07 | ||
|
d9cf6d7e1b | ||
|
f4e4252227 | ||
|
7107409b1b | ||
|
6c086fab6f | ||
|
d027d67e08 | ||
|
a7d9e635eb | ||
|
e7196efaea | ||
|
0a2a903c74 | ||
|
8104c26b19 | ||
|
101d5c7eb0 | ||
|
cad253b85f | ||
|
f17009a988 | ||
|
1f76278d2b | ||
|
e032d29c91 | ||
|
31d1b0c994 | ||
|
611c6d415c | ||
|
e3b7ac00fd | ||
|
7c952822db | ||
|
b9e435c0e2 | ||
|
356d40e640 | ||
|
123ffd4e66 | ||
|
c952659620 | ||
|
ea8e1e9c57 | ||
|
478d63893b |
@ -17,10 +17,13 @@ use App\Models\ProjectUser;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Module\Base;
|
||||
use App\Module\BillExport;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Madzipper;
|
||||
use Request;
|
||||
use Response;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* @apiDefine project
|
||||
@ -604,13 +607,14 @@ class ProjectController extends AbstractController
|
||||
if (!is_array($item['task'])) continue;
|
||||
$index = 0;
|
||||
foreach ($item['task'] as $task_id) {
|
||||
ProjectTask::whereId($task_id)->whereProjectId($project->id)->update([
|
||||
if (ProjectTask::whereId($task_id)->whereProjectId($project->id)->whereCompleteAt(null)->update([
|
||||
'column_id' => $item['id'],
|
||||
'sort' => $index
|
||||
]);
|
||||
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
]);
|
||||
])) {
|
||||
ProjectTask::whereParentId($task_id)->whereProjectId($project->id)->update([
|
||||
'column_id' => $item['id'],
|
||||
]);
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
@ -976,6 +980,154 @@ class ProjectController extends AbstractController
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/export 18. 导出任务(限管理员)
|
||||
*
|
||||
* @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__export
|
||||
*
|
||||
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
|
||||
* @apiParam {Array} [time] 指定时间范围,如:['2020-12-12', '2020-12-30']
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function task__export()
|
||||
{
|
||||
$user = User::auth('admin');
|
||||
//
|
||||
$userid = Base::arrayRetainInt(Request::input('userid'), true);
|
||||
$time = Request::input('time');
|
||||
if (empty($userid) || empty($time)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
if (count($userid) > 20) {
|
||||
return Base::retError('导出会员限制最多20个');
|
||||
}
|
||||
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
|
||||
return Base::retError('时间选择错误');
|
||||
}
|
||||
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
|
||||
return Base::retError('时间范围限制最大90天');
|
||||
}
|
||||
//
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$headings[] = '是否完成';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '是否归档';
|
||||
$headings[] = '归档时间';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '结束剩余';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '父级任务ID';
|
||||
$datas = [];
|
||||
//
|
||||
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.owner', 1)
|
||||
->whereIn('project_task_users.userid', $userid)
|
||||
->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay());
|
||||
$builder->orderByDesc('project_tasks.id')->chunk(100, function($tasks) use (&$datas) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
if ($task->complete_at) {
|
||||
$a = Carbon::parse($task->complete_at)->timestamp;
|
||||
$b = Carbon::parse($task->end_at)->timestamp;
|
||||
if ($b > $a) {
|
||||
$endSurplus = Base::timeDiff($a, $b);
|
||||
} else {
|
||||
$endSurplus = "-" . Base::timeDiff($b, $a);
|
||||
}
|
||||
} else {
|
||||
$endSurplus = '-';
|
||||
}
|
||||
$datas[] = [
|
||||
$task->id,
|
||||
Base::filterEmoji($task->name),
|
||||
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
$task->complete_at ? '已完成' : '-',
|
||||
$task->complete_at ?: '-',
|
||||
$task->archived_at ? '已归档' : '-',
|
||||
$task->archived_at ?: '-',
|
||||
$task->start_at ?: '-',
|
||||
$task->end_at ?: '-',
|
||||
$endSurplus,
|
||||
Base::filterEmoji($task->project?->name) ?: '-',
|
||||
$task->parent_id ?: '-',
|
||||
];
|
||||
}
|
||||
});
|
||||
//
|
||||
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
|
||||
if (count($userid) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
}
|
||||
$fileName .= '任务统计_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
return Base::retError('导出失败,' . $fileName . '!');
|
||||
}
|
||||
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
|
||||
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls'). ".zip";
|
||||
$zipPath = storage_path($zipFile);
|
||||
if (file_exists($zipPath)) {
|
||||
Base::deleteDirAndFile($zipPath, true);
|
||||
}
|
||||
try {
|
||||
Madzipper::make($zipPath)->add($xlsPath)->close();
|
||||
} catch (\Exception) { }
|
||||
//
|
||||
if (file_exists($zipPath)) {
|
||||
$base64 = base64_encode(Base::array2string([
|
||||
'file' => $zipFile,
|
||||
]));
|
||||
Session::put('task::export:userid', $user->userid);
|
||||
return Base::retSuccess('success', [
|
||||
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
|
||||
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
|
||||
]);
|
||||
} else {
|
||||
return Base::retError('打包失败,请稍后再试...');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/down 18. 导出任务(限管理员)
|
||||
*
|
||||
* @apiDescription 导出指定范围任务(已完成、未完成、已归档),返回下载地址,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__down
|
||||
*
|
||||
* @apiParam {String} key 通过export接口得到的下载钥匙
|
||||
*
|
||||
* @apiSuccess {File} 文件下载
|
||||
*/
|
||||
public function task__down()
|
||||
{
|
||||
$userid = Session::get('task::export:userid');
|
||||
if (empty($userid)) {
|
||||
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
|
||||
}
|
||||
//
|
||||
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
|
||||
$file = $array['file'];
|
||||
if (empty($file) || !file_exists(storage_path($file))) {
|
||||
return Base::ajaxError("文件不存在!", [], 0, 502);
|
||||
}
|
||||
return response()->download(storage_path($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/one 19. 获取单个任务信息
|
||||
*
|
||||
|
@ -153,9 +153,10 @@ class AbstractModel extends Model
|
||||
* @param $where
|
||||
* @param array $update 存在时更新的内容
|
||||
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
|
||||
* @param bool $isInsert 是否是插入数据
|
||||
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
|
||||
*/
|
||||
public static function updateInsert($where, $update = [], $insert = [])
|
||||
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
|
||||
{
|
||||
$row = static::where($where)->first();
|
||||
if (empty($row)) {
|
||||
@ -165,8 +166,10 @@ class AbstractModel extends Model
|
||||
unset($array[$row->primaryKey]);
|
||||
}
|
||||
$row->updateInstance($array);
|
||||
$isInsert = true;
|
||||
} elseif ($update) {
|
||||
$row->updateInstance($update);
|
||||
$isInsert = false;
|
||||
}
|
||||
if (!$row->save()) {
|
||||
return null;
|
||||
|
@ -275,15 +275,16 @@ class File extends AbstractModel
|
||||
|
||||
/**
|
||||
* 格式化内容数据
|
||||
* @param array $data [path, ext, size, name]
|
||||
* @param array $data [path, size, ext, name]
|
||||
* @return array
|
||||
*/
|
||||
public static function formatFileData(array $data)
|
||||
{
|
||||
$filePath = $data['path'];
|
||||
$fileExt = $data['ext'];
|
||||
$fileSize = $data['size'];
|
||||
$fileName = $data['name'];
|
||||
$fileExt = $data['ext'];
|
||||
$fileDotExt = '.' . $fileExt;
|
||||
$fileName = Base::rightDelete($data['name'], $fileDotExt) . $fileDotExt;
|
||||
$publicPath = public_path($filePath);
|
||||
//
|
||||
switch ($fileExt) {
|
||||
|
@ -376,6 +376,7 @@ class Project extends AbstractModel
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
$upTaskList = [];
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
@ -403,7 +404,7 @@ class Project extends AbstractModel
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
]);
|
||||
], [], $isInsert);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
@ -415,6 +416,9 @@ class Project extends AbstractModel
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
if (!$isInsert) {
|
||||
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
@ -429,6 +433,12 @@ class Project extends AbstractModel
|
||||
}
|
||||
});
|
||||
//
|
||||
foreach ($upTaskList as $id => $value) {
|
||||
ProjectTask::whereFlowItemId($id)->update([
|
||||
'flow_item_name' => $value
|
||||
]);
|
||||
}
|
||||
//
|
||||
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
|
||||
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||
foreach ($projectFlow->projectFlowItem as $item) {
|
||||
|
@ -585,6 +585,14 @@ class ProjectTask extends AbstractModel
|
||||
'flow' => $flowData,
|
||||
'change' => [$currentFlowItem?->name, $newFlowItem->name]
|
||||
]);
|
||||
ProjectTaskFlowChange::createInstance([
|
||||
'task_id' => $this->id,
|
||||
'userid' => User::userid(),
|
||||
'before_flow_item_id' => $flowData['flow_item_id'],
|
||||
'before_flow_item_name' => $flowData['flow_item_name'],
|
||||
'after_flow_item_id' => $this->flow_item_id,
|
||||
'after_flow_item_name' => $this->flow_item_name,
|
||||
])->save();
|
||||
}
|
||||
// 状态
|
||||
if (Arr::exists($data, 'complete_at')) {
|
||||
|
34
app/Models/ProjectTaskFlowChange.php
Normal file
34
app/Models/ProjectTaskFlowChange.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskFlowChange
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property int|null $before_flow_item_id (变化前)工作流状态ID
|
||||
* @property string|null $before_flow_item_name (变化前)工作流状态名称
|
||||
* @property int|null $after_flow_item_id (变化后)工作流状态ID
|
||||
* @property string|null $after_flow_item_name (变化后)工作流状态名称
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskFlowChange extends AbstractModel
|
||||
{
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ use App\Module\Base;
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $owner 是否负责人
|
||||
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
@ -22,6 +22,7 @@ use App\Module\Base;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
|
@ -70,11 +70,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
public function getPercentageAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['percentage'])) {
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
$this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
$this->generatePercentage();
|
||||
}
|
||||
return $this->appendattrs['percentage'];
|
||||
}
|
||||
@ -98,6 +94,22 @@ 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
|
||||
@ -127,13 +139,17 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (!$msgRead->read_at) {
|
||||
$msgRead->read_at = Carbon::now();
|
||||
$msgRead->save();
|
||||
$this->increment('read');
|
||||
$this->generatePercentage(true);
|
||||
PushTask::push([
|
||||
'userid' => $this->userid,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'update',
|
||||
'data' => $this->toArray(),
|
||||
'mode' => 'readed',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'read' => $this->read,
|
||||
'percentage' => $this->percentage,
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace App\Models;
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
|
||||
@ -17,6 +17,7 @@ namespace App\Models;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
|
@ -342,19 +342,15 @@ class Base
|
||||
{
|
||||
if (strtolower($charset) == 'utf-8') {
|
||||
if (Base::getStrlen($string) <= $length) return $string;
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = Base::utf8Substr($strcut, $length, $start);
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
$strcut = Base::utf8Substr($string, $length, $start);
|
||||
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;
|
||||
}
|
||||
@ -2967,4 +2963,19 @@ class Base
|
||||
$matrix = array_unique($matrix, SORT_REGULAR);
|
||||
return array_merge($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除emoji表情
|
||||
* @param $str
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function filterEmoji($str)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/./u',
|
||||
function (array $match) {
|
||||
return strlen($match[0]) >= 4 ? '' : $match[0];
|
||||
},
|
||||
$str);
|
||||
}
|
||||
}
|
||||
|
144
app/Module/BillExport.php
Normal file
144
app/Module/BillExport.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Excel;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Events\AfterSheet;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception;
|
||||
|
||||
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
|
||||
{
|
||||
public $title;
|
||||
public $headings = [];
|
||||
public $data = [];
|
||||
public $typeLists = [];
|
||||
public $typeNumber = 0;
|
||||
|
||||
public function __construct($title, array $data)
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public static function create($data = [], $title = "Sheet1") {
|
||||
if (is_string($data)) {
|
||||
list($title, $data) = [$data, $title];
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
return new BillExport($title, $data);
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHeadings(array $headings) {
|
||||
$this->headings = $headings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTypeList(array $typeList, $number = 0) {
|
||||
$this->typeLists = $typeList;
|
||||
$this->typeNumber = $number;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function store($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::store($this, $fileName);
|
||||
} catch (Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function download($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::download($this, $fileName);
|
||||
} catch (Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的文件标题
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题行
|
||||
* @return array
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
return $this->headings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的内容
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return collect($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单元格事件
|
||||
* @return array
|
||||
*/
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
AfterSheet::Class => function (AfterSheet $event) {
|
||||
$count = count($this->data);
|
||||
foreach ($this->typeLists AS $cell => $typeList) {
|
||||
if ($cell && $typeList) {
|
||||
$p = $this->headings ? 1 : 0;
|
||||
for ($i = 1 + $p; $i <= max($count, $this->typeNumber) + $p; $i++) {
|
||||
$validation = $event->sheet->getDelegate()->getCell($cell . $i)->getDataValidation();
|
||||
$validation->setType(DataValidation::TYPE_LIST);
|
||||
$validation->setErrorStyle(DataValidation::STYLE_WARNING);
|
||||
$validation->setAllowBlank(false);
|
||||
$validation->setShowDropDown(true);
|
||||
$validation->setShowInputMessage(true);
|
||||
$validation->setShowErrorMessage(true);
|
||||
$validation->setErrorTitle('输入的值不合法');
|
||||
$validation->setError('选择的值不在列表中,请选择列表中的值');
|
||||
$validation->setPromptTitle('从列表中选择');
|
||||
$validation->setPrompt('请选择下拉列表中的值');
|
||||
$validation->setFormula1('"' . implode(',', $typeList) . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
16
app/Module/BillImport.php
Normal file
16
app/Module/BillImport.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use Maatwebsite\Excel\Concerns\ToArray;
|
||||
|
||||
class BillImport implements ToArray
|
||||
{
|
||||
public function Array(Array $tables)
|
||||
{
|
||||
return $tables;
|
||||
}
|
||||
|
||||
}
|
@ -127,9 +127,11 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
case 'readMsg':
|
||||
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
|
||||
$userid = $this->getUserid($frame->fd);
|
||||
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
|
||||
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
|
||||
$item->readSuccess($userid);
|
||||
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
|
||||
/** @var WebSocketDialogMsg $item */
|
||||
foreach ($list as $item) {
|
||||
$item->readSuccess($userid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
|
37
cmd
37
cmd
@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
|
||||
|
||||
cur_path="$(pwd)"
|
||||
cur_arg=$@
|
||||
COMPOSE="docker-compose"
|
||||
|
||||
judge() {
|
||||
if [[ 0 -eq $? ]]; then
|
||||
@ -55,20 +56,24 @@ check_docker() {
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
docker-compose --version &> /dev/null
|
||||
docker-compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
exit 1
|
||||
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 `docker-compose --version | grep "docker-compose" | grep -E "\sv*1"` ]]; then
|
||||
docker-compose --version
|
||||
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
|
||||
@ -76,7 +81,7 @@ check_node() {
|
||||
}
|
||||
|
||||
docker_name() {
|
||||
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
}
|
||||
|
||||
run_compile() {
|
||||
@ -272,7 +277,7 @@ if [ $# -gt 0 ]; then
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
# 启动容器
|
||||
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
|
||||
docker-compose up php -d
|
||||
$COMPOSE up php -d
|
||||
# 安装composer依赖
|
||||
run_exec php "composer install"
|
||||
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
|
||||
@ -300,7 +305,7 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "php artisan migrate --seed"
|
||||
# 设置初始化密码
|
||||
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
|
||||
docker-compose up -d
|
||||
$COMPOSE up -d
|
||||
supervisorctl_restart php
|
||||
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
@ -314,7 +319,7 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "composer update"
|
||||
run_exec php "php artisan migrate"
|
||||
supervisorctl_restart php
|
||||
docker-compose up -d
|
||||
$COMPOSE up -d
|
||||
elif [[ "$1" == "uninstall" ]]; then
|
||||
shift 1
|
||||
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
|
||||
@ -328,7 +333,7 @@ if [ $# -gt 0 ]; then
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
docker-compose down
|
||||
$COMPOSE down
|
||||
rm -rf "./docker/mysql/data"
|
||||
rm -rf "./docker/log/supervisor"
|
||||
find "./storage/logs" -name "*.log" | xargs rm -rf
|
||||
@ -341,7 +346,7 @@ if [ $# -gt 0 ]; then
|
||||
elif [[ "$1" == "port" ]]; then
|
||||
shift 1
|
||||
env_set APP_PORT "$1"
|
||||
docker-compose up -d
|
||||
$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
|
||||
@ -414,11 +419,11 @@ if [ $# -gt 0 ]; then
|
||||
e="./vendor/bin/phpunit $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "restart" ]]; then
|
||||
shift 1
|
||||
docker-compose stop "$@"
|
||||
docker-compose start "$@"
|
||||
$COMPOSE stop "$@"
|
||||
$COMPOSE start "$@"
|
||||
else
|
||||
docker-compose "$@"
|
||||
$COMPOSE "$@"
|
||||
fi
|
||||
else
|
||||
docker-compose ps
|
||||
$COMPOSE ps
|
||||
fi
|
||||
|
@ -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();
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectTaskFlowChangesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_task_flow_changes', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
|
||||
$table->bigInteger('before_item_id')->nullable()->default(0)->comment('(变化前)工作流状态ID');
|
||||
$table->string('before_item_name', 50)->nullable()->default('')->comment('(变化前)工作流状态名称');
|
||||
$table->bigInteger('after_item_id')->nullable()->default(0)->comment('(变化后)工作流状态ID');
|
||||
$table->string('after_item_name', 50)->nullable()->default('')->comment('(变化后)工作流状态名称');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('project_task_flow_changes');
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreProjectTaskFlowChangesItem extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('project_task_flow_changes', 'before_item_id')) {
|
||||
$table->renameColumn('before_item_id', 'before_flow_item_id');
|
||||
$table->renameColumn('before_item_name', 'before_flow_item_name');
|
||||
$table->renameColumn('after_item_id', 'after_flow_item_id');
|
||||
$table->renameColumn('after_item_name', 'after_flow_item_name');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "0.9.68",
|
||||
"version": "0.10.5",
|
||||
"description": "DooTask is task management system.",
|
||||
"main": "electron.js",
|
||||
"license": "MIT",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "0.9.68",
|
||||
"version": "0.10.5",
|
||||
"description": "DooTask is task management system.",
|
||||
"scripts": {
|
||||
"start": "./cmd dev",
|
||||
@ -53,7 +53,7 @@
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"node-sass": "^4.11.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"notification-koro1": "^1.1.1",
|
||||
"postcss": "^8.4.5",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/js/build/304.js
Normal file
2
public/js/build/304.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/js/build/422.js
Normal file
1
public/js/build/422.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -21,6 +21,7 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
tocObj.reset()
|
||||
this.init();
|
||||
this.createEditor();
|
||||
},
|
||||
|
@ -1,8 +1,20 @@
|
||||
<template>
|
||||
<div class="quick-edit" :class="[alwaysIcon ? 'quick-always' : '']">
|
||||
<div v-if="isEdit" v-clickoutside="onClickOut" class="quick-input">
|
||||
<TagInput v-if="isTag" ref="input" v-model="content" :disabled="isLoad" @on-enter="onEnter" @on-blur="onBlur"/>
|
||||
<Input v-else ref="input" v-model="content" :disabled="isLoad" @on-enter="onEnter" @on-blur="onBlur"/>
|
||||
<TagInput
|
||||
v-if="isTag"
|
||||
ref="input"
|
||||
v-model="content"
|
||||
:disabled="isLoad"
|
||||
@on-keyup="onKeyup"
|
||||
@on-blur="onBlur"/>
|
||||
<Input
|
||||
v-else
|
||||
ref="input"
|
||||
v-model="content"
|
||||
:disabled="isLoad"
|
||||
@on-keyup="onKeyup"
|
||||
@on-blur="onBlur"/>
|
||||
<div v-if="isLoad" class="quick-loading"><Loading/></div>
|
||||
</div>
|
||||
<template v-else>
|
||||
@ -54,9 +66,6 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
isEdit(val) {
|
||||
this.$emit("on-edit-change", val);
|
||||
},
|
||||
autoEdit(val) {
|
||||
if (val === true) {
|
||||
setTimeout(this.onEdit, 0)
|
||||
@ -65,9 +74,14 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
onEditChange(val) {
|
||||
this.isEdit = val;
|
||||
this.$emit("on-edit-change", val);
|
||||
},
|
||||
|
||||
onEdit() {
|
||||
this.content = this.value;
|
||||
this.isEdit = true;
|
||||
this.onEditChange(true);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus({
|
||||
cursor: 'all'
|
||||
@ -75,9 +89,18 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
onKeyup(e) {
|
||||
if (e.keyCode === 13) {
|
||||
this.onEnter();
|
||||
} else if (e.keyCode === 27) {
|
||||
this.isEdit = false;
|
||||
this.isLoad = false;
|
||||
}
|
||||
},
|
||||
|
||||
onEnter() {
|
||||
if (this.content == this.value) {
|
||||
this.isEdit = false;
|
||||
this.onEditChange(false);
|
||||
return;
|
||||
}
|
||||
if (this.isLoad) {
|
||||
@ -86,7 +109,7 @@ export default {
|
||||
this.isLoad = true;
|
||||
this.$emit("input", this.content);
|
||||
this.$emit("on-update", this.content, () => {
|
||||
this.isEdit = false;
|
||||
this.onEditChange(false);
|
||||
this.isLoad = false;
|
||||
})
|
||||
},
|
||||
@ -99,7 +122,7 @@ export default {
|
||||
},
|
||||
|
||||
onBlur() {
|
||||
if (this.clickOutSide) {
|
||||
if (this.clickOutSide || !this.isEdit) {
|
||||
return;
|
||||
}
|
||||
this.onEnter();
|
||||
|
@ -5,7 +5,7 @@
|
||||
{{ $L('使用 SSO 登录') }}
|
||||
</div>
|
||||
<template v-if="showDown">
|
||||
<div v-if="$Electron" class="common-right-bottom-link" @click="releasesNotification">
|
||||
<div v-if="$Electron" class="common-right-bottom-link" @click="updateWinShow=true">
|
||||
<Icon type="md-download"/>
|
||||
{{ $L(repoTitle) }}
|
||||
</div>
|
||||
@ -14,20 +14,32 @@
|
||||
{{ $L(repoTitle) }}
|
||||
</a>
|
||||
</template>
|
||||
<Modal
|
||||
v-model="updateWinShow"
|
||||
:ok-text="$L('立即升级')"
|
||||
:closable="false"
|
||||
:mask-closable="false"
|
||||
@on-ok="installApplication"
|
||||
@on-cancel="repoStatus=2"
|
||||
class-name="common-right-bottom-notification">
|
||||
<div slot="header" class="notification-head">
|
||||
<div class="notification-title">{{$L('发现新版本')}}</div>
|
||||
<Tag color="volcano">{{repoReleases.tag_name}}</Tag>
|
||||
</div>
|
||||
<MarkdownPreview class="notification-body overlay-y" :initialValue="repoReleases.body"/>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import MarkdownPreview from "./MDEditor/components/preview";
|
||||
import axios from "axios";
|
||||
Vue.component('MarkdownPreview', MarkdownPreview)
|
||||
|
||||
import {mapState} from "vuex";
|
||||
import {Store} from "le5le-store";
|
||||
|
||||
export default {
|
||||
name: 'RightBottom',
|
||||
components: {MarkdownPreview},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
@ -37,6 +49,7 @@ export default {
|
||||
repoStatus: 0, // 0 没有,1有客户端,2客户端有新版本
|
||||
repoReleases: {},
|
||||
|
||||
updateWinShow: false,
|
||||
downloadResult: {},
|
||||
|
||||
subscribe: null,
|
||||
@ -46,14 +59,15 @@ export default {
|
||||
this.getReleases();
|
||||
//
|
||||
this.subscribe = Store.subscribe('releasesNotification', () => {
|
||||
this.releasesNotification();
|
||||
this.updateWinShow = true;
|
||||
});
|
||||
//
|
||||
if (this.$Electron) {
|
||||
this.$Electron.registerMsgListener('downloadDone', ({result}) => {
|
||||
if (result.name == this.repoData.name && this.repoStatus !== 2) {
|
||||
this.$store.state.clientNewVersion = this.repoReleases.tag_name
|
||||
this.downloadResult = result;
|
||||
this.releasesNotification()
|
||||
this.updateWinShow = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -213,44 +227,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
releasesNotification() {
|
||||
const {tag_name, body} = this.repoReleases;
|
||||
this.$store.state.clientNewVersion = tag_name
|
||||
$A.modalConfirm({
|
||||
okText: this.$L('立即更新'),
|
||||
onOk: () => {
|
||||
this.installApplication();
|
||||
},
|
||||
onCancel: () => {
|
||||
this.repoStatus = 2;
|
||||
},
|
||||
render: (h) => {
|
||||
return h('div', {
|
||||
class: 'common-right-bottom-notification'
|
||||
}, [
|
||||
h('div', {
|
||||
class: "notification-head"
|
||||
}, [
|
||||
h('div', {
|
||||
class: "notification-title"
|
||||
}, this.$L('发现新版本')),
|
||||
h('Tag', {
|
||||
props: {
|
||||
color: 'volcano'
|
||||
}
|
||||
}, tag_name)
|
||||
]),
|
||||
h('MarkdownPreview', {
|
||||
class: 'notification-body',
|
||||
props: {
|
||||
initialValue: body
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
installApplication() {
|
||||
if (!this.$Electron) {
|
||||
return;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="teditor-wrapper">
|
||||
<div class="teditor-box" :class="[!inline && spinShow ? 'teditor-loadstyle' : 'teditor-loadedstyle']">
|
||||
<template v-if="inline">
|
||||
<div ref="myTextarea" :id="id" v-html="content"></div>
|
||||
<div ref="myTextarea" :id="id" v-html="spinShow ? '' : content"></div>
|
||||
<Icon v-if="spinShow" type="ios-loading" :size="18" class="icon-loading icon-inline"></Icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
@ -181,11 +181,7 @@
|
||||
newValue = "";
|
||||
}
|
||||
if (!this.isTyping) {
|
||||
if (this.getEditor() !== null) {
|
||||
this.getEditor().setContent(newValue);
|
||||
} else{
|
||||
this.content = newValue;
|
||||
}
|
||||
this.setContent(newValue);
|
||||
}
|
||||
},
|
||||
readOnly(value) {
|
||||
@ -459,6 +455,14 @@
|
||||
return this.getEditor().getContent();
|
||||
},
|
||||
|
||||
setContent(content) {
|
||||
if (this.getEditor() === null) {
|
||||
this.content = content;
|
||||
} else if (content != this.getEditor().getContent()){
|
||||
this.getEditor().setContent(content);
|
||||
}
|
||||
},
|
||||
|
||||
insertImage(src) {
|
||||
this.insertContent('<img src="' + src + '">');
|
||||
},
|
||||
|
@ -11,7 +11,7 @@
|
||||
:placeholder="tis || placeholderText"
|
||||
@keydown.enter="downEnter($event)"
|
||||
@keydown.delete="delTag(false)"
|
||||
@keyup="addTag($event, content)"
|
||||
@keyup="onKeyup"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
:disabled="disabled"
|
||||
@ -158,19 +158,22 @@
|
||||
this.addTag(false, this.content)
|
||||
this.$emit("on-blur", e)
|
||||
},
|
||||
onKeyup(e) {
|
||||
this.addTag(e, this.content);
|
||||
//
|
||||
this.$emit("on-keyup", e)
|
||||
if (e.keyCode === 13) {
|
||||
this.$nextTick(() => {
|
||||
this.$emit("on-enter", e)
|
||||
})
|
||||
}
|
||||
},
|
||||
addTag(e, content) {
|
||||
|
||||
if (e === false || e.keyCode === 13) {
|
||||
if (content.trim() != '' && this.disSource.indexOf(content.trim()) === -1) {
|
||||
this.disSource.push(content.trim());
|
||||
}
|
||||
this.content = '';
|
||||
//
|
||||
if (e.keyCode === 13) {
|
||||
this.$nextTick(() => {
|
||||
this.$emit("on-enter", e)
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.max > 0 && this.disSource.length >= this.max) {
|
||||
|
@ -102,7 +102,7 @@
|
||||
* @returns {*|string}
|
||||
*/
|
||||
formatTime(date) {
|
||||
let time = Math.round($A.Date(date).getTime() / 1000),
|
||||
let time = $A.Date(date, true),
|
||||
string = '';
|
||||
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
|
||||
string = $A.formatDate('H:i', time)
|
||||
@ -157,8 +157,10 @@
|
||||
let time = Math.round(this.Date(date).getTime() / 1000) - nowTime;
|
||||
if (time < 86400 * 7 && time > 0 ) {
|
||||
return this.formatSeconds(time);
|
||||
} else if (time <= 0) {
|
||||
} else if (time < 0) {
|
||||
return '-' + this.formatSeconds(time * -1);
|
||||
} else if (time == 0) {
|
||||
return 0 + 's';
|
||||
}
|
||||
return this.formatTime(date)
|
||||
},
|
||||
|
@ -37,7 +37,7 @@
|
||||
<i class="taskfont"></i>
|
||||
</div>
|
||||
<Dropdown-menu slot="list" class="login-setting-menu">
|
||||
<Dropdown placement="right-start" @on-click="setTheme">
|
||||
<Dropdown placement="right" @on-click="setTheme">
|
||||
<DropdownItem>
|
||||
<div class="login-setting-item">
|
||||
{{$L('主题皮肤')}}
|
||||
@ -48,7 +48,7 @@
|
||||
<Dropdown-item v-for="(item, key) in themeList" :key="key" :name="item.value" :selected="themeMode === item.value">{{$L(item.name)}}</Dropdown-item>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<Dropdown placement="right-start" @on-click="setLanguage">
|
||||
<Dropdown placement="right" @on-click="setLanguage">
|
||||
<DropdownItem divided>
|
||||
<div class="login-setting-item">
|
||||
{{currentLanguage}}
|
||||
@ -114,6 +114,9 @@ export default {
|
||||
this.subscribe = null;
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.loginType = 'login'
|
||||
},
|
||||
deactivated() {
|
||||
this.loginJump = false;
|
||||
this.password = "";
|
||||
|
@ -19,46 +19,93 @@
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem
|
||||
v-for="(item, key) in menu"
|
||||
v-if="item.visible !== false"
|
||||
:key="key"
|
||||
:divided="!!item.divided"
|
||||
:name="item.path">
|
||||
{{$L(item.name)}}
|
||||
<Badge v-if="item.path === 'version'" class="manage-menu-report-badge" :text="clientNewVersion"/>
|
||||
<Badge v-if="item.path === 'workReport'" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
|
||||
</DropdownItem>
|
||||
<Dropdown placement="right-start" @on-click="setTheme">
|
||||
<DropdownItem divided>
|
||||
<div class="manage-menu-language">
|
||||
{{$L('主题皮肤')}}
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
<template v-for="item in menu">
|
||||
<!-- 团队管理 -->
|
||||
<Dropdown
|
||||
v-if="item.path === 'team'"
|
||||
placement="right-start">
|
||||
<DropdownItem divided>
|
||||
<div class="manage-menu-flex">
|
||||
{{$L(item.name)}}
|
||||
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
|
||||
<Icon v-else type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem name="allUser">{{$L('团队管理')}}</DropdownItem>
|
||||
<DropdownItem name="workReport">
|
||||
<div class="manage-menu-flex">
|
||||
{{$L('工作报告')}}
|
||||
<Badge v-if="reportUnreadNumber > 0" class="manage-menu-report-badge" :count="reportUnreadNumber"/>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<!-- 主题皮肤 -->
|
||||
<Dropdown
|
||||
v-else-if="item.path === 'theme'"
|
||||
placement="right-start"
|
||||
@on-click="setTheme">
|
||||
<DropdownItem divided>
|
||||
<div class="manage-menu-flex">
|
||||
{{$L(item.name)}}
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem
|
||||
v-for="(item, key) in themeList"
|
||||
:key="key"
|
||||
:name="item.value"
|
||||
:selected="themeMode === item.value">{{$L(item.name)}}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<!-- 语言设置 -->
|
||||
<Dropdown
|
||||
v-else-if="item.path === 'language'"
|
||||
placement="right-start"
|
||||
@on-click="setLanguage">
|
||||
<DropdownItem divided>
|
||||
<div class="manage-menu-flex">
|
||||
{{currentLanguage}}
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem
|
||||
v-for="(item, key) in languageList"
|
||||
:key="key"
|
||||
:name="key"
|
||||
:selected="getLanguage() === key">{{item}}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<!-- 其他菜单 -->
|
||||
<DropdownItem
|
||||
v-else-if="item.visible !== false"
|
||||
:divided="!!item.divided"
|
||||
:name="item.path"
|
||||
:style="item.style || {}">
|
||||
{{$L(item.name)}}
|
||||
<Badge
|
||||
v-if="item.path === 'version'"
|
||||
class="manage-menu-report-badge"
|
||||
:text="clientNewVersion"/>
|
||||
<Badge
|
||||
v-else-if="item.path === 'workReport' && reportUnreadNumber > 0"
|
||||
class="manage-menu-report-badge"
|
||||
:count="reportUnreadNumber"/>
|
||||
</DropdownItem>
|
||||
<DropdownMenu slot="list">
|
||||
<Dropdown-item v-for="(item, key) in themeList" :key="key" :name="item.value" :selected="themeMode === item.value">{{$L(item.name)}}</Dropdown-item>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<Dropdown placement="right-start" @on-click="setLanguage">
|
||||
<DropdownItem divided>
|
||||
<div class="manage-menu-language">
|
||||
{{currentLanguage}}
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
<DropdownMenu slot="list">
|
||||
<Dropdown-item v-for="(item, key) in languageList" :key="key" :name="key" :selected="getLanguage() === key">{{item}}</Dropdown-item>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<DropdownItem divided name="signout" style="color:#f40">{{$L('退出登录')}}</DropdownItem>
|
||||
</template>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<ul :class="overlayClass" @scroll="handleClickTopOperateOutside">
|
||||
<li @click="toggleRoute('dashboard')" :class="classNameRoute('dashboard')">
|
||||
<i class="taskfont"></i>
|
||||
<div class="menu-title">{{$L('仪表盘')}}</div>
|
||||
<Badge class="menu-badge" :type="dashboardTask.overdue.length > 0 ? 'error' : 'primary'" :count="dashboardTotal"></Badge>
|
||||
<Badge v-if="dashboardTask.overdue.length > 0" class="menu-badge" type="error" :count="dashboardTask.overdue.length"/>
|
||||
<Badge v-else-if="dashboardTask.today.length > 0" class="menu-badge" type="info" :count="dashboardTask.today.length"/>
|
||||
<Badge v-else-if="dashboardTask.all.length > 0" class="menu-badge" type="primary" :count="dashboardTask.all.length"/>
|
||||
</li>
|
||||
<li @click="toggleRoute('calendar')" :class="classNameRoute('calendar')">
|
||||
<i class="taskfont"></i>
|
||||
@ -67,7 +114,7 @@
|
||||
<li @click="toggleRoute('messenger')" :class="classNameRoute('messenger')">
|
||||
<i class="taskfont"></i>
|
||||
<div class="menu-title">{{$L('消息')}}</div>
|
||||
<Badge class="menu-badge" :count="msgAllUnread"></Badge>
|
||||
<Badge class="menu-badge" :count="msgAllUnread"/>
|
||||
</li>
|
||||
<li @click="toggleRoute('file')" :class="classNameRoute('file')">
|
||||
<i class="taskfont"></i>
|
||||
@ -184,18 +231,42 @@
|
||||
<TaskAdd ref="addTask" v-model="addTaskShow"/>
|
||||
</Modal>
|
||||
|
||||
<!--导出任务统计-->
|
||||
<Modal
|
||||
v-model="exportTaskShow"
|
||||
:title="$L('导出任务统计')"
|
||||
:mask-closable="false">
|
||||
<Form ref="exportTask" :model="exportData" label-width="auto" @submit.native.prevent>
|
||||
<FormItem :label="$L('导出会员')">
|
||||
<UserInput v-model="exportData.userid" :multiple-max="20" :placeholder="$L('请选择会员')"/>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('时间范围')">
|
||||
<DatePicker
|
||||
v-model="exportData.time"
|
||||
type="daterange"
|
||||
format="yyyy/MM/dd"
|
||||
style="width:100%"
|
||||
:placeholder="$L('请选择时间')"/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer" class="adaption">
|
||||
<Button type="default" @click="exportTaskShow=false">{{$L('取消')}}</Button>
|
||||
<Button type="primary" :loading="exportLoadIng > 0" @click="onExportTask">{{$L('导出')}}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!--任务详情-->
|
||||
<Modal
|
||||
:value="taskId > 0"
|
||||
:mask-closable="false"
|
||||
:styles="{
|
||||
width: '90%',
|
||||
maxWidth: taskData.dialog_id ? '1200px' : '700px'
|
||||
}"
|
||||
@on-visible-change="taskVisibleChange"
|
||||
footer-hide>
|
||||
:mask-closable="false"
|
||||
:footer-hide="true"
|
||||
@on-visible-change="taskVisibleChange">
|
||||
<div class="page-manage-task-modal" :style="taskStyle">
|
||||
<TaskDetail :task-id="taskId" :open-task="taskData"/>
|
||||
<TaskDetail ref="taskDetail" :task-id="taskId" :open-task="taskData"/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@ -248,20 +319,27 @@
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import TaskDetail from "./manage/components/TaskDetail";
|
||||
import ProjectArchived from "./manage/components/ProjectArchived";
|
||||
import notificationKoro from "notification-koro1";
|
||||
import TeamManagement from "./manage/components/TeamManagement";
|
||||
import ProjectManagement from "./manage/components/ProjectManagement";
|
||||
import DrawerOverlay from "../components/DrawerOverlay";
|
||||
import DragBallComponent from "../components/DragBallComponent";
|
||||
import TaskAdd from "./manage/components/TaskAdd";
|
||||
import Report from "./manage/components/Report";
|
||||
import notificationKoro from "notification-koro1";
|
||||
import {Store} from "le5le-store";
|
||||
import UserInput from "../components/UserInput";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserInput,
|
||||
TaskAdd,
|
||||
TaskDetail,
|
||||
Report,
|
||||
DragBallComponent, DrawerOverlay, ProjectManagement, TeamManagement, ProjectArchived, TaskDetail},
|
||||
DragBallComponent,
|
||||
DrawerOverlay,
|
||||
ProjectManagement,
|
||||
TeamManagement,
|
||||
ProjectArchived},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
@ -280,6 +358,13 @@ export default {
|
||||
addTaskShow: false,
|
||||
addTaskSubscribe: null,
|
||||
|
||||
exportTaskShow: false,
|
||||
exportLoadIng: 0,
|
||||
exportData: {
|
||||
userid: [],
|
||||
time: [],
|
||||
},
|
||||
|
||||
dialogMsgSubscribe: null,
|
||||
|
||||
projectKeyValue: '',
|
||||
@ -380,12 +465,8 @@ export default {
|
||||
return num;
|
||||
},
|
||||
|
||||
dashboardTotal() {
|
||||
return this.dashboardTask.today.length + this.dashboardTask.overdue.length
|
||||
},
|
||||
|
||||
unreadTotal() {
|
||||
return this.msgAllUnread + this.dashboardTotal + this.reportUnreadNumber;
|
||||
return this.msgAllUnread + this.dashboardTask.overdue.length + this.reportUnreadNumber;
|
||||
},
|
||||
|
||||
currentLanguage() {
|
||||
@ -399,21 +480,37 @@ export default {
|
||||
{path: 'personal', name: '个人设置'},
|
||||
{path: 'password', name: '密码设置'},
|
||||
{path: 'clearCache', name: '清除缓存'},
|
||||
|
||||
{path: 'system', name: '系统设置', divided: true},
|
||||
{path: 'version', name: '更新版本', visible: !!this.clientNewVersion},
|
||||
{path: 'workReport', name: '工作报告', divided: true},
|
||||
{path: 'allUser', name: '团队管理'},
|
||||
{path: 'allProject', name: '所有项目'},
|
||||
{path: 'archivedProject', name: '已归档的项目'}
|
||||
|
||||
{path: 'allProject', name: '所有项目', divided: true},
|
||||
{path: 'archivedProject', name: '已归档的项目'},
|
||||
|
||||
{path: 'team', name: '团队管理', divided: true},
|
||||
|
||||
{path: 'theme', name: '主题皮肤', divided: true},
|
||||
|
||||
{path: 'language', name: this.currentLanguage, divided: true},
|
||||
|
||||
{path: 'logout', name: '退出登录', style: {color: '#f40'}, divided: true},
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{path: 'personal', name: '个人设置'},
|
||||
{path: 'password', name: '密码设置'},
|
||||
{path: 'clearCache', name: '清除缓存'},
|
||||
|
||||
{path: 'version', name: '更新版本', divided: true, visible: !!this.clientNewVersion},
|
||||
|
||||
{path: 'workReport', name: '工作报告', divided: true},
|
||||
{path: 'archivedProject', name: '已归档的项目'}
|
||||
{path: 'archivedProject', name: '已归档的项目'},
|
||||
|
||||
{path: 'theme', name: '主题皮肤', divided: true},
|
||||
|
||||
{path: 'language', name: this.currentLanguage, divided: true},
|
||||
|
||||
{path: 'logout', name: '退出登录', style: {color: '#f40'}, divided: true},
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -429,7 +526,7 @@ export default {
|
||||
|
||||
projectLists() {
|
||||
const {projectKeyValue, cacheProjects} = this;
|
||||
const data = cacheProjects.sort((a, b) => {
|
||||
const data = $A.cloneJSON(cacheProjects).sort((a, b) => {
|
||||
if (a.top_at || b.top_at) {
|
||||
return $A.Date(b.top_at) - $A.Date(a.top_at);
|
||||
}
|
||||
@ -575,6 +672,9 @@ export default {
|
||||
case 'archivedProject':
|
||||
this.archivedProjectShow = true;
|
||||
return;
|
||||
case 'exportTask':
|
||||
this.exportTaskShow = true;
|
||||
return;
|
||||
case 'workReport':
|
||||
if (this.reportUnreadNumber > 0) {
|
||||
this.reportTabs = "receive";
|
||||
@ -592,7 +692,7 @@ export default {
|
||||
window.location.reload()
|
||||
});
|
||||
return;
|
||||
case 'signout':
|
||||
case 'logout':
|
||||
$A.modalConfirm({
|
||||
title: '退出登录',
|
||||
content: '你确定要登出系统?',
|
||||
@ -685,10 +785,13 @@ export default {
|
||||
},
|
||||
|
||||
shortcutEvent(e) {
|
||||
if (e.keyCode === 75 || e.keyCode === 78) {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
if (e.keyCode === 75 || e.keyCode === 78) {
|
||||
e.preventDefault();
|
||||
this.onAddTask(0)
|
||||
} else if (e.keyCode === 83 && this.taskId > 0) {
|
||||
e.preventDefault();
|
||||
this.$refs.taskDetail.checkUpdate(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -756,11 +859,60 @@ export default {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'report/unread',
|
||||
}).then(({data}) => {
|
||||
this.reportUnreadNumber = data.total ? data.total : 0;
|
||||
this.reportUnreadNumber = data.total || 0;
|
||||
}).catch(() => {});
|
||||
}, typeof timeout === "number" ? timeout : 1000)
|
||||
},
|
||||
|
||||
handleRightClick(event, item) {
|
||||
this.handleClickTopOperateOutside();
|
||||
this.topOperateItem = item;
|
||||
this.$nextTick(() => {
|
||||
const projectWrap = this.$refs.projectWrapper;
|
||||
const projectBounding = projectWrap.getBoundingClientRect();
|
||||
this.topOperateStyles = {
|
||||
left: `${event.clientX - projectBounding.left}px`,
|
||||
top: `${event.clientY - projectBounding.top}px`
|
||||
};
|
||||
this.topOperateVisible = true;
|
||||
})
|
||||
},
|
||||
|
||||
handleClickTopOperateOutside() {
|
||||
this.topOperateVisible = false;
|
||||
},
|
||||
|
||||
handleTopClick() {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/top',
|
||||
data: {
|
||||
project_id: this.topOperateItem.id,
|
||||
},
|
||||
}).then(() => {
|
||||
this.$store.dispatch("getProjects").catch(() => {});
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg, 301);
|
||||
});
|
||||
},
|
||||
|
||||
onExportTask() {
|
||||
if (this.exportLoadIng > 0) {
|
||||
return;
|
||||
}
|
||||
this.exportLoadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/task/export',
|
||||
data: this.exportData,
|
||||
}).then(({data}) => {
|
||||
this.exportLoadIng--;
|
||||
this.exportTaskShow = false;
|
||||
$A.downFile(data.url);
|
||||
}).catch(({msg}) => {
|
||||
this.exportLoadIng--;
|
||||
$A.modalError(msg);
|
||||
});
|
||||
},
|
||||
|
||||
notificationInit() {
|
||||
this.notificationClass = new notificationKoro(this.$L("打开通知成功"));
|
||||
if (this.notificationClass.support) {
|
||||
@ -821,37 +973,6 @@ export default {
|
||||
}
|
||||
document.addEventListener(visibilityChangeEvent, visibilityChangeListener);
|
||||
},
|
||||
|
||||
handleRightClick(event, item) {
|
||||
this.handleClickTopOperateOutside();
|
||||
this.topOperateItem = item;
|
||||
this.$nextTick(() => {
|
||||
const projectWrap = this.$refs.projectWrapper;
|
||||
const projectBounding = projectWrap.getBoundingClientRect();
|
||||
this.topOperateStyles = {
|
||||
left: `${event.clientX - projectBounding.left}px`,
|
||||
top: `${event.clientY - projectBounding.top}px`
|
||||
};
|
||||
this.topOperateVisible = true;
|
||||
})
|
||||
},
|
||||
|
||||
handleClickTopOperateOutside() {
|
||||
this.topOperateVisible = false;
|
||||
},
|
||||
|
||||
handleTopClick() {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/top',
|
||||
data: {
|
||||
project_id: this.topOperateItem.id,
|
||||
},
|
||||
}).then(() => {
|
||||
this.$store.dispatch("getProjects").catch(() => {});
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg, 301);
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -46,6 +46,7 @@
|
||||
<div class="time" :title="msgData.created_at">{{$A.formatTime(msgData.created_at)}}</div>
|
||||
<Poptip
|
||||
v-if="msgData.send > 1 || dialogType == 'group'"
|
||||
ref="percent"
|
||||
class="percent"
|
||||
placement="left-end"
|
||||
transfer
|
||||
@ -134,13 +135,13 @@ export default {
|
||||
}
|
||||
this.msgData._r = true;
|
||||
//
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (!this.$el.offsetParent) {
|
||||
this.msgData._r = false;
|
||||
return
|
||||
}
|
||||
this.$store.dispatch("dialogMsgRead", this.msgData);
|
||||
})
|
||||
}, 50)
|
||||
},
|
||||
|
||||
popperShow() {
|
||||
@ -151,6 +152,7 @@ export default {
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.read_list = data;
|
||||
this.$refs.percent.updatePopper();
|
||||
}).catch(() => {
|
||||
this.read_list = [];
|
||||
});
|
||||
|
@ -90,6 +90,7 @@
|
||||
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
|
||||
</div>
|
||||
|
||||
<!--拖动发送提示-->
|
||||
<Modal
|
||||
v-model="pasteShow"
|
||||
:title="$L(pasteTitle)"
|
||||
|
@ -623,10 +623,8 @@ export default {
|
||||
column.tasks = this.transforTasks(allTask.filter(task => {
|
||||
return task.column_id == column.id;
|
||||
})).sort((a, b) => {
|
||||
let at1 = $A.Date(a.complete_at),
|
||||
at2 = $A.Date(b.complete_at);
|
||||
if (at1 || at2) {
|
||||
return at1 - at2;
|
||||
if (a.complete_at || b.complete_at) {
|
||||
return $A.Date(a.complete_at) - $A.Date(b.complete_at);
|
||||
}
|
||||
if (a.sort != b.sort) {
|
||||
return a.sort - b.sort;
|
||||
|
@ -238,7 +238,7 @@ export default {
|
||||
const {times} = this.addData;
|
||||
let temp = $A.date2string(times, "Y-m-d H:i");
|
||||
if (temp[0] && temp[1]) {
|
||||
let d = Math.ceil(($A.Date(temp[1]).getTime() - $A.Date(temp[0]).getTime()) / 86400000);
|
||||
let d = Math.ceil(($A.Date(temp[1], true) - $A.Date(temp[0], true)) / 86400);
|
||||
if (d > 0) {
|
||||
return d;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<li v-if="ready && taskDetail.parent_id > 0">
|
||||
<div class="subtask-icon">
|
||||
<TaskMenu
|
||||
v-if="taskId > 0"
|
||||
:ref="`taskMenu_${taskDetail.id}`"
|
||||
:task="taskDetail"
|
||||
:load-status="taskDetail.loading === true"
|
||||
@ -72,6 +73,7 @@
|
||||
<div v-show="taskDetail.id > 0" class="task-info">
|
||||
<div class="head">
|
||||
<TaskMenu
|
||||
v-if="taskId > 0"
|
||||
:ref="`taskMenu_${taskDetail.id}`"
|
||||
:task="taskDetail"
|
||||
class="icon"
|
||||
@ -121,6 +123,7 @@
|
||||
</ETooltip>
|
||||
<div class="menu">
|
||||
<TaskMenu
|
||||
v-if="taskId > 0"
|
||||
:task="taskDetail"
|
||||
icon="ios-more"
|
||||
completed-icon="ios-more"
|
||||
@ -150,7 +153,6 @@
|
||||
:option-full="taskOptionFull"
|
||||
:placeholder="$L('详细描述...')"
|
||||
@on-blur="updateData('content')"
|
||||
@editorSave="updateData('content')"
|
||||
inline/>
|
||||
</div>
|
||||
<Form class="items" label-position="left" label-width="auto" @submit.native.prevent>
|
||||
@ -261,7 +263,7 @@
|
||||
<div @click="openTime" class="time">{{taskDetail.end_at ? cutTime : '--'}}</div>
|
||||
<template v-if="!taskDetail.complete_at && taskDetail.end_at">
|
||||
<Tag v-if="within24Hours(taskDetail.end_at)" color="blue"><i class="taskfont"></i>{{expiresFormat(taskDetail.end_at)}}</Tag>
|
||||
<Tag v-if="taskDetail.overdue" color="red">{{$L('超期未完成')}}</Tag>
|
||||
<Tag v-if="isOverdue(taskDetail)" color="red">{{$L('超期未完成')}}</Tag>
|
||||
</template>
|
||||
</div>
|
||||
</DatePicker>
|
||||
@ -307,7 +309,13 @@
|
||||
<i class="taskfont"></i>{{$L('子任务')}}
|
||||
</div>
|
||||
<ul class="item-content subtask">
|
||||
<TaskDetail v-for="(task, key) in subList" :key="key" :task-id="task.id" :open-task="task" :main-end-at="taskDetail.end_at"/>
|
||||
<TaskDetail
|
||||
v-for="(task, key) in subList"
|
||||
:ref="`subTask_${task.id}`"
|
||||
:key="key"
|
||||
:task-id="task.id"
|
||||
:open-task="task"
|
||||
:main-end-at="taskDetail.end_at"/>
|
||||
</ul>
|
||||
<ul :class="['item-content', subList.length === 0 ? 'nosub' : '']">
|
||||
<li>
|
||||
@ -399,7 +407,10 @@
|
||||
@on-input-paste="msgPasteDrag"/>
|
||||
<div class="no-send" @click="msgDialog">
|
||||
<Loading v-if="sendLoad > 0"/>
|
||||
<Icon v-else type="md-send" />
|
||||
<template v-else>
|
||||
<Badge :count="taskDetail.msg_num"/>
|
||||
<Icon type="md-send" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
|
||||
@ -613,8 +624,8 @@ export default {
|
||||
|
||||
cutTime() {
|
||||
const {taskDetail} = this;
|
||||
let start_at = Math.round($A.Date(taskDetail.start_at).getTime() / 1000);
|
||||
let end_at = Math.round($A.Date(taskDetail.end_at).getTime() / 1000);
|
||||
let start_at = $A.Date(taskDetail.start_at, true);
|
||||
let end_at = $A.Date(taskDetail.end_at, true);
|
||||
let string = "";
|
||||
if ($A.formatDate('Y/m/d', start_at) == $A.formatDate('Y/m/d', end_at)) {
|
||||
string = $A.formatDate('Y/m/d H:i', start_at) + " ~ " + $A.formatDate('H:i', end_at)
|
||||
@ -722,29 +733,27 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
initLanguage() {
|
||||
|
||||
},
|
||||
|
||||
innerHeightListener() {
|
||||
this.innerHeight = Math.min(1100, window.innerHeight);
|
||||
},
|
||||
|
||||
within24Hours(date) {
|
||||
return Math.round($A.Date(date).getTime() / 1000) - this.nowTime < 86400
|
||||
return $A.Date(date, true) - this.nowTime < 86400
|
||||
},
|
||||
|
||||
expiresFormat(date) {
|
||||
return $A.countDownFormat(date, this.nowTime)
|
||||
},
|
||||
|
||||
isOverdue(taskDetail) {
|
||||
if (taskDetail.overdue) {
|
||||
return true;
|
||||
}
|
||||
return $A.Date(taskDetail.end_at, true) < this.nowTime;
|
||||
},
|
||||
|
||||
onNameKeydown(e) {
|
||||
if (e.keyCode === 83) {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
this.updateData('name');
|
||||
}
|
||||
} else if (e.keyCode === 13) {
|
||||
if (e.keyCode === 13) {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
this.updateData('name');
|
||||
@ -752,6 +761,40 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
checkUpdate(update) {
|
||||
let isModify = false;
|
||||
if (this.openTask.name != this.taskDetail.name) {
|
||||
isModify = true;
|
||||
if (update) {
|
||||
this.updateData('name');
|
||||
} else if (isModify) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (this.$refs.desc && this.$refs.desc.getContent() != this.taskContent) {
|
||||
isModify = true;
|
||||
if (update) {
|
||||
this.updateData('content');
|
||||
} else if (isModify) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (this.addsubShow && this.addsubName) {
|
||||
isModify = true;
|
||||
if (update) {
|
||||
this.onAddsub();
|
||||
} else if (isModify) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
this.subList.some(({id}) => {
|
||||
if (this.$refs[`subTask_${id}`][0].checkUpdate(update)) {
|
||||
isModify = true;
|
||||
}
|
||||
})
|
||||
return isModify;
|
||||
},
|
||||
|
||||
updateData(action, params) {
|
||||
switch (action) {
|
||||
case 'priority':
|
||||
|
@ -15,7 +15,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</slot>
|
||||
<EDropdownMenu slot="dropdown" class="task-menu-more-dropdown">
|
||||
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-menu-more-dropdown">
|
||||
<li class="task-menu-more-warp" :class="size">
|
||||
<ul>
|
||||
<EDropdownItem v-if="!flow" class="load-flow" disabled>
|
||||
@ -218,7 +218,9 @@ export default {
|
||||
|
||||
visibleChange(visible) {
|
||||
if (visible) {
|
||||
this.$store.dispatch("getTaskFlow", this.task.id).catch(() => {})
|
||||
this.$store.dispatch("getTaskFlow", this.task.id)
|
||||
.then(this.$refs.dropdownMenu.updatePopper)
|
||||
.catch(this.$refs.dropdownMenu.updatePopper)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -266,7 +266,7 @@ export default {
|
||||
},
|
||||
|
||||
completeAtFormat(date) {
|
||||
let time = Math.round($A.Date(date).getTime() / 1000);
|
||||
let time = $A.Date(date, true);
|
||||
if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
|
||||
return $A.formatDate('m-d H:i', time)
|
||||
} else {
|
||||
|
@ -57,73 +57,82 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="fileList"
|
||||
:height="tableHeight"
|
||||
:no-data-text="$L('没有任何文件')"
|
||||
@on-cell-click="clickRow"
|
||||
@on-contextmenu="handleContextMenu"
|
||||
@on-select="handleTableSelect"
|
||||
@on-select-cancel="handleTableSelect"
|
||||
@on-select-all-cancel="handleTableSelect"
|
||||
@on-select-all="handleTableSelect"
|
||||
context-menu
|
||||
stripe/>
|
||||
<div
|
||||
class="file-drag"
|
||||
@drop.prevent="filePasteDrag($event, 'drag')"
|
||||
@dragover.prevent="fileDragOver(true, $event)"
|
||||
@dragleave.prevent="fileDragOver(false, $event)">
|
||||
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="fileList"
|
||||
:height="tableHeight"
|
||||
:no-data-text="$L('没有任何文件')"
|
||||
@on-cell-click="clickRow"
|
||||
@on-contextmenu="handleContextMenu"
|
||||
@on-select="handleTableSelect"
|
||||
@on-select-cancel="handleTableSelect"
|
||||
@on-select-all-cancel="handleTableSelect"
|
||||
@on-select-all="handleTableSelect"
|
||||
context-menu
|
||||
stripe/>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
|
||||
<i class="taskfont"></i>
|
||||
<p>{{$L('没有任何文件')}}</p>
|
||||
</div>
|
||||
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
|
||||
<ul class="clearfix">
|
||||
<li
|
||||
v-for="item in fileList"
|
||||
:class="{
|
||||
shear: shearIds.includes(item.id),
|
||||
highlight: selectIds.includes(item.id),
|
||||
}"
|
||||
@contextmenu.prevent.stop="handleRightClick($event, item)"
|
||||
@click="openFile(item)">
|
||||
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
|
||||
<Checkbox :value="selectIds.includes(item.id)"/>
|
||||
</div>
|
||||
<div class="file-menu" @click.stop="handleRightClick($event, item)">
|
||||
<Icon type="ios-more" />
|
||||
</div>
|
||||
<div :class="`no-dark-mode-before file-icon ${item.type}`">
|
||||
<template v-if="item.share">
|
||||
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
|
||||
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
|
||||
</UserAvatar>
|
||||
<div v-else class="share-icon no-dark-mode">
|
||||
<i class="taskfont"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="isParentShare">
|
||||
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
|
||||
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
|
||||
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
|
||||
</UserAvatar>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="item._edit" class="file-input">
|
||||
<Input
|
||||
:ref="'input_' + item.id"
|
||||
v-model="item.newname"
|
||||
size="small"
|
||||
:disabled="!!item._load"
|
||||
@on-blur="onBlur(item)"
|
||||
@on-keyup="onKeyup($event, item)"/>
|
||||
<div v-if="item._load" class="file-load"><Loading/></div>
|
||||
</div>
|
||||
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
|
||||
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
|
||||
<i class="taskfont"></i>
|
||||
<p>{{$L('没有任何文件')}}</p>
|
||||
</div>
|
||||
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
|
||||
<ul class="clearfix">
|
||||
<li
|
||||
v-for="item in fileList"
|
||||
:class="{
|
||||
shear: shearIds.includes(item.id),
|
||||
highlight: selectIds.includes(item.id),
|
||||
}"
|
||||
@contextmenu.prevent.stop="handleRightClick($event, item)"
|
||||
@click="openFile(item)">
|
||||
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
|
||||
<Checkbox :value="selectIds.includes(item.id)"/>
|
||||
</div>
|
||||
<div class="file-menu" @click.stop="handleRightClick($event, item)">
|
||||
<Icon type="ios-more" />
|
||||
</div>
|
||||
<div :class="`no-dark-mode-before file-icon ${item.type}`">
|
||||
<template v-if="item.share">
|
||||
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
|
||||
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
|
||||
</UserAvatar>
|
||||
<div v-else class="share-icon no-dark-mode">
|
||||
<i class="taskfont"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="isParentShare">
|
||||
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
|
||||
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
|
||||
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
|
||||
</UserAvatar>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="item._edit" class="file-input">
|
||||
<Input
|
||||
:ref="'input_' + item.id"
|
||||
v-model="item.newname"
|
||||
size="small"
|
||||
:disabled="!!item._load"
|
||||
@on-blur="onBlur(item)"
|
||||
@on-enter="onEnter(item)"/>
|
||||
<div v-if="item._load" class="file-load"><Loading/></div>
|
||||
</div>
|
||||
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="file-menu" :style="contextMenuStyles">
|
||||
<Dropdown
|
||||
@ -323,6 +332,21 @@
|
||||
<FileContent v-else v-model="fileShow" :file="fileInfo"/>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--拖动上传提示-->
|
||||
<Modal
|
||||
v-model="pasteShow"
|
||||
:title="$L(pasteTitle)"
|
||||
:cancel-text="$L('取消')"
|
||||
:ok-text="$L('发送')"
|
||||
:enter-ok="true"
|
||||
@on-ok="pasteSend">
|
||||
<div class="dialog-wrapper-paste">
|
||||
<template v-for="item in pasteItem">
|
||||
<img v-if="item.type == 'image'" :src="item.result"/>
|
||||
<div v-else>{{$L('文件')}}: {{item.name}} ({{$A.bytesToSize(item.size)}})</div>
|
||||
</template>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -457,6 +481,11 @@ export default {
|
||||
|
||||
shearIds: [],
|
||||
selectIds: [],
|
||||
|
||||
dialogDrag: false,
|
||||
pasteShow: false,
|
||||
pasteFile: [],
|
||||
pasteItem: [],
|
||||
}
|
||||
},
|
||||
|
||||
@ -537,6 +566,18 @@ export default {
|
||||
const {navigator} = this;
|
||||
return !!navigator.find(({share}) => share);
|
||||
},
|
||||
|
||||
pasteTitle() {
|
||||
const {pasteItem} = this;
|
||||
let hasImage = pasteItem.find(({type}) => type == 'image')
|
||||
let hasFile = pasteItem.find(({type}) => type != 'image')
|
||||
if (hasImage && hasFile) {
|
||||
return '上传文件/图片'
|
||||
} else if (hasImage) {
|
||||
return '上传图片'
|
||||
}
|
||||
return '上传文件'
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -918,7 +959,6 @@ export default {
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
this.$set(item, 'newname', item.name);
|
||||
this.setEdit(item.id, true)
|
||||
this.autoBlur(item.id)
|
||||
break;
|
||||
@ -1113,9 +1153,21 @@ export default {
|
||||
},
|
||||
|
||||
onBlur(item) {
|
||||
if (this.files.find(({id, _edit}) => id == item.id && !_edit)) {
|
||||
return;
|
||||
}
|
||||
this.onEnter(item);
|
||||
},
|
||||
|
||||
onKeyup(e, item) {
|
||||
if (e.keyCode === 13) {
|
||||
this.onEnter(item);
|
||||
} else if (e.keyCode === 27) {
|
||||
this.setLoad(item.id, false)
|
||||
this.setEdit(item.id, false)
|
||||
}
|
||||
},
|
||||
|
||||
onEnter(item) {
|
||||
let isCreate = !/^\d+$/.test(item.id);
|
||||
if (!item.newname) {
|
||||
@ -1163,6 +1215,9 @@ export default {
|
||||
let item = this.$store.state.files.find(({id}) => id == fileId)
|
||||
if (item) {
|
||||
this.$set(item, '_edit', is);
|
||||
if (is) {
|
||||
this.$set(item, 'newname', item.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -1281,6 +1336,61 @@ export default {
|
||||
this.selectIds = [];
|
||||
},
|
||||
|
||||
/********************拖动上传部分************************/
|
||||
|
||||
pasteDragNext(e, type) {
|
||||
let files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||
files = Array.prototype.slice.call(files);
|
||||
if (files.length > 0) {
|
||||
e.preventDefault();
|
||||
if (files.length > 0) {
|
||||
this.pasteFile = [];
|
||||
this.pasteItem = [];
|
||||
files.some(file => {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = ({target}) => {
|
||||
this.pasteFile.push(file)
|
||||
this.pasteItem.push({
|
||||
type: $A.getMiddle(file.type, null, '/'),
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
result: target.result
|
||||
})
|
||||
this.pasteShow = true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
filePasteDrag(e, type) {
|
||||
this.dialogDrag = false;
|
||||
this.pasteDragNext(e, type);
|
||||
},
|
||||
|
||||
fileDragOver(show, e) {
|
||||
let random = (this.__dialogDrag = $A.randomString(8));
|
||||
if (!show) {
|
||||
setTimeout(() => {
|
||||
if (random === this.__dialogDrag) {
|
||||
this.dialogDrag = show;
|
||||
}
|
||||
}, 150);
|
||||
} else {
|
||||
if (e.dataTransfer.effectAllowed === 'move') {
|
||||
return;
|
||||
}
|
||||
this.dialogDrag = true;
|
||||
}
|
||||
},
|
||||
|
||||
pasteSend() {
|
||||
this.pasteFile.some(file => {
|
||||
this.$refs.fileUpload.upload(file)
|
||||
});
|
||||
},
|
||||
|
||||
/********************文件上传部分************************/
|
||||
|
||||
uploadUpdate(fileList) {
|
||||
|
@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
|
||||
previewUrl() {
|
||||
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.msgDetail.url))
|
||||
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.msgDetail.content.url))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
|
||||
previewUrl() {
|
||||
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.url))
|
||||
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.content.url))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -74,8 +74,12 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
params.error = () => {
|
||||
reject({data: {}, msg: "System error"})
|
||||
params.error = (xhr, status) => {
|
||||
if (window.navigator.onLine === false || (status === 0 && xhr.readyState === 4)) {
|
||||
reject({data: {}, msg: $A.L('网络异常,请稍后再试!')})
|
||||
} else {
|
||||
reject({data: {}, msg: "System error"})
|
||||
}
|
||||
};
|
||||
//
|
||||
if (params.websocket === true || params.ws === true) {
|
||||
@ -1628,6 +1632,7 @@ export default {
|
||||
task_id: task_id
|
||||
},
|
||||
}).then(result => {
|
||||
let task = state.cacheTasks.find(({id}) => id == task_id)
|
||||
let {data} = result
|
||||
data.turns.some(item => {
|
||||
let index = state.taskFlowItems.findIndex(({id}) => id == item.id);
|
||||
@ -1636,6 +1641,16 @@ export default {
|
||||
} else {
|
||||
state.taskFlowItems.push(item);
|
||||
}
|
||||
if (task
|
||||
&& task.flow_item_id == item.id
|
||||
&& task.flow_item_name != item.name) {
|
||||
state.cacheTasks.filter(({flow_item_id})=> flow_item_id == item.id).some(task => {
|
||||
dispatch("saveTask", {
|
||||
id: task.id,
|
||||
flow_item_name: `${item.status}|${item.name}`,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
//
|
||||
delete data.turns;
|
||||
@ -2035,7 +2050,7 @@ export default {
|
||||
}
|
||||
});
|
||||
state.wsReadWaitList = [];
|
||||
}, 20);
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2163,6 +2178,12 @@ export default {
|
||||
// 更新最后消息
|
||||
dispatch("updateDialogLastMsg", data);
|
||||
break;
|
||||
case 'readed':
|
||||
// 已读回执
|
||||
if (state.dialogMsgs.find(({id}) => id == data.id)) {
|
||||
dispatch("saveDialogMsg", data)
|
||||
}
|
||||
break;
|
||||
}
|
||||
})(msgDetail);
|
||||
break;
|
||||
|
@ -44,7 +44,7 @@
|
||||
max-height: 210px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin: 18px 0;
|
||||
margin-bottom: 16px;
|
||||
.markdown-preview {
|
||||
margin: -20px -12px;
|
||||
h2 {
|
||||
|
@ -476,7 +476,7 @@
|
||||
.dialog-send {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 28px;
|
||||
right: 14px;
|
||||
bottom: 0;
|
||||
font-size: 18px;
|
||||
width: 46px;
|
||||
@ -604,10 +604,6 @@
|
||||
.dialog-footer {
|
||||
padding: 0 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.dialog-send {
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -478,9 +478,9 @@
|
||||
padding: 0;
|
||||
margin: 10px 0 0 0;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.task-tags {
|
||||
|
@ -77,6 +77,13 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
pre {
|
||||
padding: 14px;
|
||||
margin: 7px 0;
|
||||
overflow: auto;
|
||||
background: #f5f2f0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
&[data-mce-placeholder]:not(.mce-visualblocks)::before {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
@ -163,6 +163,13 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
pre {
|
||||
padding: 14px;
|
||||
margin: 7px 0;
|
||||
overflow: auto;
|
||||
background: #f5f2f0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
&[data-mce-placeholder]:not(.mce-visualblocks)::before {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
@ -433,6 +440,7 @@
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
height: 32px;
|
||||
white-space: nowrap;
|
||||
> i {
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
@ -554,6 +562,12 @@
|
||||
}
|
||||
.no-send {
|
||||
display: none;
|
||||
.ivu-badge {
|
||||
position: absolute;
|
||||
transform: scale(0.6);
|
||||
top: 5px;
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drag-over {
|
||||
|
@ -108,8 +108,8 @@
|
||||
padding: 8px;
|
||||
|
||||
.load-flow-warp {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,8 +51,6 @@
|
||||
&:last-child {
|
||||
background-color: #98de6e;
|
||||
margin-right: 0;
|
||||
cursor: default;
|
||||
box-shadow: none;
|
||||
}
|
||||
.block-title {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
|
@ -90,24 +90,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-no {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-bottom: 5%;
|
||||
opacity: 0.8;
|
||||
> i {
|
||||
font-size: 64px;
|
||||
}
|
||||
> p {
|
||||
margin-top: 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
.file-navigator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -254,192 +236,245 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-table {
|
||||
.file-drag {
|
||||
flex: 1;
|
||||
cursor: default;
|
||||
margin: 16px 32px 32px;
|
||||
.ivu-table {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
.ivu-table-tip {
|
||||
opacity: 0.8;
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.8;
|
||||
&:before {
|
||||
display: block;
|
||||
content: "\e60b";
|
||||
font-family: "taskfont", "serif" !important;
|
||||
font-size: 64px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-stroke-width: 0.2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-nbox {
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.file-no {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
&.shear {
|
||||
opacity: 0.38;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-bottom: 5%;
|
||||
opacity: 0.8;
|
||||
> i {
|
||||
font-size: 64px;
|
||||
}
|
||||
.file-name {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-right: 46px;
|
||||
&:before {
|
||||
flex-shrink: 0;
|
||||
content: "";
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.permission {
|
||||
padding-left: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.taskfont {
|
||||
color: #aaaaaa;
|
||||
font-size: 16px;
|
||||
margin: 0 3px;
|
||||
> p {
|
||||
margin-top: 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-list {
|
||||
flex: 1;
|
||||
padding: 0 20px 20px;
|
||||
margin-top: 16px;
|
||||
overflow: auto;
|
||||
> ul {
|
||||
margin-top: -12px;
|
||||
> li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
margin: 12px;
|
||||
width: 100px;
|
||||
height: 110px;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
.file-table {
|
||||
flex: 1;
|
||||
cursor: default;
|
||||
margin: 16px 32px 32px;
|
||||
.ivu-table {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
.ivu-table-tip {
|
||||
opacity: 0.8;
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.8;
|
||||
&:before {
|
||||
display: block;
|
||||
content: "\e60b";
|
||||
font-family: "taskfont", "serif" !important;
|
||||
font-size: 64px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-stroke-width: 0.2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-nbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
.file-input {
|
||||
margin: 0 4px 4px;
|
||||
position: relative;
|
||||
input {
|
||||
margin: 0;
|
||||
padding: 1px 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.file-load {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 6px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
.common-loading {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-name {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: $primary-text-color;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
padding: 0 6px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.file-check {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
transition: opacity 0.2s;
|
||||
&.file-checked {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.file-menu {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
transition: opacity 0.2s;
|
||||
display: flex;
|
||||
.ivu-icon {
|
||||
font-size: 16px;
|
||||
color: #aaaaaa;
|
||||
transition: color 0.2s;
|
||||
padding: 2px 5px;
|
||||
&:hover {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-icon {
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.share-icon,
|
||||
.share-avatar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #9ACD7B;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(0.9);
|
||||
.taskfont {
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
position: relative;
|
||||
&.shear {
|
||||
opacity: 0.38;
|
||||
}
|
||||
&.highlight {
|
||||
background-color: #f4f5f7;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f4f5f7;
|
||||
.file-menu,
|
||||
.file-check {
|
||||
opacity: 1;
|
||||
.file-name {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-right: 46px;
|
||||
&:before {
|
||||
flex-shrink: 0;
|
||||
content: "";
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.permission {
|
||||
padding-left: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.taskfont {
|
||||
color: #aaaaaa;
|
||||
font-size: 16px;
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-list {
|
||||
flex: 1;
|
||||
padding: 0 20px 20px;
|
||||
margin-top: 16px;
|
||||
overflow: auto;
|
||||
> ul {
|
||||
margin-top: -12px;
|
||||
> li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
margin: 12px;
|
||||
width: 100px;
|
||||
height: 110px;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
.file-input {
|
||||
margin: 0 4px 4px;
|
||||
position: relative;
|
||||
input {
|
||||
margin: 0;
|
||||
padding: 1px 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.file-load {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 6px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
.common-loading {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-name {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: $primary-text-color;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
padding: 0 6px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.file-check {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
transition: opacity 0.2s;
|
||||
&.file-checked {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.file-menu {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
transition: opacity 0.2s;
|
||||
display: flex;
|
||||
.ivu-icon {
|
||||
font-size: 16px;
|
||||
color: #aaaaaa;
|
||||
transition: color 0.2s;
|
||||
padding: 2px 5px;
|
||||
&:hover {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-icon {
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.share-icon,
|
||||
.share-avatar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #9ACD7B;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(0.9);
|
||||
.taskfont {
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
&.shear {
|
||||
opacity: 0.38;
|
||||
}
|
||||
&.highlight {
|
||||
background-color: #f4f5f7;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f4f5f7;
|
||||
.file-menu,
|
||||
.file-check {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.drag-over {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
background-color: rgba(255, 255, 255, 0.78);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 16px 32px 32px;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 2px dashed #7b7b7b;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.drag-text {
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -450,7 +485,7 @@
|
||||
.file-upload-list {
|
||||
display: flex;
|
||||
width: 380px;
|
||||
padding: 14px 26px 14px 13px;
|
||||
padding: 14px 26px 14px 26px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
position: fixed;
|
||||
@ -462,8 +497,7 @@
|
||||
overflow: hidden;
|
||||
.upload-wrap {
|
||||
flex: 1;
|
||||
margin-left: 13px;
|
||||
margin-right: 8px;
|
||||
width: 100%;
|
||||
.title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
@ -481,16 +515,18 @@
|
||||
.content {
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
margin: 12px -16px 0 0;
|
||||
margin: 12px 0 0;
|
||||
color: #606266;
|
||||
max-height: 500px;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
> li {
|
||||
list-style: none;
|
||||
padding: 4px 16px 4px 0;
|
||||
padding: 4px 0;
|
||||
position: relative;
|
||||
.file-name {
|
||||
line-height: 18px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
.file-error {
|
||||
font-size: 12px;
|
||||
@ -498,8 +534,9 @@
|
||||
}
|
||||
.file-close {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
top: 7px;
|
||||
right: 0;
|
||||
right: -1px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -620,8 +657,10 @@
|
||||
.file-navigator {
|
||||
margin: 0 24px 0;
|
||||
}
|
||||
.file-table {
|
||||
margin: 16px 24px 24px;
|
||||
.file-drag {
|
||||
.file-table {
|
||||
margin: 16px 24px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@
|
||||
transform: scale(0.9);
|
||||
vertical-align: top;
|
||||
}
|
||||
.manage-menu-language {
|
||||
.manage-menu-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
Loading…
x
Reference in New Issue
Block a user