diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php new file mode 100755 index 00000000..1129aee4 --- /dev/null +++ b/app/Http/Controllers/Api/DialogController.php @@ -0,0 +1,97 @@ +whereUserid($user->userid)->exists()) { + return Base::retError('不在成员列表内'); + } + $dialog = WebSocketDialog::whereId($dialog_id)->first(); + if (empty($dialog)) { + return Base::retError('对话不存在或已被删除'); + } + // + $list = WebSocketDialogMsg::whereDialogId($dialog_id)->orderByDesc('id')->paginate(Base::getPaginate(100, 50)); + // + return Base::retSuccess('success', $list); + } + + /** + * 发送消息 + * + * @apiParam {Number} dialog_id 对话ID + * @apiParam {Number} [extra_int] 额外参数(数字) + * @apiParam {String} [extra_str] 额外参数(字符) + * @apiParam {String} text 消息内容 + */ + public function msg__sendtext() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $dialog_id = intval(Request::input('dialog_id')); + $extra_int = intval(Request::input('extra_int')); + $extra_str = trim(Request::input('extra_str')); + $text = trim(Request::input('text')); + // + if (mb_strlen($text) < 1) { + return Base::retError('消息内容不能为空'); + } elseif (mb_strlen($text) > 20000) { + return Base::retError('消息内容最大不能超过20000字'); + } + // + if (!WebSocketDialogUser::whereDialogId($dialog_id)->whereUserid($user->userid)->exists()) { + return Base::retError('不在成员列表内'); + } + $dialog = WebSocketDialog::whereId($dialog_id)->first(); + if (empty($dialog)) { + return Base::retError('对话不存在或已被删除'); + } + // + $msg = [ + 'text' => $text + ]; + // + if ($dialog->type == 'group') { + return WebSocketDialogMsg::addGroupMsg($dialog_id, 'text', $msg, $user->userid, $extra_int, $extra_str); + } else { + return WebSocketDialogMsg::addUserMsg($dialog_id, 'text', $msg, $user->userid, $extra_int, $extra_str); + } + } +} diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index b470ca24..b6cfe8a7 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -9,9 +9,8 @@ use App\Models\ProjectLog; use App\Models\ProjectTask; use App\Models\ProjectUser; use App\Models\User; -use App\Models\WebSocketDialog; +use App\Models\WebSocketDialogMsg; use App\Module\Base; -use Carbon\Carbon; use Request; /** @@ -209,6 +208,54 @@ class ProjectController extends AbstractController return Base::retSuccess('修改成功'); } + /** + * 修改项目成员 + * + * @apiParam {Number} project_id 项目ID + * @apiParam {Number} userid 成员ID或成员ID组 + */ + public function user() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $project_id = intval(Request::input('project_id')); + $userid = Request::input('userid'); + $userid = is_array($userid) ? $userid : [$userid]; + // + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + if (!$project->owner) { + return Base::retError('你不是项目负责人'); + } + // + return AbstractModel::transaction(function() use ($project, $userid) { + $array = []; + foreach ($userid as $value) { + if ($value > 0 && $project->joinProject($value)) { + $array[] = $value; + } + } + $delUser = ProjectUser::whereProjectId($project->id)->whereNotIn('userid', $array)->get(); + if ($delUser->isNotEmpty()) { + foreach ($delUser as $value) { + $value->exitProject(); + } + } + return Base::retSuccess('修改成功'); + }); + } + /** * 移交项目 * @@ -256,6 +303,42 @@ class ProjectController extends AbstractController }); } + /** + * 退出项目 + * + * @apiParam {Number} project_id 项目ID + */ + public function exit() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $project_id = intval(Request::input('project_id')); + // + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + // + if ($project->owner) { + return Base::retError('项目负责人无法退出项目'); + } + // + $projectUser = ProjectUser::whereProjectId($project->id)->whereUserid($user->userid)->first(); + if ($projectUser->exitProject()) { + return Base::retSuccess('退出成功'); + } + return Base::retError('退出失败'); + } + /** * 删除项目 * @@ -284,12 +367,91 @@ class ProjectController extends AbstractController return Base::retError('你不是项目负责人'); } // - return AbstractModel::transaction(function() use ($project) { - ProjectTask::whereProjectId($project->id)->delete(); - $project->delete(); - // + if ($project->deleteProject()) { return Base::retSuccess('删除成功'); - }); + } + return Base::retError('删除失败'); + } + + /** + * 【消息】消息列表 + * + * @apiParam {Number} project_id 项目ID + * @apiParam {Number} [task_id] 任务ID + * + * @apiParam {Number} [page] 当前页,默认:1 + * @apiParam {Number} [pagesize] 每页显示数量,默认:30,最大:100 + */ + public function msg__lists() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $project_id = intval(Request::input('project_id')); + $task_id = intval(Request::input('task_id')); + // + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + // + $builder = WebSocketDialogMsg::whereDialogId($project->dialog_id); + if ($task_id > 0) { + $builder->whereExtraInt($task_id); + } + $list = $builder->orderByDesc('id')->paginate(Base::getPaginate(100, 30)); + // + return Base::retSuccess('success', $list); + } + + /** + * 【消息】发送消息 + * + * @apiParam {Number} project_id 项目ID + * @apiParam {Number} [task_id] 任务ID + * @apiParam {String} text 消息内容 + */ + public function msg__sendtext() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $project_id = intval(Request::input('project_id')); + $task_id = intval(Request::input('task_id')); + $text = trim(Request::input('text')); + // + if (mb_strlen($text) < 1) { + return Base::retError('消息内容不能为空'); + } elseif (mb_strlen($text) > 20000) { + return Base::retError('消息内容最大不能超过20000字'); + } + // + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + // + $msg = [ + 'text' => $text + ]; + // + return WebSocketDialogMsg::addGroupMsg($project->dialog_id, 'text', $msg, $user->userid, $task_id); } /** diff --git a/app/Models/Project.php b/app/Models/Project.php index e7172eb1..82e564bf 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Module\Base; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -82,4 +83,41 @@ class Project extends AbstractModel { return $this->hasMany(projectUser::class, 'project_id', 'id')->orderBy('id'); } + + /** + * 加入项目 + * @param int $userid 加入的会员ID + * @return bool + */ + public function joinProject($userid) { + $result = AbstractModel::transaction(function () use ($userid) { + ProjectUser::updateInsert([ + 'project_id' => $this->id, + 'userid' => $userid, + ]); + WebSocketDialogUser::updateInsert([ + 'dialog_id' => $this->dialog_id, + 'userid' => $userid, + ]); + }); + return Base::isSuccess($result); + } + + /** + * 删除项目 + * @return bool + */ + public function deleteProject() + { + $result = AbstractModel::transaction(function () { + ProjectTask::whereProjectId($this->id)->delete(); + WebSocketDialog::whereId($this->dialog_id)->delete(); + if ($this->delete()) { + return Base::retSuccess('success'); + } else { + return Base::retError('error'); + } + }); + return Base::isSuccess($result); + } } diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index bc206133..e111e46b 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -27,12 +27,14 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read int $dialog_id * @property-read int $file_num * @property-read int $msg_num * @property-read bool $overdue * @property-read int $percent * @property-read int $sub_num * @property-read bool $today + * @property-read \App\Models\Project|null $project * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag * @property-read int|null $task_tag_count * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser @@ -70,6 +72,7 @@ class ProjectTask extends AbstractModel 'file_num', 'msg_num', 'sub_num', + 'dialog_id', 'percent', 'today', 'overdue', @@ -94,7 +97,7 @@ class ProjectTask extends AbstractModel public function getMsgNumAttribute() { if (!isset($this->attributes['msg_num'])) { - $this->attributes['msg_num'] = ProjectTaskMsg::whereTaskId($this->id)->count(); + $this->attributes['msg_num'] = WebSocketDialogMsg::whereDialogId($this->dialog_id)->whereExtraInt($this->id)->count(); } return $this->attributes['msg_num']; } @@ -114,6 +117,18 @@ class ProjectTask extends AbstractModel return $this->attributes['sub_num']; } + /** + * 对话ID + * @return int + */ + public function getDialogIdAttribute() + { + if (!isset($this->attributes['dialog_id'])) { + $this->attributes['dialog_id'] = intval(Project::whereId($this->project_id)->value('dialog_id')); + } + return $this->attributes['dialog_id']; + } + /** * 进度(0-100) * @return int @@ -167,6 +182,14 @@ class ProjectTask extends AbstractModel return false; } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function project(): \Illuminate\Database\Eloquent\Relations\HasOne + { + return $this->hasOne(Project::class, 'id', 'project_id'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Models/ProjectTaskMsg.php b/app/Models/ProjectTaskMsg.php deleted file mode 100644 index 6d8c5c14..00000000 --- a/app/Models/ProjectTaskMsg.php +++ /dev/null @@ -1,31 +0,0 @@ -hasOne(Project::class, 'id', 'project_id'); + } + + /** + * 退出项目 + * @return bool + */ + public function exitProject() { + $result = AbstractModel::transaction(function () { + WebSocketDialogUser::whereDialogId($this->project->dialog_id)->whereUserid($this->userid)->delete(); + if ($this->delete()) { + return Base::retSuccess('success'); + } else { + return Base::retError('error'); + } + }); + return Base::isSuccess($result); + } + } diff --git a/app/Models/WebSocketDialog.php b/app/Models/WebSocketDialog.php index 82c5fee3..9b1f4e90 100644 --- a/app/Models/WebSocketDialog.php +++ b/app/Models/WebSocketDialog.php @@ -26,6 +26,8 @@ use App\Module\Base; * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value) * @mixin \Eloquent + * @property \Illuminate\Support\Carbon|null $deleted_at + * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereDeletedAt($value) */ class WebSocketDialog extends AbstractModel { @@ -89,7 +91,7 @@ class WebSocketDialog extends AbstractModel * @param int|array $userid 加入的会员ID或会员ID组 * @return bool */ - public static function quitGroup($dialog_id, $userid) + public static function exitGroup($dialog_id, $userid) { if (is_array($userid)) { WebSocketDialogUser::whereDialogId($dialog_id)->whereIn('userid', $userid)->delete(); diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index e3b2f41d..75db61cf 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -13,8 +13,11 @@ use Carbon\Carbon; * @property int $id * @property int|null $dialog_id 对话ID * @property int|null $userid 发送会员ID - * @property string|null $msg 详细消息 + * @property string|null $type 消息类型 + * @property array|mixed $msg 详细消息 * @property int|null $send 是否已送达 + * @property int|null $extra_int 额外数字参数 + * @property string|null $extra_str 额外字符参数 * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery() @@ -22,27 +25,53 @@ use Carbon\Carbon; * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value) + * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereExtraInt($value) + * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereExtraStr($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value) + * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value) * @mixin \Eloquent */ class WebSocketDialogMsg extends AbstractModel { + protected $hidden = [ + 'updated_at', + ]; + + /** + * 消息 + * @param $value + * @return array|mixed + */ + public function getMsgAttribute($value) + { + if (is_array($value)) { + return $value; + } + return Base::json2array($value); + } + /** * 给会员添加并发送消息 * @param int $dialog_id 会话ID(即 聊天室ID) + * @param string $type 消息类型 * @param array $msg 发送的消息 * @param int $sender 发送的会员ID(默认自己,0为系统) + * @param int $extra_int + * @param string $extra_str * @return array */ - public static function addGroupMsg($dialog_id, $msg, $sender = 0) + public static function addGroupMsg($dialog_id, $type, $msg, $sender = 0, $extra_int = 0, $extra_str = '') { $dialogMsg = self::createInstance([ 'userid' => $sender ?: User::token2userid(), + 'type' => $type, 'msg' => $msg, + 'extra_int' => $extra_int, + 'extra_str' => $extra_str, ]); return AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) { $dialog = WebSocketDialog::checkGroupDialog($dialogMsg->userid, $dialog_id); @@ -60,11 +89,11 @@ class WebSocketDialogMsg extends AbstractModel 'userid' => $userids, 'msg' => [ 'type' => 'dialog', - 'data' => $msg, + 'data' => $dialogMsg->toArray(), ] ]); } - return Base::retSuccess('发送成功'); + return Base::retSuccess('发送成功', $dialogMsg); }); } @@ -72,15 +101,21 @@ class WebSocketDialogMsg extends AbstractModel /** * 给会员添加并发送消息 * @param int $userid 接收的会员ID + * @param string $type 消息类型 * @param array $msg 发送的消息 * @param int $sender 发送的会员ID(默认自己,0为系统) + * @param int $extra_int + * @param string $extra_str * @return array */ - public static function addUserMsg($userid, $msg, $sender = 0) + public static function addUserMsg($userid, $type, $msg, $sender = 0, $extra_int = 0, $extra_str = '') { $dialogMsg = self::createInstance([ 'userid' => $sender ?: User::token2userid(), + 'type' => $type, 'msg' => $msg, + 'extra_int' => $extra_int, + 'extra_str' => $extra_str, ]); return AbstractModel::transaction(function () use ($userid, $msg, $dialogMsg) { $dialog = WebSocketDialog::checkUserDialog($dialogMsg->userid, $userid); @@ -96,10 +131,10 @@ class WebSocketDialogMsg extends AbstractModel 'userid' => $userid, 'msg' => [ 'type' => 'dialog', - 'data' => $msg, + 'data' => $dialogMsg->toArray(), ] ]); - return Base::retSuccess('发送成功'); + return Base::retSuccess('发送成功', $dialogMsg); }); } diff --git a/package.json b/package.json index 00e82413..32d48d63 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "dependencies": { "echarts": "^5.1.1", "tinymce": "^5.8.1", - "view-design-hi": "^4.5.0-10", + "view-design-hi": "^4.5.0-11", "vue-clipboard2": "^0.3.1", "vue-emoji-picker": "^1.0.1", "vue-kityminder-gg": "^1.3.6", diff --git a/resources/assets/js/components/ScrollerY.vue b/resources/assets/js/components/ScrollerY.vue index 1993edd2..f862b0b4 100644 --- a/resources/assets/js/components/ScrollerY.vue +++ b/resources/assets/js/components/ScrollerY.vue @@ -91,6 +91,18 @@ export default { }, scrollToBottom(animate) { this.scrollTo(this.$refs.scrollerView.scrollHeight, animate); + }, + getScrollInfo() { + let scrollerView = $A(this.$refs.scrollerView); + let wInnerH = Math.round(scrollerView.innerHeight()); + let wScrollY = scrollerView.scrollTop(); + let bScrollH = this.$refs.scrollerView.scrollHeight; + this.scrollY = wScrollY; + return { + scale: wScrollY / (bScrollH - wInnerH), //已滚动比例 + scrollY: wScrollY, //滚动的距离 + scrollE: bScrollH - wInnerH - wScrollY, //与底部距离 + } } } } diff --git a/resources/assets/js/components/UserInput.vue b/resources/assets/js/components/UserInput.vue index 8695d469..b7670135 100755 --- a/resources/assets/js/components/UserInput.vue +++ b/resources/assets/js/components/UserInput.vue @@ -10,9 +10,11 @@ :default-label="value" :default-event-object="true" :multipleMax="multipleMax" + :multipleUncancelable="uncancelable" multiple filterable transfer-class-name="common-user-transfer" + @on-open-change="openChange" @on-set-default-options="setDefaultOptions">