diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index cfae1001d..3cdd3c0d7 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -155,7 +155,7 @@ class UsersController extends AbstractController // if (!Project::withTrashed()->whereUserid($user->userid)->wherePersonal(1)->exists()) { Project::createProject([ - 'name' => Doo::translate('个人项目'), + 'name' => "📝 " . Doo::translate('个人项目'), 'desc' => Doo::translate('注册时系统自动创建项目,你可以自由删除。'), 'personal' => 1, ], $user->userid); diff --git a/database/migrations/2022_05_22_225924_add_projects_personal.php b/database/migrations/2022_05_22_225924_add_projects_personal.php index a7f0f5061..16a7848d3 100644 --- a/database/migrations/2022_05_22_225924_add_projects_personal.php +++ b/database/migrations/2022_05_22_225924_add_projects_personal.php @@ -22,7 +22,7 @@ class AddProjectsPersonal extends Migration }); if ($isAdd) { // 更新数据 - \App\Models\Project::whereName('个人项目')->chunkById(100, function ($lists) { + \App\Models\Project::where('name','like', '%个人项目%')->chunkById(100, function ($lists) { /** @var \App\Models\Project $item */ foreach ($lists as $item) { if ($item->desc == '注册时系统自动创建项目,你可以自由删除。') { diff --git a/resources/assets/js/directives/emoji-class.js b/resources/assets/js/directives/emoji-class.js index a8c13dd4f..bf18723ca 100644 --- a/resources/assets/js/directives/emoji-class.js +++ b/resources/assets/js/directives/emoji-class.js @@ -1,16 +1,16 @@ /** * Vue指令: v-emoji-class - * + * * 用法: * 1. 基本用法: v-emoji-class="className" - 将emoji包装在emoji中 * 2. 高级用法: v-emoji-class="{className: 'className', tagName: 'div'}" - 自定义标签名 - * + * * 示例: *
Hello 😊
*/ -import { debounce } from "lodash"; +import {debounce} from "lodash"; // 正则表达式用于匹配emoji - 使用预编译正则提高性能 const emojiRegex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g; @@ -37,37 +37,37 @@ function containsEmoji(text) { */ function processTextNode(textNode, className, tagName) { const text = textNode.textContent; - + // 快速检查是否包含emoji if (!containsEmoji(text)) return false; - + // 重置正则索引并准备替换 emojiRegex.lastIndex = 0; const fragment = document.createDocumentFragment(); let lastIndex = 0; let match; - + // 逐个匹配emoji并替换 while ((match = emojiRegex.exec(text)) !== null) { // 添加emoji前的文本 if (match.index > lastIndex) { fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index))); } - + // 创建包装emoji的元素 const emojiWrapper = document.createElement(tagName); emojiWrapper.className = className; emojiWrapper.textContent = match[0]; fragment.appendChild(emojiWrapper); - + lastIndex = emojiRegex.lastIndex; } - + // 添加剩余文本 if (lastIndex < text.length) { fragment.appendChild(document.createTextNode(text.substring(lastIndex))); } - + // 替换原始节点 textNode.parentNode.replaceChild(fragment, textNode); return true; @@ -84,12 +84,12 @@ function processNodeEmojis(node, className, tagName) { // 如果是文本节点,直接处理 if (node.nodeType === Node.TEXT_NODE) { return processTextNode(node, className, tagName); - } - + } + // 如果是元素节点,递归处理其子节点 if (node.nodeType === Node.ELEMENT_NODE) { let modified = false; - + // 使用childNodes的副本避免在迭代过程中修改集合 const childNodes = Array.from(node.childNodes); for (const childNode of childNodes) { @@ -97,10 +97,10 @@ function processNodeEmojis(node, className, tagName) { modified = true; } } - + return modified; } - + return false; } @@ -135,32 +135,32 @@ function getContentHash(el) { */ function processEmoji(el, binding) { if (!el) return; - + // 解析绑定值 - const { className, tagName } = parseBinding(binding); + const {className, tagName} = parseBinding(binding); if (!className) return; - + // 获取或初始化元素状态 let state = elementStates.get(el) || {}; elementStates.set(el, state); - + // 计算内容哈希值,用于快速比较 const contentHash = getContentHash(el); - + // 如果内容哈希值与上次相同且已处理过,则跳过 if (state.contentHash === contentHash && state.processed) { return; } - + // 创建一个克隆节点进行处理 const clone = el.cloneNode(true); - + // 递归处理所有文本节点 if (processNodeEmojis(clone, className, tagName)) { // 使用requestAnimationFrame优化DOM更新 requestAnimationFrame(() => { el.innerHTML = clone.innerHTML; - + // 更新元素状态 state.contentHash = contentHash; state.processed = true; @@ -169,6 +169,53 @@ function processEmoji(el, binding) { } } +/** + * 将文本中的emoji转换成HTML标签 + * @param {string} text - 输入文本 + * @param {string} className - 添加给emoji的类名 + * @param {string} tagName - 包裹emoji的标签名,默认为'span' + * @returns {string} - 转换后的HTML字符串 + * + * 示例: + * transformEmojiToHtml("我❤️你", "heart", "span") + * // 返回: "我❤️你" + */ +export function transformEmojiToHtml(text, className, tagName = 'span') { + // 参数验证 + if (typeof text !== 'string') return ''; + if (!className || typeof className !== 'string') return text; + if (!tagName || typeof tagName !== 'string') tagName = 'span'; + + // 快速检查是否包含emoji + if (!containsEmoji(text)) return text; + + // 重置正则索引并准备替换 + emojiRegex.lastIndex = 0; + let result = ''; + let lastIndex = 0; + let match; + + // 逐个匹配emoji并替换 + while ((match = emojiRegex.exec(text)) !== null) { + // 添加emoji前的文本 + if (match.index > lastIndex) { + result += text.substring(lastIndex, match.index); + } + + // 添加包装后的emoji + result += `<${tagName} class="${className}">${match[0]}${tagName}>`; + + lastIndex = emojiRegex.lastIndex; + } + + // 添加剩余文本 + if (lastIndex < text.length) { + result += text.substring(lastIndex); + } + + return result; +} + // 创建防抖处理函数 - 使用更短的防抖时间提高响应速度 const debouncedProcessEmoji = debounce(processEmoji, 20); @@ -177,24 +224,24 @@ export default { // 直接处理,不使用防抖 processEmoji(el, binding); }, - + update(el, binding) { // 获取元素状态 const state = elementStates.get(el) || {}; - + // 只有当绑定值变化时才重新处理 if (binding.oldValue !== binding.value) { debouncedProcessEmoji(el, binding); return; } - + // 内容变化时也需要重新处理 const contentHash = getContentHash(el); if (state.contentHash !== contentHash) { debouncedProcessEmoji(el, binding); } }, - + unbind(el) { // 清理元素状态 elementStates.delete(el); diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue index f4c2ab009..c38449351 100644 --- a/resources/assets/js/pages/manage.vue +++ b/resources/assets/js/pages/manage.vue @@ -138,7 +138,7 @@ @click="toggleRoute('project', {projectId: item.id})">