mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 19:35:50 +00:00
Merge branch 'dev' into pro
# Conflicts: # CHANGELOG.md # cmd # package.json # public/js/build/404.5645cb91.js # public/js/build/404.9598cd97.js # public/js/build/404.a5736629.js # public/js/build/AceEditor.8747edb1.js # public/js/build/AceEditor.af35593f.js # public/js/build/AceEditor.e7f5b602.js # public/js/build/DialogWrapper.0c7cd033.js # public/js/build/DialogWrapper.64072671.js # public/js/build/DialogWrapper.7fcb5b27.js # public/js/build/Drawio.2ca59c31.js # public/js/build/Drawio.6691a6ef.js # public/js/build/Drawio.e3576e4e.js # public/js/build/FileContent.3a899bcc.js # public/js/build/FileContent.c311c89c.js # public/js/build/FileContent.d8e600e1.js # public/js/build/FilePreview.87ca99d9.js # public/js/build/FilePreview.f8134ee5.js # public/js/build/FilePreview.f9f90ff4.js # public/js/build/IFrame.02598edc.js # public/js/build/IFrame.2a7489ee.js # public/js/build/IFrame.be9780e1.js # public/js/build/ImgUpload.29e2d88d.js # public/js/build/ImgUpload.a4eff264.js # public/js/build/ImgUpload.e96999cf.js # public/js/build/Minder.2bce6c16.js # public/js/build/Minder.b1d1145f.js # public/js/build/Minder.f5bc5aca.js # public/js/build/OnlyOffice.31e7af4f.js # public/js/build/OnlyOffice.574ad560.js # public/js/build/OnlyOffice.9ce921ed.js # public/js/build/ReportEdit.5eb3a319.js # public/js/build/ReportEdit.9141bb93.js # public/js/build/ReportEdit.e3369e09.js # public/js/build/SearchButton.906cea81.js # public/js/build/SearchButton.cf201525.js # public/js/build/SearchButton.d41addb6.js # public/js/build/TEditor.7b9a9d91.js # public/js/build/TEditor.971af80f.js # public/js/build/TEditor.cc94d929.js # public/js/build/TaskDetail.38815236.js # public/js/build/TaskDetail.d1a9952e.js # public/js/build/TaskDetail.dfd78b4a.js # public/js/build/add.0cfbdd9e.js # public/js/build/add.3673f91c.js # public/js/build/add.423bc480.js # public/js/build/application.005cc174.js # public/js/build/application.5587ac3b.js # public/js/build/application.5b8f123b.js # public/js/build/apps.4e0bf65b.js # public/js/build/apps.b0a3d4f5.js # public/js/build/apps.f77a8c4e.js # public/js/build/calendar.31470aa0.js # public/js/build/calendar.ad5d85d5.js # public/js/build/calendar.e08e7575.js # public/js/build/checkin.5d4c364e.js # public/js/build/checkin.ab08f01e.js # public/js/build/checkin.c05284a9.js # public/js/build/dashboard.7cced7be.js # public/js/build/dashboard.c82415db.js # public/js/build/dashboard.f6ed8299.js # public/js/build/dayjs.495f600d.js # public/js/build/dayjs.71653272.js # public/js/build/dayjs.cf033d87.js # public/js/build/delete.4072c68f.js # public/js/build/delete.5f06c51d.js # public/js/build/delete.b26aa3fd.js # public/js/build/device.4cff22ad.js # public/js/build/device.66a7e05a.js # public/js/build/device.a13f3ef0.js # public/js/build/dialog.97b951ce.js # public/js/build/dialog.e9f6d55f.js # public/js/build/dialog.eb7b795a.js # public/js/build/editor.18a511b5.js # public/js/build/editor.2cca497c.js # public/js/build/editor.e034df4e.js # public/js/build/email.0643f86b.js # public/js/build/email.1d00cb0c.js # public/js/build/email.d95a35c0.js # public/js/build/file.4fe82c29.js # public/js/build/file.684a63df.js # public/js/build/file.9dceb82f.js # public/js/build/fileMsg.0a0029c2.js # public/js/build/fileMsg.1f4ecb0f.js # public/js/build/fileMsg.f99b6f61.js # public/js/build/fileTask.72914205.js # public/js/build/fileTask.bf35fb6b.js # public/js/build/fileTask.f4356f14.js # public/js/build/index.236af26f.js # public/js/build/index.299c9f99.js # public/js/build/index.2ffa8f9e.js # public/js/build/index.7d6e1bbe.js # public/js/build/index.94a5d2da.css # public/js/build/index.af34aeb9.js # public/js/build/index.b0ae9460.js # public/js/build/index.b69b5f25.js # public/js/build/index.b71c2859.js # public/js/build/index.c3968cad.js # public/js/build/index.d1ae44be.js # public/js/build/index.e07db7f9.css # public/js/build/index.edee4b6e.css # public/js/build/index.ef9e1e57.js # public/js/build/index.fe32159a.js # public/js/build/jquery.0909250e.js # public/js/build/jquery.16b446fd.js # public/js/build/jquery.27f590f5.js # public/js/build/keyboard.3f5b3ac6.js # public/js/build/keyboard.5de3dd2c.js # public/js/build/keyboard.c3ef7d49.js # public/js/build/language.1fadd54c.js # public/js/build/language.8bb72294.js # public/js/build/language.f3d03ece.js # public/js/build/license.21482fde.js # public/js/build/license.60871496.js # public/js/build/license.add318a7.js # public/js/build/localforage.65ac7a2a.js # public/js/build/localforage.be4775a0.js # public/js/build/localforage.dd58f5ac.js # public/js/build/login.7560afa5.js # public/js/build/login.75b3978c.js # public/js/build/login.aa163163.js # public/js/build/meeting.a60d7e8d.js # public/js/build/meeting.aa5510c7.js # public/js/build/meeting.fdb9793b.js # public/js/build/password.267357fd.js # public/js/build/password.749ce44d.js # public/js/build/password.e6d81eb1.js # public/js/build/personal.69279937.js # public/js/build/personal.a27cef8e.js # public/js/build/personal.c613af3c.js # public/js/build/preload.5827bd38.js # public/js/build/preload.8ec61a5b.js # public/js/build/preload.c6189d87.js # public/js/build/preview.29e49902.js # public/js/build/preview.7329f0f4.js # public/js/build/preview.b452b0ee.js # public/js/build/preview.c64402ed.js # public/js/build/preview.ec796a92.js # public/js/build/preview.ec85a43c.js # public/js/build/pro.2128a514.js # public/js/build/pro.213d8da6.js # public/js/build/pro.9fb60d27.js # public/js/build/projectInvite.0b3bf524.js # public/js/build/projectInvite.393920f8.js # public/js/build/projectInvite.e9cee390.js # public/js/build/reportDetail.2db50632.js # public/js/build/reportDetail.90aaf973.js # public/js/build/reportDetail.d93cc650.js # public/js/build/reportEdit.84a81076.js # public/js/build/reportEdit.8baf23d4.js # public/js/build/reportEdit.d008dd34.js # public/js/build/swipe.0c72cce1.js # public/js/build/swipe.4567bb5d.js # public/js/build/swipe.92aebd0c.js # public/js/build/system.67c1b700.js # public/js/build/system.c45c70de.js # public/js/build/system.f3384133.js # public/js/build/task.1b9e0e77.js # public/js/build/task.a445c89e.js # public/js/build/task.d43091db.js # public/js/build/taskContent.20b80714.js # public/js/build/taskContent.3ebbd2f9.js # public/js/build/taskContent.9dc7a121.js # public/js/build/theme.72d103d1.js # public/js/build/theme.7f1b2ffd.js # public/js/build/theme.df79fe8f.js # public/js/build/token.0ecffef5.js # public/js/build/token.a7f5ccf5.js # public/js/build/token.ece75257.js # public/js/build/validEmail.1462dd30.js # public/js/build/validEmail.17a3e0d2.js # public/js/build/validEmail.ee19c1f3.js # public/js/build/version.137935c7.js # public/js/build/version.1441c1fd.js # public/js/build/version.b0154505.js # public/js/build/video.03b62c93.js # public/js/build/video.2dc7f3c6.js # public/js/build/video.531c68e2.js # public/js/build/view.18713f1b.js # public/js/build/view.7770155e.js # public/js/build/view.8c6a0cc1.js # public/manifest.json
This commit is contained in:
commit
5370bee369
@ -12,6 +12,7 @@ use App\Module\AI;
|
||||
use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Models\UserBot;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Models\Setting;
|
||||
@ -443,6 +444,29 @@ class DialogController extends AbstractController
|
||||
return Base::retError('打开会话失败');
|
||||
}
|
||||
$data = WebSocketDialog::synthesizeData($dialog->id, $user->userid);
|
||||
|
||||
if ($userid > 0) {
|
||||
$botTarget = User::whereUserid($userid)->whereBot(1)->first();
|
||||
if ($botTarget) {
|
||||
$userBot = UserBot::whereBotId($botTarget->userid)->first();
|
||||
if ($userBot) {
|
||||
$userBot->dispatchWebhook(UserBot::WEBHOOK_EVENT_DIALOG_OPEN, [
|
||||
'dialog_id' => $dialog->id,
|
||||
'dialog_type' => $dialog->type,
|
||||
'session_id' => $dialog->session_id,
|
||||
'dialog_name' => $dialog->getGroupName(),
|
||||
'user' => [
|
||||
'userid' => $user->userid,
|
||||
'email' => $user->email,
|
||||
'nickname' => $user->nickname,
|
||||
],
|
||||
], 10, [
|
||||
'dialog' => $dialog->id,
|
||||
'operator' => $user->userid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
@ -1256,6 +1280,9 @@ class DialogController extends AbstractController
|
||||
if ($model_name) {
|
||||
$msgData['model_name'] = $model_name;
|
||||
}
|
||||
if (User::isBot($user->userid)) {
|
||||
$msgData['force_webhook'] = true; // 强制使用webhook发送
|
||||
}
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence, $key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,8 @@ use App\Models\WebSocketDialogUser;
|
||||
use App\Models\UserTaskBrowse;
|
||||
use App\Models\UserFavorite;
|
||||
use App\Models\UserRecentItem;
|
||||
use App\Models\UserTag;
|
||||
use App\Models\UserTagRecognition;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\UserEmailVerification;
|
||||
use App\Module\AgoraIO\AgoraTokenGenerator;
|
||||
@ -386,6 +388,9 @@ class UsersController extends AbstractController
|
||||
$data['nickname_original'] = $user->getRawOriginal('nickname');
|
||||
$data['department_name'] = $user->getDepartmentName();
|
||||
$data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid)->exists(); // 适用默认部门下第1级负责人才能添加部门OKR
|
||||
$tagMeta = UserTag::listWithMeta($user->userid, $user);
|
||||
$data['personal_tags'] = $tagMeta['top'];
|
||||
$data['personal_tags_total'] = $tagMeta['total'];
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
@ -446,6 +451,9 @@ class UsersController extends AbstractController
|
||||
* @apiParam {String} [tel] 电话
|
||||
* @apiParam {String} [nickname] 昵称
|
||||
* @apiParam {String} [profession] 职位/职称
|
||||
* @apiParam {String} [birthday] 生日(格式:YYYY-MM-DD)
|
||||
* @apiParam {String} [address] 地址
|
||||
* @apiParam {String} [introduction] 个人简介
|
||||
* @apiParam {String} [lang] 语言(比如:zh/en)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@ -509,6 +517,40 @@ class UsersController extends AbstractController
|
||||
$upLdap['employeeType'] = $profession;
|
||||
}
|
||||
}
|
||||
// 生日
|
||||
if (Arr::exists($data, 'birthday')) {
|
||||
$birthday = trim((string) Request::input('birthday'));
|
||||
if ($birthday === '') {
|
||||
$user->birthday = null;
|
||||
} else {
|
||||
try {
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthday)) {
|
||||
$birthdayDate = Carbon::createFromFormat('Y-m-d', $birthday);
|
||||
} else {
|
||||
$birthdayDate = Carbon::parse($birthday);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return Base::retError('生日格式错误');
|
||||
}
|
||||
$user->birthday = $birthdayDate->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
// 地址
|
||||
if (Arr::exists($data, 'address')) {
|
||||
$address = trim((string) Request::input('address'));
|
||||
if (mb_strlen($address) > 100) {
|
||||
return Base::retError('地址最多只能设置100个字');
|
||||
}
|
||||
$user->address = $address ?: null;
|
||||
}
|
||||
// 个人简介
|
||||
if (Arr::exists($data, 'introduction')) {
|
||||
$introduction = trim((string) Request::input('introduction'));
|
||||
if (mb_strlen($introduction) > 500) {
|
||||
return Base::retError('个人简介最多只能设置500个字');
|
||||
}
|
||||
$user->introduction = $introduction ?: null;
|
||||
}
|
||||
// 语言
|
||||
if (Arr::exists($data, 'lang')) {
|
||||
$lang = trim(Request::input('lang'));
|
||||
@ -767,8 +809,12 @@ class UsersController extends AbstractController
|
||||
public function basic()
|
||||
{
|
||||
$sharekey = Request::header('sharekey');
|
||||
if (empty($sharekey) || !Meeting::getShareInfo($sharekey)) {
|
||||
User::auth();
|
||||
$shareInfo = $sharekey ? Meeting::getShareInfo($sharekey) : null;
|
||||
$viewer = null;
|
||||
if (empty($shareInfo)) {
|
||||
$viewer = User::auth();
|
||||
} elseif (Doo::userId() > 0) {
|
||||
$viewer = User::whereUserid(Doo::userId())->first();
|
||||
}
|
||||
//
|
||||
$userid = Request::input('userid');
|
||||
@ -786,6 +832,9 @@ class UsersController extends AbstractController
|
||||
$basic = UserDelete::userid2basic($id);
|
||||
}
|
||||
if ($basic) {
|
||||
$tagMeta = UserTag::listWithMeta($basic->userid, $viewer);
|
||||
$basic->personal_tags = $tagMeta['top'];
|
||||
$basic->personal_tags_total = $tagMeta['total'];
|
||||
//
|
||||
$retArray[] = $basic;
|
||||
}
|
||||
@ -1450,6 +1499,233 @@ class UsersController extends AbstractController
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
protected function buildUserTagResponse(?User $viewer, int $targetUserId, string $message = 'success')
|
||||
{
|
||||
return Base::retSuccess($message, UserTag::listWithMeta($targetUserId, $viewer));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/tags/lists 10.1. 获取个性标签列表
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName tags__lists
|
||||
*
|
||||
* @apiParam {Number} [userid] 会员ID(不传默认为当前用户)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
* @apiSuccessExample {json} data:
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "认真负责",
|
||||
"recognition_total": 3,
|
||||
"recognized": true,
|
||||
"can_edit": true,
|
||||
"can_delete": true
|
||||
}
|
||||
],
|
||||
"top": [ ],
|
||||
"total": 1
|
||||
}
|
||||
*/
|
||||
public function tags__lists()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$userid = intval(Request::input('userid')) ?: $viewer->userid;
|
||||
$target = User::whereUserid($userid)->first();
|
||||
if (empty($target)) {
|
||||
return Base::retError('会员不存在');
|
||||
}
|
||||
return $this->buildUserTagResponse($viewer, $target->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/tags/add 10.2. 新增个性标签
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName tags__add
|
||||
*
|
||||
* @apiParam {Number} [userid] 会员ID(不传默认为当前用户)
|
||||
* @apiParam {String} name 标签名称(1-20个字符)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据,同“获取个性标签列表”
|
||||
*/
|
||||
public function tags__add()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$userid = intval(Request::input('userid')) ?: $viewer->userid;
|
||||
$target = User::whereUserid($userid)->first();
|
||||
if (empty($target)) {
|
||||
return Base::retError('会员不存在');
|
||||
}
|
||||
|
||||
$name = trim((string) Request::input('name'));
|
||||
if ($name === '') {
|
||||
return Base::retError('请输入个性标签');
|
||||
}
|
||||
if (mb_strlen($name) > 20) {
|
||||
return Base::retError('标签名称最多只能设置20个字');
|
||||
}
|
||||
if (UserTag::where('user_id', $userid)->where('name', $name)->exists()) {
|
||||
return Base::retError('标签已存在');
|
||||
}
|
||||
if (UserTag::where('user_id', $userid)->count() >= 100) {
|
||||
return Base::retError('每位会员最多添加100个标签');
|
||||
}
|
||||
|
||||
$tag = UserTag::create([
|
||||
'user_id' => $userid,
|
||||
'name' => $name,
|
||||
'created_by' => $viewer->userid,
|
||||
'updated_by' => $viewer->userid,
|
||||
]);
|
||||
$tag->save();
|
||||
|
||||
return $this->buildUserTagResponse($viewer, $userid, '添加成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/tags/update 10.3. 修改个性标签
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName tags__update
|
||||
*
|
||||
* @apiParam {Number} tag_id 标签ID
|
||||
* @apiParam {String} name 标签名称(1-20个字符)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据,同“获取个性标签列表”
|
||||
*/
|
||||
public function tags__update()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$tagId = intval(Request::input('tag_id'));
|
||||
$name = trim((string) Request::input('name'));
|
||||
if ($tagId <= 0) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
if ($name === '') {
|
||||
return Base::retError('请输入个性标签');
|
||||
}
|
||||
if (mb_strlen($name) > 20) {
|
||||
return Base::retError('标签名称最多只能设置20个字');
|
||||
}
|
||||
$tag = UserTag::find($tagId);
|
||||
if (empty($tag)) {
|
||||
return Base::retError('标签不存在');
|
||||
}
|
||||
if (!$tag->canManage($viewer)) {
|
||||
return Base::retError('无权操作该标签');
|
||||
}
|
||||
if ($name !== $tag->name && UserTag::where('user_id', $tag->user_id)->where('name', $name)->where('id', '!=', $tag->id)->exists()) {
|
||||
return Base::retError('标签已存在');
|
||||
}
|
||||
|
||||
if ($name !== $tag->name) {
|
||||
$tag->updateInstance([
|
||||
'name' => $name,
|
||||
'updated_by' => $viewer->userid,
|
||||
]);
|
||||
} else {
|
||||
$tag->updateInstance([
|
||||
'updated_by' => $viewer->userid,
|
||||
]);
|
||||
}
|
||||
$tag->save();
|
||||
|
||||
return $this->buildUserTagResponse($viewer, $tag->user_id, '保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/tags/delete 10.4. 删除个性标签
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName tags__delete
|
||||
*
|
||||
* @apiParam {Number} tag_id 标签ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据,同“获取个性标签列表”
|
||||
*/
|
||||
public function tags__delete()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$tagId = intval(Request::input('tag_id'));
|
||||
if ($tagId <= 0) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$tag = UserTag::find($tagId);
|
||||
if (empty($tag)) {
|
||||
return Base::retError('标签不存在');
|
||||
}
|
||||
if (!$tag->canManage($viewer)) {
|
||||
return Base::retError('无权操作该标签');
|
||||
}
|
||||
|
||||
$userId = $tag->user_id;
|
||||
$tag->delete();
|
||||
|
||||
return $this->buildUserTagResponse($viewer, $userId, '删除成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/tags/recognize 10.5. 认可个性标签
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName tags__recognize
|
||||
*
|
||||
* @apiParam {Number} tag_id 标签ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据,同“获取个性标签列表”
|
||||
*/
|
||||
public function tags__recognize()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$tagId = intval(Request::input('tag_id'));
|
||||
if ($tagId <= 0) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$tag = UserTag::find($tagId);
|
||||
if (empty($tag)) {
|
||||
return Base::retError('标签不存在');
|
||||
}
|
||||
|
||||
$recognition = UserTagRecognition::where('tag_id', $tagId)
|
||||
->where('user_id', $viewer->userid)
|
||||
->first();
|
||||
if ($recognition) {
|
||||
$recognition->delete();
|
||||
$message = '已取消认可';
|
||||
} else {
|
||||
UserTagRecognition::create([
|
||||
'tag_id' => $tagId,
|
||||
'user_id' => $viewer->userid,
|
||||
]);
|
||||
$message = '认可成功';
|
||||
}
|
||||
|
||||
return $this->buildUserTagResponse($viewer, $tag->user_id, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/meeting/link 【会议】获取分享链接
|
||||
*
|
||||
@ -2181,7 +2457,8 @@ class UsersController extends AbstractController
|
||||
'users.nickname',
|
||||
'users.userimg',
|
||||
'user_bots.clear_day',
|
||||
'user_bots.webhook_url'
|
||||
'user_bots.webhook_url',
|
||||
'user_bots.webhook_events'
|
||||
])
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
@ -2191,6 +2468,7 @@ class UsersController extends AbstractController
|
||||
$bot['name'] = $bot['nickname'];
|
||||
$bot['avatar'] = $bot['userimg'];
|
||||
$bot['system_name'] = UserBot::systemBotName($bot['name']);
|
||||
$bot['webhook_events'] = UserBot::normalizeWebhookEvents($bot['webhook_events'] ?? null, empty($bot['webhook_events']));
|
||||
unset($bot['userid'], $bot['nickname'], $bot['userimg']);
|
||||
}
|
||||
|
||||
@ -2242,11 +2520,13 @@ class UsersController extends AbstractController
|
||||
'avatar' => $botUser->userimg,
|
||||
'clear_day' => 0,
|
||||
'webhook_url' => '',
|
||||
'webhook_events' => [UserBot::WEBHOOK_EVENT_MESSAGE],
|
||||
'system_name' => UserBot::systemBotName($botUser->email),
|
||||
];
|
||||
if ($userBot) {
|
||||
$data['clear_day'] = $userBot->clear_day;
|
||||
$data['webhook_url'] = $userBot->webhook_url;
|
||||
$data['webhook_events'] = $userBot->webhook_events;
|
||||
}
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
@ -2327,6 +2607,9 @@ class UsersController extends AbstractController
|
||||
if (Arr::exists($data, 'webhook_url')) {
|
||||
$upBot['webhook_url'] = trim($data['webhook_url']);
|
||||
}
|
||||
if (Arr::exists($data, 'webhook_events')) {
|
||||
$upBot['webhook_events'] = UserBot::normalizeWebhookEvents($data['webhook_events'], false);
|
||||
}
|
||||
//
|
||||
if ($upUser) {
|
||||
$botUser->updateInstance($upUser);
|
||||
@ -2343,11 +2626,13 @@ class UsersController extends AbstractController
|
||||
'avatar' => $botUser->userimg,
|
||||
'clear_day' => 0,
|
||||
'webhook_url' => '',
|
||||
'webhook_events' => [UserBot::WEBHOOK_EVENT_MESSAGE],
|
||||
'system_name' => UserBot::systemBotName($botUser->email),
|
||||
];
|
||||
if ($userBot) {
|
||||
$data['clear_day'] = $userBot->clear_day;
|
||||
$data['webhook_url'] = $userBot->webhook_url;
|
||||
$data['webhook_events'] = $userBot->webhook_events;
|
||||
}
|
||||
return Base::retSuccess($botId ? '修改成功' : '添加成功', $data);
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@ use Carbon\Carbon;
|
||||
* @property string|null $tel 联系电话
|
||||
* @property string $nickname 昵称
|
||||
* @property string|null $profession 职位/职称
|
||||
* @property \Illuminate\Support\Carbon|null $birthday 生日
|
||||
* @property string|null $address 地址
|
||||
* @property string|null $introduction 个人简介
|
||||
* @property string $userimg 头像
|
||||
* @property string|null $encrypt
|
||||
* @property string|null $password 登录密码
|
||||
@ -89,7 +92,7 @@ class User extends AbstractModel
|
||||
public static $defaultAvatarMode = 'auto';
|
||||
|
||||
// 基本信息的字段
|
||||
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
|
||||
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'birthday', 'address', 'introduction', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
|
||||
@ -5,10 +5,12 @@ namespace App\Models;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\Ihttp;
|
||||
use App\Module\Timer;
|
||||
use App\Tasks\JokeSoupTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* App\Models\UserBot
|
||||
@ -20,6 +22,7 @@ use Carbon\Carbon;
|
||||
* @property \Illuminate\Support\Carbon|null $clear_at 下一次清理时间
|
||||
* @property string|null $webhook_url 消息webhook地址
|
||||
* @property int|null $webhook_num 消息webhook请求次数
|
||||
* @property array|null $webhook_events Webhook事件配置
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
@ -44,6 +47,131 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class UserBot extends AbstractModel
|
||||
{
|
||||
public const WEBHOOK_EVENT_MESSAGE = 'message';
|
||||
public const WEBHOOK_EVENT_DIALOG_OPEN = 'dialog_open';
|
||||
public const WEBHOOK_EVENT_MEMBER_JOIN = 'member_join';
|
||||
public const WEBHOOK_EVENT_MEMBER_LEAVE = 'member_leave';
|
||||
|
||||
protected $casts = [
|
||||
'webhook_events' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取可选的 webhook 事件
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function webhookEventOptions(): array
|
||||
{
|
||||
return [
|
||||
self::WEBHOOK_EVENT_MESSAGE,
|
||||
self::WEBHOOK_EVENT_DIALOG_OPEN,
|
||||
self::WEBHOOK_EVENT_MEMBER_JOIN,
|
||||
self::WEBHOOK_EVENT_MEMBER_LEAVE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化 webhook 事件配置
|
||||
*
|
||||
* @param mixed $events
|
||||
* @return array
|
||||
*/
|
||||
public static function normalizeWebhookEvents(mixed $events, bool $useFallback = true): array
|
||||
{
|
||||
if (is_string($events)) {
|
||||
$events = Base::json2array($events);
|
||||
}
|
||||
if ($events === null) {
|
||||
$events = [];
|
||||
}
|
||||
if (!is_array($events)) {
|
||||
$events = [$events];
|
||||
}
|
||||
$events = array_filter(array_map('strval', $events));
|
||||
$events = array_values(array_intersect($events, self::webhookEventOptions()));
|
||||
return $events ?: ($useFallback ? [self::WEBHOOK_EVENT_MESSAGE] : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 webhook 事件配置
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
public function getWebhookEventsAttribute(mixed $value): array
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return self::normalizeWebhookEvents(null, true);
|
||||
}
|
||||
return self::normalizeWebhookEvents($value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 webhook 事件配置
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function setWebhookEventsAttribute(mixed $value): void
|
||||
{
|
||||
$useFallback = $value === null;
|
||||
$this->attributes['webhook_events'] = Base::array2json(self::normalizeWebhookEvents($value, $useFallback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要触发指定 webhook 事件
|
||||
*
|
||||
* @param string $event
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldDispatchWebhook(string $event): bool
|
||||
{
|
||||
if (!$this->webhook_url) {
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/^https?:\/\//', $this->webhook_url)) {
|
||||
return false;
|
||||
}
|
||||
return in_array($event, $this->webhook_events ?? [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 webhook
|
||||
*
|
||||
* @param string $event
|
||||
* @param array $payload
|
||||
* @param int $timeout
|
||||
* @param array $context
|
||||
* @return array|null
|
||||
*/
|
||||
public function dispatchWebhook(string $event, array $payload, int $timeout = 30, array $context = []): ?array
|
||||
{
|
||||
if (!$this->shouldDispatchWebhook($event)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payload = array_merge([
|
||||
'event' => $event,
|
||||
'timestamp' => time(),
|
||||
'bot_uid' => $this->bot_id,
|
||||
'owner_uid' => $this->userid,
|
||||
], $payload);
|
||||
|
||||
try {
|
||||
$result = Ihttp::ihttp_post($this->webhook_url, $payload, $timeout);
|
||||
$this->increment('webhook_num');
|
||||
return $result;
|
||||
} catch (Throwable $th) {
|
||||
info(Base::array2json(array_merge($context, [
|
||||
'bot_userid' => $this->bot_id,
|
||||
'event' => $event,
|
||||
'webhook_url' => $this->webhook_url,
|
||||
'error' => $th->getMessage(),
|
||||
])));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否系统机器人
|
||||
|
||||
84
app/Models/UserTag.php
Normal file
84
app/Models/UserTag.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class UserTag extends AbstractModel
|
||||
{
|
||||
protected $table = 'user_tags';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'name',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by', 'userid')
|
||||
->select(['userid', 'nickname']);
|
||||
}
|
||||
|
||||
public function recognitions(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserTagRecognition::class, 'tag_id');
|
||||
}
|
||||
|
||||
public function canManage(User $viewer): bool
|
||||
{
|
||||
return $viewer->isAdmin()
|
||||
|| $viewer->userid === $this->user_id
|
||||
|| $viewer->userid === $this->created_by;
|
||||
}
|
||||
|
||||
public static function listWithMeta(int $targetUserId, ?User $viewer): array
|
||||
{
|
||||
$query = static::query()
|
||||
->where('user_id', $targetUserId)
|
||||
->with(['creator'])
|
||||
->withCount(['recognitions as recognition_total'])
|
||||
->orderByDesc('recognition_total')
|
||||
->orderBy('id');
|
||||
|
||||
$tags = $query->get();
|
||||
|
||||
$viewerId = $viewer?->userid ?? 0;
|
||||
$viewerIsAdmin = $viewer?->isAdmin() ?? false;
|
||||
$viewerIsOwner = $viewerId > 0 && $viewerId === $targetUserId;
|
||||
|
||||
$recognizedIds = [];
|
||||
if ($viewerId > 0 && $tags->isNotEmpty()) {
|
||||
$recognizedIds = UserTagRecognition::query()
|
||||
->where('user_id', $viewerId)
|
||||
->whereIn('tag_id', $tags->pluck('id'))
|
||||
->pluck('tag_id')
|
||||
->all();
|
||||
}
|
||||
$recognizedLookup = array_flip($recognizedIds);
|
||||
|
||||
$list = $tags->map(function (self $tag) use ($viewerId, $viewerIsAdmin, $viewerIsOwner, $recognizedLookup) {
|
||||
$canManage = $viewerIsAdmin || $viewerIsOwner || $viewerId === $tag->created_by;
|
||||
|
||||
return [
|
||||
'id' => $tag->id,
|
||||
'user_id' => $tag->user_id,
|
||||
'name' => $tag->name,
|
||||
'created_by' => $tag->created_by,
|
||||
'created_by_name' => $tag->creator?->nickname ?: '',
|
||||
'recognition_total' => (int) $tag->recognition_total,
|
||||
'recognized' => isset($recognizedLookup[$tag->id]),
|
||||
'can_edit' => $canManage,
|
||||
'can_delete' => $canManage,
|
||||
];
|
||||
})->values()->toArray();
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'top' => array_slice($list, 0, 10),
|
||||
'total' => count($list),
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Models/UserTagRecognition.php
Normal file
26
app/Models/UserTagRecognition.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class UserTagRecognition extends AbstractModel
|
||||
{
|
||||
protected $table = 'user_tag_recognitions';
|
||||
|
||||
protected $fillable = [
|
||||
'tag_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
public function tag(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserTag::class, 'tag_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'userid')
|
||||
->select(['userid', 'nickname']);
|
||||
}
|
||||
}
|
||||
@ -461,7 +461,8 @@ class WebSocketDialog extends AbstractModel
|
||||
*/
|
||||
public function joinGroup($userid, $inviter, $important = null)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($important, $inviter, $userid) {
|
||||
$addedUserIds = [];
|
||||
AbstractModel::transaction(function () use ($important, $inviter, $userid, &$addedUserIds) {
|
||||
foreach (is_array($userid) ? $userid : [$userid] as $value) {
|
||||
if ($value > 0) {
|
||||
$updateData = [
|
||||
@ -480,6 +481,7 @@ class WebSocketDialog extends AbstractModel
|
||||
]);
|
||||
}, $isInsert);
|
||||
if ($isInsert) {
|
||||
$addedUserIds[] = $value;
|
||||
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
|
||||
'notice' => User::userid2nickname($value) . " 已加入群组"
|
||||
], $inviter, true, true);
|
||||
@ -490,6 +492,16 @@ class WebSocketDialog extends AbstractModel
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
if ($addedUserIds) {
|
||||
$meta = ['action' => 'join'];
|
||||
if ($inviter > 0) {
|
||||
$actor = $this->getUserSnapshots([$inviter]);
|
||||
if (!empty($actor)) {
|
||||
$meta['actor'] = $actor[0];
|
||||
}
|
||||
}
|
||||
$this->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_JOIN, $addedUserIds, $meta);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -503,14 +515,15 @@ class WebSocketDialog extends AbstractModel
|
||||
public function exitGroup($userid, $type = 'exit', $checkDelete = true, $pushMsg = true)
|
||||
{
|
||||
$typeDesc = $type === 'remove' ? '移出' : '退出';
|
||||
AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid) {
|
||||
$removedUserIds = [];
|
||||
AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid, &$removedUserIds) {
|
||||
$builder = WebSocketDialogUser::whereDialogId($this->id);
|
||||
if (is_array($userid)) {
|
||||
$builder->whereIn('userid', $userid);
|
||||
} else {
|
||||
$builder->whereUserid($userid);
|
||||
}
|
||||
$builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type) {
|
||||
$builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type, &$removedUserIds) {
|
||||
/** @var WebSocketDialogUser $item */
|
||||
foreach ($list as $item) {
|
||||
if ($checkDelete) {
|
||||
@ -531,6 +544,7 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
//
|
||||
$item->delete();
|
||||
$removedUserIds[] = $item->userid;
|
||||
//
|
||||
if ($pushMsg) {
|
||||
if ($type === 'remove') {
|
||||
@ -549,6 +563,87 @@ class WebSocketDialog extends AbstractModel
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
if ($removedUserIds) {
|
||||
$meta = ['action' => $type];
|
||||
$operatorId = User::userid();
|
||||
if ($operatorId > 0) {
|
||||
$actor = $this->getUserSnapshots([$operatorId]);
|
||||
if (!empty($actor)) {
|
||||
$meta['actor'] = $actor[0];
|
||||
}
|
||||
}
|
||||
$this->dispatchMemberWebhook(UserBot::WEBHOOK_EVENT_MEMBER_LEAVE, $removedUserIds, $meta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户快照
|
||||
* @param array $userIds
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserSnapshots(array $userIds): array
|
||||
{
|
||||
$userIds = array_values(array_unique(array_filter($userIds)));
|
||||
if (empty($userIds)) {
|
||||
return [];
|
||||
}
|
||||
return User::whereIn('userid', $userIds)
|
||||
->get(['userid', 'nickname', 'email', 'bot'])
|
||||
->map(function (User $user) {
|
||||
return [
|
||||
'userid' => $user->userid,
|
||||
'nickname' => $user->nickname,
|
||||
'email' => $user->email,
|
||||
'is_bot' => (bool)$user->bot,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送成员事件到机器人 webhook
|
||||
* @param string $event
|
||||
* @param array $memberIds
|
||||
* @param array $meta
|
||||
* @return void
|
||||
*/
|
||||
protected function dispatchMemberWebhook(string $event, array $memberIds, array $meta = []): void
|
||||
{
|
||||
$memberIds = array_values(array_unique(array_filter($memberIds)));
|
||||
if (empty($memberIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$botIds = $this->dialogUser()->where('bot', 1)->pluck('userid')->toArray();
|
||||
if (empty($botIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userBots = UserBot::whereIn('bot_id', $botIds)->get();
|
||||
if ($userBots->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$members = $this->getUserSnapshots($memberIds);
|
||||
if (empty($members)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = array_merge([
|
||||
'dialog_id' => $this->id,
|
||||
'dialog_type' => $this->type,
|
||||
'group_type' => $this->group_type,
|
||||
'dialog_name' => $this->getGroupName(),
|
||||
'members' => $members,
|
||||
], array_filter($meta, fn ($value) => $value !== null));
|
||||
|
||||
foreach ($userBots as $userBot) {
|
||||
$userBot->dispatchWebhook($event, $payload, 10, [
|
||||
'dialog' => $this->id,
|
||||
'event_members' => $memberIds,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -66,7 +66,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
}
|
||||
|
||||
// 判断消息是否存在
|
||||
$msg = WebSocketDialogMsg::with(['user'])->find($this->msgId);
|
||||
$msg = WebSocketDialogMsg::with(['user', 'webSocketDialog'])->find($this->msgId);
|
||||
if (empty($msg)) {
|
||||
return;
|
||||
}
|
||||
@ -75,7 +75,11 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$msg->readSuccess($botUser->userid);
|
||||
|
||||
// 判断消息是否是机器人发送的则不处理,避免循环
|
||||
if (!$msg->user || $msg->user->bot) {
|
||||
if ((!$msg->user || $msg->user->bot)) {
|
||||
$msgData = Base::json2array($msg->msg);
|
||||
if (Base::val($msgData, 'force_webhook') && $msg->webSocketDialog) {
|
||||
$this->handleWebhookRequest($msgData['text'], null, $msg, $msg->webSocketDialog, $botUser);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -427,6 +431,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
private function handleWebhookRequest($sendText, $replyText, WebSocketDialogMsg $msg, WebSocketDialog $dialog, User $botUser)
|
||||
{
|
||||
$webhookUrl = null;
|
||||
$userBot = null;
|
||||
$extras = ['timestamp' => time()];
|
||||
|
||||
try {
|
||||
@ -530,13 +535,11 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
return;
|
||||
}
|
||||
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
||||
if ($userBot) {
|
||||
$userBot->webhook_num++;
|
||||
$userBot->save();
|
||||
$webhookUrl = $userBot->webhook_url;
|
||||
if (!$userBot || !$userBot->shouldDispatchWebhook(UserBot::WEBHOOK_EVENT_MESSAGE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!preg_match("/^https?:\/\//", $webhookUrl)) {
|
||||
if (!$userBot && !preg_match("/^https?:\/\//", $webhookUrl)) {
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
@ -547,50 +550,60 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
return;
|
||||
}
|
||||
//
|
||||
try {
|
||||
$data = [
|
||||
'text' => $sendText,
|
||||
'reply_text' => $replyText,
|
||||
'token' => User::generateToken($botUser),
|
||||
'session_id' => $dialog->session_id,
|
||||
'dialog_id' => $dialog->id,
|
||||
'dialog_type' => $dialog->type,
|
||||
'msg_id' => $msg->id,
|
||||
'msg_uid' => $msg->userid,
|
||||
'mention' => $this->mention ? 1 : 0,
|
||||
'bot_uid' => $botUser->userid,
|
||||
'version' => Base::getVersion(),
|
||||
'extras' => Base::array2json($extras)
|
||||
$data = [
|
||||
'text' => $sendText,
|
||||
'reply_text' => $replyText,
|
||||
'token' => User::generateToken($botUser),
|
||||
'session_id' => $dialog->session_id,
|
||||
'dialog_id' => $dialog->id,
|
||||
'dialog_type' => $dialog->type,
|
||||
'msg_id' => $msg->id,
|
||||
'msg_uid' => $msg->userid,
|
||||
'mention' => $this->mention ? 1 : 0,
|
||||
'bot_uid' => $botUser->userid,
|
||||
'version' => Base::getVersion(),
|
||||
'extras' => Base::array2json($extras)
|
||||
];
|
||||
// 添加用户信息
|
||||
$userInfo = User::find($msg->userid);
|
||||
if ($userInfo) {
|
||||
$data['msg_user'] = [
|
||||
'userid' => $userInfo->userid,
|
||||
'email' => $userInfo->email,
|
||||
'nickname' => $userInfo->nickname,
|
||||
'profession' => $userInfo->profession,
|
||||
'lang' => $userInfo->lang,
|
||||
'token' => User::generateTokenNoDevice($userInfo, now()->addHour()),
|
||||
];
|
||||
// 添加用户信息
|
||||
$userInfo = User::find($msg->userid);
|
||||
if ($userInfo) {
|
||||
$data['msg_user'] = [
|
||||
'userid' => $userInfo->userid,
|
||||
'email' => $userInfo->email,
|
||||
'nickname' => $userInfo->nickname,
|
||||
'profession' => $userInfo->profession,
|
||||
'lang' => $userInfo->lang,
|
||||
'token' => User::generateTokenNoDevice($userInfo, now()->addHour()),
|
||||
];
|
||||
}
|
||||
// 请求Webhook
|
||||
$result = Ihttp::ihttp_post($webhookUrl, $data, 30);
|
||||
if ($result['data'] && $data = Base::json2array($result['data'])) {
|
||||
if ($data['code'] != 200 && $data['message']) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', [
|
||||
'text' => $result['data']['message']
|
||||
], $botUser->userid, false, false, true);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
info(Base::array2json([
|
||||
'bot_userid' => $botUser->userid,
|
||||
}
|
||||
|
||||
$result = null;
|
||||
if ($userBot) {
|
||||
$result = $userBot->dispatchWebhook(UserBot::WEBHOOK_EVENT_MESSAGE, $data, 30, [
|
||||
'dialog' => $dialog->id,
|
||||
'msg' => $msg->id,
|
||||
'webhook_url' => $webhookUrl,
|
||||
'error' => $th->getMessage(),
|
||||
]));
|
||||
]);
|
||||
} else {
|
||||
try {
|
||||
$result = Ihttp::ihttp_post($webhookUrl, $data, 30);
|
||||
} catch (\Throwable $th) {
|
||||
info(Base::array2json([
|
||||
'bot_userid' => $botUser->userid,
|
||||
'dialog' => $dialog->id,
|
||||
'msg' => $msg->id,
|
||||
'webhook_url' => $webhookUrl,
|
||||
'error' => $th->getMessage(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($result && isset($result['data'])) {
|
||||
$responseData = Base::json2array($result['data']);
|
||||
if (($responseData['code'] ?? 0) != 200 && !empty($responseData['message'])) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', [
|
||||
'text' => $responseData['message']
|
||||
], $botUser->userid, false, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -138,11 +138,11 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
'dot' => $dot,
|
||||
'updated' => $updated,
|
||||
];
|
||||
// 机器人收到消处理
|
||||
$botUser = User::whereUserid($userid)->whereBot(1)->first();
|
||||
if ($botUser) {
|
||||
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id, $mentions, $this->client);
|
||||
}
|
||||
}
|
||||
// 机器人收到消处理
|
||||
$botUser = User::whereUserid($userid)->whereBot(1)->first();
|
||||
if ($botUser) { // 避免机器人处理自己发送的消息
|
||||
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id, $mentions, $this->client);
|
||||
}
|
||||
}
|
||||
// 更新已发送数量
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('user_bots', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('user_bots', 'webhook_events')) {
|
||||
$table->text('webhook_events')->nullable()->after('webhook_num')->comment('Webhook事件配置');
|
||||
}
|
||||
});
|
||||
|
||||
DB::table('user_bots')
|
||||
->where(function ($query) {
|
||||
$query->whereNull('webhook_events')->orWhere('webhook_events', '');
|
||||
})
|
||||
->update(['webhook_events' => json_encode(['message'])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('user_bots', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('user_bots', 'webhook_events')) {
|
||||
$table->dropColumn('webhook_events');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->date('birthday')->nullable()->after('profession');
|
||||
$table->string('address', 255)->nullable()->after('birthday');
|
||||
$table->text('introduction')->nullable()->after('address');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn(['birthday', 'address', 'introduction']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserTagsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_tags', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedBigInteger('user_id')->index()->comment('被标签用户ID');
|
||||
$table->string('name', 50)->comment('标签名称');
|
||||
$table->unsignedBigInteger('created_by')->index()->comment('创建人');
|
||||
$table->unsignedBigInteger('updated_by')->nullable()->comment('最后更新人');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'name'], 'user_tags_unique_name');
|
||||
$table->foreign('user_id')->references('userid')->on('users')->onDelete('cascade');
|
||||
$table->foreign('created_by')->references('userid')->on('users')->onDelete('cascade');
|
||||
$table->foreign('updated_by')->references('userid')->on('users')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_tags');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserTagRecognitionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_tag_recognitions', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedBigInteger('tag_id')->index()->comment('标签ID');
|
||||
$table->unsignedBigInteger('user_id')->index()->comment('认可人ID');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tag_id', 'user_id'], 'user_tag_recognitions_unique');
|
||||
$table->foreign('tag_id')->references('id')->on('user_tags')->onDelete('cascade');
|
||||
$table->foreign('user_id')->references('userid')->on('users')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_tag_recognitions');
|
||||
}
|
||||
}
|
||||
@ -106,6 +106,7 @@
|
||||
<p><span>ID:</span>{{ item.id }}</p>
|
||||
<p><span>{{ $L('清理时间') }}:</span>{{ item.clear_day }}</p>
|
||||
<p><span>Webhook:</span>{{ item.webhook_url || '-' }}</p>
|
||||
<p><span>{{ $L('Webhook事件') }}:</span>{{ formatWebhookEvents(item.webhook_events) }}</p>
|
||||
</div>
|
||||
<div class="modal-item-btns">
|
||||
<Button icon="md-chatbubbles" @click="applyClick({value: 'mybot-chat'}, item)">{{ $L('开始聊天') }}</Button>
|
||||
@ -140,6 +141,13 @@
|
||||
<FormItem prop="webhook_url" label="Webhook">
|
||||
<Input v-model="mybotModifyData.webhook_url" :maxlength="255" :show-word-limit="0.9" type="textarea" placeholder="Webhook"/>
|
||||
</FormItem>
|
||||
<FormItem prop="webhook_events" :label="$L('Webhook事件')">
|
||||
<CheckboxGroup v-model="mybotModifyData.webhook_events">
|
||||
<Checkbox v-for="option in webhookEventOptions" :key="option.value" :label="option.value">
|
||||
{{ $L(option.label) }}
|
||||
</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer" class="adaption">
|
||||
<Button type="default" @click="mybotModifyShow=false">{{ $L('取消') }}</Button>
|
||||
@ -377,6 +385,12 @@ export default {
|
||||
mybotModifyShow: false,
|
||||
mybotModifyData: {},
|
||||
mybotModifyLoad: 0,
|
||||
webhookEventOptions: [
|
||||
{value: 'message', label: '接收消息'},
|
||||
{value: 'dialog_open', label: '打开会话'},
|
||||
{value: 'member_join', label: '成员加入'},
|
||||
{value: 'member_leave', label: '成员退出'},
|
||||
],
|
||||
//
|
||||
aibotShow: false,
|
||||
aibotList: AIBotList,
|
||||
@ -473,6 +487,37 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
normalizeWebhookEvents(events = [], useFallback = false) {
|
||||
if (!Array.isArray(events)) {
|
||||
events = events ? [events] : [];
|
||||
}
|
||||
const allowed = this.webhookEventOptions.map(item => item.value);
|
||||
const result = events.filter(item => allowed.includes(item));
|
||||
if (result.length) {
|
||||
return Array.from(new Set(result));
|
||||
}
|
||||
return useFallback ? ['message'] : [];
|
||||
},
|
||||
enhanceMybotItem(item = {}) {
|
||||
const data = $A.cloneJSON(item || {});
|
||||
let events = data.webhook_events;
|
||||
if (typeof events === 'undefined' || events === null) {
|
||||
events = ['message'];
|
||||
}
|
||||
events = this.normalizeWebhookEvents(events, false);
|
||||
if (!events.length) {
|
||||
events = [];
|
||||
}
|
||||
data.webhook_events = events;
|
||||
return data;
|
||||
},
|
||||
formatWebhookEvents(events) {
|
||||
const values = this.normalizeWebhookEvents(events, false);
|
||||
const labels = this.webhookEventOptions
|
||||
.filter(option => values.includes(option.value))
|
||||
.map(option => this.$L(option.label));
|
||||
return labels.length ? labels.join('、') : '-';
|
||||
},
|
||||
getLogoClass(name) {
|
||||
name = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
return name
|
||||
@ -568,7 +613,7 @@ export default {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'users/bot/list',
|
||||
}).then(({data}) => {
|
||||
this.mybotList = data.list;
|
||||
this.mybotList = (data.list || []).map(item => this.enhanceMybotItem(item));
|
||||
}).finally(_ => {
|
||||
this.mybotLoad--
|
||||
});
|
||||
@ -581,7 +626,7 @@ export default {
|
||||
},
|
||||
// 添加修改我的机器人
|
||||
addMybot(info) {
|
||||
this.mybotModifyData = $A.cloneJSON(info)
|
||||
this.mybotModifyData = this.enhanceMybotItem(info)
|
||||
this.mybotModifyShow = true;
|
||||
},
|
||||
// 删除我的机器人
|
||||
@ -620,11 +665,12 @@ export default {
|
||||
onMybotModify() {
|
||||
this.mybotModifyLoad++
|
||||
this.$store.dispatch("editUserBot", this.mybotModifyData).then(({data, msg}) => {
|
||||
const index = this.mybotList.findIndex(item => item.id === data.id);
|
||||
const botData = this.enhanceMybotItem(data);
|
||||
const index = this.mybotList.findIndex(item => item.id === botData.id);
|
||||
if (index > -1) {
|
||||
this.mybotList.splice(index, 1, data);
|
||||
this.mybotList.splice(index, 1, botData);
|
||||
} else {
|
||||
this.mybotList.unshift(data);
|
||||
this.mybotList.unshift(botData);
|
||||
}
|
||||
this.mybotModifyShow = false;
|
||||
this.mybotModifyData = {};
|
||||
|
||||
@ -69,9 +69,72 @@ export default {
|
||||
"| `mention` | {{是否被@到}} | boolean |",
|
||||
"| `bot_uid` | {{机器人ID}} | string |",
|
||||
"| `version` | {{系统版本}} | string |",
|
||||
"### 3. {{打开会话 消息推送}}",
|
||||
"",
|
||||
"{{打开机器人会话后会自动POST推送到配置的Webhook地址,请求超时为30秒。}}",
|
||||
"",
|
||||
"#### {{推送参数}}",
|
||||
"",
|
||||
"| {{参数名}} | {{说明}} | {{类型}} |",
|
||||
"|--------|------|------|",
|
||||
"| `event` | {{推送事件}} | string |",
|
||||
"| `timestamp` | {{推送时间戳}} | string |",
|
||||
"| `dialog_id` | {{对话ID}} | string |",
|
||||
"| `dialog_type` | {{对话类型}} | string |",
|
||||
"| `bot_uid` | {{机器人ID}} | string |",
|
||||
"| `owner_uid` | {{机器人所属用户ID}} | string |",
|
||||
"| `user` | {{机器人所属用户信息}} | object |",
|
||||
"| `user.userid` | {{用户ID}} | string |",
|
||||
"| `user.email` | {{用户邮箱}} | string |",
|
||||
"| `user.nickname` | {{用户昵称}} | string |",
|
||||
"### 4. {{成员加入 消息推送}}",
|
||||
"",
|
||||
"{{成员加入群组后会自动POST推送到配置的Webhook地址,请求超时为30秒。}}",
|
||||
"",
|
||||
"#### {{推送参数}}",
|
||||
"",
|
||||
"| {{参数名}} | {{说明}} | {{类型}} |",
|
||||
"|--------|------|------|",
|
||||
"| `event` | {{推送事件}} | string |",
|
||||
"| `timestamp` | {{推送时间戳}} | string |",
|
||||
"| `dialog_id` | {{对话ID}} | string |",
|
||||
"| `dialog_type` | {{对话类型}} | string |",
|
||||
"| `bot_uid` | {{机器人ID}} | string |",
|
||||
"| `owner_uid` | {{机器人所属用户ID}} | string |",
|
||||
"| `user` | {{机器人所属用户信息}} | object |",
|
||||
"| `user.userid` | {{用户ID}} | string |",
|
||||
"| `user.email` | {{用户邮箱}} | string |",
|
||||
"| `user.nickname` | {{用户昵称}} | string |",
|
||||
"### 5. {{成员退出 消息推送}}",
|
||||
"",
|
||||
"{{成员退出群组后会自动POST推送到配置的Webhook地址,请求超时为30秒。}}",
|
||||
"",
|
||||
"#### {{推送参数}}",
|
||||
"",
|
||||
"| {{参数名}} | {{说明}} | {{类型}} |",
|
||||
"|--------|------|------|",
|
||||
"| `event` | {{推送事件}} | string |",
|
||||
"| `timestamp` | {{推送时间戳}} | string |",
|
||||
"| `dialog_id` | {{对话ID}} | string |",
|
||||
"| `dialog_type` | {{对话类型}} | string |",
|
||||
"| `dialog_name` | {{对话名称}} | string |",
|
||||
"| `group_type` | {{群组类型}} | string |",
|
||||
"| `bot_uid` | {{机器人ID}} | string |",
|
||||
"| `owner_uid` | {{机器人所属用户ID}} | string |",
|
||||
"| `action` | {{动作}} | string |",
|
||||
"| `actor` | {{操作人信息}} | object |",
|
||||
"| `actor.userid` | {{用户ID}} | string |",
|
||||
"| `actor.email` | {{用户邮箱}} | string |",
|
||||
"| `actor.nickname` | {{用户昵称}} | string |",
|
||||
"| `actor.is_bot` | {{是否机器人}} | boolean |",
|
||||
"| `members` | {{成员信息}} | array |",
|
||||
"| `members.userid` | {{用户ID}} | string |",
|
||||
"| `members.email` | {{用户邮箱}} | string |",
|
||||
"| `members.nickname` | {{用户昵称}} | string |",
|
||||
"| `members.is_bot` | {{是否机器人}} | boolean |",
|
||||
].map(item => item.replace(/\{\{([^}]+)\}\}/g, (_, v1) => this.$L(v1))).join("\n");
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@ -462,6 +462,13 @@
|
||||
<FormItem v-if="typeof modifyData.webhook_url !== 'undefined'" prop="webhook_url" label="Webhook">
|
||||
<Input v-model="modifyData.webhook_url" :maxlength="255" />
|
||||
</FormItem>
|
||||
<FormItem v-if="typeof modifyData.webhook_events !== 'undefined'" prop="webhook_events" :label="$L('Webhook事件')">
|
||||
<CheckboxGroup v-model="webhookEvents">
|
||||
<Checkbox v-for="option in webhookEventOptions" :key="option.value" :label="option.value">
|
||||
{{$L(option.label)}}
|
||||
</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
<div slot="footer" class="adaption">
|
||||
@ -768,6 +775,13 @@ export default {
|
||||
modifyAiShow: false,
|
||||
modifyData: {},
|
||||
modifyLoad: 0,
|
||||
webhookEventOptions: [
|
||||
{value: 'message', label: '接收消息'},
|
||||
{value: 'dialog_open', label: '打开会话'},
|
||||
{value: 'member_join', label: '成员加入'},
|
||||
{value: 'member_leave', label: '成员退出'},
|
||||
],
|
||||
webhookEvents: [],
|
||||
|
||||
openId: 0,
|
||||
errorId: 0,
|
||||
@ -1449,6 +1463,28 @@ export default {
|
||||
|
||||
methods: {
|
||||
transformEmojiToHtml,
|
||||
normalizeWebhookEvents(events = [], useFallback = false) {
|
||||
if (!Array.isArray(events)) {
|
||||
events = events ? [events] : [];
|
||||
}
|
||||
const allowed = this.webhookEventOptions.map(item => item.value);
|
||||
const result = events.filter(item => allowed.includes(item));
|
||||
if (result.length) {
|
||||
return Array.from(new Set(result));
|
||||
}
|
||||
return useFallback ? ['message'] : [];
|
||||
},
|
||||
prepareWebhookEvents(events, useFallback = false) {
|
||||
let value = events;
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
value = useFallback ? ['message'] : [];
|
||||
}
|
||||
value = this.normalizeWebhookEvents(value, false);
|
||||
if (!value.length && useFallback) {
|
||||
return ['message'];
|
||||
}
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 获取会话基本信息
|
||||
* @param dialog_id
|
||||
@ -2727,7 +2763,9 @@ export default {
|
||||
clear_day: 0,
|
||||
webhook_url: '',
|
||||
system_name: '',
|
||||
webhook_events: [],
|
||||
})
|
||||
this.webhookEvents = this.prepareWebhookEvents([], true)
|
||||
this.modifyLoad++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'users/bot/info',
|
||||
@ -2738,6 +2776,7 @@ export default {
|
||||
this.modifyData.clear_day = data.clear_day
|
||||
this.modifyData.webhook_url = data.webhook_url
|
||||
this.modifyData.system_name = data.system_name
|
||||
this.webhookEvents = this.prepareWebhookEvents(data.webhook_events, true)
|
||||
}).finally(() => {
|
||||
this.modifyLoad--;
|
||||
})
|
||||
@ -2892,6 +2931,7 @@ export default {
|
||||
name: this.modifyData.name,
|
||||
clear_day: this.modifyData.clear_day,
|
||||
webhook_url: this.modifyData.webhook_url,
|
||||
webhook_events: this.normalizeWebhookEvents(this.webhookEvents),
|
||||
dialog_id: this.modifyData.dialog_id
|
||||
}).then(({msg}) => {
|
||||
$A.messageSuccess(msg);
|
||||
|
||||
@ -31,6 +31,35 @@
|
||||
<span>{{$L('职位/职称')}}: </span>
|
||||
{{userData.profession || '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('生日')}}: </span>
|
||||
{{userData.birthday ? ($A.newDateString(userData.birthday, 'YYYY-MM-DD') || userData.birthday) : '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('地址')}}: </span>
|
||||
{{userData.address || '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('个人简介')}}: </span>
|
||||
{{userData.introduction || '-'}}
|
||||
</li>
|
||||
<li class="user-tags-line">
|
||||
<span>{{$L('个性标签')}}: </span>
|
||||
<div class="tags-content" @click="onOpenTagsModal">
|
||||
<div v-if="displayTags.length" class="tags-list">
|
||||
<Tag
|
||||
v-for="tag in displayTags"
|
||||
:key="tag.id"
|
||||
:color="tag.recognized ? 'primary' : 'default'"
|
||||
class="tag-pill">{{tag.name}}</Tag>
|
||||
</div>
|
||||
<span v-else class="tags-empty">{{$L('暂无个性标签')}}</span>
|
||||
<div class="tags-extra">
|
||||
<span v-if="personalTagTotal > displayTags.length" class="tags-total">{{$L('共(*)个', personalTagTotal)}}</span>
|
||||
<Button type="text" size="small" class="manage-button" @click.stop="onOpenTagsModal">{{$L('管理')}}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('最后在线')}}: </span>
|
||||
{{$A.newDateString(userData.line_at, 'YYYY-MM-DD HH:mm') || '-'}}
|
||||
@ -43,7 +72,10 @@
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
||||
<div class="user-detail-actions">
|
||||
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
||||
<Button icon="md-people" :disabled="!!userData.delete_at" @click="onOpenCreateGroup">{{ $L('创建群组') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 共同群组 -->
|
||||
@ -87,6 +119,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<UserTagsModal
|
||||
v-if="userData.userid"
|
||||
v-model="tagModalVisible"
|
||||
:userid="userData.userid"
|
||||
@updated="onTagsUpdated"/>
|
||||
</ModalAlive>
|
||||
</template>
|
||||
|
||||
@ -94,10 +131,13 @@
|
||||
import emitter from "../../../store/events";
|
||||
import transformEmojiToHtml from "../../../utils/emoji";
|
||||
import {mapState} from "vuex";
|
||||
import UserTagsModal from "./UserTagsModal.vue";
|
||||
|
||||
export default {
|
||||
name: 'UserDetail',
|
||||
|
||||
components: {UserTagsModal},
|
||||
|
||||
data() {
|
||||
return {
|
||||
userData: {
|
||||
@ -106,6 +146,8 @@ export default {
|
||||
|
||||
showModal: false,
|
||||
|
||||
tagModalVisible: false,
|
||||
|
||||
commonDialog: {
|
||||
userid: null,
|
||||
total: null,
|
||||
@ -145,6 +187,17 @@ export default {
|
||||
commonDialogList() {
|
||||
return this.commonDialog.list || [];
|
||||
},
|
||||
|
||||
displayTags() {
|
||||
return Array.isArray(this.userData.personal_tags) ? this.userData.personal_tags : [];
|
||||
},
|
||||
|
||||
personalTagTotal() {
|
||||
if (typeof this.userData.personal_tags_total === 'number') {
|
||||
return this.userData.personal_tags_total;
|
||||
}
|
||||
return this.displayTags.length;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -157,6 +210,7 @@ export default {
|
||||
this.$store.dispatch("showSpinner", 600)
|
||||
this.$store.dispatch('getUserData', userid).then(user => {
|
||||
this.userData = user;
|
||||
this.ensureTagDefaults();
|
||||
this.showModal = true;
|
||||
this.loadCommonDialogCount()
|
||||
}).finally(_ => {
|
||||
@ -166,7 +220,8 @@ export default {
|
||||
|
||||
onHide() {
|
||||
this.commonDialogShow = false;
|
||||
this.showModal = false
|
||||
this.showModal = false;
|
||||
this.tagModalVisible = false;
|
||||
},
|
||||
|
||||
onOpenAvatar() {
|
||||
@ -181,6 +236,41 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
onOpenCreateGroup() {
|
||||
const userids = [];
|
||||
if (this.userId) {
|
||||
userids.push(this.userId);
|
||||
}
|
||||
if (this.userData.userid && this.userData.userid !== this.userId) {
|
||||
userids.push(this.userData.userid);
|
||||
}
|
||||
if (userids.length === 0 && this.userData.userid) {
|
||||
userids.push(this.userData.userid);
|
||||
}
|
||||
emitter.emit('createGroup', userids);
|
||||
},
|
||||
|
||||
ensureTagDefaults() {
|
||||
if (!Array.isArray(this.userData.personal_tags)) {
|
||||
this.$set(this.userData, 'personal_tags', []);
|
||||
}
|
||||
if (typeof this.userData.personal_tags_total !== 'number') {
|
||||
this.$set(this.userData, 'personal_tags_total', this.userData.personal_tags.length);
|
||||
}
|
||||
},
|
||||
|
||||
onOpenTagsModal() {
|
||||
if (!this.userData.userid) {
|
||||
return;
|
||||
}
|
||||
this.tagModalVisible = true;
|
||||
},
|
||||
|
||||
onTagsUpdated({top, total}) {
|
||||
this.$set(this.userData, 'personal_tags', Array.isArray(top) ? top : []);
|
||||
this.$set(this.userData, 'personal_tags_total', typeof total === 'number' ? total : this.userData.personal_tags.length);
|
||||
},
|
||||
|
||||
loadCommonDialogCount() {
|
||||
const target_userid = this.userData.userid;
|
||||
const previousUserId = this.commonDialog.userid;
|
||||
@ -280,3 +370,52 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-tags-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
span:first-child {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tags-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
|
||||
.tag-pill {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-empty {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.tags-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.tags-total {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.manage-button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
459
resources/assets/js/pages/manage/components/UserTagsModal.vue
Normal file
459
resources/assets/js/pages/manage/components/UserTagsModal.vue
Normal file
@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<ModalAlive
|
||||
v-model="visible"
|
||||
class-name="user-tags-manage-modal"
|
||||
:mask-closable="false"
|
||||
:footer-hide="true"
|
||||
width="520"
|
||||
:closable="true">
|
||||
<div class="tag-modal-container">
|
||||
<div class="tag-modal-header">
|
||||
<h3>{{$L('个性标签管理')}}</h3>
|
||||
<p class="tag-modal-meta">
|
||||
<span>{{$L('当前共(*)个标签', total)}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="tag-modal-form">
|
||||
<Input
|
||||
v-model="newTagName"
|
||||
:maxlength="20"
|
||||
:disabled="pending.add"
|
||||
:placeholder="$L('请输入个性标签')"
|
||||
@on-enter="handleAdd">
|
||||
<Button
|
||||
slot="append"
|
||||
type="primary"
|
||||
:loading="pending.add"
|
||||
@click="handleAdd">{{$L('添加')}}</Button>
|
||||
</Input>
|
||||
</div>
|
||||
<div class="tag-modal-body">
|
||||
<div v-if="loading > 0 && tags.length === 0" class="tag-loading">
|
||||
<Loading />
|
||||
</div>
|
||||
<div v-else-if="tags.length === 0" class="tag-empty">
|
||||
<Icon type="ios-pricetags-outline" size="32" />
|
||||
<p>{{$L('还没有个性标签,快来添加吧~')}}</p>
|
||||
</div>
|
||||
<ul v-else class="tag-list">
|
||||
<li
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
class="tag-item"
|
||||
:class="{'is-editing': editId === tag.id}">
|
||||
<div class="tag-item-main">
|
||||
<div class="tag-name" v-if="editId !== tag.id">
|
||||
<Tag :color="tag.recognized ? 'primary' : 'default'" class="tag-pill">{{tag.name}}</Tag>
|
||||
</div>
|
||||
<div class="tag-name edit" v-else>
|
||||
<Input
|
||||
ref="editInput"
|
||||
size="small"
|
||||
v-model="editName"
|
||||
:maxlength="20"
|
||||
:disabled="isPending(tag.id, 'edit')"
|
||||
@on-enter="confirmEdit(tag)"/>
|
||||
</div>
|
||||
<div class="tag-actions">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
class="recognize-btn"
|
||||
:loading="isPending(tag.id, 'recognize')"
|
||||
@click="toggleRecognize(tag)">
|
||||
<Icon type="md-thumbs-up" />
|
||||
<span>{{tag.recognition_total}}</span>
|
||||
<span class="recognize-text">{{$L('认可')}}</span>
|
||||
</Button>
|
||||
<template v-if="editId === tag.id">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="isPending(tag.id, 'edit')"
|
||||
@click="confirmEdit(tag)">{{$L('保存')}}</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="cancelEdit">{{$L('取消')}}</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button
|
||||
v-if="tag.can_edit"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="startEdit(tag)">{{$L('编辑')}}</Button>
|
||||
<Button
|
||||
v-if="tag.can_delete"
|
||||
type="text"
|
||||
size="small"
|
||||
:loading="isPending(tag.id, 'delete')"
|
||||
@click="confirmDelete(tag)">{{$L('删除')}}</Button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag-meta-info" v-if="tag.created_by_name">
|
||||
<span>{{$L('由(*)创建', tag.created_by_name)}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ModalAlive>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserTagsModal',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
userid: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: this.value,
|
||||
loading: 0,
|
||||
tags: [],
|
||||
newTagName: '',
|
||||
editId: null,
|
||||
editName: '',
|
||||
pending: {
|
||||
add: false,
|
||||
tagId: null,
|
||||
type: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
userId() {
|
||||
return this.$store.state.userId;
|
||||
},
|
||||
total() {
|
||||
return this.tags.length;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
this.visible = v;
|
||||
if (v) {
|
||||
this.openModal();
|
||||
}
|
||||
},
|
||||
visible(v) {
|
||||
this.$emit('input', v);
|
||||
if (!v) {
|
||||
this.resetInlineState();
|
||||
}
|
||||
},
|
||||
userid() {
|
||||
if (this.visible) {
|
||||
this.loadTags();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.resetInlineState();
|
||||
this.loadTags();
|
||||
},
|
||||
resetInlineState() {
|
||||
this.newTagName = '';
|
||||
this.editId = null;
|
||||
this.editName = '';
|
||||
this.pending = {
|
||||
add: false,
|
||||
tagId: null,
|
||||
type: ''
|
||||
};
|
||||
},
|
||||
setPending(type, tagId = null) {
|
||||
if (type === 'add') {
|
||||
this.pending.add = true;
|
||||
} else {
|
||||
this.pending.tagId = tagId;
|
||||
this.pending.type = type;
|
||||
}
|
||||
},
|
||||
clearPending(type) {
|
||||
if (type === 'add') {
|
||||
this.pending.add = false;
|
||||
} else if (this.pending.type === type) {
|
||||
this.pending.tagId = null;
|
||||
this.pending.type = '';
|
||||
}
|
||||
},
|
||||
isPending(tagId, type) {
|
||||
return this.pending.tagId === tagId && this.pending.type === type;
|
||||
},
|
||||
loadTags() {
|
||||
if (!this.userid) {
|
||||
return;
|
||||
}
|
||||
this.loading++;
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/tags/lists',
|
||||
data: {userid: this.userid},
|
||||
}).then(({data}) => {
|
||||
this.applyTagData(data);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('加载失败'));
|
||||
}).finally(() => {
|
||||
this.loading--;
|
||||
});
|
||||
},
|
||||
applyTagData(data) {
|
||||
const list = Array.isArray(data?.list) ? data.list : [];
|
||||
this.tags = list;
|
||||
const top = Array.isArray(data?.top) ? data.top : list.slice(0, 10);
|
||||
const total = typeof data?.total === 'number' ? data.total : list.length;
|
||||
this.emitUpdated({list, top, total});
|
||||
},
|
||||
emitUpdated(payload) {
|
||||
this.$emit('updated', payload);
|
||||
if (this.userid === this.$store.state.userInfo.userid) {
|
||||
const info = Object.assign({}, this.$store.state.userInfo, {
|
||||
personal_tags: payload.top,
|
||||
personal_tags_total: payload.total
|
||||
});
|
||||
this.$store.dispatch('saveUserInfoBase', info);
|
||||
}
|
||||
this.$store.dispatch('saveUserBasic', {
|
||||
userid: this.userid,
|
||||
personal_tags: payload.top,
|
||||
personal_tags_total: payload.total
|
||||
});
|
||||
},
|
||||
handleAdd() {
|
||||
const name = this.newTagName.trim();
|
||||
if (!name) {
|
||||
$A.messageError(this.$L('请输入个性标签'));
|
||||
return;
|
||||
}
|
||||
if (name.length > 20) {
|
||||
$A.messageError(this.$L('标签名称最多只能设置20个字'));
|
||||
return;
|
||||
}
|
||||
if (this.pending.add) {
|
||||
return;
|
||||
}
|
||||
this.setPending('add');
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/tags/add',
|
||||
method: 'post',
|
||||
data: {userid: this.userid, name},
|
||||
}).then(({data, msg}) => {
|
||||
this.applyTagData(data);
|
||||
this.newTagName = '';
|
||||
if (msg) {
|
||||
$A.messageSuccess(msg);
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('添加失败'));
|
||||
}).finally(() => {
|
||||
this.clearPending('add');
|
||||
});
|
||||
},
|
||||
startEdit(tag) {
|
||||
this.editId = tag.id;
|
||||
this.editName = tag.name;
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.editInput;
|
||||
if (input && input.focus) {
|
||||
input.focus();
|
||||
} else if (Array.isArray(input) && input.length > 0 && input[0].focus) {
|
||||
input[0].focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editId = null;
|
||||
this.editName = '';
|
||||
},
|
||||
confirmEdit(tag) {
|
||||
const name = this.editName.trim();
|
||||
if (!name) {
|
||||
$A.messageError(this.$L('请输入个性标签'));
|
||||
return;
|
||||
}
|
||||
if (name.length > 20) {
|
||||
$A.messageError(this.$L('标签名称最多只能设置20个字'));
|
||||
return;
|
||||
}
|
||||
if (name === tag.name) {
|
||||
this.cancelEdit();
|
||||
return;
|
||||
}
|
||||
if (this.isPending(tag.id, 'edit')) {
|
||||
return;
|
||||
}
|
||||
this.setPending('edit', tag.id);
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/tags/update',
|
||||
method: 'post',
|
||||
data: {tag_id: tag.id, name},
|
||||
}).then(({data, msg}) => {
|
||||
this.applyTagData(data);
|
||||
this.cancelEdit();
|
||||
if (msg) {
|
||||
$A.messageSuccess(msg);
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('保存失败'));
|
||||
}).finally(() => {
|
||||
this.clearPending('edit');
|
||||
});
|
||||
},
|
||||
confirmDelete(tag) {
|
||||
if (this.isPending(tag.id, 'delete')) {
|
||||
return;
|
||||
}
|
||||
$A.modalConfirm({
|
||||
title: this.$L('删除标签'),
|
||||
content: this.$L('确定要删除该标签吗?'),
|
||||
onOk: () => {
|
||||
this.deleteTag(tag);
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteTag(tag) {
|
||||
this.setPending('delete', tag.id);
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/tags/delete',
|
||||
method: 'post',
|
||||
data: {tag_id: tag.id},
|
||||
}).then(({data, msg}) => {
|
||||
this.applyTagData(data);
|
||||
if (msg) {
|
||||
$A.messageSuccess(msg);
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('删除失败'));
|
||||
}).finally(() => {
|
||||
this.clearPending('delete');
|
||||
});
|
||||
},
|
||||
toggleRecognize(tag) {
|
||||
if (this.isPending(tag.id, 'recognize')) {
|
||||
return;
|
||||
}
|
||||
this.setPending('recognize', tag.id);
|
||||
this.$store.dispatch('call', {
|
||||
url: 'users/tags/recognize',
|
||||
method: 'post',
|
||||
data: {tag_id: tag.id},
|
||||
}).then(({data, msg}) => {
|
||||
this.applyTagData(data);
|
||||
if (msg) {
|
||||
$A.messageSuccess(msg);
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('操作失败'));
|
||||
}).finally(() => {
|
||||
this.clearPending('recognize');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-tags-manage-modal {
|
||||
.tag-modal-container {
|
||||
padding: 16px 20px 12px;
|
||||
}
|
||||
.tag-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tag-modal-meta {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.tag-modal-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.tag-modal-body {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tag-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
.tag-empty {
|
||||
text-align: center;
|
||||
padding: 32px 0;
|
||||
color: #909399;
|
||||
p {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.tag-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.tag-item {
|
||||
border: 1px solid var(--divider-color, #ebeef5);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&.is-editing {
|
||||
background-color: rgba(64, 158, 255, 0.08);
|
||||
}
|
||||
.tag-item-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.tag-name {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.edit {
|
||||
max-width: 220px;
|
||||
}
|
||||
}
|
||||
.tag-pill {
|
||||
cursor: default;
|
||||
}
|
||||
.tag-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.recognize-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.recognize-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tag-meta-info {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #a0a3a6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -22,19 +22,64 @@
|
||||
<FormItem :label="$L('职位/职称')" prop="profession">
|
||||
<Input v-model="formData.profession" :maxlength="20" :placeholder="$L('请输入职位/职称')"></Input>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('生日')" prop="birthday">
|
||||
<DatePicker
|
||||
v-model="formData.birthday"
|
||||
type="date"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
:placeholder="$L('请选择生日')"
|
||||
confirm
|
||||
transfer/>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('地址')" prop="address">
|
||||
<Input v-model="formData.address" :maxlength="100" :placeholder="$L('请输入地址')"></Input>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('个人简介')" prop="introduction">
|
||||
<Input
|
||||
v-model="formData.introduction"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:autosize="{ minRows: 2, maxRows: 8 }"
|
||||
:maxlength="500"
|
||||
:placeholder="$L('请输入个人简介')"></Input>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('个性标签')">
|
||||
<div class="user-tags-preview" @click="openTagModal">
|
||||
<template v-if="displayTags.length">
|
||||
<Tag
|
||||
v-for="tag in displayTags"
|
||||
:key="tag.id"
|
||||
:color="tag.recognized ? 'primary' : 'default'"
|
||||
class="tag-pill">{{tag.name}}</Tag>
|
||||
</template>
|
||||
<span v-else class="tags-empty">{{$L('暂无个性标签')}}</span>
|
||||
<span v-if="personalTagTotal > displayTags.length" class="tags-total">{{$L('共(*)个', personalTagTotal)}}</span>
|
||||
<Button type="text" size="small" class="manage-button" @click.stop="openTagModal">
|
||||
<Icon type="md-create" />
|
||||
{{$L('管理')}}
|
||||
</Button>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="setting-footer">
|
||||
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
|
||||
<Button :loading="loadIng > 0" @click="resetForm" style="margin-left: 8px">{{$L('重置')}}</Button>
|
||||
</div>
|
||||
<UserTagsModal
|
||||
v-if="userInfo.userid"
|
||||
v-model="tagModalVisible"
|
||||
:userid="userInfo.userid"
|
||||
@updated="onTagsUpdated"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImgUpload from "../../../components/ImgUpload";
|
||||
import UserTagsModal from "../components/UserTagsModal.vue";
|
||||
import {mapState} from "vuex";
|
||||
export default {
|
||||
components: {ImgUpload},
|
||||
components: {ImgUpload, UserTagsModal},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
@ -44,7 +89,10 @@ export default {
|
||||
email: '',
|
||||
tel: '',
|
||||
nickname: '',
|
||||
profession: ''
|
||||
profession: '',
|
||||
birthday: '',
|
||||
address: '',
|
||||
introduction: ''
|
||||
},
|
||||
|
||||
ruleData: {
|
||||
@ -60,6 +108,10 @@ export default {
|
||||
{type: 'string', min: 2, message: this.$L('昵称长度至少2位!'), trigger: 'change'}
|
||||
]
|
||||
},
|
||||
|
||||
tagModalVisible: false,
|
||||
personalTags: [],
|
||||
personalTagTotal: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -67,6 +119,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(['userInfo', 'formOptions']),
|
||||
|
||||
displayTags() {
|
||||
return this.personalTags;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
userInfo() {
|
||||
@ -80,7 +136,19 @@ export default {
|
||||
this.$set(this.formData, 'tel', this.userInfo.tel);
|
||||
this.$set(this.formData, 'nickname', typeof this.userInfo.nickname_original !== "undefined" ? this.userInfo.nickname_original : this.userInfo.nickname);
|
||||
this.$set(this.formData, 'profession', this.userInfo.profession);
|
||||
this.$set(this.formData, 'birthday', this.userInfo.birthday || '');
|
||||
this.$set(this.formData, 'address', this.userInfo.address || '');
|
||||
this.$set(this.formData, 'introduction', this.userInfo.introduction || '');
|
||||
this.formData_bak = $A.cloneJSON(this.formData);
|
||||
this.syncPersonalTags();
|
||||
},
|
||||
|
||||
syncPersonalTags() {
|
||||
const tags = Array.isArray(this.userInfo.personal_tags) ? this.userInfo.personal_tags : [];
|
||||
this.personalTags = tags.slice(0, 10);
|
||||
this.personalTagTotal = typeof this.userInfo.personal_tags_total === 'number'
|
||||
? this.userInfo.personal_tags_total
|
||||
: this.personalTags.length;
|
||||
},
|
||||
|
||||
submitForm() {
|
||||
@ -106,7 +174,50 @@ export default {
|
||||
|
||||
resetForm() {
|
||||
this.formData = $A.cloneJSON(this.formData_bak);
|
||||
},
|
||||
|
||||
openTagModal() {
|
||||
if (!this.userInfo.userid) {
|
||||
return;
|
||||
}
|
||||
this.tagModalVisible = true;
|
||||
},
|
||||
|
||||
onTagsUpdated({top, total}) {
|
||||
this.personalTags = Array.isArray(top) ? top : [];
|
||||
this.personalTagTotal = typeof total === 'number' ? total : this.personalTags.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-tags-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
.tag-pill {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tags-empty {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.tags-total {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.manage-button {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
2
resources/assets/js/store/actions.js
vendored
2
resources/assets/js/store/actions.js
vendored
@ -3470,7 +3470,7 @@ export default {
|
||||
}
|
||||
return item.dialog_user.userid === userid
|
||||
});
|
||||
if (dialog) {
|
||||
if (dialog && dialog.bot !== 1) {
|
||||
return dispatch("openDialog", dialog.id).then(resolve).catch(reject)
|
||||
}
|
||||
dispatch("call", {
|
||||
|
||||
@ -19,6 +19,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.user-detail-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ivu-modal-content {
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@ -152,9 +152,15 @@
|
||||
|
||||
.setting-item {
|
||||
.ivu-input,
|
||||
.ivu-select-default {
|
||||
.ivu-select-default,
|
||||
.ivu-date-picker,
|
||||
.user-tags-preview {
|
||||
max-width: 460px;
|
||||
}
|
||||
.ivu-date-picker,
|
||||
.user-tags-preview {
|
||||
width: 100%;
|
||||
}
|
||||
.ivu-form {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user