mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-10 18:02:55 +00:00
feat: 添加收藏备注功能
- 在 UsersController 中新增 favorite__remark 方法,支持用户修改收藏的备注 - 在 UserFavorite 模型中添加更新备注的逻辑 - 新增数据库迁移以添加备注字段 - 更新前端组件以支持备注的显示和编辑 - 优化收藏操作的用户体验
This commit is contained in:
parent
89bdd86f14
commit
a268391e68
@ -3188,6 +3188,58 @@ class UsersController extends AbstractController
|
||||
return Base::retSuccess($message, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/favorite/remark 47-1. 修改收藏备注
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName favorite__remark
|
||||
*
|
||||
* @apiParam {String} type 收藏类型 (task/project/file/message)
|
||||
* @apiParam {Number} id 收藏对象ID
|
||||
* @apiParam {String} remark 收藏备注(<=255个字符)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function favorite__remark()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
$id = intval(Request::input('id'));
|
||||
$remark = trim(Request::input('remark', ''));
|
||||
|
||||
if (!$type || $id <= 0) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
|
||||
$allowedTypes = [UserFavorite::TYPE_TASK, UserFavorite::TYPE_PROJECT, UserFavorite::TYPE_FILE, UserFavorite::TYPE_MESSAGE];
|
||||
if (!in_array($type, $allowedTypes)) {
|
||||
return Base::retError('无效的收藏类型');
|
||||
}
|
||||
|
||||
if ($remark === '') {
|
||||
return Base::retError('请输入修改备注');
|
||||
}
|
||||
|
||||
if (mb_strlen($remark) > 255) {
|
||||
return Base::retError('备注最多支持255个字符');
|
||||
}
|
||||
|
||||
$favorite = UserFavorite::updateRemark($user->userid, $type, $id, $remark);
|
||||
|
||||
if (!$favorite) {
|
||||
return Base::retError('收藏记录不存在');
|
||||
}
|
||||
|
||||
return Base::retSuccess('修改备注成功', [
|
||||
'remark' => $favorite->remark,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/favorites/clean 48. 清理用户收藏
|
||||
*
|
||||
|
||||
@ -44,6 +44,7 @@ class UserFavorite extends AbstractModel
|
||||
'userid',
|
||||
'favoritable_type',
|
||||
'favoritable_id',
|
||||
'remark',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -79,16 +80,42 @@ class UserFavorite extends AbstractModel
|
||||
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'];
|
||||
return ['favorited' => false, 'action' => 'removed', 'remark' => ''];
|
||||
}
|
||||
|
||||
// 添加收藏
|
||||
$favorite = self::create([
|
||||
'userid' => $userid,
|
||||
'favoritable_type' => $type,
|
||||
'favoritable_id' => $id,
|
||||
]);
|
||||
|
||||
return ['favorited' => true, 'action' => 'added', 'remark' => $favorite->remark ?? ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新收藏备注
|
||||
* @param int $userid
|
||||
* @param string $type
|
||||
* @param int $id
|
||||
* @param string $remark
|
||||
* @return static|null
|
||||
*/
|
||||
public static function updateRemark($userid, $type, $id, $remark)
|
||||
{
|
||||
$favorite = self::whereUserid($userid)
|
||||
->whereFavoritableType($type)
|
||||
->whereFavoritableId($id)
|
||||
->first();
|
||||
|
||||
if (!$favorite) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$favorite->remark = $remark;
|
||||
$favorite->save();
|
||||
|
||||
return $favorite;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,6 +219,7 @@ class UserFavorite extends AbstractModel
|
||||
'flow_item_status' => $flowItemStatus,
|
||||
'flow_item_color' => $flowItemColor,
|
||||
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||
'remark' => $favorite->remark,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -211,6 +239,7 @@ class UserFavorite extends AbstractModel
|
||||
'desc' => $project->desc,
|
||||
'archived_at' => $project->archived_at,
|
||||
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||
'remark' => $favorite->remark,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -231,6 +260,7 @@ class UserFavorite extends AbstractModel
|
||||
'size' => $file->size,
|
||||
'pid' => $file->pid,
|
||||
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||
'remark' => $favorite->remark,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -263,6 +293,7 @@ class UserFavorite extends AbstractModel
|
||||
'userid' => $message->userid,
|
||||
'type' => $message->type,
|
||||
'favorited_at' => Carbon::parse($favorite->created_at)->format('Y-m-d H:i:s'),
|
||||
'remark' => $favorite->remark,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddRemarkToUserFavoritesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('user_favorites', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('user_favorites', 'remark')) {
|
||||
$table->string('remark', 255)->default('')->after('favoritable_id')->comment('收藏备注');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('user_favorites', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('user_favorites', 'remark')) {
|
||||
$table->dropColumn('remark');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
5
resources/assets/js/app.js
vendored
5
resources/assets/js/app.js
vendored
@ -108,6 +108,11 @@ router.afterEach(() => {
|
||||
store.commit('route/loading', false);
|
||||
});
|
||||
|
||||
// 消息配置
|
||||
ViewUI.Message.config({
|
||||
duration: 2.5
|
||||
});
|
||||
|
||||
// 加载路由
|
||||
Vue.prototype.goForward = function(route, isReplace, autoBroadcast = true) {
|
||||
if ($A.Ready && $A.isSubElectron && autoBroadcast) {
|
||||
|
||||
@ -4079,25 +4079,22 @@ export default {
|
||||
if (this.operateVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.$store.dispatch("toggleFavorite", {
|
||||
type: 'message',
|
||||
id: this.operateItem.id
|
||||
}).then(({data, msg}) => {
|
||||
}).then(({data}) => {
|
||||
this.$set(this.operateItem, 'favorited', data.favorited);
|
||||
const message = this.dialogMsgs.find(msg => msg.id === this.operateItem.id);
|
||||
if (message) {
|
||||
this.$set(message, 'favorited', data.favorited);
|
||||
}
|
||||
this.$Message.success(msg);
|
||||
}).catch(({msg}) => {
|
||||
$A.messageError(msg);
|
||||
});
|
||||
},
|
||||
|
||||
checkMessageFavoriteStatus(message) {
|
||||
if (!message.id) return;
|
||||
|
||||
|
||||
this.$store.dispatch("checkFavoriteStatus", {
|
||||
type: 'message',
|
||||
id: message.id
|
||||
|
||||
@ -66,10 +66,11 @@
|
||||
|
||||
<script>
|
||||
import SearchButton from "../../../components/SearchButton.vue";
|
||||
import QuickEdit from "../../../components/QuickEdit.vue";
|
||||
|
||||
export default {
|
||||
name: "FavoriteManagement",
|
||||
components: {SearchButton},
|
||||
components: {SearchButton, QuickEdit},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
@ -118,6 +119,53 @@ export default {
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('备注'),
|
||||
key: 'remark',
|
||||
minWidth: 160,
|
||||
render: (h, {row}) => {
|
||||
return h('QuickEdit', {
|
||||
props: {
|
||||
value: row.remark || '',
|
||||
attrTitle: row.remark || '',
|
||||
alwaysIcon: true,
|
||||
},
|
||||
on: {
|
||||
'on-update': (val, cb) => {
|
||||
const remark = (val || '').trim();
|
||||
if (!remark) {
|
||||
$A.messageWarning(this.$L('请输入修改备注'));
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/favorite/remark',
|
||||
data: {
|
||||
type: row.type,
|
||||
id: row.id,
|
||||
remark,
|
||||
},
|
||||
method: 'post',
|
||||
}).then(({data, msg}) => {
|
||||
const newRemark = data && typeof data.remark !== 'undefined' ? data.remark : remark;
|
||||
row.remark = newRemark;
|
||||
const target = this.allData.find(item => item.id === row.id && item.type === row.type);
|
||||
if (target) {
|
||||
target.remark = newRemark;
|
||||
}
|
||||
$A.messageSuccess(msg || this.$L('操作成功'));
|
||||
cb();
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('操作失败'));
|
||||
cb();
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
h('AutoTip', row.remark || '-')
|
||||
]);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('所属项目'),
|
||||
key: 'project_name',
|
||||
@ -232,7 +280,7 @@ export default {
|
||||
getLists() {
|
||||
this.loadIng++;
|
||||
this.keyIs = $A.objImplode(this.keys) != "";
|
||||
|
||||
|
||||
this.$store.dispatch("call", {
|
||||
url: 'users/favorites',
|
||||
data: {
|
||||
@ -243,7 +291,7 @@ export default {
|
||||
}).then(({data}) => {
|
||||
// 处理返回的数据,将三种类型合并到一个列表中
|
||||
this.allData = [];
|
||||
|
||||
|
||||
// 处理任务收藏
|
||||
if (data.data.tasks) {
|
||||
data.data.tasks.forEach(task => {
|
||||
@ -259,10 +307,11 @@ export default {
|
||||
flow_item_status: task.flow_item_status,
|
||||
flow_item_color: task.flow_item_color,
|
||||
favorited_at: task.favorited_at,
|
||||
remark: task.remark || '',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 处理项目收藏
|
||||
if (data.data.projects) {
|
||||
data.data.projects.forEach(project => {
|
||||
@ -273,10 +322,11 @@ export default {
|
||||
desc: project.desc,
|
||||
archived_at: project.archived_at,
|
||||
favorited_at: project.favorited_at,
|
||||
remark: project.remark || '',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 处理文件收藏
|
||||
if (data.data.files) {
|
||||
data.data.files.forEach(file => {
|
||||
@ -288,10 +338,11 @@ export default {
|
||||
size: file.size,
|
||||
pid: file.pid,
|
||||
favorited_at: file.favorited_at,
|
||||
remark: file.remark || '',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 处理消息收藏
|
||||
if (data.data.messages) {
|
||||
data.data.messages.forEach(message => {
|
||||
@ -303,10 +354,11 @@ export default {
|
||||
userid: message.userid,
|
||||
msg_type: message.type,
|
||||
favorited_at: message.favorited_at,
|
||||
remark: message.remark || '',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.total = data.total || this.allData.length;
|
||||
this.filterData();
|
||||
this.noText = '没有相关的收藏';
|
||||
@ -319,14 +371,14 @@ export default {
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
@ -355,10 +407,10 @@ export default {
|
||||
break;
|
||||
case 'file':
|
||||
this.$router.push({
|
||||
name: 'manage-file',
|
||||
name: 'manage-file',
|
||||
params: {
|
||||
folderId: item.pid || 0,
|
||||
fileId: null,
|
||||
folderId: item.pid || 0,
|
||||
fileId: null,
|
||||
shakeId: item.id
|
||||
}
|
||||
});
|
||||
@ -386,10 +438,7 @@ export default {
|
||||
type: item.type,
|
||||
id: item.id
|
||||
}).then(() => {
|
||||
$A.messageSuccess('取消收藏成功');
|
||||
this.getLists();
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1892,15 +1892,12 @@ export default {
|
||||
*/
|
||||
toggleProjectFavorite() {
|
||||
if (!this.projectData.id) return;
|
||||
|
||||
|
||||
this.$store.dispatch("toggleFavorite", {
|
||||
type: 'project',
|
||||
id: this.projectData.id
|
||||
}).then(({data, msg}) => {
|
||||
}).then(({data}) => {
|
||||
this.$set(this.projectData, 'favorited', data.favorited);
|
||||
$A.messageSuccess(msg);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('操作失败'));
|
||||
});
|
||||
},
|
||||
|
||||
@ -1909,7 +1906,7 @@ export default {
|
||||
*/
|
||||
checkProjectFavoriteStatus() {
|
||||
if (!this.projectData.id) return;
|
||||
|
||||
|
||||
this.$store.dispatch("checkFavoriteStatus", {
|
||||
type: 'project',
|
||||
id: this.projectData.id
|
||||
|
||||
@ -505,7 +505,7 @@ export default {
|
||||
*/
|
||||
checkFavoriteStatus() {
|
||||
if (!this.task.id) return;
|
||||
|
||||
|
||||
this.$store.dispatch("checkFavoriteStatus", {
|
||||
type: 'task',
|
||||
id: this.task.id
|
||||
@ -521,16 +521,13 @@ export default {
|
||||
*/
|
||||
toggleFavorite() {
|
||||
if (!this.task.id) return;
|
||||
|
||||
|
||||
this.$store.dispatch("toggleFavorite", {
|
||||
type: 'task',
|
||||
id: this.task.id
|
||||
}).then(({data, msg}) => {
|
||||
}).then(({data}) => {
|
||||
this.isFavorited = data.favorited;
|
||||
this.hide();
|
||||
$A.messageSuccess(msg);
|
||||
}).catch(({msg}) => {
|
||||
$A.messageError(msg || '操作失败');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -392,7 +392,7 @@
|
||||
<div>
|
||||
<div style="margin:-10px 0 8px">{{$L('文件名称')}}: {{linkData.name}}</div>
|
||||
<Input ref="linkInput" v-model="linkData.url" type="textarea" :rows="2" @on-focus="linkFocus" readonly/>
|
||||
|
||||
|
||||
<!-- 游客访问权限控制 -->
|
||||
<div style="margin:12px 0">
|
||||
<Checkbox v-model="linkData.guest_access" @on-change="onGuestAccessChange">
|
||||
@ -403,7 +403,7 @@
|
||||
{{$L('警告:任何人都可通过此链接访问文件')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-tip" style="padding-top:6px">
|
||||
{{$L('可通过此链接浏览文件。')}}
|
||||
<Poptip
|
||||
@ -2087,11 +2087,11 @@ export default {
|
||||
*/
|
||||
toggleFileFavorite(item) {
|
||||
if (!item.id || item.type === 'folder') return;
|
||||
|
||||
|
||||
this.$store.dispatch("toggleFavorite", {
|
||||
type: 'file',
|
||||
id: item.id
|
||||
}).then(({data, msg}) => {
|
||||
}).then(({data}) => {
|
||||
const fileIndex = this.fileList.findIndex(file => file.id === item.id);
|
||||
if (fileIndex > -1) {
|
||||
this.$set(this.fileList[fileIndex], 'favorited', data.favorited);
|
||||
@ -2099,9 +2099,6 @@ export default {
|
||||
if (this.contextMenuItem.id === item.id) {
|
||||
this.$set(this.contextMenuItem, 'favorited', data.favorited);
|
||||
}
|
||||
$A.messageSuccess(msg);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('操作失败'));
|
||||
});
|
||||
},
|
||||
|
||||
@ -2110,7 +2107,7 @@ export default {
|
||||
*/
|
||||
checkSingleFileFavoriteStatus(file) {
|
||||
if (!file.id || file.type === 'folder') return;
|
||||
|
||||
|
||||
this.$store.dispatch("checkFavoriteStatus", {
|
||||
type: 'file',
|
||||
id: file.id
|
||||
|
||||
73
resources/assets/js/store/actions.js
vendored
73
resources/assets/js/store/actions.js
vendored
@ -2898,13 +2898,72 @@ export default {
|
||||
* @param {object} params {type: 'task|project|file|message', id: number}
|
||||
*/
|
||||
toggleFavorite({dispatch}, {type, id}) {
|
||||
return dispatch('call', {
|
||||
url: 'users/favorite/toggle',
|
||||
data: {
|
||||
type: type,
|
||||
id: id
|
||||
},
|
||||
method: 'post',
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch('call', {
|
||||
url: 'users/favorite/toggle',
|
||||
data: {
|
||||
type: type,
|
||||
id: id
|
||||
},
|
||||
method: 'post',
|
||||
}).then(result => {
|
||||
resolve(result)
|
||||
//
|
||||
const {data, msg} = result
|
||||
if (!data.favorited) {
|
||||
$A.messageSuccess(msg);
|
||||
return
|
||||
}
|
||||
$A.Message.success({
|
||||
duration: 5,
|
||||
render: h => {
|
||||
return h('span', [
|
||||
h('span', $A.L(msg)),
|
||||
h('a', {
|
||||
style: {
|
||||
marginLeft: '8px'
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
const currentRemark = data && typeof data.remark === 'string' ? data.remark : '';
|
||||
$A.modalInput({
|
||||
title: $A.L('修改备注'),
|
||||
placeholder: $A.L('请输入修改备注'),
|
||||
okText: $A.L('保存'),
|
||||
value: currentRemark,
|
||||
onOk: (inputValue) => {
|
||||
const remark = typeof inputValue === 'string' ? inputValue.trim() : '';
|
||||
if (!remark) {
|
||||
return $A.L('请输入修改备注');
|
||||
}
|
||||
return new Promise((resolveRemark, rejectRemark) => {
|
||||
dispatch('call', {
|
||||
url: 'users/favorite/remark',
|
||||
data: {
|
||||
type,
|
||||
id,
|
||||
remark,
|
||||
},
|
||||
method: 'post',
|
||||
}).then(({msg}) => {
|
||||
$A.messageSuccess(msg || $A.L('操作成功'));
|
||||
resolveRemark();
|
||||
}).catch(({msg}) => {
|
||||
rejectRemark(msg || $A.L('操作失败'));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, $A.L('修改备注')),
|
||||
])
|
||||
}
|
||||
});
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('操作失败'));
|
||||
reject()
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user