mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 03:01:12 +00:00
feat: 添加用户收藏功能
- 在 UsersController 中新增获取、切换、清理用户收藏的 API 接口 - 创建 UserFavorite 模型以管理用户的收藏记录 - 更新前端 Vue 组件以支持收藏管理界面和交互 - 添加相关样式以美化收藏管理界面
This commit is contained in:
parent
0401b8a6e6
commit
379d3811a8
@ -30,6 +30,7 @@ use App\Models\WebSocketDialog;
|
|||||||
use App\Models\UserCheckinRecord;
|
use App\Models\UserCheckinRecord;
|
||||||
use App\Models\WebSocketDialogMsg;
|
use App\Models\WebSocketDialogMsg;
|
||||||
use App\Models\UserTaskBrowse;
|
use App\Models\UserTaskBrowse;
|
||||||
|
use App\Models\UserFavorite;
|
||||||
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;
|
||||||
@ -2825,4 +2826,170 @@ class UsersController extends AbstractController
|
|||||||
//
|
//
|
||||||
return Base::retSuccess('清理完成', ['deleted_count' => $deletedCount]);
|
return Base::retSuccess('清理完成', ['deleted_count' => $deletedCount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/users/favorites 46. 获取用户收藏列表
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName favorites
|
||||||
|
*
|
||||||
|
* @apiParam {String} [type] 收藏类型过滤 (task/project/file)
|
||||||
|
* @apiParam {Number} [page=1] 页码
|
||||||
|
* @apiParam {Number} [pagesize=20] 每页数量
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function favorites()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
//
|
||||||
|
$type = Request::input('type');
|
||||||
|
$page = intval(Request::input('page', 1));
|
||||||
|
$pageSize = min(intval(Request::input('pagesize', 20)), 100);
|
||||||
|
//
|
||||||
|
// 验证收藏类型
|
||||||
|
$allowedTypes = [UserFavorite::TYPE_TASK, UserFavorite::TYPE_PROJECT, UserFavorite::TYPE_FILE];
|
||||||
|
if ($type && !in_array($type, $allowedTypes)) {
|
||||||
|
return Base::retError('无效的收藏类型');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$result = UserFavorite::getUserFavorites($user->userid, $type, $page, $pageSize);
|
||||||
|
//
|
||||||
|
return Base::retSuccess('success', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/users/favorite/toggle 47. 切换收藏状态
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName favorite__toggle
|
||||||
|
*
|
||||||
|
* @apiParam {String} type 收藏类型 (task/project/file)
|
||||||
|
* @apiParam {Number} id 收藏对象ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function favorite__toggle()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
//
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
$id = intval(Request::input('id'));
|
||||||
|
//
|
||||||
|
if (!$type || $id <= 0) {
|
||||||
|
return Base::retError('参数错误');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 验证收藏类型
|
||||||
|
$allowedTypes = [UserFavorite::TYPE_TASK, UserFavorite::TYPE_PROJECT, UserFavorite::TYPE_FILE];
|
||||||
|
if (!in_array($type, $allowedTypes)) {
|
||||||
|
return Base::retError('无效的收藏类型');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 验证对象是否存在(简化验证,实际应该加上权限检查)
|
||||||
|
switch ($type) {
|
||||||
|
case UserFavorite::TYPE_TASK:
|
||||||
|
$object = ProjectTask::whereId($id)->first();
|
||||||
|
if (!$object) {
|
||||||
|
return Base::retError('任务不存在');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UserFavorite::TYPE_PROJECT:
|
||||||
|
$object = Project::whereId($id)->first();
|
||||||
|
if (!$object) {
|
||||||
|
return Base::retError('项目不存在');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UserFavorite::TYPE_FILE:
|
||||||
|
$object = File::whereId($id)->first();
|
||||||
|
if (!$object) {
|
||||||
|
return Base::retError('文件不存在');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$result = UserFavorite::toggleFavorite($user->userid, $type, $id);
|
||||||
|
//
|
||||||
|
$message = $result['favorited'] ? '收藏成功' : '取消收藏成功';
|
||||||
|
return Base::retSuccess($message, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/users/favorites/clean 48. 清理用户收藏
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName favorites__clean
|
||||||
|
*
|
||||||
|
* @apiParam {String} [type] 收藏类型 (task/project/file),不传则清理全部
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function favorites__clean()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
//
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
//
|
||||||
|
// 验证收藏类型
|
||||||
|
if ($type) {
|
||||||
|
$allowedTypes = [UserFavorite::TYPE_TASK, UserFavorite::TYPE_PROJECT, UserFavorite::TYPE_FILE];
|
||||||
|
if (!in_array($type, $allowedTypes)) {
|
||||||
|
return Base::retError('无效的收藏类型');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$deletedCount = UserFavorite::cleanUserFavorites($user->userid, $type);
|
||||||
|
//
|
||||||
|
$message = $type ? "清理{$type}收藏成功" : '清理全部收藏成功';
|
||||||
|
return Base::retSuccess($message, ['deleted_count' => $deletedCount]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/users/favorite/check 49. 检查收藏状态
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName favorite__check
|
||||||
|
*
|
||||||
|
* @apiParam {String} type 收藏类型 (task/project/file)
|
||||||
|
* @apiParam {Number} id 收藏对象ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function favorite__check()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
//
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
$id = intval(Request::input('id'));
|
||||||
|
//
|
||||||
|
if (!$type || $id <= 0) {
|
||||||
|
return Base::retError('参数错误');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 验证收藏类型
|
||||||
|
$allowedTypes = [UserFavorite::TYPE_TASK, UserFavorite::TYPE_PROJECT, UserFavorite::TYPE_FILE];
|
||||||
|
if (!in_array($type, $allowedTypes)) {
|
||||||
|
return Base::retError('无效的收藏类型');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$isFavorited = UserFavorite::isFavorited($user->userid, $type, $id);
|
||||||
|
//
|
||||||
|
return Base::retSuccess('success', ['favorited' => $isFavorited]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
256
app/Models/UserFavorite.php
Normal file
256
app/Models/UserFavorite.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App\Models\UserFavorite
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $userid 用户ID
|
||||||
|
* @property string $favoritable_type 收藏类型
|
||||||
|
* @property int $favoritable_id 收藏对象ID
|
||||||
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $favoritable
|
||||||
|
* @property-read \App\Models\User|null $user
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereCreatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereFavoritableId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereFavoritableType($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereUpdatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserFavorite whereUserid($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class UserFavorite extends AbstractModel
|
||||||
|
{
|
||||||
|
const TYPE_TASK = 'task';
|
||||||
|
const TYPE_PROJECT = 'project';
|
||||||
|
const TYPE_FILE = 'file';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'userid',
|
||||||
|
'favoritable_type',
|
||||||
|
'favoritable_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联用户
|
||||||
|
*/
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'userid', 'userid');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多态关联
|
||||||
|
*/
|
||||||
|
public function favoritable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换收藏状态
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @param string $type 收藏类型
|
||||||
|
* @param int $id 收藏对象ID
|
||||||
|
* @return array ['favorited' => bool, 'action' => 'added'|'removed']
|
||||||
|
*/
|
||||||
|
public static function toggleFavorite($userid, $type, $id)
|
||||||
|
{
|
||||||
|
$favorite = self::whereUserid($userid)
|
||||||
|
->whereFavoritableType($type)
|
||||||
|
->whereFavoritableId($id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($favorite) {
|
||||||
|
// 取消收藏
|
||||||
|
$favorite->delete();
|
||||||
|
return ['favorited' => false, 'action' => 'removed'];
|
||||||
|
} else {
|
||||||
|
// 添加收藏
|
||||||
|
self::create([
|
||||||
|
'userid' => $userid,
|
||||||
|
'favoritable_type' => $type,
|
||||||
|
'favoritable_id' => $id,
|
||||||
|
]);
|
||||||
|
return ['favorited' => true, 'action' => 'added'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已收藏
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @param string $type 收藏类型
|
||||||
|
* @param int $id 收藏对象ID
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isFavorited($userid, $type, $id)
|
||||||
|
{
|
||||||
|
return self::whereUserid($userid)
|
||||||
|
->whereFavoritableType($type)
|
||||||
|
->whereFavoritableId($id)
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏列表
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @param string|null $type 收藏类型过滤
|
||||||
|
* @param int $page 页码
|
||||||
|
* @param int $pageSize 每页数量
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getUserFavorites($userid, $type = null, $page = 1, $pageSize = 20)
|
||||||
|
{
|
||||||
|
$query = self::whereUserid($userid)->orderByDesc('created_at');
|
||||||
|
|
||||||
|
if ($type) {
|
||||||
|
$query->whereFavoritableType($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
$favorites = $query->paginate($pageSize, ['*'], 'page', $page);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'tasks' => [],
|
||||||
|
'projects' => [],
|
||||||
|
'files' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
// 分组收集ID
|
||||||
|
$taskIds = [];
|
||||||
|
$projectIds = [];
|
||||||
|
$fileIds = [];
|
||||||
|
|
||||||
|
foreach ($favorites->items() as $favorite) {
|
||||||
|
switch ($favorite->favoritable_type) {
|
||||||
|
case self::TYPE_TASK:
|
||||||
|
$taskIds[] = $favorite->favoritable_id;
|
||||||
|
break;
|
||||||
|
case self::TYPE_PROJECT:
|
||||||
|
$projectIds[] = $favorite->favoritable_id;
|
||||||
|
break;
|
||||||
|
case self::TYPE_FILE:
|
||||||
|
$fileIds[] = $favorite->favoritable_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询具体数据
|
||||||
|
if (!empty($taskIds)) {
|
||||||
|
$tasks = ProjectTask::select([
|
||||||
|
'project_tasks.id',
|
||||||
|
'project_tasks.name',
|
||||||
|
'project_tasks.project_id',
|
||||||
|
'project_tasks.complete_at',
|
||||||
|
'project_tasks.created_at',
|
||||||
|
'project_tasks.flow_item_id',
|
||||||
|
'project_tasks.flow_item_name',
|
||||||
|
'projects.name as project_name'
|
||||||
|
])
|
||||||
|
->leftJoin('projects', 'project_tasks.project_id', '=', 'projects.id')
|
||||||
|
->whereIn('project_tasks.id', $taskIds)
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
foreach ($favorites->items() as $favorite) {
|
||||||
|
if ($favorite->favoritable_type === self::TYPE_TASK && isset($tasks[$favorite->favoritable_id])) {
|
||||||
|
$task = $tasks[$favorite->favoritable_id];
|
||||||
|
|
||||||
|
// 解析 flow_item_name 字段(格式:status|name|color)
|
||||||
|
$flowItemParts = explode('|', $task->flow_item_name ?: '');
|
||||||
|
$flowItemStatus = $flowItemParts[0] ?? '';
|
||||||
|
$flowItemName = $flowItemParts[1] ?? $task->flow_item_name;
|
||||||
|
$flowItemColor = $flowItemParts[2] ?? '';
|
||||||
|
|
||||||
|
$data['tasks'][] = [
|
||||||
|
'id' => $task->id,
|
||||||
|
'name' => $task->name,
|
||||||
|
'project_id' => $task->project_id,
|
||||||
|
'project_name' => $task->project_name,
|
||||||
|
'complete_at' => $task->complete_at,
|
||||||
|
'flow_item_id' => $task->flow_item_id,
|
||||||
|
'flow_item_name' => $flowItemName,
|
||||||
|
'flow_item_status' => $flowItemStatus,
|
||||||
|
'flow_item_color' => $flowItemColor,
|
||||||
|
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($projectIds)) {
|
||||||
|
$projects = Project::select([
|
||||||
|
'id', 'name', 'desc', 'archived_at', 'created_at'
|
||||||
|
])->whereIn('id', $projectIds)->get()->keyBy('id');
|
||||||
|
|
||||||
|
foreach ($favorites->items() as $favorite) {
|
||||||
|
if ($favorite->favoritable_type === self::TYPE_PROJECT && isset($projects[$favorite->favoritable_id])) {
|
||||||
|
$project = $projects[$favorite->favoritable_id];
|
||||||
|
$data['projects'][] = [
|
||||||
|
'id' => $project->id,
|
||||||
|
'name' => $project->name,
|
||||||
|
'desc' => $project->desc,
|
||||||
|
'archived_at' => $project->archived_at,
|
||||||
|
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($fileIds)) {
|
||||||
|
$files = File::select([
|
||||||
|
'id', 'name', 'ext', 'size', 'created_at'
|
||||||
|
])->whereIn('id', $fileIds)->get()->keyBy('id');
|
||||||
|
|
||||||
|
foreach ($favorites->items() as $favorite) {
|
||||||
|
if ($favorite->favoritable_type === self::TYPE_FILE && isset($files[$favorite->favoritable_id])) {
|
||||||
|
$file = $files[$favorite->favoritable_id];
|
||||||
|
$data['files'][] = [
|
||||||
|
'id' => $file->id,
|
||||||
|
'name' => $file->name,
|
||||||
|
'ext' => $file->ext,
|
||||||
|
'size' => $file->size,
|
||||||
|
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'data' => $data,
|
||||||
|
'total' => $favorites->total(),
|
||||||
|
'current_page' => $favorites->currentPage(),
|
||||||
|
'per_page' => $favorites->perPage(),
|
||||||
|
'last_page' => $favorites->lastPage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理用户收藏
|
||||||
|
* @param int $userid 用户ID
|
||||||
|
* @param string|null $type 收藏类型,null表示全部类型
|
||||||
|
* @return int 删除的记录数
|
||||||
|
*/
|
||||||
|
public static function cleanUserFavorites($userid, $type = null)
|
||||||
|
{
|
||||||
|
$query = self::whereUserid($userid);
|
||||||
|
|
||||||
|
if ($type) {
|
||||||
|
$query->whereFavoritableType($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateUserFavoritesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
if (Schema::hasTable('user_favorites'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Schema::create('user_favorites', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('userid')->index()->nullable()->default(0)->comment('用户ID');
|
||||||
|
$table->string('favoritable_type', 50)->index()->nullable()->default('')->comment('收藏类型(task/project/file)');
|
||||||
|
$table->bigInteger('favoritable_id')->index()->nullable()->default(0)->comment('收藏对象ID');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// 复合索引:用户ID + 收藏类型(用于按类型获取收藏列表)
|
||||||
|
$table->index(['userid', 'favoritable_type']);
|
||||||
|
// 唯一索引:用户ID + 收藏类型 + 收藏对象ID(防止重复收藏)
|
||||||
|
$table->unique(['userid', 'favoritable_type', 'favoritable_id'], 'user_favorites_unique');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_favorites');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,10 +18,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu slot="list">
|
<DropdownMenu slot="list">
|
||||||
<template v-for="item in menu">
|
<template v-for="(item, index) in menu">
|
||||||
<!--最近打开的任务-->
|
<!--最近打开的任务-->
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-if="item.path === 'taskBrowse'"
|
v-if="item.path === 'taskBrowse'"
|
||||||
|
:key="`taskBrowse-${index}`"
|
||||||
transfer
|
transfer
|
||||||
transfer-class-name="page-manage-menu-dropdown"
|
transfer-class-name="page-manage-menu-dropdown"
|
||||||
placement="right-start">
|
placement="right-start">
|
||||||
@ -32,10 +33,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownMenu slot="list" v-if="taskBrowseLists.length > 0">
|
<DropdownMenu slot="list" v-if="taskBrowseLists.length > 0">
|
||||||
|
<template v-for="(item, key) in taskBrowseLists">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
v-for="(item, key) in taskBrowseLists"
|
|
||||||
v-if="item.id > 0 && key < 10"
|
v-if="item.id > 0 && key < 10"
|
||||||
:key="key"
|
:key="`task-${key}`"
|
||||||
:style="$A.generateColorVarStyle(item.flow_item_color, [10], 'flow-item-custom-color')"
|
:style="$A.generateColorVarStyle(item.flow_item_color, [10], 'flow-item-custom-color')"
|
||||||
class="task-title"
|
class="task-title"
|
||||||
@click.native="openTask(item)"
|
@click.native="openTask(item)"
|
||||||
@ -43,6 +44,7 @@
|
|||||||
<span v-if="item.flow_item_name" :class="item.flow_item_status">{{item.flow_item_name}}</span>
|
<span v-if="item.flow_item_name" :class="item.flow_item_status">{{item.flow_item_name}}</span>
|
||||||
<div class="task-title-text">{{ item.name }}</div>
|
<div class="task-title-text">{{ item.name }}</div>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
</template>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu v-else slot="list">
|
<DropdownMenu v-else slot="list">
|
||||||
<DropdownItem style="color:darkgrey">{{ $L('暂无打开记录') }}</DropdownItem>
|
<DropdownItem style="color:darkgrey">{{ $L('暂无打开记录') }}</DropdownItem>
|
||||||
@ -51,6 +53,7 @@
|
|||||||
<!-- 团队管理 -->
|
<!-- 团队管理 -->
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-else-if="item.path === 'team'"
|
v-else-if="item.path === 'team'"
|
||||||
|
:key="`team-${index}`"
|
||||||
transfer
|
transfer
|
||||||
transfer-class-name="page-manage-menu-dropdown"
|
transfer-class-name="page-manage-menu-dropdown"
|
||||||
placement="right-start">
|
placement="right-start">
|
||||||
@ -71,6 +74,7 @@
|
|||||||
<!-- 其他菜单 -->
|
<!-- 其他菜单 -->
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
v-else-if="item.visible !== false"
|
v-else-if="item.visible !== false"
|
||||||
|
:key="`menu-${index}`"
|
||||||
:divided="!!item.divided"
|
:divided="!!item.divided"
|
||||||
:name="item.path"
|
:name="item.path"
|
||||||
:style="item.style || {}">
|
:style="item.style || {}">
|
||||||
@ -324,6 +328,14 @@
|
|||||||
<Report v-if="workReportShow" v-model="workReportTab" @on-read="$store.dispatch('getReportUnread', 1000)" />
|
<Report v-if="workReportShow" v-model="workReportTab" @on-read="$store.dispatch('getReportUnread', 1000)" />
|
||||||
</DrawerOverlay>
|
</DrawerOverlay>
|
||||||
|
|
||||||
|
<!--我的收藏-->
|
||||||
|
<DrawerOverlay
|
||||||
|
v-model="favoriteShow"
|
||||||
|
placement="right"
|
||||||
|
:size="1200">
|
||||||
|
<FavoriteManagement v-if="favoriteShow" @on-close="favoriteShow = false"/>
|
||||||
|
</DrawerOverlay>
|
||||||
|
|
||||||
<!--团队成员管理-->
|
<!--团队成员管理-->
|
||||||
<DrawerOverlay
|
<DrawerOverlay
|
||||||
v-model="allUserShow"
|
v-model="allUserShow"
|
||||||
@ -380,6 +392,7 @@
|
|||||||
import { mapState, mapGetters } from 'vuex'
|
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 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";
|
||||||
@ -423,6 +436,7 @@ export default {
|
|||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
ProjectManagement,
|
ProjectManagement,
|
||||||
TeamManagement,
|
TeamManagement,
|
||||||
|
FavoriteManagement,
|
||||||
ProjectArchived,
|
ProjectArchived,
|
||||||
MicroApps,
|
MicroApps,
|
||||||
ComplaintManagement,
|
ComplaintManagement,
|
||||||
@ -472,6 +486,8 @@ export default {
|
|||||||
allProjectShow: false,
|
allProjectShow: false,
|
||||||
archivedProjectShow: false,
|
archivedProjectShow: false,
|
||||||
|
|
||||||
|
favoriteShow: false,
|
||||||
|
|
||||||
natificationReady: false,
|
natificationReady: false,
|
||||||
notificationManage: null,
|
notificationManage: null,
|
||||||
|
|
||||||
@ -501,6 +517,7 @@ export default {
|
|||||||
emitter.on('dialogMsgPush', this.addDialogMsg);
|
emitter.on('dialogMsgPush', this.addDialogMsg);
|
||||||
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);
|
||||||
//
|
//
|
||||||
document.addEventListener('keydown', this.shortcutEvent);
|
document.addEventListener('keydown', this.shortcutEvent);
|
||||||
},
|
},
|
||||||
@ -518,6 +535,7 @@ export default {
|
|||||||
emitter.off('dialogMsgPush', this.addDialogMsg);
|
emitter.off('dialogMsgPush', this.addDialogMsg);
|
||||||
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);
|
||||||
//
|
//
|
||||||
document.removeEventListener('keydown', this.shortcutEvent);
|
document.removeEventListener('keydown', this.shortcutEvent);
|
||||||
},
|
},
|
||||||
@ -649,6 +667,7 @@ export default {
|
|||||||
const {userIsAdmin} = this;
|
const {userIsAdmin} = this;
|
||||||
const array = [
|
const array = [
|
||||||
{path: 'taskBrowse', name: '最近打开的任务'},
|
{path: 'taskBrowse', name: '最近打开的任务'},
|
||||||
|
{path: 'favorite', name: '我的收藏'},
|
||||||
{path: 'download', name: '下载内容', visible: !!this.$Electron},
|
{path: 'download', name: '下载内容', visible: !!this.$Electron},
|
||||||
];
|
];
|
||||||
if (userIsAdmin) {
|
if (userIsAdmin) {
|
||||||
@ -848,6 +867,9 @@ export default {
|
|||||||
case 'workReport':
|
case 'workReport':
|
||||||
this.openReport(this.reportUnreadNumber > 0 ? 'receive' : 'my');
|
this.openReport(this.reportUnreadNumber > 0 ? 'receive' : 'my');
|
||||||
return;
|
return;
|
||||||
|
case 'favorite':
|
||||||
|
this.openFavorite();
|
||||||
|
return;
|
||||||
case 'version':
|
case 'version':
|
||||||
emitter.emit('updateNotification', null);
|
emitter.emit('updateNotification', null);
|
||||||
return;
|
return;
|
||||||
@ -1208,6 +1230,10 @@ export default {
|
|||||||
this.workReportShow = true;
|
this.workReportShow = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openFavorite() {
|
||||||
|
this.favoriteShow = true;
|
||||||
|
},
|
||||||
|
|
||||||
handleLongpress(event) {
|
handleLongpress(event) {
|
||||||
const {type, data, element} = this.longpressData;
|
const {type, data, element} = this.longpressData;
|
||||||
this.$store.commit("longpress/clear")
|
this.$store.commit("longpress/clear")
|
||||||
|
|||||||
@ -0,0 +1,360 @@
|
|||||||
|
<template>
|
||||||
|
<div class="favorite-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="keys.type" :placeholder="$L('全部类型')">
|
||||||
|
<Option value="">{{$L('全部类型')}}</Option>
|
||||||
|
<Option value="task">{{$L('任务')}}</Option>
|
||||||
|
<Option value="project">{{$L('项目')}}</Option>
|
||||||
|
<Option value="file">{{$L('文件')}}</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="search-label">
|
||||||
|
{{$L("名称")}}
|
||||||
|
</div>
|
||||||
|
<div class="search-content">
|
||||||
|
<Input v-model="keys.name" clearable :placeholder="$L('搜索收藏名称')"/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="search-button">
|
||||||
|
<SearchButton
|
||||||
|
:loading="loadIng > 0"
|
||||||
|
:filtering="keyIs"
|
||||||
|
placement="right"
|
||||||
|
@search="onSearch"
|
||||||
|
@refresh="getLists"
|
||||||
|
@cancelFilter="keyIs=false"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="table-page-box">
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data="list"
|
||||||
|
:loading="loadIng > 0"
|
||||||
|
:no-data-text="$L(noText)"
|
||||||
|
stripe/>
|
||||||
|
<Page
|
||||||
|
:total="total"
|
||||||
|
:current="page"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:disabled="loadIng > 0"
|
||||||
|
:simple="windowPortrait"
|
||||||
|
:page-size-opts="[10,20,30,50,100]"
|
||||||
|
show-elevator
|
||||||
|
show-sizer
|
||||||
|
show-total
|
||||||
|
@on-change="setPage"
|
||||||
|
@on-page-size-change="setPageSize"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SearchButton from "../../../components/SearchButton.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FavoriteManagement",
|
||||||
|
components: {SearchButton},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loadIng: 0,
|
||||||
|
|
||||||
|
keys: {},
|
||||||
|
keyIs: false,
|
||||||
|
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: this.$L('类型'),
|
||||||
|
key: 'type',
|
||||||
|
width: 80,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
const typeMap = {
|
||||||
|
'task': this.$L('任务'),
|
||||||
|
'project': this.$L('项目'),
|
||||||
|
'file': this.$L('文件')
|
||||||
|
};
|
||||||
|
const color = {
|
||||||
|
'task': 'primary',
|
||||||
|
'project': 'success',
|
||||||
|
'file': 'warning'
|
||||||
|
};
|
||||||
|
return h('Tag', {
|
||||||
|
props: {
|
||||||
|
color: color[row.type] || 'default'
|
||||||
|
}
|
||||||
|
}, typeMap[row.type] || row.type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('名称'),
|
||||||
|
key: 'name',
|
||||||
|
minWidth: 150,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
return h('div', {
|
||||||
|
class: 'favorite-name',
|
||||||
|
on: {
|
||||||
|
click: () => this.openFavorite(row)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('AutoTip', row.name)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('所属项目'),
|
||||||
|
key: 'project_name',
|
||||||
|
minWidth: 120,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
return row.project_name ? h('AutoTip', row.project_name) : h('span', '-');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('状态'),
|
||||||
|
minWidth: 80,
|
||||||
|
render: (h, {row}) => {
|
||||||
|
if (row.type === 'task') {
|
||||||
|
// 任务使用工作流状态显示
|
||||||
|
if (row.flow_item_name) {
|
||||||
|
return h('span', {
|
||||||
|
class: `flow-name ${row.flow_item_status}`,
|
||||||
|
style: this.$A.generateColorVarStyle(row.flow_item_color, [10], 'flow-item-custom-color')
|
||||||
|
}, row.flow_item_name);
|
||||||
|
} else {
|
||||||
|
// 没有工作流状态时的后备显示
|
||||||
|
if (row.complete_at) {
|
||||||
|
return h('span', {
|
||||||
|
class: 'favorite-status-tag favorite-status-success'
|
||||||
|
}, this.$L('已完成'));
|
||||||
|
} else {
|
||||||
|
return h('span', {
|
||||||
|
class: 'favorite-status-tag favorite-status-processing'
|
||||||
|
}, this.$L('进行中'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (row.type === 'project') {
|
||||||
|
if (row.archived_at) {
|
||||||
|
return h('span', {
|
||||||
|
class: 'favorite-status-tag favorite-status-error'
|
||||||
|
}, this.$L('已归档'));
|
||||||
|
} else {
|
||||||
|
return h('span', {
|
||||||
|
class: 'favorite-status-tag favorite-status-success'
|
||||||
|
}, this.$L('正常'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h('span', '-');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('收藏时间'),
|
||||||
|
key: 'favorited_at',
|
||||||
|
width: 168,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$L('操作'),
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
render: (h, params) => {
|
||||||
|
const vNode = [
|
||||||
|
h('Poptip', {
|
||||||
|
props: {
|
||||||
|
title: this.$L(`确定要取消收藏"${params.row.name}"吗?`),
|
||||||
|
confirm: true,
|
||||||
|
transfer: true,
|
||||||
|
placement: 'left',
|
||||||
|
okText: this.$L('确定'),
|
||||||
|
cancelText: this.$L('取消'),
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fontSize: '13px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#f00',
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
'on-ok': () => {
|
||||||
|
this.removeFavorite(params.row)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, this.$L('取消收藏'))
|
||||||
|
];
|
||||||
|
return h('TableAction', {
|
||||||
|
props: {
|
||||||
|
column: params.column
|
||||||
|
}
|
||||||
|
}, vNode);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
list: [],
|
||||||
|
allData: [], // 存储所有数据用于筛选
|
||||||
|
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
noText: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getLists();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
keyIs(v) {
|
||||||
|
if (!v) {
|
||||||
|
this.keys = {}
|
||||||
|
this.setPage(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSearch() {
|
||||||
|
this.page = 1;
|
||||||
|
this.filterData();
|
||||||
|
},
|
||||||
|
|
||||||
|
getLists() {
|
||||||
|
this.loadIng++;
|
||||||
|
this.keyIs = $A.objImplode(this.keys) != "";
|
||||||
|
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/favorites',
|
||||||
|
data: {
|
||||||
|
type: this.keys.type || '',
|
||||||
|
page: this.page,
|
||||||
|
pagesize: this.pageSize,
|
||||||
|
},
|
||||||
|
}).then(({data}) => {
|
||||||
|
// 处理返回的数据,将三种类型合并到一个列表中
|
||||||
|
this.allData = [];
|
||||||
|
|
||||||
|
// 处理任务收藏
|
||||||
|
if (data.data.tasks) {
|
||||||
|
data.data.tasks.forEach(task => {
|
||||||
|
this.allData.push({
|
||||||
|
id: task.id,
|
||||||
|
type: 'task',
|
||||||
|
name: task.name,
|
||||||
|
project_id: task.project_id,
|
||||||
|
project_name: task.project_name,
|
||||||
|
complete_at: task.complete_at,
|
||||||
|
flow_item_id: task.flow_item_id,
|
||||||
|
flow_item_name: task.flow_item_name,
|
||||||
|
flow_item_status: task.flow_item_status,
|
||||||
|
flow_item_color: task.flow_item_color,
|
||||||
|
favorited_at: task.favorited_at,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理项目收藏
|
||||||
|
if (data.data.projects) {
|
||||||
|
data.data.projects.forEach(project => {
|
||||||
|
this.allData.push({
|
||||||
|
id: project.id,
|
||||||
|
type: 'project',
|
||||||
|
name: project.name,
|
||||||
|
desc: project.desc,
|
||||||
|
archived_at: project.archived_at,
|
||||||
|
favorited_at: project.favorited_at,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件收藏
|
||||||
|
if (data.data.files) {
|
||||||
|
data.data.files.forEach(file => {
|
||||||
|
this.allData.push({
|
||||||
|
id: file.id,
|
||||||
|
type: 'file',
|
||||||
|
name: file.name,
|
||||||
|
ext: file.ext,
|
||||||
|
size: file.size,
|
||||||
|
favorited_at: file.favorited_at,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.total = data.total || this.allData.length;
|
||||||
|
this.filterData();
|
||||||
|
this.noText = '没有相关的收藏';
|
||||||
|
}).catch(() => {
|
||||||
|
this.noText = '数据加载失败';
|
||||||
|
}).finally(_ => {
|
||||||
|
this.loadIng--;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
filterData() {
|
||||||
|
let filteredData = this.allData;
|
||||||
|
|
||||||
|
// 按名称筛选
|
||||||
|
if (this.keys.name) {
|
||||||
|
filteredData = filteredData.filter(item => {
|
||||||
|
return item.name && item.name.toLowerCase().includes(this.keys.name.toLowerCase());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.list = filteredData;
|
||||||
|
},
|
||||||
|
|
||||||
|
setPage(page) {
|
||||||
|
this.page = page;
|
||||||
|
this.getLists();
|
||||||
|
},
|
||||||
|
|
||||||
|
setPageSize(pageSize) {
|
||||||
|
this.page = 1;
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
this.getLists();
|
||||||
|
},
|
||||||
|
|
||||||
|
openFavorite(item) {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'task':
|
||||||
|
this.$store.dispatch("openTask", {id: item.id});
|
||||||
|
break;
|
||||||
|
case 'project':
|
||||||
|
this.$router.push({
|
||||||
|
name: 'manage-project',
|
||||||
|
params: {projectId: item.id}
|
||||||
|
});
|
||||||
|
this.$emit('on-close');
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
this.$router.push({name: 'manage-file'});
|
||||||
|
this.$emit('on-close');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFavorite(item) {
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/favorite/toggle',
|
||||||
|
data: {
|
||||||
|
type: item.type,
|
||||||
|
id: item.id
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
}).then(() => {
|
||||||
|
$A.messageSuccess('取消收藏成功');
|
||||||
|
this.getLists();
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
$A.modalError(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -180,7 +180,7 @@ export default {
|
|||||||
},
|
},
|
||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
this.formData = data.permissions;
|
this.formData = data.permissions;
|
||||||
this.$Message.success(this.$L('修改成功'));
|
$A.messageSuccess('修改成功');
|
||||||
}).catch(({msg}) => {
|
}).catch(({msg}) => {
|
||||||
$A.modalError(msg);
|
$A.modalError(msg);
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
|
|||||||
@ -47,7 +47,12 @@
|
|||||||
|
|
||||||
<template v-if="task.parent_id === 0">
|
<template v-if="task.parent_id === 0">
|
||||||
<template v-if="operationShow">
|
<template v-if="operationShow">
|
||||||
<EDropdownItem command="send" :divided="turns.length > 0">
|
<EDropdownItem command="favorite" :divided="turns.length > 0">
|
||||||
|
<div class="item" :class="{'favorited': isFavorited}">
|
||||||
|
<Icon :type="isFavorited ? 'ios-star' : 'ios-star-outline'" />{{$L(isFavorited ? '取消收藏' : '收藏')}}
|
||||||
|
</div>
|
||||||
|
</EDropdownItem>
|
||||||
|
<EDropdownItem command="send">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<i class="taskfont movefont"></i>{{$L('发送')}}
|
<i class="taskfont movefont"></i>{{$L('发送')}}
|
||||||
</div>
|
</div>
|
||||||
@ -140,6 +145,7 @@ export default {
|
|||||||
styles: {},
|
styles: {},
|
||||||
|
|
||||||
moveTaskShow: false,
|
moveTaskShow: false,
|
||||||
|
isFavorited: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -201,6 +207,7 @@ export default {
|
|||||||
this.placement = typeof data.placement === "undefined" ? "bottom" : data.placement;
|
this.placement = typeof data.placement === "undefined" ? "bottom" : data.placement;
|
||||||
this.projectId = typeof data.projectId === "undefined" ? 0 : data.projectId;
|
this.projectId = typeof data.projectId === "undefined" ? 0 : data.projectId;
|
||||||
this.onUpdate = typeof data.onUpdate === "function" ? data.onUpdate : null;
|
this.onUpdate = typeof data.onUpdate === "function" ? data.onUpdate : null;
|
||||||
|
this.checkFavoriteStatus();
|
||||||
//
|
//
|
||||||
this.$refs.icon.focus();
|
this.$refs.icon.focus();
|
||||||
this.updatePopper();
|
this.updatePopper();
|
||||||
@ -308,6 +315,10 @@ export default {
|
|||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'favorite':
|
||||||
|
this.toggleFavorite();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'send':
|
case 'send':
|
||||||
this.$refs.forwarder.onSelection()
|
this.$refs.forwarder.onSelection()
|
||||||
break;
|
break;
|
||||||
@ -487,6 +498,47 @@ export default {
|
|||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查收藏状态
|
||||||
|
*/
|
||||||
|
checkFavoriteStatus() {
|
||||||
|
if (!this.task.id) return;
|
||||||
|
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/favorite/check',
|
||||||
|
data: {
|
||||||
|
type: 'task',
|
||||||
|
id: this.task.id
|
||||||
|
},
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.isFavorited = data.favorited || false;
|
||||||
|
}).catch(() => {
|
||||||
|
this.isFavorited = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换收藏状态
|
||||||
|
*/
|
||||||
|
toggleFavorite() {
|
||||||
|
if (!this.task.id) return;
|
||||||
|
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/favorite/toggle',
|
||||||
|
data: {
|
||||||
|
type: 'task',
|
||||||
|
id: this.task.id
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
}).then(({data, msg}) => {
|
||||||
|
this.isFavorited = data.favorited;
|
||||||
|
this.hide();
|
||||||
|
$A.messageSuccess(msg);
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
$A.messageError(msg || '操作失败');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
@import "dialog-select";
|
@import "dialog-select";
|
||||||
@import "dialog-session-history";
|
@import "dialog-session-history";
|
||||||
@import "dialog-wrapper";
|
@import "dialog-wrapper";
|
||||||
|
@import "favorite-management";
|
||||||
@import "file-content";
|
@import "file-content";
|
||||||
@import "forwarder";
|
@import "forwarder";
|
||||||
@import "general-operation";
|
@import "general-operation";
|
||||||
|
|||||||
128
resources/assets/sass/pages/components/favorite-management.scss
vendored
Normal file
128
resources/assets/sass/pages/components/favorite-management.scss
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
.favorite-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;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorite-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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-page-box {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态标签样式 - 使用项目标准配色
|
||||||
|
.favorite-status-tag {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.2;
|
||||||
|
border: 1px solid;
|
||||||
|
|
||||||
|
// 已完成/正常状态 - 绿色
|
||||||
|
&.favorite-status-success {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-end-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-30, rgba($flow-status-end-color, 0.3));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-end-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行中状态 - 橙色
|
||||||
|
&.favorite-status-processing {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-progress-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-30, rgba($flow-status-progress-color, 0.3));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-progress-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已归档/错误状态 - 灰色
|
||||||
|
&.favorite-status-error {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-archived-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-30, rgba($flow-status-archived-color, 0.3));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-archived-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工作流状态样式 - 与项目面板保持一致
|
||||||
|
.flow-name {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.2;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&.start {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-start-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-10, rgba($flow-status-start-color, 0.1));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-start-color);
|
||||||
|
}
|
||||||
|
&.progress {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-progress-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-10, rgba($flow-status-progress-color, 0.1));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-progress-color);
|
||||||
|
}
|
||||||
|
&.test {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-test-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-10, rgba($flow-status-test-color, 0.1));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-test-color);
|
||||||
|
}
|
||||||
|
&.end {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-end-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-10, rgba($flow-status-end-color, 0.1));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-end-color);
|
||||||
|
}
|
||||||
|
&.archived {
|
||||||
|
background-color: var(--flow-item-custom-color-10, rgba($flow-status-archived-color, 0.1));
|
||||||
|
border-color: var(--flow-item-custom-color-10, rgba($flow-status-archived-color, 0.1));
|
||||||
|
color: var(--flow-item-custom-color-100, $flow-status-archived-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -61,6 +61,14 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.favorited {
|
||||||
|
color: #faad14;
|
||||||
|
|
||||||
|
.ivu-icon {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-prefix {
|
.item-prefix {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user