kuaifan 22926e19cd refactor: 统一 dootask:// 链接处理与资源格式指南
- 将 dootask:// 协议链接处理逻辑从 AIAssistant 迁移到 DialogMarkdown 组件
  - 新增 beforeNavigate prop 支持导航前回调(如关闭弹窗)
  - 后端 BotReceiveMsgTask 添加条件性资源格式指南提示词
  - 前端 ai.js 新增 SEARCH_AI_SYSTEM_PROMPT 和 DOOTASK_RESOURCE_FORMAT_GUIDE
  - SearchBox 改用统一的 SEARCH_AI_SYSTEM_PROMPT 常量
  - 重构 ai.js 代码组织,添加注释说明各常量用途
2026-01-13 10:31:31 +00:00

146 lines
4.5 KiB
Vue

<template>
<div @click="onCLick" class="markdown-body" v-html="html"></div>
</template>
<script>
import '../../../../sass/pages/components/dialog-markdown/markdown.less'
import {MarkdownConver} from "../../../utils/markdown";
export default {
name: "DialogMarkdown",
props: {
text: {
type: String,
default: ''
},
// 导航前回调(如关闭弹窗)
beforeNavigate: {
type: Function,
default: null
},
},
data() {
return {
mdi: null,
}
},
mounted() {
this.copyCodeBlock()
},
updated() {
this.copyCodeBlock()
},
computed: {
html({text}) {
return MarkdownConver(text)
}
},
methods: {
copyCodeBlock() {
const codeBlockWrapper = this.$el.querySelectorAll('.code-block-wrapper')
codeBlockWrapper.forEach((wrapper) => {
const copyBtn = wrapper.querySelector('.code-block-header__copy')
const codeBlock = wrapper.querySelector('.code-block-body')
if (copyBtn && codeBlock && copyBtn.getAttribute("data-copy") !== "click") {
copyBtn.setAttribute("data-copy", "click")
copyBtn.addEventListener('click', () => {
if (navigator.clipboard?.writeText)
navigator.clipboard.writeText(codeBlock.textContent ?? '')
else
this.copyContent({text: codeBlock.textContent ?? '', origin: true})
})
}
})
},
copyContent(options) {
const props = {origin: true, ...options}
let input
if (props.origin)
input = document.createElement('textarea')
else
input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.value = props.text
document.body.appendChild(input)
input.select()
if (document.execCommand('copy'))
document.execCommand('copy')
document.body.removeChild(input)
},
onCLick(e) {
const target = e.target;
if (target.tagName === 'A') {
const href = target.getAttribute('href');
if (href && href.startsWith('dootask://')) {
e.preventDefault();
e.stopPropagation();
this.handleDooTaskLink(href);
return;
}
}
this.$emit('click', e)
},
/**
* 处理 dootask:// 协议链接
* 格式: dootask://type/id 或 dootask://type/id1/id2
*/
handleDooTaskLink(href) {
const match = href.match(/^dootask:\/\/(\w+)\/(\d+)(?:\/(\d+))?$/);
if (!match) {
return;
}
const [, type, id, id2] = match;
const numId = parseInt(id, 10);
const numId2 = id2 ? parseInt(id2, 10) : null;
switch (type) {
case 'task':
this.$store.dispatch('openTask', { id: (numId2 && numId2 > 0) ? numId2 : numId });
break;
case 'project':
this.beforeNavigate?.();
this.goForward({ name: 'manage-project', params: { projectId: numId } });
break;
case 'file':
this.beforeNavigate?.();
this.goForward({ name: 'manage-file', params: { folderId: 0, fileId: null, shakeId: numId } });
this.$store.state.fileShakeId = numId;
setTimeout(() => {
this.$store.state.fileShakeId = 0;
}, 600);
break;
case 'contact':
this.$store.dispatch('openDialogUserid', numId).catch(({ msg }) => {
$A.modalError(msg);
});
break;
case 'message':
this.$store.dispatch('openDialog', numId).then(() => {
if (numId2) {
this.$store.state.dialogSearchMsgId = numId2;
}
}).catch(({ msg }) => {
$A.modalError(msg);
});
break;
}
}
}
}
</script>