no message

This commit is contained in:
kuaifan 2021-06-04 23:32:44 +08:00
parent f3d4d3199f
commit 7405c5bba8
18 changed files with 863 additions and 235 deletions

View File

@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogUser;
use App\Module\Base;
use Request;
/**
* @apiDefine dialog
*
* 对话
*/
class DialogController extends AbstractController
{
/**
* 消息列表
*
* @apiParam {Number} dialog_id 对话ID
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:50,最大:100
*/
public function msg__lists()
{
$user = User::authE();
if (Base::isError($user)) {
return $user;
} else {
$user = User::IDE($user['data']);
}
//
$dialog_id = intval(Request::input('dialog_id'));
//
if (!WebSocketDialogUser::whereDialogId($dialog_id)->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);
}
}
}

View File

@ -9,9 +9,8 @@ use App\Models\ProjectLog;
use App\Models\ProjectTask; use App\Models\ProjectTask;
use App\Models\ProjectUser; use App\Models\ProjectUser;
use App\Models\User; use App\Models\User;
use App\Models\WebSocketDialog; use App\Models\WebSocketDialogMsg;
use App\Module\Base; use App\Module\Base;
use Carbon\Carbon;
use Request; use Request;
/** /**
@ -209,6 +208,54 @@ class ProjectController extends AbstractController
return Base::retSuccess('修改成功'); 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 Base::retError('你不是项目负责人');
} }
// //
return AbstractModel::transaction(function() use ($project) { if ($project->deleteProject()) {
ProjectTask::whereProjectId($project->id)->delete();
$project->delete();
//
return Base::retSuccess('删除成功'); 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);
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Module\Base;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
/** /**
@ -82,4 +83,41 @@ class Project extends AbstractModel
{ {
return $this->hasMany(projectUser::class, 'project_id', 'id')->orderBy('id'); 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);
}
} }

View File

@ -27,12 +27,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int $dialog_id
* @property-read int $file_num * @property-read int $file_num
* @property-read int $msg_num * @property-read int $msg_num
* @property-read bool $overdue * @property-read bool $overdue
* @property-read int $percent * @property-read int $percent
* @property-read int $sub_num * @property-read int $sub_num
* @property-read bool $today * @property-read bool $today
* @property-read \App\Models\Project|null $project
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag
* @property-read int|null $task_tag_count * @property-read int|null $task_tag_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser
@ -70,6 +72,7 @@ class ProjectTask extends AbstractModel
'file_num', 'file_num',
'msg_num', 'msg_num',
'sub_num', 'sub_num',
'dialog_id',
'percent', 'percent',
'today', 'today',
'overdue', 'overdue',
@ -94,7 +97,7 @@ class ProjectTask extends AbstractModel
public function getMsgNumAttribute() public function getMsgNumAttribute()
{ {
if (!isset($this->attributes['msg_num'])) { 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']; return $this->attributes['msg_num'];
} }
@ -114,6 +117,18 @@ class ProjectTask extends AbstractModel
return $this->attributes['sub_num']; 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 * 进度0-100
* @return int * @return int
@ -167,6 +182,14 @@ class ProjectTask extends AbstractModel
return false; 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 * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */

View File

@ -1,31 +0,0 @@
<?php
namespace App\Models;
/**
* Class ProjectTaskMsg
*
* @package App\Models
* @property int $id
* @property int|null $project_id 项目ID
* @property int|null $task_id 任务ID
* @property string|null $msg 详细内容(JSON)
* @property int|null $userid 发送用户ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMsg whereUserid($value)
* @mixin \Eloquent
*/
class ProjectTaskMsg extends AbstractModel
{
}

View File

@ -2,6 +2,8 @@
namespace App\Models; namespace App\Models;
use App\Module\Base;
/** /**
* Class ProjectUser * Class ProjectUser
* *
@ -12,6 +14,7 @@ namespace App\Models;
* @property int|null $owner 是否负责人 * @property int|null $owner 是否负责人
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectUser newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectUser newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser query() * @method static \Illuminate\Database\Eloquent\Builder|ProjectUser query()
@ -26,4 +29,28 @@ namespace App\Models;
class ProjectUser extends AbstractModel class ProjectUser extends AbstractModel
{ {
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function project(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->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);
}
} }

View File

@ -26,6 +26,8 @@ use App\Module\Base;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value)
* @mixin \Eloquent * @mixin \Eloquent
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereDeletedAt($value)
*/ */
class WebSocketDialog extends AbstractModel class WebSocketDialog extends AbstractModel
{ {
@ -89,7 +91,7 @@ class WebSocketDialog extends AbstractModel
* @param int|array $userid 加入的会员ID或会员ID组 * @param int|array $userid 加入的会员ID或会员ID组
* @return bool * @return bool
*/ */
public static function quitGroup($dialog_id, $userid) public static function exitGroup($dialog_id, $userid)
{ {
if (is_array($userid)) { if (is_array($userid)) {
WebSocketDialogUser::whereDialogId($dialog_id)->whereIn('userid', $userid)->delete(); WebSocketDialogUser::whereDialogId($dialog_id)->whereIn('userid', $userid)->delete();

View File

@ -13,8 +13,11 @@ use Carbon\Carbon;
* @property int $id * @property int $id
* @property int|null $dialog_id 对话ID * @property int|null $dialog_id 对话ID
* @property int|null $userid 发送会员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 $send 是否已送达
* @property int|null $extra_int 额外数字参数
* @property string|null $extra_str 额外字符参数
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery() * @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 query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value) * @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 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 whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($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 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 whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class WebSocketDialogMsg extends AbstractModel 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 int $dialog_id 会话ID 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息 * @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统 * @param int $sender 发送的会员ID默认自己0为系统
* @param int $extra_int
* @param string $extra_str
* @return array * @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([ $dialogMsg = self::createInstance([
'userid' => $sender ?: User::token2userid(), 'userid' => $sender ?: User::token2userid(),
'type' => $type,
'msg' => $msg, 'msg' => $msg,
'extra_int' => $extra_int,
'extra_str' => $extra_str,
]); ]);
return AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) { return AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) {
$dialog = WebSocketDialog::checkGroupDialog($dialogMsg->userid, $dialog_id); $dialog = WebSocketDialog::checkGroupDialog($dialogMsg->userid, $dialog_id);
@ -60,11 +89,11 @@ class WebSocketDialogMsg extends AbstractModel
'userid' => $userids, 'userid' => $userids,
'msg' => [ 'msg' => [
'type' => 'dialog', '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 int $userid 接收的会员ID
* @param string $type 消息类型
* @param array $msg 发送的消息 * @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统 * @param int $sender 发送的会员ID默认自己0为系统
* @param int $extra_int
* @param string $extra_str
* @return array * @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([ $dialogMsg = self::createInstance([
'userid' => $sender ?: User::token2userid(), 'userid' => $sender ?: User::token2userid(),
'type' => $type,
'msg' => $msg, 'msg' => $msg,
'extra_int' => $extra_int,
'extra_str' => $extra_str,
]); ]);
return AbstractModel::transaction(function () use ($userid, $msg, $dialogMsg) { return AbstractModel::transaction(function () use ($userid, $msg, $dialogMsg) {
$dialog = WebSocketDialog::checkUserDialog($dialogMsg->userid, $userid); $dialog = WebSocketDialog::checkUserDialog($dialogMsg->userid, $userid);
@ -96,10 +131,10 @@ class WebSocketDialogMsg extends AbstractModel
'userid' => $userid, 'userid' => $userid,
'msg' => [ 'msg' => [
'type' => 'dialog', 'type' => 'dialog',
'data' => $msg, 'data' => $dialogMsg->toArray(),
] ]
]); ]);
return Base::retSuccess('发送成功'); return Base::retSuccess('发送成功', $dialogMsg);
}); });
} }

View File

@ -34,7 +34,7 @@
"dependencies": { "dependencies": {
"echarts": "^5.1.1", "echarts": "^5.1.1",
"tinymce": "^5.8.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-clipboard2": "^0.3.1",
"vue-emoji-picker": "^1.0.1", "vue-emoji-picker": "^1.0.1",
"vue-kityminder-gg": "^1.3.6", "vue-kityminder-gg": "^1.3.6",

View File

@ -91,6 +91,18 @@ export default {
}, },
scrollToBottom(animate) { scrollToBottom(animate) {
this.scrollTo(this.$refs.scrollerView.scrollHeight, 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, //
}
} }
} }
} }

View File

@ -10,9 +10,11 @@
:default-label="value" :default-label="value"
:default-event-object="true" :default-event-object="true"
:multipleMax="multipleMax" :multipleMax="multipleMax"
:multipleUncancelable="uncancelable"
multiple multiple
filterable filterable
transfer-class-name="common-user-transfer" transfer-class-name="common-user-transfer"
@on-open-change="openChange"
@on-set-default-options="setDefaultOptions"> @on-set-default-options="setDefaultOptions">
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '')}}</div> <div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '')}}</div>
<Option v-for="(item, key) in lists" :value="item.userid" :key="key" :label="item.nickname" :avatar="item.userimg"> <Option v-for="(item, key) in lists" :value="item.userid" :key="key" :label="item.nickname" :avatar="item.userimg">
@ -35,6 +37,12 @@
type: [String, Number, Array], type: [String, Number, Array],
default: '' default: ''
}, },
uncancelable: {
type: Array,
default: () => {
return [];
}
},
placeholder: { placeholder: {
default: '' default: ''
}, },
@ -55,6 +63,7 @@
ready: false, ready: false,
initialized: false, initialized: false,
loading: false, loading: false,
openLoad: false,
values: [], values: [],
lists: [] lists: []
} }
@ -89,6 +98,15 @@
} }
}, },
methods: { methods: {
openChange(show) {
if (show && !this.openLoad) {
this.openLoad = true;
if (this.lists.length == this.values.length) {
this.$nextTick(this.searchUser);
}
}
},
setDefaultOptions(options) { setDefaultOptions(options) {
const userids = []; const userids = [];
options.forEach(({value, label}) => { options.forEach(({value, label}) => {
@ -126,7 +144,7 @@
url: 'users/search', url: 'users/search',
data: { data: {
keys: { keys: {
key: query key: query || ''
}, },
take: 30 take: 30
}, },

View File

@ -0,0 +1,49 @@
<template>
<div class="message-view">
<div v-if="msgData.type == 'text'" class="message-content" v-html="textMsg(msgData.msg.text)"></div>
<div v-else class="message-content message-unknown">{{$L("未知的消息类型")}}</div>
<div v-if="msgData.created_at" class="message-time">{{formatTime(msgData.created_at)}}</div>
<div v-else class="message-time"><Loading/></div>
</div>
</template>
<script>
export default {
name: "MessageView",
props: {
msgData: {
type: Object,
default: () => {
return {};
}
},
},
methods: {
formatTime(date) {
let time = Math.round(new Date(date).getTime() / 1000),
string = '';
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
string = $A.formatDate('H:i', time)
} else if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
string = $A.formatDate('m-d', time)
} else {
string = $A.formatDate('Y-m-d', time)
}
return string || '';
},
textMsg(text) {
if (!text) {
return ""
}
text = text.trim().replace(/(\n\x20*){3,}/g, "<br/><br/>");
text = text.trim().replace(/\n/g, "<br/>");
return text;
}
}
}
</script>

View File

@ -31,10 +31,14 @@
<li class="project-icon"> <li class="project-icon">
<Dropdown @on-click="projectDropdown" transfer> <Dropdown @on-click="projectDropdown" transfer>
<Icon type="ios-more" /> <Icon type="ios-more" />
<DropdownMenu slot="list"> <DropdownMenu v-if="projectDetail.owner_userid === userId" slot="list">
<DropdownItem name="setting">{{$L('项目设置')}}</DropdownItem> <DropdownItem name="setting">{{$L('项目设置')}}</DropdownItem>
<DropdownItem name="transfer">{{$L('移交项目')}}</DropdownItem> <DropdownItem name="user">{{$L('成员管理')}}</DropdownItem>
<DropdownItem name="delete" style="color:#f40" divided>{{$L('删除项目')}}</DropdownItem> <DropdownItem name="transfer" divided>{{$L('移交项目')}}</DropdownItem>
<DropdownItem name="delete" style="color:#f40">{{$L('删除项目')}}</DropdownItem>
</DropdownMenu>
<DropdownMenu v-else slot="list">
<DropdownItem name="exit">{{$L('退出项目')}}</DropdownItem>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
@ -293,6 +297,23 @@
</div> </div>
</Modal> </Modal>
<!--成员管理-->
<Modal
v-model="userShow"
:title="$L('成员管理')"
:mask-closable="false"
class-name="simple-modal">
<Form ref="addProject" :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('项目成员')">
<UserInput v-if="userShow" v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/>
</FormItem>
</Form>
<div slot="footer">
<Button type="default" @click="userShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="userLoad > 0" @click="onUser">{{$L('保存')}}</Button>
</div>
</Modal>
<!--移交项目--> <!--移交项目-->
<Modal <Modal
v-model="transferShow" v-model="transferShow"
@ -833,6 +854,10 @@ export default {
settingData: {}, settingData: {},
settingLoad: 0, settingLoad: 0,
userShow: false,
userData: {},
userLoad: 0,
transferShow: false, transferShow: false,
transferData: {}, transferData: {},
transferLoad: 0, transferLoad: 0,
@ -978,6 +1003,29 @@ export default {
}); });
}, },
onUser() {
this.userLoad++;
$A.apiAjax({
url: 'project/user',
data: {
project_id: this.userData.project_id,
userid: this.userData.userids,
},
complete: () => {
this.userLoad--;
},
success: ({ret, data, msg}) => {
if (ret === 1) {
$A.messageSuccess(msg);
this.$store.commit('getProjectDetail', this.userData.project_id);
this.userShow = false;
} else {
$A.modalError(msg);
}
}
});
},
onTransfer() { onTransfer() {
this.transferLoad++; this.transferLoad++;
$A.apiAjax({ $A.apiAjax({
@ -1004,7 +1052,7 @@ export default {
onDelete() { onDelete() {
$A.modalConfirm({ $A.modalConfirm({
title: '删除项目', title: '删除项目',
content: '你确定要删除项目吗?', content: '你确定要删除项目【' + this.projectDetail.name + '】吗?',
loading: true, loading: true,
onOk: () => { onOk: () => {
$A.apiAjax({ $A.apiAjax({
@ -1031,6 +1079,36 @@ export default {
}); });
}, },
onExit() {
$A.modalConfirm({
title: '退出项目',
content: '你确定要退出项目【' + this.projectDetail.name + '】吗?',
loading: true,
onOk: () => {
$A.apiAjax({
url: 'project/exit',
data: {
project_id: this.projectDetail.id,
},
error: () => {
this.$Modal.remove();
$A.modalAlert('网络繁忙,请稍后再试!');
},
success: ({ret, data, msg}) => {
this.$Modal.remove();
if (ret === 1) {
$A.messageSuccess(msg);
this.$store.commit('getProjectList');
this.goForward({path: '/manage/dashboard'}, true);
}else{
$A.modalError(msg, 301);
}
}
});
}
});
},
projectDropdown(name) { projectDropdown(name) {
switch (name) { switch (name) {
case "setting": case "setting":
@ -1040,6 +1118,13 @@ export default {
this.settingShow = true; this.settingShow = true;
break; break;
case "user":
this.$set(this.userData, 'project_id', this.projectDetail.id);
this.$set(this.userData, 'userids', this.projectDetail.project_user.map(({userid}) => userid));
this.$set(this.userData, 'uncancelable', [this.projectDetail.owner_userid]);
this.userShow = true;
break;
case "transfer": case "transfer":
this.$set(this.transferData, 'project_id', this.projectDetail.id); this.$set(this.transferData, 'project_id', this.projectDetail.id);
this.$set(this.transferData, 'owner_userid', [this.projectDetail.owner_userid]); this.$set(this.transferData, 'owner_userid', [this.projectDetail.owner_userid]);
@ -1049,6 +1134,10 @@ export default {
case "delete": case "delete":
this.onDelete(); this.onDelete();
break; break;
case "exit":
this.onExit();
break;
} }
}, },

View File

@ -2,85 +2,32 @@
<div v-if="$store.state.projectChatShow" class="project-message"> <div v-if="$store.state.projectChatShow" class="project-message">
<div class="group-member"> <div class="group-member">
<div class="member-head"> <div class="member-head">
<div class="member-title">Member<span>(25)</span></div> <div class="member-title">{{$L('项目成员')}}<span>({{projectDetail.project_user.length}})</span></div>
<div class="member-view-all">View All</div> <div class="member-view-all" @click="memberShowAll=!memberShowAll">{{$L('查看所有')}}</div>
</div> </div>
<ul class="member-list"> <ul :class="['member-list', memberShowAll ? 'member-all' : '']">
<li class="online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li> <li v-for="item in projectDetail.project_user">
<li class="online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li> <UserAvatar :userid="item.userid" :size="36"/>
<li class="online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li> </li>
<li class="online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li>
<li><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li>
<li><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li>
<li><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li>
<li><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></li>
</ul> </ul>
</div> </div>
<div class="group-title">Group Chat</div> <div class="group-title">Group Chat</div>
<ScrollerY ref="groupChat" class="group-chat" @on-scroll="groupChatScroll"> <ScrollerY ref="groupChat" class="group-chat message-scroller" @on-scroll="groupChatScroll">
<div ref="manageList" class="message-list"> <div ref="manageList" class="message-list">
<ul> <ul>
<li> <li v-if="dialogLoad > 0" class="loading"><Loading/></li>
<div class="message-avatar online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div> <li v-else-if="dialogList.length === 0" class="nothing">{{$L('暂无消息')}}</li>
<div class="message-item"> <li v-for="(item, key) in dialogList" :key="key" :class="{self:item.userid == userId}">
<div class="message-text">Selamat pagi, Mas!</div> <div class="message-avatar">
<div class="message-time">08:00 AM</div> <UserAvatar :userid="item.userid" :size="30"/>
</div>
</li>
<li class="self">
<div class="message-avatar"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Selamat pagi, Mas!</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li>
<div class="message-avatar offline"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Pagi Mas Piko, Langsung saja Ada apa Gerangan mas?</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li class="self">
<div class="message-avatar"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Pagi Mas Piko, Langsung saja Ada apa Gerangan mas?</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li>
<div class="message-avatar online"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Selamat pagi, Mas!</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li class="self">
<div class="message-avatar"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Selamat pagi, Mas!</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li>
<div class="message-avatar offline"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Pagi Mas Piko, Langsung saja Ada apa Gerangan mas?</div>
<div class="message-time">08:00 AM</div>
</div>
</li>
<li class="self">
<div class="message-avatar"><Avatar src="https://i.loli.net/2017/08/21/599a521472424.jpg" /></div>
<div class="message-item">
<div class="message-text">Pagi Mas Piko, Langsung saja Ada apa Gerangan mas?</div>
<div class="message-time">08:00 AM</div>
</div> </div>
<MessageView :msg-data="item"/>
</li> </li>
</ul> </ul>
</div> </div>
</ScrollerY> </ScrollerY>
<div class="group-footer"> <div class="group-footer">
<DragInput class="group-input" v-model="groupText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="groupKeydown" @on-input-paste="groupPasteDrag" :placeholder="$L('输入消息...')" /> <DragInput class="group-input" v-model="msgText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="groupKeydown" @on-input-paste="groupPasteDrag" :placeholder="$L('输入消息...')" />
</div> </div>
</div> </div>
</template> </template>
@ -130,6 +77,11 @@
} }
.member-view-all { .member-view-all {
color: #999; color: #999;
font-size: 13px;
cursor: pointer;
&:hover {
color: #777;
}
} }
} }
.member-list { .member-list {
@ -141,33 +93,19 @@
position: relative; position: relative;
list-style: none; list-style: none;
margin-right: 14px; margin-right: 14px;
.ivu-avatar { margin-bottom: 8px;
width: 36px;
height: 36px;
}
&:before {
content: "";
position: absolute;
right: 0;
bottom: 0;
width: 9px;
height: 9px;
border-radius: 50%;
background-color: #ff0000;
border: 1px solid #ffffff;
z-index: 1;
}
&.online {
&:before {
background-color: #87d068;
} }
&.member-all {
display: block;
> li {
display: inline-block;
} }
} }
} }
} }
.group-title { .group-title {
padding: 0 32px; padding: 0 32px;
margin-top: 28px; margin-top: 20px;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
} }
@ -175,78 +113,6 @@
flex: 1; flex: 1;
padding: 0 32px; padding: 0 32px;
margin-top: 18px; margin-top: 18px;
overflow: auto;
.message-list {
> ul {
> li {
display: flex;
flex-direction: row;
align-items: flex-end;
list-style: none;
margin-bottom: 16px;
.message-avatar {
position: relative;
margin-bottom: 20px;
.ivu-avatar {
width: 28px;
height: 28px;
flex-shrink: 0;
}
&.online,
&.offline {
&:before {
content: "";
position: absolute;
transform: scale(0.8);
right: 0;
bottom: 0;
width: 9px;
height: 9px;
border-radius: 50%;
background-color: #ff0000;
border: 1px solid #ffffff;
z-index: 1;
}
}
&.online {
&:before {
background-color: #87d068;
}
}
}
.message-item {
max-width: 70%;
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 0 8px;
.message-text {
color: #333333;
background-color: #F4F5F7;
padding: 8px;
border-radius: 6px 6px 6px 0;
}
.message-time {
color: #bbbbbb;
font-size: 12px;
padding-top: 3px;
}
}
&.self {
flex-direction: row-reverse;
.message-item {
align-items: flex-end;
margin: 0 8px 0 0;
.message-text {
color: #ffffff;
background-color: #2d8cf0;
border-radius: 6px 6px 0 6px;
}
}
}
}
}
}
} }
.group-footer { .group-footer {
padding: 0 28px; padding: 0 28px;
@ -259,27 +125,98 @@
<script> <script>
import DragInput from "../../../components/DragInput"; import DragInput from "../../../components/DragInput";
import ScrollerY from "../../../components/ScrollerY"; import ScrollerY from "../../../components/ScrollerY";
import {mapState} from "vuex";
import MessageView from "./message-view";
export default { export default {
name: "ProjectMessage", name: "ProjectMessage",
components: {ScrollerY, DragInput}, components: {MessageView, ScrollerY, DragInput},
data() { data() {
return { return {
groupText: '', autoBottom: true,
autoBottom: true memberShowAll: false,
dialogId: 0,
msgText: '',
} }
}, },
mounted() { mounted() {
this.groupChatGoAuto(); this.groupChatGoAuto();
this.groupChatGoBottom(); this.groupChatGoBottom();
}, },
computed: {
...mapState(['userId', 'projectDetail', 'projectMsgUnread', 'dialogLoad', 'dialogList']),
},
watch: {
projectDetail(detail) {
this.dialogId = detail.dialog_id;
},
dialogId(id) {
this.$store.commit('getDialogMsg', id);
}
},
methods: { methods: {
groupKeydown() { sendMsg() {
let mid = $A.randomString(16);
this.dialogList.push({
id: mid,
userid: this.userId,
type: 'text',
msg: {
text: this.msgText,
}, },
groupPasteDrag() { });
this.groupChatGoBottom(true);
//
$A.apiAjax({
url: 'dialog/msg/sendtext',
data: {
dialog_id: this.projectDetail.dialog_id,
text: this.msgText,
}, },
error:() => {
this.dialogList = this.dialogList.filter(({id}) => id != mid);
},
success: ({ret, data, msg}) => {
if (ret === 1) {
let index = this.dialogList.findIndex(({id}) => id == mid);
if (index > -1) this.dialogList.splice(index, 1, data);
} else {
this.dialogList = this.dialogList.filter(({id}) => id != mid);
}
}
});
//
this.msgText = '';
},
groupKeydown(e) {
if (e.keyCode === 13) {
if (e.shiftKey) {
return;
}
e.preventDefault();
this.sendMsg();
}
},
groupPasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
postFiles.forEach((file) => {
//
});
}
},
groupChatScroll(res) { groupChatScroll(res) {
if (res.directionreal === 'up') { if (res.directionreal === 'up') {
if (res.scrollE < 10) { if (res.scrollE < 10) {
@ -289,23 +226,40 @@ export default {
this.autoBottom = false; this.autoBottom = false;
} }
}, },
groupChatGoAuto() { groupChatGoAuto() {
clearTimeout(this.groupChatGoTimeout); clearTimeout(this.groupChatGoTimeout);
this.groupChatGoTimeout = setTimeout(() => { this.groupChatGoTimeout = setTimeout(() => {
if (this.autoBottom) { if (this.autoBottom) {
this.groupChatGoBottom(); this.groupChatGoBottom(true);
} }
this.groupChatGoAuto(); this.groupChatGoAuto();
}, 1000); }, 1000);
}, },
groupChatGoBottom(animation = false) { groupChatGoBottom(animation = false) {
this.$nextTick(() => { this.$nextTick(() => {
if (typeof this.$refs.groupChat !== "undefined") { if (typeof this.$refs.groupChat !== "undefined") {
if (this.$refs.groupChat.getScrollInfo().scrollE > 0) {
this.$refs.groupChat.scrollTo(this.$refs.manageList.clientHeight, animation); this.$refs.groupChat.scrollTo(this.$refs.manageList.clientHeight, animation);
}
this.autoBottom = true; this.autoBottom = true;
} }
}); });
}, },
formatTime(date) {
let time = Math.round(new Date(date).getTime() / 1000),
string = '';
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
string = $A.formatDate('H:i', time)
} else if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
string = $A.formatDate('m-d', time)
} else {
string = $A.formatDate('Y-m-d', time)
}
return string || '';
},
} }
} }
</script> </script>

View File

@ -260,7 +260,22 @@ export default {
console.log("[WS] Callerr", err); console.log("[WS] Callerr", err);
} }
} }
}) });
if (type === "dialog") {
const msgData = msgDetail.data;
const dialog_id = msgData.dialog_id;
if (dialog_id == state.dialogId) {
let index = state.dialogList.findIndex(({id}) => id === msgData.id);
if (index === -1) {
if (state.dialogList.length >= 200) {
state.dialogList.splice(0, 1);
}
state.dialogList.push(msgData);
} else {
state.dialogList.splice(index, 1, msgData);
}
}
}
break break
} }
} }
@ -315,5 +330,54 @@ export default {
*/ */
wsClose(state) { wsClose(state) {
state.ws && state.ws.close(); state.ws && state.ws.close();
},
/**
* 获取对话消息
* @param state
* @param dialog_id
*/
getDialogMsg(state, dialog_id) {
if (state.method.runNum(dialog_id) === 0) {
return;
}
if (state.method.isArray(state.cacheDialog[dialog_id])) {
state.dialogList = state.cacheDialog[dialog_id]
} else {
state.dialogList = [];
}
state.dialogId = dialog_id;
//
if (state.cacheDialog[dialog_id + "::load"]) {
return;
}
state.cacheDialog[dialog_id + "::load"] = true;
//
state.dialogLoad++;
$A.apiAjax({
url: 'dialog/msg/lists',
data: {
dialog_id: dialog_id,
},
complete: () => {
state.dialogLoad--;
state.cacheDialog[dialog_id + "::load"] = false;
},
success: ({ret, data, msg}) => {
if (ret === 1) {
state.cacheDialog[dialog_id] = data.data.reverse();
if (state.dialogId === dialog_id) {
state.cacheDialog[dialog_id].forEach((item) => {
let index = state.dialogList.findIndex(({id}) => id === item.id);
if (index === -1) {
state.dialogList.push(item);
} else {
state.dialogList.splice(index, 1, item);
}
})
}
}
}
});
} }
} }

View File

@ -172,18 +172,27 @@ state.wsCall = {};
state.wsTimeout = null; state.wsTimeout = null;
state.wsListener = {}; state.wsListener = {};
export default Object.assign(state, { // 项目信息
cacheProject: {}, state.projectLoad = 0;
cacheUserBasic: {}, state.projectList = [];
state.projectDetail = {
projectLoad: 0,
projectList: [],
projectDetail: {
id: 0, id: 0,
project_column: [], project_column: [],
project_user: [] project_user: []
}, };
projectMsgUnread: 0, state.projectMsgUnread = 0;
taskPriority: [], // 会话消息
}) state.dialogId = 0;
state.dialogLoad = 0;
state.dialogList = [];
// 任务优先级
state.taskPriority = [];
// 其他
state.cacheProject = {};
state.cacheUserBasic = {};
state.cacheDialog = {};
export default state

View File

@ -666,3 +666,79 @@ body {
} }
} }
.message-scroller {
position: relative;
overflow: auto;
.message-list {
> ul {
> li {
display: flex;
flex-direction: row;
align-items: flex-end;
list-style: none;
margin-bottom: 16px;
.message-avatar {
position: relative;
margin-bottom: 20px;
flex-shrink: 0;
}
.message-view {
max-width: 70%;
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 0 8px;
.message-content {
color: #333333;
background-color: #F4F5F7;
padding: 8px;
border-radius: 6px 6px 6px 0;
}
.message-unknown {
text-decoration: underline;
}
.message-time {
color: #bbbbbb;
font-size: 12px;
padding-top: 3px;
height: 21px;
line-height: 21px;
.common-loading {
margin: 0 2px;
width: 10px;
height: 10px;
}
}
}
&.loading {
padding: 12px 0;
justify-content: center;
.common-loading {
margin: 0;
width: 18px;
height: 18px;
}
}
&.nothing {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #999999;
}
&.self {
flex-direction: row-reverse;
.message-view {
align-items: flex-end;
margin: 0 8px 0 0;
.message-content {
color: #ffffff;
background-color: #2d8cf0;
border-radius: 6px 6px 0 6px;
}
}
}
}
}
}
}

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Http\Controllers\Api\DialogController;
use App\Http\Controllers\Api\ProjectController; use App\Http\Controllers\Api\ProjectController;
use App\Http\Controllers\Api\SystemController; use App\Http\Controllers\Api\SystemController;
use App\Http\Controllers\Api\UsersController; use App\Http\Controllers\Api\UsersController;
@ -31,6 +32,9 @@ Route::prefix('api')->middleware(['webapi'])->group(function () {
// 系统 // 系统
Route::any('system/{method}', SystemController::class); Route::any('system/{method}', SystemController::class);
Route::any('system/{method}/{action}', SystemController::class); Route::any('system/{method}/{action}', SystemController::class);
// 对话
Route::any('dialog/{method}', DialogController::class);
Route::any('dialog/{method}/{action}', DialogController::class);
}); });
/** /**