diff --git a/server/app/admin/controller/system/SysNoticeController.php b/server/app/admin/controller/system/SysNoticeController.php index 72ca2252a8580439673fa54e029f25fa14f1f9e6..f555c3770ef6fafc872dc65c2faf359e706c410d 100644 --- a/server/app/admin/controller/system/SysNoticeController.php +++ b/server/app/admin/controller/system/SysNoticeController.php @@ -16,20 +16,39 @@ use app\admin\controller\Crud; use app\admin\validate\system\SysNoticeValidate; use app\common\services\system\SysNoticeService; use core\email\MessagePushService; +use core\utils\Json; use support\Container; +use support\Request; +use Webman\RedisQueue\Client; class SysNoticeController extends Crud { public function __construct() { parent::__construct(); - $this->validate=Container::make(SysNoticeValidate::class); - $this->service = Container::make(SysNoticeService::class); + $this->validate = Container::make(SysNoticeValidate::class); + $this->service = Container::make(SysNoticeService::class); } - public function test(){ - MessagePushService::broadcastMessage(); -// MessagePushService::pushMessage(1, '发送私人信息', 2); + /** + * 发布公告 + * + * @param \support\Request $request + * + * @return \support\Response + */ + public function publish(Request $request): \support\Response + { + try { + $id = $request->input('id', null); + //推送公告 + $model = $this->service->get($id); + //推送消息 + $queue = 'admin-announcement-push'; + Client::send($queue, $model->makeVisible('tenant_id')->toArray(), 0); + return Json::success('ok', []); + } catch (\Throwable $e) { + return Json::fail($e->getMessage()); + } } - } diff --git a/server/app/admin/route/system.php b/server/app/admin/route/system.php index 69466f9c7185d368ce146b52beb1795e52584e26..b6a988d9fd2316bbb9f7b83e8dcdd1807da73b8c 100644 --- a/server/app/admin/route/system.php +++ b/server/app/admin/route/system.php @@ -217,7 +217,7 @@ Route::group('/system', function () { Route::post('/notice', [\app\admin\controller\system\SysNoticeController::class, 'store'])->name('系统设置.通知公告.保存'); Route::put('/notice', [\app\admin\controller\system\SysNoticeController::class, 'update'])->name('系统设置.通知公告.更新'); Route::delete('/notice/{id}', [\app\admin\controller\system\SysNoticeController::class, 'destroy'])->name('系统设置.通知公告.删除'); - Route::post('/notice/test', [\app\admin\controller\system\SysNoticeController::class, 'test'])->name('系统设置.通知公告.保存'); + Route::put('/notice/publish', [\app\admin\controller\system\SysNoticeController::class, 'publish'])->name('系统设置.通知公告.发布'); }); /** diff --git a/server/app/common/dao/system/SysAdminDao.php b/server/app/common/dao/system/SysAdminDao.php index 8fd6262414696609d8f9cdc06afba0a6614bc847..9f5573cacf43fb71e0deae1c7650a420272db5e9 100644 --- a/server/app/common/dao/system/SysAdminDao.php +++ b/server/app/common/dao/system/SysAdminDao.php @@ -90,7 +90,7 @@ class SysAdminDao extends BaseDao */ public function getList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): array { - $where['enabled'] = 1; +// $where['enabled'] = 1;//注释显示禁用用户 if (empty($with)) { $with = ['tenant', 'depts', 'posts', 'casbin.roles']; } diff --git a/server/app/common/model/system/SysAdmin.php b/server/app/common/model/system/SysAdmin.php index b83fd2e663f122ac9ea88d7a4079bf503c60e97d..8050a46e825c579dc949d3306bba5721233f5d58 100644 --- a/server/app/common/model/system/SysAdmin.php +++ b/server/app/common/model/system/SysAdmin.php @@ -188,6 +188,8 @@ class SysAdmin extends BaseModel return $this->belongsToMany(RuleModel::class, SysAdminCasbin::class, 'admin_id', 'admin_casbin_id', 'id', 'v0')->where('v2', $tenantId); } + + /** * 默认链接 */ diff --git a/server/app/common/model/system/SysAdminTenant.php b/server/app/common/model/system/SysAdminTenant.php index f51f5454d7b9797a861f986672b61ba3323ce115..b60b176b3d77335e7fbc889870f1fd38f15aed3f 100644 --- a/server/app/common/model/system/SysAdminTenant.php +++ b/server/app/common/model/system/SysAdminTenant.php @@ -48,7 +48,6 @@ class SysAdminTenant extends BasePivot protected $appends = ['created_date', 'updated_date']; - protected $fillable = [ "id", "admin_id", @@ -61,6 +60,16 @@ class SysAdminTenant extends BasePivot "updated_at", ]; + /** + * 管理消息-列表 + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function message(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(SysMessage::class, 'receiver_id', 'admin_id'); + } + /** * 关联租户 * diff --git a/server/app/common/model/system/SysMessage.php b/server/app/common/model/system/SysMessage.php index 9aacde44141403091442fdd7b061b99a264ee394..b85ff11bb364c80009616e0808376fa6c211c31d 100644 --- a/server/app/common/model/system/SysMessage.php +++ b/server/app/common/model/system/SysMessage.php @@ -12,7 +12,9 @@ namespace app\common\model\system; +use Carbon\Carbon; use core\abstract\BaseModel; +use Illuminate\Database\Eloquent\SoftDeletes; /** * 系统公告 @@ -23,6 +25,11 @@ use core\abstract\BaseModel; class SysMessage extends BaseModel { + /** + * 启用软删除 + */ + use SoftDeletes; + protected $table = 'sys_message'; /** @@ -32,7 +39,7 @@ class SysMessage extends BaseModel */ public $timestamps = true; - protected $appends = ['created_date', 'updated_date']; + protected $appends = ['created_date', 'updated_date', 'read_date']; protected $fillable = [ 'id', @@ -49,11 +56,34 @@ class SysMessage extends BaseModel 'related_type', 'action_url', 'action_params', + 'message_uuid', 'read_at', 'created_at', 'expired_at', ]; + /** + * 追加已读时间 + * + * @return string|null + */ + public function getReadDateAttribute(): ?string + { + if ($this->getAttribute('read_at')) { + try { + $timestamp = $this->getRawOriginal('read_at'); + if (empty($timestamp)) { + return null; + } + $carbonInstance = Carbon::createFromTimestamp($timestamp); + return $carbonInstance->setTimezone(config('app.default_timezone'))->format('Y-m-d H:i:s'); + } catch (\Exception $e) { + return null; + } + } + return null; + } + /** * 关联发送用户 * diff --git a/server/app/common/model/system/SysNotice.php b/server/app/common/model/system/SysNotice.php index 0f11639dbdecd7e5517c68599dedb7c4189f85f8..fdc05d7ccfe851a869ef903d1efdb107840fa6a1 100644 --- a/server/app/common/model/system/SysNotice.php +++ b/server/app/common/model/system/SysNotice.php @@ -30,7 +30,7 @@ class SysNotice extends BaseModel * * @var bool */ - public $timestamps = false; + public $timestamps = true; protected $appends = ['created_date', 'updated_date']; @@ -43,6 +43,7 @@ class SysNotice extends BaseModel 'content', 'enabled', 'created_dept', + 'uuid', 'created_by', 'created_at', 'updated_by', diff --git a/server/app/common/queue/redis/AdminAnnouncementPushConsumer.php b/server/app/common/queue/redis/AdminAnnouncementPushConsumer.php new file mode 100644 index 0000000000000000000000000000000000000000..ab905c12e91d33ec9aeac2f646c161ded59ea175 --- /dev/null +++ b/server/app/common/queue/redis/AdminAnnouncementPushConsumer.php @@ -0,0 +1,191 @@ +validateData($data); + + // 2. 获取租户信息 + $tenantInfo = $this->getTenantInfo($data['tenant_id']); + if (!$tenantInfo) { + Logger::error('租户信息不存在', ['tenant_id' => $data['tenant_id']]); + return false; + } + + // 3. 设置租户上下文 + $this->setTenantContext($tenantInfo); + + // 4. 获取目标用户ID列表 + $adminIds = $this->getTargetAdminIds($tenantInfo->id, $data['uuid'] ?? null); + + // 5. 构建并发送通知 + $this->sendNotifications($adminIds, $data, $tenantInfo->id); + + Logger::debug("公告推送完成: {$data['title']}"); + return true; + } catch (\Throwable $e) { + var_dump($e->getMessage()); + Logger::error("公告推送失败: " . $e->getMessage(), [ + 'error' => $e->getTraceAsString(), + 'data' => $data, + ]); + throw $e; + } + } + + /** + * 验证消息数据 + * + * @param array $data + * + * @throws \InvalidArgumentException + */ + private function validateData(array $data): void + { + $requiredFields = ['id', 'title', 'content', 'tenant_id']; + foreach ($requiredFields as $field) { + if (empty($data[$field])) { + throw new \InvalidArgumentException("缺少必要字段: {$field}"); + } + } + } + + /** + * 获取租户信息 + * + * @param int|null $tenantId + * + * @return Tenant|null + */ + private function getTenantInfo(?int $tenantId): ?Tenant + { + if (empty($tenantId)) { + Logger::error('租户ID不能为空'); + return null; + } + + $tenantService = new TenantService(); + return $tenantService->get($tenantId); + } + + /** + * 设置租户上下文 + * + * @param Tenant $tenantInfo + */ + private function setTenantContext(Tenant $tenantInfo): void + { + TenantContext::setContext( + $tenantInfo->id, + $tenantInfo->code, + $tenantInfo->isolation_mode, + $tenantInfo->db_name, + $tenantInfo->expired_at ?? null + ); + } + + /** + * 获取目标管理员ID列表 + * + * @param int $tenantId + * @param string|null $uuid + * + * @return array + */ + private function getTargetAdminIds(int $tenantId, ?string $uuid): array + { + $userService = new SysAdminTenantService(); + + $query = $userService->getModel() + ->where('tenant_id', $tenantId); + + // 如果有UUID,则排除已推送的用户 + if (!empty($uuid)) { + $query->whereDoesntHave('message', function ($q) use ($uuid) { + $q->where('message_uuid', $uuid); + }); + } + + return $query->pluck('admin_id') + ->unique() + ->values() + ->toArray(); + } + + /** + * 发送通知 + * + * @param array $adminIds + * @param array $data + * @param int $tenantId + */ + private function sendNotifications(array $adminIds, array $data, int $tenantId): void + { + if (empty($adminIds)) { + Logger::warning('没有符合条件的目标用户', ['tenant_id' => $tenantId]); + return; + } + + $sendData = []; + foreach ($adminIds as $id) { + $sendData[] = [ + 'module' => 'admin', + 'receiver_id' => $id, + 'event' => 'message', + 'data' => [], + 'title' => $data['title'], + 'content' => $data['content'], + 'message_type' => 'message', + 'priority' => 1, + 'related_id' => $data['id'] ?? '', + 'expired_at' => time() + 86400 * 7, + 'message_uuid' => $data['uuid'] ?? null, + ]; + } + + Notification::batchSend(PushClientType::BACKEND, $tenantId, $sendData); + } +} \ No newline at end of file diff --git a/server/app/common/services/system/SysMessageService.php b/server/app/common/services/system/SysMessageService.php index 3964551f777b687b40b78c909571ac001ba33092..027be018e5b19eb02719ee01d4914031e87ec709 100644 --- a/server/app/common/services/system/SysMessageService.php +++ b/server/app/common/services/system/SysMessageService.php @@ -16,6 +16,7 @@ use app\common\dao\system\SysMessageDao; use core\abstract\BaseService; use core\email\MessagePushService; use core\enum\system\MessageStatus; +use madong\helper\Arr; use madong\ingenious\libs\utils\ArrayHelper; use support\Container; @@ -64,7 +65,7 @@ class SysMessageService extends BaseService public function isRead(string|array $param): void { try { - $data = ArrayHelper::normalize($param); + $data = Arr::normalize($param); $messageModel = $this->dao->getModel(); $models = $messageModel->find($data); foreach ($models as $model) { @@ -87,7 +88,7 @@ class SysMessageService extends BaseService public function isUnread(string|array $param): void { try { - $data = ArrayHelper::normalize($param); + $data = Arr::normalize($param); $messageModel = $this->dao->getModel(); $models = $messageModel->find($data); foreach ($models as $model) { diff --git a/server/config/core/app.php b/server/config/core/app.php index cad4e38e1fa26f4af549d052ad0e5487607ff1ca..46570404709516fe839efc520bf05e8efb3cea1d 100644 --- a/server/config/core/app.php +++ b/server/config/core/app.php @@ -12,5 +12,5 @@ return [ 'enable' => true, - 'version' => '4.0.2', + 'version' => '4.0.3', ]; diff --git a/server/config/core/notify/app.php b/server/config/core/notify/app.php new file mode 100644 index 0000000000000000000000000000000000000000..edf8b565605038eeb47a5a4e9952529bce5fd3cf --- /dev/null +++ b/server/config/core/notify/app.php @@ -0,0 +1,23 @@ + true, + 'webman-push' => [ + 'websocket' => config('plugin.webman.push.app.websocket'), + 'api' => config('plugin.webman.push.app.api'), + 'app_key' => config('plugin.webman.push.app.app_key'), + 'app_secret' => config('plugin.webman.push.app.app_secret'), + 'channel_hook' => config('plugin.webman.push.app.channel_hook'), + 'auth' => config('plugin.webman.push.app.auth'), + ], +]; diff --git a/server/config/plugin/webman/redis-queue/process.php b/server/config/plugin/webman/redis-queue/process.php index 26f112e97fef0fe54ffa09c23a91118269a7efb9..3e86d2b38a52efeec13857b647cc5cdd41f77f9c 100644 --- a/server/config/plugin/webman/redis-queue/process.php +++ b/server/config/plugin/webman/redis-queue/process.php @@ -1,18 +1,18 @@ [ -// 'handler' => Webman\RedisQueue\Process\Consumer::class, -// 'count' => 8, // 可以设置多进程同时消费 -// 'constructor' => [ -// // 消费者类目录 -// 'consumer_dir' => app_path() . '/queue/redis', -// ], -// ], + 'consumer' => [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => 8, // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + 'consumer_dir' => app_path() . '/queue', + ], + ], 'consumer_common' => [ 'handler' => Webman\RedisQueue\Process\Consumer::class, 'count' => 8, 'constructor' => [ - 'consumer_dir' => app_path() . '/common/queue/redis', + 'consumer_dir' => app_path() . '/common/queue', ], ], ]; diff --git a/server/core/notify/Notification.php b/server/core/notify/Notification.php new file mode 100644 index 0000000000000000000000000000000000000000..a1899d86477df2bcaa394d2a08e5020fe7776da6 --- /dev/null +++ b/server/core/notify/Notification.php @@ -0,0 +1,68 @@ +$method(...$args); + } + + + + +} diff --git a/server/core/notify/NotificationService.php b/server/core/notify/NotificationService.php new file mode 100644 index 0000000000000000000000000000000000000000..9ada485ecb13b9abb62217128488a6ed2494bd0c --- /dev/null +++ b/server/core/notify/NotificationService.php @@ -0,0 +1,320 @@ +getConfig(); + $this->pushApi = new Api( + $config['api'], + $config['app_key'], + $config['app_secret'] + ); + $this->messageModel = new SysMessage(); + } + + /** + * 推送消息并记录(支持单条/批量) + * + * @param PushClientType $clientType 客户端类型枚举值 + * @param string $businessModule 业务模块标识 + * @param string $tenantId 租户ID + * @param string|int|array $userIds 接收者ID(单个ID或ID数组) + * @param string $event 事件类型 + * @param array $data 推送数据(将直接传递给客户端) + * @param array $messageData 消息记录数据,包含: + * - 'title' (string): 消息标题 + * - 'content' (string): 消息内容 + * - 'type' (string): 消息类型(默认为事件类型) + * - 'priority' (int): 消息优先级 + * - 'related_id' (string|null): 关联ID + * - 'expired_at' (int|null): 过期时间戳(默认为null,使用默认过期天数) + * - 'message_uuid'(string|null):消息uuid 区别 + * @param string|null $socketId 指定socket连接ID + * + * @return array 返回结果数组包含: + * - 'push_count' (int): 成功推送的频道数量 + * - 'messages' (array): 创建的消息对象数组(包含完整消息详情) + */ + public function sendAndRecord( + PushClientType $clientType, + string $businessModule, + string $tenantId, + string|int|array $userIds, + string $event = 'message', + array $data = [], + array $messageData = [], + ?string $socketId = null + ): array + { + $userIds = Arr::normalize($userIds); + $result = [ + 'push_count' => 0, + 'messages' => [], // 直接返回创建的消息对象 + ]; + + try { + // 1. 批量创建消息记录 + // 使用合并后的消息数据创建记录,包含事件类型、租户ID、频道和过期时间 + $messages = $this->createMessages( + $userIds, + array_merge($messageData, [ + 'type' => $event, + 'tenant_id' => $tenantId, + 'channel' => $this->buildChannel($clientType, $businessModule, $tenantId, null), + 'expired_at' => $this->calculateExpireTimestamp($messageData['expired_at'] ?? null), + ]), + ); + + // 2. 准备推送数据(直接使用已创建的消息对象) + // 将消息格式化为适合推送的格式后附加到推送数据中 + $pushData = array_merge($data, [ + 'messages' => $this->formatMessagesForPush($messages), + ]); + + // 3. 执行推送 + $channels = array_map( + fn($userId) => $this->buildChannel($clientType, $businessModule, $tenantId, $userId), + $userIds + ); + + // 批量单条消息批量推送异常等官方修复 + $this->pushApi->trigger($channels, $event, $pushData, $socketId); + + $result['push_count'] = count($channels); + $result['messages'] = $messages; + + Logger::debug("推送成功", [ + 'channels' => $channels, + 'message_count' => count($messages), + ]); + + } catch (\Throwable $e) { + Logger::error("推送失败: " . $e->getMessage(), [ + 'exception' => $e, + 'user_ids' => $userIds, + ]); + } + + return $result; + } + + /** + * 批量推送不同业务消息(带记录) + * + * @param PushClientType $clientType 客户端类型枚举值 + * @param string $tenantId 租户ID + * @param array $messages 批量消息数组,每个元素包含以下字段: + * - 'module' (string): 业务模块标识 + * - 'userIds' (string|array): 接收者ID(单个ID或ID数组) + * - 'event' (string): 事件类型 + * - 'data' (array, optional): 推送数据,默认为空数组 + * - 'title' (string, optional): 消息标题,默认为空字符串 + * - 'content' (string, optional): 消息内容,默认为空字符串 + * - 'message_type' (string, optional): 消息类型,默认为事件类型(event) + * - 'priority' (int, optional): 消息优先级,默认为0 + * - 'related_id' (string|null, optional): 关联ID,默认为null + * - 'expired_at' (int|null, optional): 过期时间戳,默认为null(使用默认过期天数) + * - 'message_uuid'(string|null):消息uuid 区别公共重复推送 + * + * @return array 返回批量推送结果数组,每个元素包含: + * - 'push_count' (int): 成功推送数量 + * - 'message_ids' (array): 创建的消息ID数组 + * - 'message_details' (array): 完整的消息记录详情 + */ + public function batchSend( + PushClientType $clientType, + string $tenantId, + array $messages + ): array + { + $results = []; + + foreach ($messages as $msg) { + $results[] = $this->sendAndRecord( + $clientType, + $msg['module'], + $tenantId, + $msg['receiver_id'], + $msg['event'] ?? 'message', + $msg['data'] ?? [], + [ + 'title' => $msg['title'] ?? '', + 'content' => $msg['content'] ?? '', + 'type' => $msg['message_type'] ?? $msg['event'], + 'priority' => $msg['priority'] ?? 0, + 'related_id' => $msg['related_id'] ?? null, + 'expired_at' => $msg['expired_at'] ?? null, + 'message_uuid' => $msg['message_uuid'] ?? null, + ] + ); + } + return $results; + } + + /** + * 仅推送消息(不记录) + */ + public function pushOnly( + PushClientType $clientType, + string $businessModule, + string $tenantId, + string|int|array $userIds, + string $event = 'message', + array $data = [], + ?string $socketId = null + ): int + { + $userIds = Arr::normalize($userIds); + $channels = array_map( + fn($userId) => $this->buildChannel($clientType, $businessModule, $tenantId, $userId), + $userIds + ); + + return $this->pushApi->trigger($channels, $event, $data, $socketId); + } + + /** + * 仅记录消息(不推送) + */ + public function recordOnly( + array $userIds, + array $messageData + ): array + { + return $this->createMessages(Arr::normalize($userIds), $messageData); + } + + /** + * 批量创建消息记录(返回完整消息对象) + */ + private function createMessages(array $receiverIds, array $data): array + { + $defaults = [ + 'status' => 'unread', + 'priority' => 3, + 'expired_at' => $data['expired_at'] ?? $this->calculateExpireTimestamp(self::DEFAULT_EXPIRE_DAYS), + ]; + + $messages = []; + foreach ($receiverIds as $receiverId) { + try { + $message = $this->messageModel->create(array_merge( + $defaults, + $data, + ['message_uuid' => $this->generateMessageUuid($data['message_uuid'] ?? null)], + ['receiver_id' => $receiverId] + )); + $messages[] = $message->toArray(); // 直接返回模型数据 + } catch (\Throwable $e) { + throw new \Exception($e->getMessage()); + } + + } + return $messages; + } + + /** + * 生成唯一的 UUID(如果未提供则自动生成) + * + * @param string|null $uuid 可选的外部 UUID(如果为 null 则自动生成) + * + * @return string 有效的 UUID + * @throws \Exception 如果 UUID 生成失败 + */ + private function generateMessageUuid(?string $uuid = null): string + { + if (empty($uuid)) { + try { + return UUIDGenerator::generate(); // 假设返回字符串 UUID + } catch (\Exception $e) { + throw new \Exception("Failed to generate UUID: " . $e->getMessage()); + } + } + return $uuid; + } + + /** + * 计算过期时间戳 + */ + private function calculateExpireTimestamp(?int $expireDays): int + { + if ($expireDays === null) { + $expireDays = self::DEFAULT_EXPIRE_DAYS; + } + return time() + ($expireDays * 86400); // 86400秒=1天 + } + + /** + * 格式化消息数据用于推送 + */ + private function formatMessagesForPush(array $messages): array + { + return array_map(function ($message) { + return $message; + }, $messages); + } + + /** + * 生成标准化频道名称 + */ + public function buildChannel( + PushClientType $clientType, + string $businessModule, + string $tenantId, + ?string $userId = null + ): string + { + // 业务模块校验 + if (!preg_match('/^[a-z0-9_]+$/', $businessModule)) { + throw new InvalidArgumentException('业务模块只能包含小写字母、数字和下划线'); + } + + return implode('-', [ + $clientType->value, + $businessModule, + $tenantId, + $userId ?: '*', + ]); + } + + /** + * 获取配置 + * + * @return array + */ + private function getConfig(): array + { + $config = config('core.notify.app.webman-push'); + if (empty($config)) { + throw new \RuntimeException('消息推送配置未定义'); + } + return $config; + } +} + diff --git a/server/core/notify/config/core/notify/app.php b/server/core/notify/config/core/notify/app.php new file mode 100644 index 0000000000000000000000000000000000000000..edf8b565605038eeb47a5a4e9952529bce5fd3cf --- /dev/null +++ b/server/core/notify/config/core/notify/app.php @@ -0,0 +1,23 @@ + true, + 'webman-push' => [ + 'websocket' => config('plugin.webman.push.app.websocket'), + 'api' => config('plugin.webman.push.app.api'), + 'app_key' => config('plugin.webman.push.app.app_key'), + 'app_secret' => config('plugin.webman.push.app.app_secret'), + 'channel_hook' => config('plugin.webman.push.app.channel_hook'), + 'auth' => config('plugin.webman.push.app.auth'), + ], +]; diff --git a/server/core/notify/enum/PushClientType.php b/server/core/notify/enum/PushClientType.php new file mode 100644 index 0000000000000000000000000000000000000000..f0c3622b069d19f818016ef1feda12926fcf5640 --- /dev/null +++ b/server/core/notify/enum/PushClientType.php @@ -0,0 +1,53 @@ + '管理后台', + self::APP => '移动应用', + self::MP => '小程序', + self::WEB => '网页端', + }; + } + + /** + * 获取所有枚举值 + */ + public static function values(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/server/scripts/config/menu.php b/server/scripts/config/menu.php index d9e1a6076a44aee04f584273dac51d26fd420d83..44954cebbd7772984f784968bc09871b0a854e34 100644 --- a/server/scripts/config/menu.php +++ b/server/scripts/config/menu.php @@ -1316,348 +1316,6 @@ return [ // ], ], ], -// [ -// "app" => "admin", -// "title" => "业务流程", -// "code" => "wf:manager", -// "level" => null, -// "type" => 1, -// "sort" => 999, -// "path" => "/wf/manager", -// "component" => "BasicLayout", -// "redirect" => null, -// "icon" => "ant-design:right-square-filled", -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 1, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// "children" => [ -// [ -// "app" => "admin", -// "title" => "流程设置", -// "code" => "wf:setting", -// "level" => null, -// "type" => 1, -// "sort" => 999, -// "path" => "/wf/setting", -// "component" => null, -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 1, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// "children" => [ -// [ -// "app" => "admin", -// "title" => "流程类型", -// "code" => "wf:category", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/category", -// "component" => "/wf/category/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// "app" => "admin", -// "title" => "流程定义", -// "code" => "wf:define", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/define", -// "component" => "/wf/define/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// "app" => "admin", -// "title" => "流程设计", -// "code" => "wf:designer", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/designer", -// "component" => "/wf/designer/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// "app" => "admin", -// "title" => "表单设计", -// "code" => "wf:form-builder", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/form-builder", -// "component" => "/wf/form-builder/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// "app" => "admin", -// "title" => "委托设置", -// "code" => "wf:surrogate", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/surrogate", -// "component" => "/wf/surrogate/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// ], -// ], -// [ -// "app" => "admin", -// "title" => "流程查询", -// "code" => "wf:query", -// "level" => null, -// "type" => 1, -// "sort" => 999, -// "path" => "/wf/query", -// "component" => null, -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 1, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// "children" => [ -// [ -// "app" => "admin", -// "title" => "我的申请", -// "code" => "wf:instance", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/instance", -// "component" => "/wf/instance/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// -// ], -// [ -// "app" => "admin", -// "title" => "我的收藏", -// "code" => "wf:favorite", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/favorite", -// "component" => "/wf/favorite/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// -// "app" => "admin", -// "title" => "我的已办", -// "code" => "wf:task:done", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/task/done", -// "component" => "/wf/task/done/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// ], -// ], -// [ -// "app" => "admin", -// "title" => "基础功能", -// "code" => "wf:basic", -// "level" => null, -// "type" => 1, -// "sort" => 999, -// "path" => "/wf/basic", -// "component" => "", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 1, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// "children" => [ -// [ -// "app" => "admin", -// "title" => "待办", -// "code" => "wf:task:todo", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/task/todo", -// "component" => "/wf/task/todo/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// [ -// -// "app" => "admin", -// "title" => "待阅", -// "code" => "wf:carbon", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/carbon", -// "component" => "/wf/carbon/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// -// ], -// [ -// "app" => "admin", -// "title" => "发起", -// "code" => "wf:basic:launch", -// "level" => null, -// "type" => 2, -// "sort" => 999, -// "path" => "/wf/basic/launch", -// "component" => "/wf/launch/index", -// "redirect" => null, -// "icon" => null, -// "is_show" => 1, -// "is_link" => 0, -// "link_url" => null, -// "enabled" => 1, -// "open_type" => 0, -// "is_cache" => 0, -// "is_sync" => 1, -// "is_affix" => 0, -// "methods" => "GET", -// "is_frame" => null, -// ], -// ], -// ], -// ], -// ], [ "app" => "admin", "title" => "人工智能", @@ -1794,6 +1452,52 @@ return [ ], ], ], + [ + "app" => "admin", + "title" => "消息管理", + "code" => "system:message", + "level" => null, + "type" => 2, + "sort" => 999, + "path" => "/system/message", + "component" => "/system/message/index", + "redirect" => null, + "icon" => "ant-design:message-outlined", + "is_show" => 1, + "is_link" => 0, + "link_url" => null, + "enabled" => 1, + "open_type" => 1, + "is_cache" => 0, + "is_sync" => 1, + "is_affix" => 0, + "methods" => "GET", + "is_frame" => null, + "children" => [ + [ + "app" => "admin", + "title" => "删除", + "code" => "system:message:delete", + "level" => null, + "type" => 3, + "sort" => 999, + "path" => null, + "component" => null, + "redirect" => null, + "icon" => null, + "is_show" => 1, + "is_link" => 0, + "link_url" => null, + "enabled" => 1, + "open_type" => 1, + "is_cache" => 0, + "is_sync" => 1, + "is_affix" => 0, + "methods" => "GET", + "is_frame" => null, + ], + ], + ], [ "app" => "admin", "title" => "通知公告", diff --git a/server/scripts/sql/install.sql b/server/scripts/sql/install.sql index 0974d72efa9f32df263701b0645c1d97e8b25411..1768862fbe26abb2fad98d71f0d520175590e543 100644 --- a/server/scripts/sql/install.sql +++ b/server/scripts/sql/install.sql @@ -494,20 +494,23 @@ CREATE TABLE `ma_sys_menu` DROP TABLE IF EXISTS `ma_sys_message`; CREATE TABLE `ma_sys_message` ( - `id` bigint(20) NOT NULL COMMENT '主键', - `type` tinyint(4) NOT NULL COMMENT '消息类型参考枚举类', - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '消息标题(日志类消息可为空)', - `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息内容(支持富文本存储)', - `sender_id` bigint(20) NULL DEFAULT 0 COMMENT '发送者ID(0表示系统发送)', - `receiver_id` bigint(20) NOT NULL COMMENT '接收者ID(可关联用户表)', - `status` enum('unread','read') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'unread' COMMENT '消息状态', - `priority` tinyint(1) NULL DEFAULT 3 COMMENT '优先级(1紧急 2急迫 3普通)', - `channel` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'message' COMMENT '发送渠道', - `related_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '关联业务ID(如订单号、日志ID等)', - `created_at` bigint(20) NULL DEFAULT NULL COMMENT '创建时间', - `expired_at` bigint(20) NULL DEFAULT NULL COMMENT '过期时间', - `read_at` bigint(20) NULL DEFAULT NULL COMMENT '已读时间', - `updated_at` bigint(20) NULL DEFAULT NULL COMMENT '更新时间', + `id` bigint(20) NOT NULL COMMENT '主键', + `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户id', + `type` varchar(20) NOT NULL COMMENT '消息类型参考枚举类', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '消息标题(日志类消息可为空)', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息内容(支持富文本存储)', + `sender_id` bigint(20) NULL DEFAULT 0 COMMENT '发送者ID(0表示系统发送)', + `receiver_id` bigint(20) NOT NULL COMMENT '接收者ID(可关联用户表)', + `status` enum('unread','read') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'unread' COMMENT '消息状态', + `priority` tinyint(1) NULL DEFAULT 3 COMMENT '优先级(1紧急 2急迫 3普通)', + `channel` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'message' COMMENT '发送渠道', + `related_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '关联业务ID(如订单号、日志ID等)', + `message_uuid` varchar(50) NULL DEFAULT NULL COMMENT '消息uuid', + `created_at` bigint(20) NULL DEFAULT NULL COMMENT '创建时间', + `expired_at` bigint(20) NULL DEFAULT NULL COMMENT '过期时间', + `read_at` bigint(20) NULL DEFAULT NULL COMMENT '已读时间', + `updated_at` bigint(20) NULL DEFAULT NULL COMMENT '更新时间', + `deleted_at` bigint(20) NULL DEFAULT NULL COMMENT '删除时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统消息表' ROW_FORMAT = DYNAMIC; @@ -524,6 +527,7 @@ CREATE TABLE `ma_sys_notice` `content` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL COMMENT '公告内容', `sort` int(11) NULL DEFAULT 10 COMMENT '排序', `enabled` tinyint(1) NULL DEFAULT 0 COMMENT '公告状态(0正常 1关闭)', + `uuid` varchar(50) NULL DEFAULT NULL COMMENT 'uuid 区分识别是否已推送', `created_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', `created_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', `created_at` bigint(20) NULL DEFAULT NULL COMMENT '创建时间', diff --git a/web/.env.development b/web/.env.development index 1190dc90f6aabf93a4440474977e004f419fef94..8c77c4fe501721acaf453de1a6a4b3a97cb5b494 100644 --- a/web/.env.development +++ b/web/.env.development @@ -20,7 +20,8 @@ VITE_GLOB_CLIENT_KEY= X-Client-ID VITE_GLOB_ENABLE_WSS=true # wss接口地址 -VITE_GLOB_WSS_URL=/ws +# VITE_GLOB_WSS_URL=/ws +VITE_GLOB_WSS_URL=ws://127.0.0.1:3898 # WSS KEY VITE_GLOB_WSS_APPKEY = 60756ede2a9737a05384aad849e220f8 @@ -30,4 +31,8 @@ VITE_GLOB_WSS_APPKEY = 60756ede2a9737a05384aad849e220f8 # 开发环境下跨域代理,请输入要跨域的api地址 - 尾部无需带'/' VITE_PROXY_URL = 'http://localhost:5566' -VITE_AXIOS_BASE_URL = 'http://127.0.0.1:8899' \ No newline at end of file +VITE_AXIOS_BASE_URL = 'http://127.0.0.1:8899' + +VITE_WSS_URL='ws://127.0.0.1:3898' + + diff --git a/web/src/api/system/notice/index.ts b/web/src/api/system/notice/index.ts index 8812702652bfd645a6c698cc8b829eb5816fd615..ef39c48a768a73fc30d9d6af75418f77a42cebb1 100644 --- a/web/src/api/system/notice/index.ts +++ b/web/src/api/system/notice/index.ts @@ -1,6 +1,6 @@ import BaseApi from "#/api/base-api"; import { requestClient } from "#/api/request"; -import type{ SystemNoticeRow } from "./model"; +import type { SystemNoticeRow } from "./model"; @@ -13,10 +13,13 @@ export class SystemNoticeApi extends BaseApi { super('/system/notice'); } - - - test() { - return requestClient.post(this.baseUrl+'/test'); + /** + * 推送公告 + * @param data + * @returns + */ + publish(data:any) { + return requestClient.put(this.baseUrl + '/publish', data); } } diff --git a/web/src/locale/langs/en-US/system.json b/web/src/locale/langs/en-US/system.json index 99cec89b28f1e4c48462779d5c741a42ca61a273..30a7bf3ca48fa72c15913dcd40bcc07aa0e0dd8d 100644 --- a/web/src/locale/langs/en-US/system.json +++ b/web/src/locale/langs/en-US/system.json @@ -439,7 +439,7 @@ }, "toolbar": { "create": "Add New", - "enum":"Enum Dict", + "enum": "Enum Dict", "delete": { "label": "Delete", "title": "Confirmation", @@ -663,6 +663,11 @@ "label": "Delete", "title": "Confirmation", "confirm": "Confirm deletion?" + }, + "publish": { + "label": "Publish", + "title": "Reminder", + "confirm": "Are you sure you want to publish the announcement 【{0}】?" } } }, diff --git a/web/src/locale/langs/zh-CN/system.json b/web/src/locale/langs/zh-CN/system.json index d0fbb64525e8209c085cbe85d52115816e006c72..1f8992fabb1e8448c7a4b9b6bab3f7e8f20c8e6f 100644 --- a/web/src/locale/langs/zh-CN/system.json +++ b/web/src/locale/langs/zh-CN/system.json @@ -670,6 +670,11 @@ "label": "删除", "title": "温馨提示", "confirm": "是否确认删除?" + }, + "publish":{ + "label":"推送", + "title": "温馨提示", + "confirm": "请确认是否推送【{0}】公告?" } } }, @@ -730,8 +735,8 @@ "status": "是否已读", "priority": "优先级", "channel": "场景", - "created_date": "接受时间", - "read_date": "查看时间", + "created_date": "接收时间", + "read_date": "阅读时间", "updated_date": "更新时间", "operation": "操作", "actions": { diff --git a/web/src/store/modules/notify.ts b/web/src/store/modules/notify.ts index 096eff09581e0b8d9f243e7b944ee94bd2d9185b..921e99a7f7c9e81e5c78e4af810f66c2bc332816 100644 --- a/web/src/store/modules/notify.ts +++ b/web/src/store/modules/notify.ts @@ -27,6 +27,11 @@ export const useNotifyStore = defineStore( return userStore.userInfo?.id || '0'; }); + const tenantId = computed(() => { + //@ts-ignore + return userStore.userInfo?.tenant?.tenant_id || '*'; + }); + const notifications = computed(() => { const currentUserId = String(userId.value); return notificationList.value.filter(item => String(item.uid) === currentUserId); @@ -70,7 +75,7 @@ export const useNotifyStore = defineStore( related_id: data.related_id || '', uid: data.receiver_id, type: data?.type || '', - channel:data.channel||'notice' + channel: data.channel || 'notice' }); } } @@ -95,27 +100,38 @@ export const useNotifyStore = defineStore( auth: '/plugin/webman/push/auth' // 订阅鉴权(仅限于私有频道) }); - const notices_channel = connection.subscribe('admin'); - const message_channel = connection.subscribe('admin-' + userId.value); + // 公共订阅-弃用 + // const notices_channel = connection.subscribe('backend-'+'admin-' + tenantId.value+'-*'); //公告订阅 - notices_channel.on('notice', function (message: any) { - if (Array.isArray(message)) { - message.forEach((data: any) => { - data['receiver_id'] = userId.value;//公告没有对应的接收人把本地登录的用户id追加到数据流 - addUniqueNotification(data); - }); - } else { - message['receiver_id'] = userId.value;//公告没有对应的接收人把本地登录的用户id追加到数据流 - addUniqueNotification(message); - } - }); + // notices_channel.on('notice', function (message: any) { + // if (Array.isArray(message)) { + // message.forEach((data: any) => { + // data['receiver_id'] = userId.value;//公告没有对应的接收人把本地登录的用户id追加到数据流 + // addUniqueNotification(data); + // }); + // } else { + // message['receiver_id'] = userId.value;//公告没有对应的接收人把本地登录的用户id追加到数据流 + // addUniqueNotification(message); + // } + // }); + /** - * 消息订阅 + * + * 订阅后端消息通道(格式:backend-{模块}-{租户ID}-{用户ID}) + * + * 通道命名规则: + * - backend : 固定前缀,标识后端服务通道(与前端其他通道区分) + * - admin : 业务模块标识(此处为管理员模块,可替换为其他模块) + * - tenantId.value : 当前租户的唯一ID + * - userId.value : 当前用户的唯一ID + * 示例最终通道名:backend-admin-1-1 */ - message_channel.on('message', function (message: any) { + const message_channel = connection.subscribe('backend-' + 'admin-' + tenantId.value + '-' + userId.value); + message_channel.on('message', function (data: any) { + const message = data.messages || {} if (Array.isArray(message)) { message.forEach((data: any) => { //添加到列表 diff --git a/web/src/views/system/message/data.ts b/web/src/views/system/message/data.ts index 113f139fecd76d84108e5fb82ccc342b7b82543e..a55e3a50de30a9a9dcba137d8c2a2f64a214937a 100644 --- a/web/src/views/system/message/data.ts +++ b/web/src/views/system/message/data.ts @@ -86,10 +86,11 @@ export function useColumns( align: 'left', field: 'title', minWidth: 150, + visible: false, slots: { //@ts-ignore default: ({ row }) => { - if(row.related_id == undefined ||row.related_id == ''||row.related_id == null){ + if (row.related_id == undefined || row.related_id == '' || row.related_id == null) { return '/'; } @@ -112,7 +113,7 @@ export function useColumns( title: $t('system.message.list.table.columns.title'), align: 'left', field: 'title', - minWidth: 200, + minWidth: 170, slots: { //@ts-ignore default: ({ row }) => { @@ -150,6 +151,7 @@ export function useColumns( title: $t('system.message.list.table.columns.type'), field: 'type', width: 100, + visible: false, cellRender: { name: 'CellTag', options: getDictOptions(DictEnum.SYS_MESSAGE_TYPE) @@ -171,6 +173,18 @@ export function useColumns( title: $t('system.message.list.table.columns.created_date'), field: 'created_date', width: 150 + }, + { + title: $t('system.message.list.table.columns.read_date'), + field: 'read_date', + width: 150, + slots: { + //@ts-ignore + default: ({ row }) => { + const { read_date } = row; + return read_date || '/' + } + } } ]; } diff --git a/web/src/views/system/message/index.vue b/web/src/views/system/message/index.vue index 45eaaa3f2bcf4c67a94e3dafac967fd6560b5059..9f865a7ae9ce083c715e3fee230ba8c83d5a6793 100644 --- a/web/src/views/system/message/index.vue +++ b/web/src/views/system/message/index.vue @@ -14,6 +14,7 @@ import { TableAction } from "#/components/table"; import { SystemMessageApi } from "#/api/system/message"; import type { SystemMessageRow } from "#/api/system/message"; import { router } from "#/router"; +import { $t } from "#/locale"; const api = new SystemMessageApi(); @@ -98,7 +99,7 @@ const [Grid, gridApi] = useVxeGrid({ }, }, // border:'inner', - id: "system-post-index", + id: "system-message-index", }, gridEvents: { menuClick({ menu, row, column }) { @@ -194,11 +195,47 @@ const messageHandlers = { } }, }; + + +/** + * 批量删除 + */ +function handleMultiDelete() { + const rows = gridApi.grid.getCheckboxRecords(); + const data = rows.map((row: SystemMessageRow) => row.id); + Modal.confirm({ + title: $t('system.message.list.table.columns.actions.delete.title'), + okType: "danger", + content: $t('system.message.list.table.columns.actions.delete.confirm',[rows.length]), + onOk: async () => { + await api.remove(0, {data}); + await gridApi.query(); + }, + }); +} + + diff --git a/web/src/views/system/notice/data.ts b/web/src/views/system/notice/data.ts index f600c4304b12ad66700f6dbe04881772abf167b4..de5e11033cfa35fcd4a86f4fc55f138fb7cd635b 100644 --- a/web/src/views/system/notice/data.ts +++ b/web/src/views/system/notice/data.ts @@ -104,6 +104,11 @@ export function useColumns( code: 'delete', text: $t('system.notice.list.table.columns.actions.delete.label'), auth: ['admin', 'system:notice:delete'], + }, + { + code: 'publish', + text: $t('system.notice.list.table.columns.actions.publish.label'), + auth: ['admin', 'system:notice:publish'], } ], }, diff --git a/web/src/views/system/notice/index.vue b/web/src/views/system/notice/index.vue index b694cad3c236ffca768fec12a84790b707bc9df7..af3406080b9c79a3738d0370d252659cbd7e890c 100644 --- a/web/src/views/system/notice/index.vue +++ b/web/src/views/system/notice/index.vue @@ -14,6 +14,7 @@ import noticeModal from "./modules/form.vue"; import { FormProps } from "#/adapter"; import { getNestedValue } from "#/utils"; import { TableAction } from "#/components/table"; +import { $t } from "#/locale"; const api = new SystemNoticeApi(); @@ -91,7 +92,7 @@ const [Grid, gridApi] = useVxeGrid({ buttons: "toolbar-buttons", }, }, - id: "system-post-index", + id: "system-notice-index", }, gridEvents: { menuClick({ menu, row, column }) { @@ -131,6 +132,9 @@ function onActionClick({ code, row }: OnActionClickParams) { handleDelete(row); break; } + case "publish":{ + handlePublish(row); + } default: { break; } @@ -157,17 +161,31 @@ async function handleDelete(row: SystemNoticeRow) { function handleMultiDelete() { const rows = gridApi.grid.getCheckboxRecords(); - const ids = rows.map((row: SystemNoticeRow) => row.id); + const data = rows.map((row: SystemNoticeRow) => row.id); Modal.confirm({ - title: "提示", + title: $t('system.notice.list.table.columns.actions.delete.title'), okType: "danger", - content: `确认删除选中的${ids.length}条记录吗?`, + content: $t('system.notice.list.table.columns.actions.delete.confirm',[rows.length]), onOk: async () => { - await api.remove(0, ids); + await api.remove(0, {data}); await gridApi.query(); }, }); } + + +function handlePublish(row:SystemNoticeRow) { + Modal.confirm({ + title:$t('system.notice.list.table.columns.actions.publish.title'), + okType: "danger", + content: $t('system.notice.list.table.columns.actions.publish.confirm',[row.title]), + onOk: async () => { + await api.publish({id:row.id}); + message.success($t('common.message.success')); + }, + }); +} +