refactor: overhaul UI for streamlined user experience

Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

View File

@@ -0,0 +1,531 @@
/**
* 增强版截图服务
* 结合多种技术方案确保能够成功捕获页面截图
*/
// 动态导入所需库
let htmlToImageModule: any = null
let domToImageModule: any = null
// 加载所需库
async function loadScreenshotLibraries() {
if (!htmlToImageModule) {
try {
htmlToImageModule = await import("html-to-image")
console.log("html-to-image库加载成功")
} catch (error) {
console.warn("html-to-image库加载失败:", error)
}
}
if (!domToImageModule) {
try {
domToImageModule = await import("dom-to-image")
console.log("dom-to-image库加载成功")
} catch (error) {
console.warn("dom-to-image库加载失败:", error)
}
}
return {
htmlToImage: htmlToImageModule,
domToImage: domToImageModule,
}
}
/**
* 使用多种方法尝试捕获页面截图
* @param targetElement 目标元素iframe或窗口
* @param url 页面URL
* @param options 配置选项
* @returns 截图的base64数据URL
*/
export async function captureScreenshot(
targetElement: HTMLIFrameElement | Window,
url: string,
options: {
maxRetries?: number
retryDelay?: number
useDirectCapture?: boolean
useWindowOpen?: boolean
} = {},
): Promise<string> {
const { maxRetries = 3, retryDelay = 1000, useDirectCapture = true, useWindowOpen = false } = options
let lastError: any = null
// 尝试多种方法捕获截图
for (let retry = 0; retry < maxRetries; retry++) {
try {
// 如果重试,等待一段时间
if (retry > 0) {
await new Promise((resolve) => setTimeout(resolve, retryDelay))
console.log(`尝试第 ${retry + 1} 次捕获截图...`)
}
// 方法1: 使用html-to-image或dom-to-image库
if (useDirectCapture) {
const libraries = await loadScreenshotLibraries()
// 确定目标元素
let element: HTMLElement | null = null
if (targetElement instanceof HTMLIFrameElement) {
try {
// 尝试访问iframe内容
const iframeDocument = targetElement.contentDocument || targetElement.contentWindow?.document
if (iframeDocument) {
element = iframeDocument.documentElement
}
} catch (error) {
console.warn("无法访问iframe内容可能是跨域限制:", error)
}
} else if (targetElement instanceof Window) {
element = targetElement.document.documentElement
}
if (element) {
// 尝试使用html-to-image
if (libraries.htmlToImage) {
try {
console.log("使用html-to-image捕获截图...")
const dataUrl = await libraries.htmlToImage.toPng(element, {
quality: 0.95,
cacheBust: true,
pixelRatio: 2,
skipAutoScale: true,
style: {
"background-color": "#ffffff",
},
})
if (dataUrl && dataUrl.startsWith("data:image/png;base64,")) {
console.log("html-to-image捕获成功!")
return dataUrl
}
} catch (error) {
console.warn("html-to-image捕获失败:", error)
}
}
// 尝试使用dom-to-image
if (libraries.domToImage) {
try {
console.log("使用dom-to-image捕获截图...")
const dataUrl = await libraries.domToImage.toPng(element, {
quality: 0.95,
bgcolor: "#ffffff",
scale: 2,
})
if (dataUrl && dataUrl.startsWith("data:image/png;base64,")) {
console.log("dom-to-image捕获成功!")
return dataUrl
}
} catch (error) {
console.warn("dom-to-image捕获失败:", error)
}
}
}
}
// 方法2: 使用Canvas API
try {
console.log("使用Canvas API捕获截图...")
const screenshot = await captureWithCanvas(targetElement)
if (screenshot) {
console.log("Canvas API捕获成功!")
return screenshot
}
} catch (error) {
console.warn("Canvas API捕获失败:", error)
lastError = error
}
// 方法3: 使用postMessage通信
if (targetElement instanceof HTMLIFrameElement) {
try {
console.log("使用postMessage尝试捕获截图...")
const screenshot = await captureWithPostMessage(targetElement)
if (screenshot) {
console.log("postMessage捕获成功!")
return screenshot
}
} catch (error) {
console.warn("postMessage捕获失败:", error)
lastError = error
}
}
// 方法4: 使用window.open打开页面
if (useWindowOpen) {
try {
console.log("使用window.open尝试捕获截图...")
const screenshot = await captureWithWindowOpen(url)
if (screenshot) {
console.log("window.open捕获成功!")
return screenshot
}
} catch (error) {
console.warn("window.open捕获失败:", error)
lastError = error
}
}
} catch (error) {
console.error(`${retry + 1} 次捕获截图失败:`, error)
lastError = error
}
}
// 所有方法都失败,使用备用方案
console.warn("所有截图方法都失败,使用备用方案")
return createFallbackScreenshot(url, lastError)
}
/**
* 使用Canvas API捕获截图
*/
async function captureWithCanvas(target: HTMLIFrameElement | Window): Promise<string | null> {
try {
let document: Document | null = null
let width = 1200
let height = 800
if (target instanceof HTMLIFrameElement) {
if (!target.contentDocument && !target.contentWindow?.document) {
throw new Error("无法访问iframe内容")
}
document = target.contentDocument || target.contentWindow?.document || null
width = target.offsetWidth || width
height = target.offsetHeight || height
} else {
document = target.document
width = target.innerWidth
height = target.innerHeight
}
if (!document) {
throw new Error("无法获取文档对象")
}
// 创建canvas
const canvas = document.createElement("canvas")
canvas.width = width
canvas.height = height
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建canvas上下文")
}
// 绘制白色背景
ctx.fillStyle = "#ffffff"
ctx.fillRect(0, 0, width, height)
// 创建一个图像对象
const img = new Image()
img.crossOrigin = "anonymous"
// 将文档转换为SVG数据URL
const data = new XMLSerializer().serializeToString(document.documentElement)
const svg = new Blob([data], { type: "image/svg+xml" })
const url = URL.createObjectURL(svg)
// 等待图像加载
await new Promise((resolve, reject) => {
img.onload = resolve
img.onerror = reject
img.src = url
})
// 绘制图像
ctx.drawImage(img, 0, 0, width, height)
URL.revokeObjectURL(url)
// 返回数据URL
return canvas.toDataURL("image/png")
} catch (error) {
console.error("Canvas捕获失败:", error)
return null
}
}
/**
* 使用postMessage与iframe通信捕获截图
*/
async function captureWithPostMessage(iframe: HTMLIFrameElement): Promise<string | null> {
return new Promise((resolve, reject) => {
// 设置超时
const timeout = setTimeout(() => {
window.removeEventListener("message", messageHandler)
reject(new Error("postMessage截图超时"))
}, 5000)
// 消息处理函数
function messageHandler(event: MessageEvent) {
if (event.data && event.data.type === "screenshot" && event.data.dataUrl) {
clearTimeout(timeout)
window.removeEventListener("message", messageHandler)
resolve(event.data.dataUrl)
}
}
// 监听消息
window.addEventListener("message", messageHandler)
// 向iframe发送消息
try {
iframe.contentWindow?.postMessage({ type: "requestScreenshot" }, "*")
} catch (error) {
clearTimeout(timeout)
window.removeEventListener("message", messageHandler)
reject(error)
}
})
}
/**
* 使用window.open打开页面并捕获截图
*/
async function captureWithWindowOpen(url: string): Promise<string | null> {
return new Promise((resolve, reject) => {
try {
// 打开新窗口
const newWindow = window.open(url, "_blank", "width=1200,height=800")
if (!newWindow) {
reject(new Error("无法打开新窗口,可能被浏览器阻止"))
return
}
// 设置超时
const timeout = setTimeout(() => {
if (newWindow) newWindow.close()
reject(new Error("window.open截图超时"))
}, 10000)
// 等待页面加载完成
newWindow.onload = async () => {
try {
// 等待一段时间,确保页面完全渲染
await new Promise((resolve) => setTimeout(resolve, 2000))
// 尝试捕获截图
const libraries = await loadScreenshotLibraries()
let screenshot = null
if (libraries.htmlToImage) {
try {
screenshot = await libraries.htmlToImage.toPng(newWindow.document.documentElement)
} catch (error) {
console.warn("html-to-image捕获失败:", error)
}
}
if (!screenshot && libraries.domToImage) {
try {
screenshot = await libraries.domToImage.toPng(newWindow.document.documentElement)
} catch (error) {
console.warn("dom-to-image捕获失败:", error)
}
}
// 关闭窗口
newWindow.close()
clearTimeout(timeout)
if (screenshot) {
resolve(screenshot)
} else {
reject(new Error("无法捕获新窗口的截图"))
}
} catch (error) {
newWindow.close()
clearTimeout(timeout)
reject(error)
}
}
// 处理加载错误
newWindow.onerror = (event) => {
newWindow.close()
clearTimeout(timeout)
reject(new Error("新窗口加载出错"))
}
} catch (error) {
reject(error)
}
})
}
/**
* 创建备用截图
* 当所有方法都失败时,生成一个包含页面信息的模拟截图
*/
function createFallbackScreenshot(url: string, error: any): string {
// 提取页面标题
const pathParts = url.split("/")
const pageName = pathParts[pathParts.length - 1] || "页面"
// 创建canvas
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
if (!ctx) {
throw new Error("无法创建canvas上下文")
}
// 设置canvas尺寸
const width = 1200
const height = 800
canvas.width = width
canvas.height = height
// 绘制背景
ctx.fillStyle = "#ffffff"
ctx.fillRect(0, 0, width, height)
// 绘制边框
ctx.strokeStyle = "#e5e7eb"
ctx.lineWidth = 2
ctx.strokeRect(1, 1, width - 2, height - 2)
// 绘制页面信息
ctx.fillStyle = "#f9fafb"
ctx.fillRect(0, 0, width, 80)
ctx.fillStyle = "#111827"
ctx.font = "bold 24px system-ui"
ctx.fillText(pageName, 20, 40)
ctx.fillStyle = "#6b7280"
ctx.font = "14px system-ui"
ctx.fillText(url, 20, 65)
// 绘制内容区域
ctx.fillStyle = "#f3f4f6"
ctx.fillRect(20, 100, width - 40, height - 120)
// 添加错误信息
ctx.fillStyle = "#ef4444"
ctx.font = "16px system-ui"
ctx.fillText("截图捕获失败", 40, 140)
ctx.fillStyle = "#6b7280"
ctx.font = "14px system-ui"
ctx.fillText(`错误信息: ${error ? error.message || "未知错误" : "未知错误"}`, 40, 170)
ctx.fillText(`URL: ${url}`, 40, 200)
ctx.fillText("请尝试手动截图或刷新页面后重试", 40, 230)
// 添加水印
ctx.save()
ctx.globalAlpha = 0.1
ctx.fillStyle = "#000000"
ctx.font = "bold 48px system-ui"
ctx.translate(width / 2, height / 2)
ctx.rotate(-Math.PI / 6)
ctx.textAlign = "center"
ctx.fillText("备用截图", 0, 0)
ctx.restore()
// 返回base64数据
return canvas.toDataURL("image/png")
}
/**
* 注入截图脚本到页面
* 这个函数会向页面注入一个脚本,使页面能够响应截图请求
*/
export function injectScreenshotScript(window: Window): void {
try {
const script = window.document.createElement("script")
script.textContent = `
// 监听截图请求
window.addEventListener('message', async function(event) {
if (event.data && event.data.type === 'requestScreenshot') {
try {
// 动态加载html2canvas
const script = document.createElement('script');
script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.min.js';
script.onload = async function() {
try {
// 使用html2canvas捕获页面
const canvas = await html2canvas(document.documentElement, {
allowTaint: true,
useCORS: true,
scale: 2,
backgroundColor: '#ffffff'
});
// 发送截图数据
const dataUrl = canvas.toDataURL('image/png');
window.parent.postMessage({
type: 'screenshot',
dataUrl: dataUrl
}, '*');
} catch (error) {
console.error('html2canvas捕获失败:', error);
window.parent.postMessage({
type: 'screenshotError',
error: error.toString()
}, '*');
}
};
script.onerror = function(error) {
console.error('加载html2canvas失败:', error);
window.parent.postMessage({
type: 'screenshotError',
error: 'Failed to load html2canvas'
}, '*');
};
document.head.appendChild(script);
} catch (error) {
console.error('处理截图请求时出错:', error);
window.parent.postMessage({
type: 'screenshotError',
error: error.toString()
}, '*');
}
}
});
console.log('截图脚本已注入');
`
window.document.head.appendChild(script)
} catch (error) {
console.error("注入截图脚本失败:", error)
}
}
/**
* 使用服务器端API捕获截图
* 注意:这需要后端支持
*/
export async function captureWithServerAPI(url: string): Promise<string | null> {
try {
// 这里应该调用后端API
// 由于我们没有实际的后端API这里只是一个示例
console.log("尝试使用服务器端API捕获截图:", url)
// 模拟API调用
const response = await fetch("/api/screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ url }),
})
if (!response.ok) {
throw new Error(`API请求失败: ${response.status} ${response.statusText}`)
}
const data = await response.json()
if (data.success && data.screenshot) {
return data.screenshot
} else {
throw new Error(data.error || "未知错误")
}
} catch (error) {
console.error("服务器端API捕获失败:", error)
return null
}
}