mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 03:01:12 +00:00
feat: 添加最近访问记录功能
- 在 UsersController 中新增获取和删除最近访问记录的接口 - 在相关控制器中记录用户最近访问的任务、文件和消息文件 - 新增 RecentManagement 组件,展示用户最近访问的记录 - 更新样式和图标以提升用户体验
This commit is contained in:
parent
feeeb26d94
commit
4b106e1f41
@ -28,6 +28,7 @@ use App\Models\WebSocketDialogMsgRead;
|
|||||||
use App\Models\WebSocketDialogMsgTodo;
|
use App\Models\WebSocketDialogMsgTodo;
|
||||||
use App\Models\WebSocketDialogMsgTranslate;
|
use App\Models\WebSocketDialogMsgTranslate;
|
||||||
use App\Models\WebSocketDialogSession;
|
use App\Models\WebSocketDialogSession;
|
||||||
|
use App\Models\UserRecentItem;
|
||||||
use App\Module\Table\OnlineData;
|
use App\Module\Table\OnlineData;
|
||||||
use App\Module\ZincSearch\ZincSearchDialogMsg;
|
use App\Module\ZincSearch\ZincSearchDialogMsg;
|
||||||
use App\Tasks\BotReceiveMsgTask;
|
use App\Tasks\BotReceiveMsgTask;
|
||||||
@ -1886,7 +1887,7 @@ class DialogController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
public function msg__detail()
|
public function msg__detail()
|
||||||
{
|
{
|
||||||
User::auth();
|
$user =User::auth();
|
||||||
//
|
//
|
||||||
$msg_id = intval(Request::input('msg_id'));
|
$msg_id = intval(Request::input('msg_id'));
|
||||||
$only_update_at = Request::input('only_update_at', 'no');
|
$only_update_at = Request::input('only_update_at', 'no');
|
||||||
@ -1924,6 +1925,16 @@ class DialogController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
if ($dialogMsg->type === 'file') {
|
||||||
|
UserRecentItem::record(
|
||||||
|
$user->userid,
|
||||||
|
UserRecentItem::TYPE_MESSAGE_FILE,
|
||||||
|
$dialogMsg->id,
|
||||||
|
UserRecentItem::SOURCE_DIALOG,
|
||||||
|
$dialogMsg->dialog_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Base::retSuccess('success', $data);
|
return Base::retSuccess('success', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ use App\Models\FileContent;
|
|||||||
use App\Models\FileLink;
|
use App\Models\FileLink;
|
||||||
use App\Models\FileUser;
|
use App\Models\FileUser;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserRecentItem;
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
use App\Module\Down;
|
use App\Module\Down;
|
||||||
use App\Module\Timer;
|
use App\Module\Timer;
|
||||||
@ -560,6 +561,16 @@ class FileController extends AbstractController
|
|||||||
$builder->whereId($history_id);
|
$builder->whereId($history_id);
|
||||||
}
|
}
|
||||||
$content = $builder->orderByDesc('id')->first();
|
$content = $builder->orderByDesc('id')->first();
|
||||||
|
if (isset($user)) {
|
||||||
|
UserRecentItem::record(
|
||||||
|
$user->userid,
|
||||||
|
UserRecentItem::TYPE_FILE,
|
||||||
|
$file->id,
|
||||||
|
UserRecentItem::SOURCE_FILESYSTEM,
|
||||||
|
intval($file->pid)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($down === 'preview') {
|
if ($down === 'preview') {
|
||||||
return Redirect::to(FileContent::formatPreview($file, $content?->content));
|
return Redirect::to(FileContent::formatPreview($file, $content?->content));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ use App\Models\ProjectPermission;
|
|||||||
use App\Models\ProjectTaskContent;
|
use App\Models\ProjectTaskContent;
|
||||||
use App\Models\WebSocketDialogMsg;
|
use App\Models\WebSocketDialogMsg;
|
||||||
use App\Module\BillMultipleExport;
|
use App\Module\BillMultipleExport;
|
||||||
|
use App\Models\UserRecentItem;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Models\ProjectTaskFlowChange;
|
use App\Models\ProjectTaskFlowChange;
|
||||||
use App\Models\ProjectTaskVisibilityUser;
|
use App\Models\ProjectTaskVisibilityUser;
|
||||||
@ -1908,7 +1909,7 @@ class ProjectController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
public function task__filedetail()
|
public function task__filedetail()
|
||||||
{
|
{
|
||||||
User::auth();
|
$user = User::auth();
|
||||||
//
|
//
|
||||||
$file_id = intval(Request::input('file_id'));
|
$file_id = intval(Request::input('file_id'));
|
||||||
$only_update_at = Request::input('only_update_at', 'no');
|
$only_update_at = Request::input('only_update_at', 'no');
|
||||||
@ -1931,6 +1932,14 @@ class ProjectController extends AbstractController
|
|||||||
//
|
//
|
||||||
ProjectTask::userTask($file->task_id, null);
|
ProjectTask::userTask($file->task_id, null);
|
||||||
//
|
//
|
||||||
|
UserRecentItem::record(
|
||||||
|
$user->userid,
|
||||||
|
UserRecentItem::TYPE_TASK_FILE,
|
||||||
|
$file->id,
|
||||||
|
UserRecentItem::SOURCE_PROJECT_TASK,
|
||||||
|
$file->task_id
|
||||||
|
);
|
||||||
|
|
||||||
return Base::retSuccess('success', File::formatFileData($data));
|
return Base::retSuccess('success', File::formatFileData($data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ use App\Ldap\LdapUser;
|
|||||||
use App\Models\Meeting;
|
use App\Models\Meeting;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectTask;
|
use App\Models\ProjectTask;
|
||||||
|
use App\Models\ProjectTaskFile;
|
||||||
use App\Models\UserBot;
|
use App\Models\UserBot;
|
||||||
use App\Models\WebSocket;
|
use App\Models\WebSocket;
|
||||||
use App\Models\UmengAlias;
|
use App\Models\UmengAlias;
|
||||||
@ -32,6 +33,7 @@ use App\Models\WebSocketDialogMsg;
|
|||||||
use App\Models\WebSocketDialogUser;
|
use App\Models\WebSocketDialogUser;
|
||||||
use App\Models\UserTaskBrowse;
|
use App\Models\UserTaskBrowse;
|
||||||
use App\Models\UserFavorite;
|
use App\Models\UserFavorite;
|
||||||
|
use App\Models\UserRecentItem;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Models\UserEmailVerification;
|
use App\Models\UserEmailVerification;
|
||||||
use App\Module\AgoraIO\AgoraTokenGenerator;
|
use App\Module\AgoraIO\AgoraTokenGenerator;
|
||||||
@ -2849,6 +2851,242 @@ class UsersController extends AbstractController
|
|||||||
return Base::retSuccess('清理完成', ['deleted_count' => $deletedCount]);
|
return Base::retSuccess('清理完成', ['deleted_count' => $deletedCount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/users/recent/browse 45. 获取最近访问记录
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName recent__browse
|
||||||
|
*
|
||||||
|
* @apiParam {String} [type] 类型过滤 (task/file/task_file/message_file)
|
||||||
|
* @apiParam {Number} [page=1] 页码
|
||||||
|
* @apiParam {Number} [page_size=20] 每页数量,最大100
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function recent__browse()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
$page = max(1, intval(Request::input('page', 1)));
|
||||||
|
$pageSize = intval(Request::input('page_size', 20));
|
||||||
|
$pageSize = max(1, min(100, $pageSize));
|
||||||
|
|
||||||
|
$query = UserRecentItem::whereUserid($user->userid);
|
||||||
|
if ($type !== '') {
|
||||||
|
$query->where('target_type', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = (clone $query)->count();
|
||||||
|
$items = $query->orderByDesc('browsed_at')
|
||||||
|
->skip(($page - 1) * $pageSize)
|
||||||
|
->take($pageSize)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$taskIds = [];
|
||||||
|
$fileIds = [];
|
||||||
|
$taskFileIds = [];
|
||||||
|
$messageIds = [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
switch ($item->target_type) {
|
||||||
|
case UserRecentItem::TYPE_TASK:
|
||||||
|
$taskIds[] = $item->target_id;
|
||||||
|
break;
|
||||||
|
case UserRecentItem::TYPE_FILE:
|
||||||
|
$fileIds[] = $item->target_id;
|
||||||
|
break;
|
||||||
|
case UserRecentItem::TYPE_TASK_FILE:
|
||||||
|
$taskFileIds[] = $item->target_id;
|
||||||
|
break;
|
||||||
|
case UserRecentItem::TYPE_MESSAGE_FILE:
|
||||||
|
$messageIds[] = $item->target_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tasks = empty($taskIds) ? collect() : ProjectTask::with(['project'])
|
||||||
|
->whereIn('id', array_unique($taskIds))
|
||||||
|
->whereNull('archived_at')
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$files = empty($fileIds) ? collect() : File::whereIn('id', array_unique($fileIds))
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$taskFiles = empty($taskFileIds) ? collect() : ProjectTaskFile::whereIn('id', array_unique($taskFileIds))
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$taskFileTaskIds = $taskFiles->pluck('task_id')->filter()->unique()->all();
|
||||||
|
$taskFileTasks = empty($taskFileTaskIds) ? collect() : ProjectTask::whereIn('id', $taskFileTaskIds)
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$projectIds = $tasks->pluck('project_id')
|
||||||
|
->merge($taskFiles->pluck('project_id'))
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$projects = empty($projectIds) ? collect() : Project::whereIn('id', $projectIds)
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$messages = empty($messageIds) ? collect() : WebSocketDialogMsg::whereIn('id', array_unique($messageIds))
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$dialogIds = $messages->pluck('dialog_id')->filter()->unique()->all();
|
||||||
|
$dialogs = empty($dialogIds) ? collect() : WebSocketDialog::whereIn('id', $dialogIds)
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$timestamp = $item->browsed_at ?: $item->updated_at;
|
||||||
|
if ($timestamp instanceof Carbon) {
|
||||||
|
$browsedAt = $timestamp->toDateTimeString();
|
||||||
|
} elseif ($timestamp) {
|
||||||
|
$browsedAt = Carbon::parse($timestamp)->toDateTimeString();
|
||||||
|
} else {
|
||||||
|
$browsedAt = Carbon::now()->toDateTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseData = [
|
||||||
|
'record_id' => $item->id,
|
||||||
|
'source_type' => $item->source_type,
|
||||||
|
'source_id' => $item->source_id,
|
||||||
|
'browsed_at' => $browsedAt,
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($item->target_type) {
|
||||||
|
case UserRecentItem::TYPE_TASK:
|
||||||
|
$task = $tasks->get($item->target_id);
|
||||||
|
if (!$task) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$flowItemParts = explode('|', $task->flow_item_name ?: '');
|
||||||
|
$flowItemName = $flowItemParts[1] ?? $task->flow_item_name;
|
||||||
|
$flowItemStatus = $flowItemParts[0] ?? '';
|
||||||
|
$flowItemColor = $flowItemParts[2] ?? '';
|
||||||
|
$result[] = array_merge($baseData, [
|
||||||
|
'type' => UserRecentItem::TYPE_TASK,
|
||||||
|
'id' => $task->id,
|
||||||
|
'name' => $task->name,
|
||||||
|
'project_id' => $task->project_id,
|
||||||
|
'project_name' => $task->project->name ?? '',
|
||||||
|
'column_id' => $task->column_id,
|
||||||
|
'flow_item_id' => $task->flow_item_id,
|
||||||
|
'flow_item_name' => $flowItemName,
|
||||||
|
'flow_item_status' => $flowItemStatus,
|
||||||
|
'flow_item_color' => $flowItemColor,
|
||||||
|
'complete_at' => $task->complete_at,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserRecentItem::TYPE_FILE:
|
||||||
|
$file = $files->get($item->target_id);
|
||||||
|
if (!$file) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$result[] = array_merge($baseData, [
|
||||||
|
'type' => UserRecentItem::TYPE_FILE,
|
||||||
|
'id' => $file->id,
|
||||||
|
'name' => $file->name,
|
||||||
|
'ext' => $file->ext,
|
||||||
|
'size' => (int) $file->size,
|
||||||
|
'file_type' => $file->type,
|
||||||
|
'folder_id' => (int) $file->pid,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserRecentItem::TYPE_TASK_FILE:
|
||||||
|
$taskFile = $taskFiles->get($item->target_id);
|
||||||
|
if (!$taskFile) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$project = $projects->get($taskFile->project_id);
|
||||||
|
$taskInfo = $taskFileTasks->get($taskFile->task_id);
|
||||||
|
$result[] = array_merge($baseData, [
|
||||||
|
'type' => UserRecentItem::TYPE_TASK_FILE,
|
||||||
|
'id' => $taskFile->id,
|
||||||
|
'name' => $taskFile->name,
|
||||||
|
'ext' => $taskFile->ext,
|
||||||
|
'size' => (int) $taskFile->size,
|
||||||
|
'task_id' => $taskFile->task_id,
|
||||||
|
'task_name' => $taskInfo->name ?? '',
|
||||||
|
'project_id' => $taskFile->project_id,
|
||||||
|
'project_name' => $project->name ?? '',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserRecentItem::TYPE_MESSAGE_FILE:
|
||||||
|
$message = $messages->get($item->target_id);
|
||||||
|
if (!$message || $message->type !== 'file') {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$msgData = Base::json2array($message->getRawOriginal('msg'));
|
||||||
|
$dialog = $dialogs->get($message->dialog_id);
|
||||||
|
$result[] = array_merge($baseData, [
|
||||||
|
'type' => UserRecentItem::TYPE_MESSAGE_FILE,
|
||||||
|
'id' => $message->id,
|
||||||
|
'name' => $msgData['name'] ?? '',
|
||||||
|
'ext' => $msgData['ext'] ?? '',
|
||||||
|
'size' => isset($msgData['size']) ? (int) $msgData['size'] : 0,
|
||||||
|
'dialog_id' => $message->dialog_id,
|
||||||
|
'dialog_name' => $dialog->name ?? '',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Base::retSuccess('success', [
|
||||||
|
'list' => $result,
|
||||||
|
'page' => $page,
|
||||||
|
'page_size' => $pageSize,
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/users/recent/delete 45.1 删除最近访问记录
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName recent__delete
|
||||||
|
*
|
||||||
|
* @apiParam {Number} id 记录ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function recent__delete()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
|
||||||
|
$id = intval(Request::input('id'));
|
||||||
|
if ($id <= 0) {
|
||||||
|
return Base::retError('参数错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = UserRecentItem::whereUserid($user->userid)->whereId($id)->first();
|
||||||
|
if (!$record) {
|
||||||
|
return Base::retError('记录不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record->delete();
|
||||||
|
|
||||||
|
return Base::retSuccess('删除成功');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/users/favorites 46. 获取用户收藏列表
|
* @api {get} api/users/favorites 46. 获取用户收藏列表
|
||||||
*
|
*
|
||||||
|
|||||||
58
app/Models/UserRecentItem.php
Normal file
58
app/Models/UserRecentItem.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $userid
|
||||||
|
* @property string $target_type
|
||||||
|
* @property int $target_id
|
||||||
|
* @property string $source_type
|
||||||
|
* @property int $source_id
|
||||||
|
* @property Carbon|null $browsed_at
|
||||||
|
* @property Carbon|null $created_at
|
||||||
|
* @property Carbon|null $updated_at
|
||||||
|
*/
|
||||||
|
class UserRecentItem extends AbstractModel
|
||||||
|
{
|
||||||
|
public const TYPE_TASK = 'task';
|
||||||
|
public const TYPE_FILE = 'file';
|
||||||
|
public const TYPE_TASK_FILE = 'task_file';
|
||||||
|
public const TYPE_MESSAGE_FILE = 'message_file';
|
||||||
|
|
||||||
|
public const SOURCE_PROJECT = 'project';
|
||||||
|
public const SOURCE_FILESYSTEM = 'filesystem';
|
||||||
|
public const SOURCE_PROJECT_TASK = 'project_task';
|
||||||
|
public const SOURCE_DIALOG = 'dialog';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'userid',
|
||||||
|
'target_type',
|
||||||
|
'target_id',
|
||||||
|
'source_type',
|
||||||
|
'source_id',
|
||||||
|
'browsed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $dates = [
|
||||||
|
'browsed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function record(int $userid, string $targetType, int $targetId, string $sourceType = '', int $sourceId = 0): self
|
||||||
|
{
|
||||||
|
return tap(self::updateOrCreate(
|
||||||
|
[
|
||||||
|
'userid' => $userid,
|
||||||
|
'target_type' => $targetType,
|
||||||
|
'target_id' => $targetId,
|
||||||
|
'source_type' => $sourceType,
|
||||||
|
'source_id' => $sourceId,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'browsed_at' => Carbon::now(),
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -68,7 +68,7 @@ class UserTaskBrowse extends AbstractModel
|
|||||||
*/
|
*/
|
||||||
public static function recordBrowse($userid, $task_id)
|
public static function recordBrowse($userid, $task_id)
|
||||||
{
|
{
|
||||||
return self::updateOrCreate(
|
$record = self::updateOrCreate(
|
||||||
[
|
[
|
||||||
'userid' => $userid,
|
'userid' => $userid,
|
||||||
'task_id' => $task_id,
|
'task_id' => $task_id,
|
||||||
@ -77,6 +77,16 @@ class UserTaskBrowse extends AbstractModel
|
|||||||
'browsed_at' => Carbon::now(),
|
'browsed_at' => Carbon::now(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
UserRecentItem::record(
|
||||||
|
$userid,
|
||||||
|
UserRecentItem::TYPE_TASK,
|
||||||
|
$task_id,
|
||||||
|
UserRecentItem::SOURCE_PROJECT,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
return $record;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateUserRecentItemsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
if (Schema::hasTable('user_recent_items')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('user_recent_items', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('userid')->index()->default(0)->comment('用户ID');
|
||||||
|
$table->string('target_type', 50)->default('')->comment('目标类型(task/file/task_file/message_file 等)');
|
||||||
|
$table->bigInteger('target_id')->default(0)->comment('目标ID');
|
||||||
|
$table->string('source_type', 50)->default('')->comment('来源类型(project/filesystem/project_task/dialog 等)');
|
||||||
|
$table->bigInteger('source_id')->default(0)->comment('来源ID');
|
||||||
|
$table->timestamp('browsed_at')->nullable()->index()->comment('浏览时间');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['userid', 'browsed_at']);
|
||||||
|
$table->unique(['userid', 'target_type', 'target_id', 'source_type', 'source_id'], 'recent_unique');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_recent_items');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -344,6 +344,14 @@
|
|||||||
<FavoriteManagement v-if="favoriteShow" @on-close="favoriteShow = false"/>
|
<FavoriteManagement v-if="favoriteShow" @on-close="favoriteShow = false"/>
|
||||||
</DrawerOverlay>
|
</DrawerOverlay>
|
||||||
|
|
||||||
|
<!--最近打开-->
|
||||||
|
<DrawerOverlay
|
||||||
|
v-model="recentShow"
|
||||||
|
placement="right"
|
||||||
|
:size="1200">
|
||||||
|
<RecentManagement v-if="recentShow" @on-close="recentShow = false"/>
|
||||||
|
</DrawerOverlay>
|
||||||
|
|
||||||
<!--团队成员管理-->
|
<!--团队成员管理-->
|
||||||
<DrawerOverlay
|
<DrawerOverlay
|
||||||
v-model="allUserShow"
|
v-model="allUserShow"
|
||||||
@ -401,6 +409,7 @@ import { mapState, mapGetters } from 'vuex'
|
|||||||
import ProjectArchived from "./manage/components/ProjectArchived";
|
import ProjectArchived from "./manage/components/ProjectArchived";
|
||||||
import TeamManagement from "./manage/components/TeamManagement";
|
import TeamManagement from "./manage/components/TeamManagement";
|
||||||
import FavoriteManagement from "./manage/components/FavoriteManagement";
|
import FavoriteManagement from "./manage/components/FavoriteManagement";
|
||||||
|
import RecentManagement from "./manage/components/RecentManagement";
|
||||||
import ProjectManagement from "./manage/components/ProjectManagement";
|
import ProjectManagement from "./manage/components/ProjectManagement";
|
||||||
import DrawerOverlay from "../components/DrawerOverlay";
|
import DrawerOverlay from "../components/DrawerOverlay";
|
||||||
import MobileTabbar from "../components/Mobile/Tabbar";
|
import MobileTabbar from "../components/Mobile/Tabbar";
|
||||||
@ -445,6 +454,7 @@ export default {
|
|||||||
ProjectManagement,
|
ProjectManagement,
|
||||||
TeamManagement,
|
TeamManagement,
|
||||||
FavoriteManagement,
|
FavoriteManagement,
|
||||||
|
RecentManagement,
|
||||||
ProjectArchived,
|
ProjectArchived,
|
||||||
MicroApps,
|
MicroApps,
|
||||||
ComplaintManagement,
|
ComplaintManagement,
|
||||||
@ -495,6 +505,7 @@ export default {
|
|||||||
archivedProjectShow: false,
|
archivedProjectShow: false,
|
||||||
|
|
||||||
favoriteShow: false,
|
favoriteShow: false,
|
||||||
|
recentShow: false,
|
||||||
|
|
||||||
natificationReady: false,
|
natificationReady: false,
|
||||||
notificationManage: null,
|
notificationManage: null,
|
||||||
@ -526,6 +537,7 @@ export default {
|
|||||||
emitter.on('approveDetails', this.openApproveDetails);
|
emitter.on('approveDetails', this.openApproveDetails);
|
||||||
emitter.on('openReport', this.openReport);
|
emitter.on('openReport', this.openReport);
|
||||||
emitter.on('openFavorite', this.openFavorite);
|
emitter.on('openFavorite', this.openFavorite);
|
||||||
|
emitter.on('openRecent', this.openRecent);
|
||||||
emitter.on('openManageExport', this.openManageExport);
|
emitter.on('openManageExport', this.openManageExport);
|
||||||
//
|
//
|
||||||
document.addEventListener('keydown', this.shortcutEvent);
|
document.addEventListener('keydown', this.shortcutEvent);
|
||||||
@ -545,6 +557,7 @@ export default {
|
|||||||
emitter.off('approveDetails', this.openApproveDetails);
|
emitter.off('approveDetails', this.openApproveDetails);
|
||||||
emitter.off('openReport', this.openReport);
|
emitter.off('openReport', this.openReport);
|
||||||
emitter.off('openFavorite', this.openFavorite);
|
emitter.off('openFavorite', this.openFavorite);
|
||||||
|
emitter.off('openRecent', this.openRecent);
|
||||||
emitter.off('openManageExport', this.openManageExport);
|
emitter.off('openManageExport', this.openManageExport);
|
||||||
//
|
//
|
||||||
document.removeEventListener('keydown', this.shortcutEvent);
|
document.removeEventListener('keydown', this.shortcutEvent);
|
||||||
@ -1319,6 +1332,10 @@ export default {
|
|||||||
this.favoriteShow = true;
|
this.favoriteShow = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openRecent() {
|
||||||
|
this.recentShow = true;
|
||||||
|
},
|
||||||
|
|
||||||
openManageExport(type) {
|
openManageExport(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'task':
|
case 'task':
|
||||||
|
|||||||
@ -416,6 +416,7 @@ export default {
|
|||||||
const list = [
|
const list = [
|
||||||
{value: "approve", label: "审批中心", sort: 30, show: this.microAppsIds.includes('approve')},
|
{value: "approve", label: "审批中心", sort: 30, show: this.microAppsIds.includes('approve')},
|
||||||
{value: "favorite", label: "我的收藏", sort: 45},
|
{value: "favorite", label: "我的收藏", sort: 45},
|
||||||
|
{value: "recent", label: "最近打开", sort: 47},
|
||||||
{value: "report", label: "工作报告", sort: 50},
|
{value: "report", label: "工作报告", sort: 50},
|
||||||
{value: "mybot", label: "我的机器人", sort: 55},
|
{value: "mybot", label: "我的机器人", sort: 55},
|
||||||
{value: "robot", label: "AI 机器人", sort: 60, show: this.microAppsIds.includes('ai')},
|
{value: "robot", label: "AI 机器人", sort: 60, show: this.microAppsIds.includes('ai')},
|
||||||
@ -485,6 +486,9 @@ export default {
|
|||||||
case 'favorite':
|
case 'favorite':
|
||||||
emitter.emit('openFavorite');
|
emitter.emit('openFavorite');
|
||||||
break;
|
break;
|
||||||
|
case 'recent':
|
||||||
|
emitter.emit('openRecent');
|
||||||
|
break;
|
||||||
case 'mybot':
|
case 'mybot':
|
||||||
this.getMybot();
|
this.getMybot();
|
||||||
this.mybotShow = true;
|
this.mybotShow = true;
|
||||||
|
|||||||
316
resources/assets/js/pages/manage/components/RecentManagement.vue
Normal file
316
resources/assets/js/pages/manage/components/RecentManagement.vue
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<template>
|
||||||
|
<div class="recent-management">
|
||||||
|
<div class="management-title">
|
||||||
|
{{$L('最近打开')}}
|
||||||
|
<div class="title-icon">
|
||||||
|
<Loading v-if="loading > 0"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-container lr">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div class="search-label">{{$L('类型')}}</div>
|
||||||
|
<div class="search-content">
|
||||||
|
<Select v-model="filters.type" clearable :placeholder="$L('全部类型')" @on-change="handleTypeChange">
|
||||||
|
<Option v-for="item in typeOptions" :key="item.value" :value="item.value">{{$L(item.label)}}</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="search-button">
|
||||||
|
<Button type="primary" :loading="loading > 0" @click="refreshList">{{$L('刷新')}}</Button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="table-page-box">
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data="records"
|
||||||
|
:loading="loading > 0"
|
||||||
|
:no-data-text="$L(noDataText)"
|
||||||
|
stripe/>
|
||||||
|
<Page
|
||||||
|
:total="total"
|
||||||
|
:current="page"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-opts="[10,20,30,50,100]"
|
||||||
|
:simple="windowPortrait"
|
||||||
|
:disabled="loading > 0"
|
||||||
|
show-elevator
|
||||||
|
show-sizer
|
||||||
|
show-total
|
||||||
|
@on-change="setPage"
|
||||||
|
@on-page-size-change="setPageSize"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapState} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "RecentManagement",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: 0,
|
||||||
|
records: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
filters: {
|
||||||
|
type: ''
|
||||||
|
},
|
||||||
|
noDataText: '暂无打开记录'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['windowPortrait']),
|
||||||
|
typeMap() {
|
||||||
|
return {
|
||||||
|
task: {label: '任务', color: 'success'},
|
||||||
|
file: {label: '文件库', color: 'warning'},
|
||||||
|
task_file: {label: '任务文件', color: 'primary'},
|
||||||
|
message_file: {label: '聊天文件', color: 'magenta'}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
typeOptions() {
|
||||||
|
return [
|
||||||
|
{value: '', label: '全部类型'},
|
||||||
|
{value: 'task', label: this.typeMap.task.label},
|
||||||
|
{value: 'file', label: this.typeMap.file.label},
|
||||||
|
{value: 'task_file', label: this.typeMap.task_file.label},
|
||||||
|
{value: 'message_file', label: this.typeMap.message_file.label},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
columns() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: this.$L('类型'),
|
||||||
|
key: 'type',
|
||||||
|
width: 120,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
const info = this.getTypeInfo(row.type);
|
||||||
|
return h('Tag', {
|
||||||
|
class: 'recent-type-tag',
|
||||||
|
props: {
|
||||||
|
color: info.color || 'primary'
|
||||||
|
}
|
||||||
|
}, this.$L(info.label || row.type));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('名称'),
|
||||||
|
key: 'name',
|
||||||
|
minWidth: 200,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
const text = row.name || this.$L('未命名');
|
||||||
|
return h('div', {
|
||||||
|
class: 'recent-name',
|
||||||
|
on: {
|
||||||
|
click: () => this.openItem(row)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('AutoTip', text)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('来源'),
|
||||||
|
minWidth: 220,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
return h('AutoTip', this.getSourceText(row));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('最近访问时间'),
|
||||||
|
key: 'browsed_at',
|
||||||
|
width: 168,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('操作'),
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
render: (h, params) => {
|
||||||
|
const actions = [
|
||||||
|
h('Poptip', {
|
||||||
|
props: {
|
||||||
|
title: this.$L(`确定要删除记录"${params.row.name || this.$L('未命名')}"吗?`),
|
||||||
|
confirm: true,
|
||||||
|
transfer: true,
|
||||||
|
placement: 'left',
|
||||||
|
okText: this.$L('确定'),
|
||||||
|
cancelText: this.$L('取消'),
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#f00',
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
'on-ok': () => this.removeItem(params.row)
|
||||||
|
},
|
||||||
|
}, this.$L('删除'))
|
||||||
|
];
|
||||||
|
return h('TableAction', {
|
||||||
|
props: {
|
||||||
|
column: params.column
|
||||||
|
}
|
||||||
|
}, actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getLists();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTypeInfo(type) {
|
||||||
|
return this.typeMap[type] || {label: type, color: 'default'};
|
||||||
|
},
|
||||||
|
getSourceText(row) {
|
||||||
|
switch (row.type) {
|
||||||
|
case 'task': {
|
||||||
|
const project = row.project_name ? `${this.$L('项目')}: ${row.project_name}` : this.$L('项目');
|
||||||
|
const status = this.getTaskStatus(row);
|
||||||
|
return status ? `${project} | ${status}` : project;
|
||||||
|
}
|
||||||
|
case 'file':
|
||||||
|
return this.$L('文件库');
|
||||||
|
case 'task_file': {
|
||||||
|
const parts = [];
|
||||||
|
if (row.project_name) {
|
||||||
|
parts.push(`${this.$L('项目')}: ${row.project_name}`);
|
||||||
|
}
|
||||||
|
if (row.task_name) {
|
||||||
|
parts.push(`${this.$L('任务')}: ${row.task_name}`);
|
||||||
|
}
|
||||||
|
return parts.length > 0 ? parts.join(' | ') : this.$L('任务文件');
|
||||||
|
}
|
||||||
|
case 'message_file':
|
||||||
|
if (row.dialog_name) {
|
||||||
|
return `${this.$L('聊天')}: ${row.dialog_name}`;
|
||||||
|
}
|
||||||
|
return this.$L('聊天文件');
|
||||||
|
}
|
||||||
|
return this.$L('未知');
|
||||||
|
},
|
||||||
|
getTaskStatus(row) {
|
||||||
|
if (row.flow_item_name) {
|
||||||
|
return row.flow_item_name;
|
||||||
|
}
|
||||||
|
if (row.complete_at) {
|
||||||
|
return this.$L('已完成');
|
||||||
|
}
|
||||||
|
return this.$L('进行中');
|
||||||
|
},
|
||||||
|
getLists(page = this.page) {
|
||||||
|
this.loading++;
|
||||||
|
const params = {
|
||||||
|
page,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
};
|
||||||
|
if (this.filters.type) {
|
||||||
|
params.type = this.filters.type;
|
||||||
|
}
|
||||||
|
this.$store.dispatch('getRecentBrowseHistory', params).then(({data}) => {
|
||||||
|
if ($A.isJson(data)) {
|
||||||
|
this.records = data.list || [];
|
||||||
|
this.total = data.total || 0;
|
||||||
|
this.page = data.page || page;
|
||||||
|
this.pageSize = data.page_size || this.pageSize;
|
||||||
|
} else {
|
||||||
|
this.records = [];
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
if (msg) {
|
||||||
|
$A.modalError(msg);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading--;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refreshList() {
|
||||||
|
this.getLists(1);
|
||||||
|
},
|
||||||
|
handleTypeChange() {
|
||||||
|
this.page = 1;
|
||||||
|
this.getLists(1);
|
||||||
|
},
|
||||||
|
setPage(page) {
|
||||||
|
this.page = page;
|
||||||
|
this.getLists(page);
|
||||||
|
},
|
||||||
|
setPageSize(size) {
|
||||||
|
this.pageSize = size;
|
||||||
|
this.getLists(1);
|
||||||
|
},
|
||||||
|
openItem(row) {
|
||||||
|
switch (row.type) {
|
||||||
|
case 'task':
|
||||||
|
this.$store.dispatch('openTask', row);
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
this.openWindow(`/single/file/${row.id}`, row.name, `file-${row.id}`, row.size);
|
||||||
|
break;
|
||||||
|
case 'task_file':
|
||||||
|
this.openWindow(`/single/file/task/${row.id}`, row.name, `file-task-${row.id}`, row.size);
|
||||||
|
break;
|
||||||
|
case 'message_file':
|
||||||
|
this.openWindow(`/single/file/msg/${row.id}`, row.name, `file-msg-${row.id}`, row.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openWindow(path, title, name, size) {
|
||||||
|
const text = title || this.$L('查看');
|
||||||
|
const finalTitle = size ? `${text} (${$A.bytesToSize(size)})` : text;
|
||||||
|
if (this.$Electron) {
|
||||||
|
this.$store.dispatch('openChildWindow', {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
userAgent: "/hideenOfficeTitle/",
|
||||||
|
force: false,
|
||||||
|
config: {
|
||||||
|
title: finalTitle,
|
||||||
|
titleFixed: true,
|
||||||
|
parent: null,
|
||||||
|
width: Math.min(window.screen.availWidth, 1440),
|
||||||
|
height: Math.min(window.screen.availHeight, 900),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (this.$isEEUIApp) {
|
||||||
|
this.$store.dispatch('openAppChildPage', {
|
||||||
|
pageType: 'app',
|
||||||
|
pageTitle: finalTitle,
|
||||||
|
url: 'web.js',
|
||||||
|
params: {
|
||||||
|
titleFixed: true,
|
||||||
|
url: $A.urlReplaceHash(path)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.open($A.mainUrl(path.substring(1)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeItem(row) {
|
||||||
|
if (!row.record_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPage = this.records.length === 1 && this.page > 1 ? this.page - 1 : this.page;
|
||||||
|
this.loading++;
|
||||||
|
this.$store.dispatch('removeRecentBrowseRecord', row.record_id).then(({msg}) => {
|
||||||
|
$A.messageSuccess(msg || this.$L('删除成功'));
|
||||||
|
this.page = targetPage;
|
||||||
|
this.getLists(targetPage);
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
if (msg) {
|
||||||
|
$A.modalError(msg);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
32
resources/assets/js/store/actions.js
vendored
32
resources/assets/js/store/actions.js
vendored
@ -2758,10 +2758,8 @@ export default {
|
|||||||
task_id: task_id
|
task_id: task_id
|
||||||
},
|
},
|
||||||
method: 'post',
|
method: 'post',
|
||||||
spinner: 0, // 静默调用,不显示loading
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.warn('保存任务浏览历史失败:', error);
|
console.warn('保存任务浏览历史失败:', error);
|
||||||
// API失败时不影响用户体验,只记录错误
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2777,7 +2775,34 @@ export default {
|
|||||||
limit: limit
|
limit: limit
|
||||||
},
|
},
|
||||||
method: 'get',
|
method: 'get',
|
||||||
spinner: 0, // 静默调用
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最近浏览历史
|
||||||
|
* @param dispatch
|
||||||
|
* @param params
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
getRecentBrowseHistory({dispatch}, params = {}) {
|
||||||
|
return dispatch('call', {
|
||||||
|
url: 'users/recent/browse',
|
||||||
|
data: params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除最近浏览记录
|
||||||
|
* @param dispatch
|
||||||
|
* @param id
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
removeRecentBrowseRecord({dispatch}, id) {
|
||||||
|
return dispatch('call', {
|
||||||
|
url: 'users/recent/delete',
|
||||||
|
data: {id},
|
||||||
|
method: 'post',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2864,7 +2889,6 @@ export default {
|
|||||||
id: id
|
id: id
|
||||||
},
|
},
|
||||||
method: 'get',
|
method: 'get',
|
||||||
spinner: 0, // 静默调用
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
@import "dialog-session-history";
|
@import "dialog-session-history";
|
||||||
@import "dialog-wrapper";
|
@import "dialog-wrapper";
|
||||||
@import "favorite-management";
|
@import "favorite-management";
|
||||||
|
@import "recent-management";
|
||||||
@import "file-content";
|
@import "file-content";
|
||||||
@import "forwarder";
|
@import "forwarder";
|
||||||
@import "general-operation";
|
@import "general-operation";
|
||||||
|
|||||||
67
resources/assets/sass/pages/components/recent-management.scss
vendored
Normal file
67
resources/assets/sass/pages/components/recent-management.scss
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
.recent-management {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.management-title {
|
||||||
|
color: $primary-title-color;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #2d8cf0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ivu-tag {
|
||||||
|
height: 18px;
|
||||||
|
line-height: 18px;
|
||||||
|
padding: 0 4px;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: right center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-type-tag {
|
||||||
|
min-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-page-box {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
resources/assets/sass/pages/page-apply.scss
vendored
4
resources/assets/sass/pages/page-apply.scss
vendored
@ -208,6 +208,10 @@
|
|||||||
background-image: url("../images/application/favorite.svg");
|
background-image: url("../images/application/favorite.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.recent {
|
||||||
|
background-image: url("../images/application/recent.svg");
|
||||||
|
}
|
||||||
|
|
||||||
&.export-manage {
|
&.export-manage {
|
||||||
background-image: url("../images/application/export.svg");
|
background-image: url("../images/application/export.svg");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
|
||||||
|
<rect width="48" height="48" rx="12" fill="#5DADEC"/>
|
||||||
|
<path fill="#FFFFFF" fill-rule="evenodd" d="M24 13c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12S30.627 13 24 13Zm0 22c-5.523 0-10-4.477-10-10s4.477-10 10-10 10 4.477 10 10-4.477 10-10 10Z" clip-rule="evenodd"/>
|
||||||
|
<path fill="#FFFFFF" d="M24 16a1 1 0 0 1 1 1v7.586l4.121 4.121a1 1 0 1 1-1.414 1.414l-4.414-4.414A1 1 0 0 1 23 24V17a1 1 0 0 1 1-1Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 495 B |
Loading…
x
Reference in New Issue
Block a user