diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index 3dfddebfb..6dac8e8e0 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -2885,6 +2885,58 @@ class DialogController extends AbstractController return Base::retSuccess('success', $topMsg); } + /** + * @api {get} api/dialog/msg/applied 55. 标记消息已应用 + * + * @apiDescription 需要token身份 + * @apiVersion 1.0.0 + * @apiGroup dialog + * @apiName msg__applied + * + * @apiParam {String} type 类型 + * - CreateTask: 创建任务 + * @apiParam {Number} index 索引 + * @apiParam {Number} msg_id 消息ID + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function msg__applied() + { + User::auth(); + // + $msg_id = intval(Request::input('msg_id')); + $type = trim(Request::input('type')); + $index = intval(Request::input('index')); + // + $msg = WebSocketDialogMsg::whereId($msg_id)->first(); + if (empty($msg)) { + return Base::retError("消息不存在或已被删除"); + } + WebSocketDialog::checkDialog($msg->dialog_id); + // + $originalMsg = $msg->getRawOriginal('msg'); + $pattern = '/```\s*' . preg_quote($type, '/') . '\s*(applying|applied)?\s*(\n|\\\\n)/'; + $count = -1; + $updatedMsg = preg_replace_callback($pattern, function($matches) use (&$count, $index, $type) { + $count++; + if ($count === $index || ($index === 0 && $count === 1)) { + return "```{$type} applied{$matches[2]}"; + } + return $matches[0]; + }, $originalMsg); + + if ($count === 0) { + return Base::retError("未找到可应用的规则"); + } + + $msg->msg = $updatedMsg; + $msg->save(); + // + return Base::retSuccess("success"); + } + /** * @api {get} api/dialog/sticker/search 56. 搜索在线表情 * diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index 2c6a19a56..c76b63e02 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -3220,6 +3220,89 @@ export default { }); }, + async applyCreateTask(event, el) { + const currentTarget = event.target; + if (currentTarget.classList.contains('applying') || currentTarget.classList.contains('applied')) { + return; + } + currentTarget.classList.add('applying') + + if (this.dialogData.group_type !== 'project') { + currentTarget.classList.remove('applying') + $A.modalError('只有在项目中才能创建任务') + return + } + + let target = event.target; + while (target) { + if (target.classList.contains('apply-create-task')) { + break; + } + if (target.classList.contains('dialog-scroller')) { + target = null; + break; + } + target = target.parentElement; + } + if (!target) { + currentTarget.classList.remove('applying') + $A.modalError('未找到任务内容') + return + } + if (!this.dialogData.group_info) { + currentTarget.classList.remove('applying') + $A.modalError('项目不存在') + return; + } + + const allTaskElements = el.querySelectorAll('.apply-create-task'); + const taskIndex = Array.from(allTaskElements).indexOf(target); + const taskList = Array.from(target.querySelectorAll('li')) + .map(item => { + const title = item.querySelector('.title')?.innerText?.trim(); + if (!title) return null; + + const desc = item.querySelector('.desc')?.innerText?.trim() || ''; + const content = desc ? desc.split('\n') + .filter(Boolean) + .map(line => `
${line.trim()}
`) + .join('') : ''; + + return { + project_id: this.dialogData.group_info.id, + name: title, + content + }; + }) + .filter(Boolean); + + const results = await Promise.all(taskList.map(item => + this.$store.dispatch("taskAdd", item).then( + success => ({ success: true, data: success }), + error => ({ success: false, error: error }) + ) + )); + const successTasks = results.filter(r => r.success).map(r => r.data); + const failedTasks = results.filter(r => !r.success).map(r => r.error); + if (failedTasks.length > 0) { + $A.modalError(`成功创建 ${successTasks.length} 个任务,${failedTasks.length} 个任务创建失败`); + } else { + $A.messageSuccess(`成功创建 ${successTasks.length} 个任务`); + } + + currentTarget.classList.remove('applying') + currentTarget.classList.add('applied') + + await this.$store.dispatch("call", { + url: 'dialog/msg/applied', + data: { + type: 'CreateTask', + index: taskIndex, + msg_id: this.operateItem.id + }, + }); + }, + openTranslationMenu(event) { const list = Object.keys(languageList).map(item => ({ label: languageList[item], @@ -3325,6 +3408,13 @@ export default { } const {target, clientX} = event + // 创建任务 + if (target.classList.contains('apply-create-task-button')) { + this.operateItem = this.findMsgByElement(el) + this.applyCreateTask(event, el) + return; + } + // 点击切换翻译 if (target.classList.contains('translation-label')) { this.operateItem = this.findMsgByElement(el) diff --git a/resources/assets/js/store/markdown.js b/resources/assets/js/store/markdown.js index 3058df8cd..69f3a9065 100644 --- a/resources/assets/js/store/markdown.js +++ b/resources/assets/js/store/markdown.js @@ -9,6 +9,12 @@ import mdKatex from "@traptitech/markdown-it-katex"; const MarkdownUtils = { mdi: null, mds: null, + + /** + * 解析Markdown + * @param {*} text + * @returns + */ formatMsg: (text) => { const array = text.match(/` }, } +const MarkdownPluginUtils = { + // 配置选项 + config: { + maxTitleLength: 200, + maxDescLength: 1000, + maxItems: 200, + defaultTitle: '创建任务' + }, + + // HTML转义函数 + escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }, + + // 验证输入 + validateInput(value, maxLength) { + if (!value) return ''; + if (typeof value !== 'string') return ''; + if (value.length > maxLength) { + return value.substring(0, maxLength) + '...'; + } + return value; + }, + + // 解析任务项 + parseTaskItems(content) { + const items = []; + let currentItem = {}; + let itemCount = 0; + + content.forEach(line => { + line = line.trim(); + if (!line) { + if (Object.keys(currentItem).length > 0) { + items.push(currentItem); + currentItem = {}; + itemCount++; + } + return; + } + + if (itemCount >= this.config.maxItems) { + return; + } + + const [key, ...valueParts] = line.split(':'); + const value = valueParts.join(':').trim(); + + if (key === 'title' && value) { + if (Object.keys(currentItem).length > 0) { + items.push(currentItem); + currentItem = {}; + itemCount++; + } + currentItem.title = this.validateInput(value, this.config.maxTitleLength); + } else if (key === 'desc' && value) { + currentItem.desc = this.validateInput(value, this.config.maxDescLength); + } + }); + + if (Object.keys(currentItem).length > 0 && itemCount < this.config.maxItems) { + items.push(currentItem); + } + + return items; + }, + + // 生成HTML + generateTaskHtml(items, status = null) { + if (!Array.isArray(items) || items.length === 0) { + return ''; + } + + const html = [ + '${lang}${$A.L('复制')}${str}