mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 18:42:54 +00:00
perf: 优化超长文本信息
This commit is contained in:
parent
9e92c61fbf
commit
49aa1434aa
@ -14,6 +14,7 @@ use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\TimeRange;
|
||||
use App\Module\MsgTool;
|
||||
use App\Module\Table\OnlineData;
|
||||
use App\Models\FileContent;
|
||||
use App\Models\AbstractModel;
|
||||
@ -1096,21 +1097,29 @@ class DialogController extends AbstractController
|
||||
return Base::retError('消息发送保存失败');
|
||||
}
|
||||
$ext = $markdown ? 'md' : 'htm';
|
||||
$fileData = [
|
||||
'name' => "LongText-{$strlen}.{$ext}",
|
||||
'size' => $size,
|
||||
'file' => $file,
|
||||
'path' => $path,
|
||||
'url' => Base::fillUrl($path),
|
||||
'thumb' => '',
|
||||
'width' => -1,
|
||||
'height' => -1,
|
||||
'ext' => $ext,
|
||||
$text = MsgTool::truncateText($text, 500, $ext);
|
||||
$desc = strip_tags($markdown ? Base::markdown2html($text) : $text);
|
||||
$desc = mb_substr(WebSocketDialogMsg::filterEscape($desc), 0, 200);
|
||||
$msgData = [
|
||||
'desc' => $desc, // 描述内容
|
||||
'text' => $text, // 简要内容
|
||||
'type' => $ext, // 内容类型
|
||||
'file' => [
|
||||
'name' => "LongText-{$strlen}.{$ext}",
|
||||
'size' => $size,
|
||||
'file' => $file,
|
||||
'path' => $path,
|
||||
'url' => Base::fillUrl($path),
|
||||
'thumb' => '',
|
||||
'width' => -1,
|
||||
'height' => -1,
|
||||
'ext' => $ext,
|
||||
],
|
||||
];
|
||||
if (empty($key)) {
|
||||
$key = mb_substr(strip_tags($text), 0, 200);
|
||||
$key = $desc;
|
||||
}
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence, $key);
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'longtext', $msgData, $user->userid, false, false, $silence, $key);
|
||||
} else {
|
||||
$msgData = ['text' => $text];
|
||||
if ($markdown) {
|
||||
@ -1638,6 +1647,18 @@ class DialogController extends AbstractController
|
||||
$msg = File::formatFileData($msg);
|
||||
$data['content'] = $msg['content'];
|
||||
$data['file_mode'] = $msg['file_mode'];
|
||||
} elseif ($data['type'] == 'longtext') {
|
||||
$data['content'] = [
|
||||
'type' => 'htm',
|
||||
'content' => Doo::translate("内容不存在")
|
||||
];
|
||||
if (isset($data['msg']['file']['path'])) {
|
||||
$filePath = public_path($data['msg']['file']['path']);
|
||||
if (file_exists($filePath)) {
|
||||
$data['content']['type'] = $data['msg']['type'];
|
||||
$data['content']['content'] = file_get_contents($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
|
||||
@ -592,6 +592,9 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
case 'text':
|
||||
return self::previewTextMsg($data['msg'], $preserveHtml);
|
||||
|
||||
case 'longtext':
|
||||
return $data['msg']['desc'] ? Base::cutStr($data['msg']['desc'], 50) : ("[" . Doo::translate("长文本") . "]");
|
||||
|
||||
case 'vote':
|
||||
$action = Doo::translate("投票");
|
||||
return "[{$action}] " . self::previewTextMsg($data['msg'], $preserveHtml);
|
||||
@ -774,14 +777,24 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
break;
|
||||
}
|
||||
}
|
||||
$key = str_replace([""", "&", "<", ">"], "", $key);
|
||||
$key = str_replace(["\r", "\n", "\t", " "], " ", $key);
|
||||
$key = preg_replace("/^\/[A-Za-z]+/", " ", $key);
|
||||
$key = preg_replace("/\s+/", " ", $key);
|
||||
$this->key = trim($key);
|
||||
$this->key = self::filterEscape($key);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤转义
|
||||
* @param $content
|
||||
* @return string
|
||||
*/
|
||||
public static function filterEscape($content)
|
||||
{
|
||||
$content = str_replace([""", "&", "<", ">"], "", $content);
|
||||
$content = str_replace(["\r", "\n", "\t", " "], " ", $content);
|
||||
$content = preg_replace("/^\/[A-Za-z]+/", " ", $content);
|
||||
$content = preg_replace("/\s+/", " ", $content);
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回引用消息(如果是文本只取预览)
|
||||
* @return array|mixed
|
||||
|
||||
108
app/Module/MsgTool.php
Normal file
108
app/Module/MsgTool.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use DOMDocument;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Exception\CommonMarkException;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
class MsgTool
|
||||
{
|
||||
/**
|
||||
* 截取文本并保持标签完整性
|
||||
*
|
||||
* @param string $text 要截取的文本
|
||||
* @param int $length 截取长度
|
||||
* @param string $type 文本类型 (htm 或 md)
|
||||
* @return string 处理后的文本
|
||||
*/
|
||||
public static function truncateText($text, $length, $type = 'htm')
|
||||
{
|
||||
if (empty($text) || mb_strlen($text) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$isMd = strtolower($type) === 'md';
|
||||
|
||||
// 如果是Markdown,转换为HTML
|
||||
if ($isMd) {
|
||||
$converter = new CommonMarkConverter();
|
||||
try {
|
||||
$text = $converter->convert($text);
|
||||
} catch (CommonMarkException) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 创建DOM文档
|
||||
$dom = new DOMDocument('1.0', 'UTF-8');
|
||||
libxml_use_internal_errors(true);
|
||||
$dom->loadHTML(mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'));
|
||||
libxml_clear_errors();
|
||||
|
||||
// 获取body元素
|
||||
$body = $dom->getElementsByTagName('body')->item(0);
|
||||
$truncatedHtml = '';
|
||||
$currentLength = 0;
|
||||
|
||||
// 递归函数来遍历节点并截取内容
|
||||
self::traverseNodes($body, $currentLength, $length, $truncatedHtml);
|
||||
|
||||
// 如果是Markdown,转换回Markdown
|
||||
if ($isMd) {
|
||||
$converter = new HtmlConverter();
|
||||
$truncatedHtml = $converter->convert($truncatedHtml);
|
||||
}
|
||||
|
||||
return $truncatedHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归遍历节点
|
||||
* @param $node
|
||||
* @param $currentLength
|
||||
* @param $length
|
||||
* @param $truncatedHtml
|
||||
* @return void
|
||||
*/
|
||||
private static function traverseNodes($node, &$currentLength, $length, &$truncatedHtml)
|
||||
{
|
||||
foreach ($node->childNodes as $child) {
|
||||
if ($currentLength >= $length) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($child->nodeType === XML_TEXT_NODE) {
|
||||
$textContent = $child->textContent;
|
||||
$remainingLength = $length - $currentLength;
|
||||
|
||||
if (mb_strlen($textContent) > $remainingLength) {
|
||||
$truncatedHtml .= htmlspecialchars(mb_substr($textContent, 0, $remainingLength) . '...');
|
||||
$currentLength += $remainingLength;
|
||||
} else {
|
||||
$truncatedHtml .= htmlspecialchars($textContent);
|
||||
$currentLength += mb_strlen($textContent);
|
||||
}
|
||||
} elseif ($child->nodeType === XML_ELEMENT_NODE) {
|
||||
$truncatedHtml .= '<' . $child->nodeName;
|
||||
|
||||
// 添加属性
|
||||
if ($child->hasAttributes()) {
|
||||
foreach ($child->attributes as $attr) {
|
||||
$truncatedHtml .= ' ' . $attr->nodeName . '="' . htmlspecialchars($attr->nodeValue) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
$truncatedHtml .= '>';
|
||||
|
||||
self::traverseNodes($child, $currentLength, $length, $truncatedHtml);
|
||||
|
||||
if ($currentLength < $length || $child->firstChild) {
|
||||
$truncatedHtml .= '</' . $child->nodeName . '>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-imagick": "*",
|
||||
"ext-json": "*",
|
||||
|
||||
@ -802,3 +802,6 @@ webhook地址最长仅支持255个字符。
|
||||
更新子任务标签
|
||||
|
||||
AI机器人不存在
|
||||
|
||||
内容不存在
|
||||
长文本
|
||||
|
||||
@ -1903,3 +1903,5 @@ WiFi签到延迟时长为±1分钟。
|
||||
请选择示例标签
|
||||
全部保存成功
|
||||
|
||||
消息详情
|
||||
长文本
|
||||
|
||||
2
resources/assets/js/functions/web.js
vendored
2
resources/assets/js/functions/web.js
vendored
@ -401,6 +401,8 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
|
||||
switch (data.type) {
|
||||
case 'text':
|
||||
return $A.getMsgTextPreview(data.msg, imgClassName)
|
||||
case 'longtext':
|
||||
return data.msg.desc ? $A.cutString(data.msg.desc, 50) : ("[" + $A.L('长文本') + "]")
|
||||
case 'vote':
|
||||
return `[${$A.L('投票')}]` + $A.getMsgTextPreview(data.msg, imgClassName)
|
||||
case 'word-chain':
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
<div ref="content" class="dialog-content" :class="contentClass">
|
||||
<!--文本-->
|
||||
<TextMsg v-if="msgData.type === 'text'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText"/>
|
||||
<!--长文本-->
|
||||
<LongTextMsg v-else-if="msgData.type === 'longtext'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText" @downFile="downFile"/>
|
||||
<!--文件-->
|
||||
<FileMsg v-else-if="msgData.type === 'file'" :msg="msgData.msg" @viewFile="viewFile" @downFile="downFile"/>
|
||||
<!--录音-->
|
||||
@ -177,6 +179,7 @@ import {mapGetters, mapState} from "vuex";
|
||||
import longpress from "../../../../directives/longpress";
|
||||
|
||||
import TextMsg from "./text.vue";
|
||||
import LongTextMsg from "./longtext.vue";
|
||||
import FileMsg from "./file.vue";
|
||||
import RecordMsg from "./record.vue";
|
||||
import LocationMsg from "./location.vue";
|
||||
@ -199,6 +202,7 @@ export default {
|
||||
MeetingMsg,
|
||||
LocationMsg,
|
||||
RecordMsg,
|
||||
LongTextMsg,
|
||||
TextMsg,
|
||||
FileMsg,
|
||||
WCircle
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="content-text no-dark-content">
|
||||
<DialogMarkdown v-if="msg.type === 'md'" @click="viewText" :text="msg.text"/>
|
||||
<pre v-else @click="viewText" v-html="$A.formatTextMsg(msg.text, userId)"></pre>
|
||||
|
||||
<div class="content-longtext-footer">
|
||||
<span @click="downFile">{{$L('查看详情')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogMarkdown from "../DialogMarkdown.vue";
|
||||
|
||||
export default {
|
||||
components: {DialogMarkdown},
|
||||
props: {
|
||||
msgId: Number,
|
||||
msg: Object,
|
||||
},
|
||||
methods: {
|
||||
viewText(e) {
|
||||
this.$emit('viewText', e);
|
||||
},
|
||||
downFile() {
|
||||
this.$emit('downFile');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -3644,6 +3644,10 @@ export default {
|
||||
if (!$A.isJson(data)) {
|
||||
data = this.operateItem
|
||||
}
|
||||
if (data.type === 'longtext') {
|
||||
this.onViewFile(data)
|
||||
return;
|
||||
}
|
||||
$A.modalConfirm({
|
||||
language: false,
|
||||
title: this.$L('下载文件'),
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
<TEditor v-else-if="isType('text')" :value="msgDetail.content.content" height="100%" readOnly/>
|
||||
<Drawio v-else-if="isType('drawio')" v-model="msgDetail.content" :title="msgDetail.msg.name" readOnly/>
|
||||
<Minder v-else-if="isType('mind')" :value="msgDetail.content" readOnly/>
|
||||
<template v-else-if="msgDetail.type === 'longtext'">
|
||||
<VMPreview v-if="msgDetail.content.type === 'md'" :value="msgDetail.content.content"/>
|
||||
<div v-else class="view-code" v-html="$A.formatTextMsg(msgDetail.content.content, userId)"></div>
|
||||
</template>
|
||||
<template v-else-if="isType('code')">
|
||||
<div v-if="isLongText(msgDetail.msg.name)" class="view-code" v-html="$A.formatTextMsg(msgDetail.content.content, userId)"></div>
|
||||
<AceEditor v-else v-model="msgDetail.content.content" :ext="msgDetail.msg.ext" class="view-editor" readOnly/>
|
||||
@ -105,7 +109,10 @@ export default {
|
||||
},
|
||||
|
||||
title() {
|
||||
const {msg} = this.msgDetail;
|
||||
const {type, msg} = this.msgDetail;
|
||||
if (type === 'longtext') {
|
||||
return this.$L('消息详情');
|
||||
}
|
||||
if (msg && msg.name) {
|
||||
return msg.name;
|
||||
}
|
||||
|
||||
@ -659,6 +659,7 @@
|
||||
position: relative;
|
||||
|
||||
&.text,
|
||||
&.longtext,
|
||||
&.record,
|
||||
&.word-chain {
|
||||
max-width: 70%;
|
||||
@ -1460,6 +1461,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.content-longtext-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid rgba(227, 227, 227, 0.42);
|
||||
padding-top: 12px;
|
||||
padding-bottom: 2px;
|
||||
> span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
color: $flow-status-end-color;
|
||||
background-color: transparent;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user