feat: 增强文件和项目的收藏功能

- 在 UserFavorite 模型中添加文件的 pid 字段以支持层级结构
- 更新前端 Vue 组件以实现文件和项目的收藏状态切换
- 添加检查文件和项目收藏状态的功能
- 优化上下文菜单以支持收藏操作
This commit is contained in:
kuaifan 2025-09-22 16:35:57 +08:00
parent 379d3811a8
commit 11b98978c1
4 changed files with 142 additions and 5 deletions

View File

@ -3,6 +3,7 @@
namespace App\Models;
use Carbon\Carbon;
use App\Models\File;
/**
* App\Models\UserFavorite
@ -211,7 +212,7 @@ class UserFavorite extends AbstractModel
if (!empty($fileIds)) {
$files = File::select([
'id', 'name', 'ext', 'size', 'created_at'
'id', 'name', 'ext', 'size', 'pid', 'created_at'
])->whereIn('id', $fileIds)->get()->keyBy('id');
foreach ($favorites->items() as $favorite) {
@ -222,6 +223,7 @@ class UserFavorite extends AbstractModel
'name' => $file->name,
'ext' => $file->ext,
'size' => $file->size,
'pid' => $file->pid,
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
];
}

View File

@ -282,6 +282,7 @@ export default {
name: file.name,
ext: file.ext,
size: file.size,
pid: file.pid,
favorited_at: file.favorited_at,
});
});
@ -334,7 +335,18 @@ export default {
this.$emit('on-close');
break;
case 'file':
this.$router.push({name: 'manage-file'});
this.$router.push({
name: 'manage-file',
params: {
folderId: item.pid || 0,
fileId: null,
shakeId: item.id
}
});
this.$store.state.fileShakeId = item.id;
setTimeout(() => {
this.$store.state.fileShakeId = 0;
}, 600);
this.$emit('on-close');
break;
}

View File

@ -58,7 +58,8 @@
<EDropdownItem command="workflow">{{$L('工作流设置')}}</EDropdownItem>
<EDropdownItem command="user" divided>{{$L('成员管理')}}</EDropdownItem>
<EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem>
<EDropdownItem command="log" divided>{{$L('项目动态')}}</EDropdownItem>
<EDropdownItem command="favorite" divided>{{$L(projectData.favorited ? '取消收藏' : '收藏项目')}}</EDropdownItem>
<EDropdownItem command="log">{{$L('项目动态')}}</EDropdownItem>
<EDropdownItem command="archived_task">{{$L('已归档任务')}}</EDropdownItem>
<EDropdownItem command="deleted_task">{{$L('已删除任务')}}</EDropdownItem>
<EDropdownItem command="transfer" divided>{{$L('移交项目')}}</EDropdownItem>
@ -67,7 +68,8 @@
</EDropdownMenu>
<EDropdownMenu v-else slot="dropdown">
<EDropdownItem command="task_tag">{{$L('任务标签')}}</EDropdownItem>
<EDropdownItem command="log" divided>{{$L('项目动态')}}</EDropdownItem>
<EDropdownItem command="favorite" divided>{{$L(projectData.favorited ? '取消收藏' : '收藏项目')}}</EDropdownItem>
<EDropdownItem command="log">{{$L('项目动态')}}</EDropdownItem>
<EDropdownItem command="archived_task">{{$L('已归档任务')}}</EDropdownItem>
<EDropdownItem command="deleted_task">{{$L('已删除任务')}}</EDropdownItem>
<EDropdownItem command="exit" divided style="color:#f40">{{$L('退出项目')}}</EDropdownItem>
@ -1066,8 +1068,12 @@ export default {
windowWidth() {
this.handleColumnDebounce(100);
},
projectData() {
projectData(newData, oldData) {
this.sortData = this.getSort();
//
if (newData && newData.id && (!oldData || newData.id !== oldData.id)) {
this.checkProjectFavoriteStatus();
}
},
projectLoad(n) {
this._loadTimeout && clearTimeout(this._loadTimeout)
@ -1435,6 +1441,10 @@ export default {
projectDropdown(name) {
switch (name) {
case "favorite":
this.toggleProjectFavorite();
break;
case "setting":
Object.assign(this.settingData, {
name: this.projectData.name,
@ -1877,6 +1887,50 @@ export default {
}
}
},
/**
* 切换项目收藏状态
*/
toggleProjectFavorite() {
if (!this.projectData.id) return;
this.$store.dispatch("call", {
url: 'users/favorite/toggle',
data: {
type: 'project',
id: this.projectData.id
},
method: 'post',
}).then(({data, msg}) => {
//
this.$set(this.projectData, 'favorited', data.favorited);
$A.messageSuccess(msg);
}).catch(({msg}) => {
$A.modalError(msg || this.$L('操作失败'));
});
},
/**
* 检查项目收藏状态
*/
checkProjectFavoriteStatus() {
if (!this.projectData.id) return;
this.$store.dispatch("call", {
url: 'users/favorite/check',
data: {
type: 'project',
id: this.projectData.id
},
method: 'get',
spinner: 0, //
}).then(({data}) => {
this.$set(this.projectData, 'favorited', data.favorited || false);
}).catch(() => {
//
this.$set(this.projectData, 'favorited', false);
});
},
}
}
</script>

View File

@ -218,6 +218,7 @@
<DropdownItem v-if="contextMenuItem.userid == userId" name="share" divided>{{$L('共享')}}</DropdownItem>
<DropdownItem v-else-if="contextMenuItem.share" name="outshare" divided>{{$L('退出共享')}}</DropdownItem>
<DropdownItem name="favorite" :disabled="contextMenuItem.type == 'folder'">{{$L(contextMenuItem.favorited ? '取消收藏' : '收藏')}}</DropdownItem>
<DropdownItem name="send" :disabled="contextMenuItem.type == 'folder'">{{$L('发送')}}</DropdownItem>
<DropdownItem name="link" :divided="contextMenuItem.userid != userId && !contextMenuItem.share" :disabled="contextMenuItem.type == 'folder'">{{$L('链接')}}</DropdownItem>
<DropdownItem name="download" :disabled="contextMenuItem.ext == '' || (contextMenuItem.userid != userId && contextMenuItem.permission == 0)">{{$L('下载')}}</DropdownItem>
@ -1011,6 +1012,7 @@ export default {
this.loadIng--;
this.openFileJudge()
this.shakeFile(this.$route.params.shakeId);
this.checkFileFavoriteStatus(this.fileList);
await $A.IDBSet("fileFolderId", this.pid)
}).catch(({msg}) => {
this.loadIng--;
@ -1302,6 +1304,10 @@ export default {
this.$refs.forwarder.onSelection()
break;
case 'favorite':
this.toggleFileFavorite(item);
break;
case 'share':
this.shareInfo = {
id: item.id,
@ -2072,6 +2078,69 @@ export default {
handleUploadNext() {
this.uploadShow = true;
this.packShow = false;
},
/**
* 切换文件收藏状态
*/
toggleFileFavorite(item) {
if (!item.id || item.type === 'folder') return;
this.$store.dispatch("call", {
url: 'users/favorite/toggle',
data: {
type: 'file',
id: item.id
},
method: 'post',
}).then(({data, msg}) => {
//
const fileIndex = this.fileList.findIndex(file => file.id === item.id);
if (fileIndex > -1) {
this.$set(this.fileList[fileIndex], 'favorited', data.favorited);
}
//
if (this.contextMenuItem.id === item.id) {
this.$set(this.contextMenuItem, 'favorited', data.favorited);
}
$A.messageSuccess(msg);
}).catch(({msg}) => {
$A.modalError(msg || this.$L('操作失败'));
});
},
/**
* 检查文件收藏状态
*/
checkFileFavoriteStatus(files) {
if (!Array.isArray(files) || files.length === 0) return;
const fileIds = files.filter(file => file.type !== 'folder').map(file => file.id);
if (fileIds.length === 0) return;
//
fileIds.forEach(fileId => {
this.$store.dispatch("call", {
url: 'users/favorite/check',
data: {
type: 'file',
id: fileId
},
method: 'get',
spinner: 0, //
}).then(({data}) => {
const fileIndex = this.fileList.findIndex(file => file.id === fileId);
if (fileIndex > -1) {
this.$set(this.fileList[fileIndex], 'favorited', data.favorited || false);
}
}).catch(() => {
//
const fileIndex = this.fileList.findIndex(file => file.id === fileId);
if (fileIndex > -1) {
this.$set(this.fileList[fileIndex], 'favorited', false);
}
});
});
}
}
}