按钮组件,链接显示修复

This commit is contained in:
2026-08-03 16:08:52 +08:00
parent e057e33601
commit 849e473c7d
3 changed files with 381 additions and 42 deletions

View File

@@ -467,6 +467,35 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
return text
}
// 判断链接是否为可下载文件
const isDownloadLink = (url) => {
const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
]
const extPattern = supportedExtensions.join('|')
const extRegex = new RegExp(`\\.(${extPattern})(?:\\?|$)`, 'i')
return extRegex.test(url)
}
// 渲染下载按钮 HTML
const renderDownloadBtn = (href, displayName) => {
const ext = ((href.split('.').pop() || '').split('?')[0] || '').toLowerCase()
const fileInfo = getFileTypeInfo(ext)
const name = displayName || extractFileNameFromUrl(href)
return '<span class="chat-inline-link chat-download-btn" data-url="' + href + '">'
+ '<span class="chat-dl-icon">' + fileInfo.emoji + '</span>'
+ '<span class="chat-dl-name">' + name + '</span>'
+ '</span>'
}
// 支持的文件扩展名(用于原始 URL 检测)
const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
]
const extPattern = supportedExtensions.join('|')
// Markdown转HTML节点
const pareseMarkdown = (content) => {
if (!content) return ''
@@ -476,14 +505,60 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
try {
// 转义单词中的单下划线,防止 snarkdown 误解析为斜体
content = escapeLoneUnderscores(content)
// 修复1: URL 中的中文括号编码,防止 snarkdown 截断URL
// [text](http://xxx/高二(1)班.xlsx) → [text](http://xxx/高二%281%29班.xlsx)
content = content.replace(
/\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g,
(m, name, url) => '[' + name + '](' + url.replace(/\(/g, '%28').replace(/\)/g, '%29') + ')'
)
// 修复2: 误用括号 [高二](1)班成绩表 → 高二(1)班成绩表
// 当 (数字/短文本) 后紧跟中文时说明不是真实URL还原为纯文本
content = content.replace(
/\[([^\]]+)\]\((\d{1,2}|[^\s)]{1,3})\)(?=[\u4e00-\u9fff])/g,
'$1($2)'
)
// 先转换表格,再用 snarkdown 处理其他 Markdown
content = convertMarkdownTable(content)
let html = snarkdown(content)
// 将 <a href="..."> 替换为自定义 span避免 WebView 触发默认下载
// 步骤1暂存 <a> 标签,避免 raw URL 替换误伤 href 内的链接
const linkPlaceholders = []
html = html.replace(
/<a\s+href="([^"]*)"[^>]*>(.*?)<\/a>/gi,
'<span class="chat-inline-link" data-url="$1">$2</span>'
(match, href, innerText) => {
const idx = linkPlaceholders.length
linkPlaceholders.push({ href, innerText })
return '\x00LINK_' + idx + '\x00'
}
)
// 步骤2将正文中的原始文件 URL 替换为下载按钮
const rawUrlRegex = new RegExp(
`https?://[^\\s<>"'']+\\.(${extPattern})(?:[?#][^\\s<>"'']*)?`,
'gi'
)
html = html.replace(rawUrlRegex, (url) => {
return renderDownloadBtn(url)
})
// 步骤3恢复 <a> 标签并分类处理
html = html.replace(/\x00LINK_(\d+)\x00/g, (_, idx) => {
const { href, innerText } = linkPlaceholders[idx]
if (isDownloadLink(href)) {
const plainText = innerText
.replace(/<[^>]*>/g, ' ')
.replace(/&nbsp;/gi, ' ')
.replace(/\s+/g, ' ')
.trim()
return renderDownloadBtn(href, plainText)
}
// 普通链接保持原有样式
return '<span class="chat-inline-link" data-url="' + href + '">' + innerText + '</span>'
})
return html
} catch (e) {
console.error('解析失败', e)
@@ -839,6 +914,50 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
}
return urlStr
}
// 文件类型 → 图标和标签映射
const getFileTypeInfo = (ext) => {
const map = {
// 文档类
pdf: { emoji: '📕', label: 'PDF', color: '#e74c3c' },
doc: { emoji: '📘', label: 'Word', color: '#2b579a' },
docx: { emoji: '📘', label: 'Word', color: '#2b579a' },
xls: { emoji: '📗', label: 'Excel', color: '#217346' },
xlsx: { emoji: '📗', label: 'Excel', color: '#217346' },
ppt: { emoji: '📙', label: 'PPT', color: '#d24726' },
pptx: { emoji: '📙', label: 'PPT', color: '#d24726' },
txt: { emoji: '📄', label: '文本', color: '#666' },
md: { emoji: '📝', label: 'Markdown', color: '#333' },
markdown: { emoji: '📝', label: 'Markdown', color: '#333' },
csv: { emoji: '📊', label: 'CSV', color: '#217346' },
json: { emoji: '📋', label: 'JSON', color: '#f0a500' },
xml: { emoji: '📋', label: 'XML', color: '#e67e22' },
yaml: { emoji: '📋', label: 'YAML', color: '#e67e22' },
yml: { emoji: '📋', label: 'YAML', color: '#e67e22' },
// 压缩包
zip: { emoji: '📦', label: 'ZIP', color: '#f39c12' },
rar: { emoji: '📦', label: 'RAR', color: '#f39c12' },
'7z': { emoji: '📦', label: '7Z', color: '#f39c12' },
// 网页
html: { emoji: '🌐', label: '网页', color: '#e44d26' },
htm: { emoji: '🌐', label: '网页', color: '#e44d26' },
// 图片
jpg: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
jpeg: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
png: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
gif: { emoji: '🖼️', label: 'GIF', color: '#9b59b6' },
bmp: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
svg: { emoji: '🖼️', label: 'SVG', color: '#9b59b6' },
webp: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
ico: { emoji: '🖼️', label: '图标', color: '#9b59b6' },
tiff: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
tif: { emoji: '🖼️', label: '图片', color: '#9b59b6' },
// CAD/设计类
dxf: { emoji: '📐', label: 'CAD/DXF', color: '#2c3e50' },
}
return map[ext] || { emoji: '📎', label: ext.toUpperCase(), color: '#888' }
}
const showMessageDetail = (message) => {
detailLinks.value = extractLinks(message.content || '')
showMessageModal.value = true
@@ -1381,7 +1500,8 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
const takeConversationMessages = async () => {
try {
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
console.log("获取到的所有消息:", allmessages.value);
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
// 数据获取后执行滚动
scrollToBottom();
@@ -1654,6 +1774,43 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
text-decoration: underline;
word-break: break-all;
}
/* 可下载文件链接 → 按钮卡片样式 */
.chat-download-btn {
display: inline-flex !important;
align-items: center;
gap: 8rpx;
padding: 8rpx 14rpx;
margin: 4rpx 0;
background: #f5f7fb;
border: 2rpx solid #e4e8f0;
border-radius: 8rpx;
text-decoration: none !important;
color: #333 !important;
word-break: break-all;
transition: all 0.15s ease;
&:active {
background: #eef1f7;
border-color: #c8cde0;
transform: scale(0.98);
}
.chat-dl-icon {
font-size: 26rpx !important;
line-height: 1;
flex-shrink: 0;
}
.chat-dl-name {
font-size: 16px !important;
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
/* 流式气泡中的加载动画 */