chore: clean up lint warnings

This commit is contained in:
Simon
2025-10-12 00:34:17 +08:00
parent f923e8a6db
commit 8ac868ebe2
6 changed files with 573 additions and 458 deletions

View File

@@ -74,11 +74,14 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
{showLineNumbers && ( {showLineNumbers && (
<div className={`flex-shrink-0 px-4 py-4 ${lineNumbersClasses} border-r select-none`}> <div className={`flex-shrink-0 px-4 py-4 ${lineNumbersClasses} border-r select-none`}>
<div className="text-xs font-mono leading-6"> <div className="text-xs font-mono leading-6">
{lines.map((_, index) => ( {lines.map((line, lineIdx) => {
<div key={index} className="text-right"> const lineNum = lineIdx + 1
{index + 1} return (
</div> <div key={`${lineNum}-${line.substring(0, 20)}`} className="text-right">
))} {lineNum}
</div>
)
})}
</div> </div>
</div> </div>
)} )}

View File

@@ -1,9 +1,8 @@
/** /**
* JS 调试台,适合在文档中直接让用户运行代码,并且实时查看运行结果 * JS 调试台,适合在文档中直接让用户运行代码,并且实时查看运行结果
*/ */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/no-base-to-string */
import { KeyboardEvent, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' import { KeyboardEvent, useEffect, useImperativeHandle, useRef, useState } from 'react'
import HighlightSyntax from './HighlightSyntax' import HighlightSyntax from './HighlightSyntax'
@@ -29,7 +28,6 @@ class ConsoleInterceptor {
} }
static getInstance() { static getInstance() {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!ConsoleInterceptor.instance) { if (!ConsoleInterceptor.instance) {
ConsoleInterceptor.instance = new ConsoleInterceptor() ConsoleInterceptor.instance = new ConsoleInterceptor()
} }
@@ -90,6 +88,7 @@ interface JSConsoleProps {
height?: string height?: string
onExecute?: (code: string, result: unknown) => void onExecute?: (code: string, result: unknown) => void
placeholder?: string placeholder?: string
ref?: React.Ref<JSConsoleRef>
} }
export interface JSConsoleRef { export interface JSConsoleRef {
@@ -104,266 +103,267 @@ interface OutputItem {
timestamp: number timestamp: number
} }
const JSConsole = forwardRef<JSConsoleRef, JSConsoleProps>( const DEFAULT_CONTEXT = {}
(
{ context = {}, height = '400px', onExecute, placeholder = 'Enter JavaScript code...' },
ref
) => {
const [input, setInput] = useState('')
const [outputs, setOutputs] = useState<OutputItem[]>([])
const [isExecuting, setIsExecuting] = useState(false)
const inputRef = useRef<HTMLTextAreaElement>(null)
const outputRef = useRef<HTMLDivElement>(null)
// 持久的执行上下文,用于多轮对话共享作用域 function JSConsole({
const executionContextRef = useRef<Record<string, unknown>>({}) context = DEFAULT_CONTEXT,
height = '400px',
onExecute,
placeholder = 'Enter JavaScript code...',
ref,
}: JSConsoleProps) {
const [input, setInput] = useState('')
const [outputs, setOutputs] = useState<OutputItem[]>([])
const [isExecuting, setIsExecuting] = useState(false)
const inputRef = useRef<HTMLTextAreaElement>(null)
const outputRef = useRef<HTMLDivElement>(null)
// 格式化结果 // 持久的执行上下文,用于多轮对话共享作用域
const formatResult = (value: unknown): string => { const executionContextRef = useRef<Record<string, unknown>>({})
if (value === null) return 'null'
if (value === undefined) return 'undefined' // 格式化结果
if (typeof value === 'string') return `"${value}"` const formatResult = (value: unknown): string => {
if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]` if (value === null) return 'null'
if (typeof value === 'object') { if (value === undefined) return 'undefined'
try { if (typeof value === 'string') return `"${value}"`
return JSON.stringify(value, null, 2) if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`
} catch { if (typeof value === 'object') {
return value.toString() try {
} return JSON.stringify(value, null, 2)
} catch {
return value.toString()
} }
return String(value)
} }
return String(value)
}
// 全局console拦截处理 // 全局console拦截处理
useEffect(() => { useEffect(() => {
const interceptor = ConsoleInterceptor.getInstance() const interceptor = ConsoleInterceptor.getInstance()
const handleGlobalConsole = (type: string, args: unknown[]) => { const handleGlobalConsole = (type: string, args: unknown[]) => {
const content = args.map((arg) => formatResult(arg)).join(' ') const content = args.map((arg) => formatResult(arg)).join(' ')
const outputItem: OutputItem = { const outputItem: OutputItem = {
type: type as any, type: type as any,
content: content, content: content,
timestamp: Date.now(),
}
setOutputs((prev) => [...prev, outputItem])
// 自动滚动到底部
setTimeout(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
}
}, 0)
}
interceptor.subscribe(handleGlobalConsole)
return () => {
interceptor.unsubscribe(handleGlobalConsole)
}
}, [])
// 执行代码
const executeCode = async (code: string): Promise<unknown> => {
if (!code.trim()) return
setIsExecuting(true)
// 添加输入到输出
const inputItem: OutputItem = {
type: 'input',
content: code,
timestamp: Date.now(), timestamp: Date.now(),
} }
setOutputs((prev) => [...prev, inputItem]) setOutputs((prev) => [...prev, outputItem])
try { // 自动滚动到底部
// 创建异步函数以支持 await setTimeout(() => {
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
// 合并外部上下文和持久执行上下文
const allContext = { ...context, ...executionContextRef.current }
const contextKeys = Object.keys(allContext)
const contextValues = Object.values(allContext)
// 注入 console.log 重定向
const logs: string[] = []
const mockConsole = {
log: (...args: unknown[]) => {
logs.push(args.map((arg) => formatResult(arg)).join(' '))
},
error: (...args: unknown[]) => {
logs.push('ERROR: ' + args.map((arg) => formatResult(arg)).join(' '))
},
warn: (...args: unknown[]) => {
logs.push('WARN: ' + args.map((arg) => formatResult(arg)).join(' '))
},
} }
}, 0)
}
// 检测代码是否是表达式还是语句 interceptor.subscribe(handleGlobalConsole)
const trimmedCode = code.trim()
const isExpression =
!trimmedCode.includes(';') &&
!trimmedCode.startsWith('const ') &&
!trimmedCode.startsWith('let ') &&
!trimmedCode.startsWith('var ') &&
!trimmedCode.startsWith('function ') &&
!trimmedCode.startsWith('class ') &&
!trimmedCode.startsWith('if ') &&
!trimmedCode.startsWith('for ') &&
!trimmedCode.startsWith('while ') &&
!trimmedCode.startsWith('try ') &&
!trimmedCode.startsWith('{') &&
!trimmedCode.includes('\n')
// 如果是表达式,自动添加 return return () => {
const codeToExecute = isExpression ? `return ${code}` : code interceptor.unsubscribe(handleGlobalConsole)
}
}, [])
const wrappedCode = ` // 执行代码
const executeCode = async (code: string): Promise<unknown> => {
if (!code.trim()) return
setIsExecuting(true)
// 添加输入到输出
const inputItem: OutputItem = {
type: 'input',
content: code,
timestamp: Date.now(),
}
setOutputs((prev) => [...prev, inputItem])
try {
// 创建异步函数以支持 await
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
// 合并外部上下文和持久执行上下文
const allContext = { ...context, ...executionContextRef.current }
const contextKeys = Object.keys(allContext)
const contextValues = Object.values(allContext)
// 注入 console.log 重定向
const logs: string[] = []
const mockConsole = {
log: (...args: unknown[]) => {
logs.push(args.map((arg) => formatResult(arg)).join(' '))
},
error: (...args: unknown[]) => {
logs.push('ERROR: ' + args.map((arg) => formatResult(arg)).join(' '))
},
warn: (...args: unknown[]) => {
logs.push('WARN: ' + args.map((arg) => formatResult(arg)).join(' '))
},
}
// 检测代码是否是表达式还是语句
const trimmedCode = code.trim()
const isExpression =
!trimmedCode.includes(';') &&
!trimmedCode.startsWith('const ') &&
!trimmedCode.startsWith('let ') &&
!trimmedCode.startsWith('var ') &&
!trimmedCode.startsWith('function ') &&
!trimmedCode.startsWith('class ') &&
!trimmedCode.startsWith('if ') &&
!trimmedCode.startsWith('for ') &&
!trimmedCode.startsWith('while ') &&
!trimmedCode.startsWith('try ') &&
!trimmedCode.startsWith('{') &&
!trimmedCode.includes('\n')
// 如果是表达式,自动添加 return
const codeToExecute = isExpression ? `return ${code}` : code
const wrappedCode = `
return (async function() { return (async function() {
${codeToExecute} ${codeToExecute}
})(); })();
` `
// 执行代码 // 执行代码
const func = new AsyncFunction('console', ...contextKeys, wrappedCode) const func = new AsyncFunction('console', ...contextKeys, wrappedCode)
const result = await func(mockConsole, ...contextValues) const result = await func(mockConsole, ...contextValues)
// 添加 console.log 输出 // 添加 console.log 输出
if (logs.length > 0) { if (logs.length > 0) {
const logItem: OutputItem = { const logItem: OutputItem = {
type: 'log', type: 'log',
content: logs.join('\n'), content: logs.join('\n'),
timestamp: Date.now(),
}
setOutputs((prev) => [...prev, logItem])
}
// 总是添加执行结果输出(包括 undefined
const outputItem: OutputItem = {
type: 'output',
content: formatResult(result),
timestamp: Date.now(), timestamp: Date.now(),
} }
setOutputs((prev) => [...prev, outputItem]) setOutputs((prev) => [...prev, logItem])
onExecute?.(code, result)
return result
} catch (error) {
const errorItem: OutputItem = {
type: 'error',
content: error instanceof Error ? error.message : String(error),
timestamp: Date.now(),
}
setOutputs((prev) => [...prev, errorItem])
throw error
} finally {
setIsExecuting(false)
// 滚动到底部
setTimeout(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
}
}, 0)
} }
}
// 清空控制台 // 总是添加执行结果输出(包括 undefined
const clear = () => {
setOutputs([])
// 同时清空执行上下文
executionContextRef.current = {}
}
// 添加输出
const appendOutput = (content: string) => {
const outputItem: OutputItem = { const outputItem: OutputItem = {
type: 'output', type: 'output',
content, content: formatResult(result),
timestamp: Date.now(), timestamp: Date.now(),
} }
setOutputs((prev) => [...prev, outputItem]) setOutputs((prev) => [...prev, outputItem])
onExecute?.(code, result)
return result
} catch (error) {
const errorItem: OutputItem = {
type: 'error',
content: error instanceof Error ? error.message : String(error),
timestamp: Date.now(),
}
setOutputs((prev) => [...prev, errorItem])
throw error
} finally {
setIsExecuting(false)
// 滚动到底部
setTimeout(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
}
}, 0)
} }
}
// 暴露方法给父组件 // 清空控制台
useImperativeHandle(ref, () => ({ const clear = () => {
executeCode, setOutputs([])
clear, // 同时清空执行上下文
appendOutput, executionContextRef.current = {}
})) }
// 处理键盘事件 // 添加输出
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { const appendOutput = (content: string) => {
if (e.key === 'Enter') { const outputItem: OutputItem = {
if (e.shiftKey) { type: 'output',
// Shift+Enter 换行 content,
return timestamp: Date.now(),
} else { }
// Enter 执行 setOutputs((prev) => [...prev, outputItem])
e.preventDefault() }
if (!isExecuting && input.trim()) {
executeCode(input) // 暴露方法给父组件
setInput('') useImperativeHandle(ref, () => ({
setTimeout(() => inputRef.current?.focus(), 0) executeCode,
} clear,
appendOutput,
}))
// 处理键盘事件
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
if (e.shiftKey) {
// Shift+Enter 换行
return
} else {
// Enter 执行
e.preventDefault()
if (!isExecuting && input.trim()) {
executeCode(input)
setInput('')
setTimeout(() => inputRef.current?.focus(), 0)
} }
} }
} }
function getPrompt(type: string) {
let prompt = ' '
if (type === 'input') {
prompt = '>'
} else if (type === 'output') {
prompt = '<'
}
return prompt
}
return (
<div className={styles.console} style={{ height }}>
{/* 历史记录和输入区域 */}
<div className={styles.historyArea} ref={outputRef}>
{outputs.map((item, index) => (
<div key={index} className={`${styles.historyItem} ${styles[item.type]}`}>
<span className={styles.prompt}>{getPrompt(item.type)}</span>
<pre className={styles.content}>
<HighlightSyntax code={item.content} />
</pre>
</div>
))}
{isExecuting && (
<div className={styles.historyItem}>
<span className={styles.prompt}>{'> '}</span>
<span className={styles.executing}>Executing...</span>
</div>
)}
</div>
{/* 当前输入行 */}
<div className={styles.inputArea}>
<span className={styles.prompt}>{'> '}</span>
<textarea
ref={inputRef}
className={styles.input}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={isExecuting}
rows={1}
style={{
height: Math.min(Math.max(20, input.split('\n').length * 20), 120),
}}
/>
</div>
</div>
)
} }
)
JSConsole.displayName = 'JSConsole' function getPrompt(type: string) {
let prompt = ' '
if (type === 'input') {
prompt = '>'
} else if (type === 'output') {
prompt = '<'
}
return prompt
}
return (
<div className={styles.console} style={{ height }}>
{/* 历史记录和输入区域 */}
<div className={styles.historyArea} ref={outputRef}>
{outputs.map((item) => (
<div key={item.timestamp} className={`${styles.historyItem} ${styles[item.type]}`}>
<span className={styles.prompt}>{getPrompt(item.type)}</span>
<pre className={styles.content}>
<HighlightSyntax code={item.content} />
</pre>
</div>
))}
{isExecuting && (
<div className={styles.historyItem}>
<span className={styles.prompt}>{'> '}</span>
<span className={styles.executing}>Executing...</span>
</div>
)}
</div>
{/* 当前输入行 */}
<div className={styles.inputArea}>
<span className={styles.prompt}>{'> '}</span>
<textarea
ref={inputRef}
className={styles.input}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={isExecuting}
rows={1}
style={{
height: Math.min(Math.max(20, input.split('\n').length * 20), 120),
}}
/>
</div>
</div>
)
}
export default JSConsole export default JSConsole

View File

@@ -1,4 +1,3 @@
// import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { Route, Router, Switch } from 'wouter' import { Route, Router, Switch } from 'wouter'
import { useHashLocation } from 'wouter/use-hash-location' import { useHashLocation } from 'wouter/use-hash-location'
@@ -8,23 +7,11 @@ import { default as TestPagesRouter } from './test-pages/router.tsx'
import './index.css' import './index.css'
let baseURL: string
// 如果是 localhost就用 /
// 如果是其他环境,阶段到 index.html
if (window.location.hostname === 'localhost') {
baseURL = '/'
} else {
baseURL = window.location.pathname.split('index.html')[0] + 'index.html'
}
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
// <StrictMode>
<Router hook={useHashLocation}> <Router hook={useHashLocation}>
<Switch> <Switch>
<Route path="/test-pages" component={TestPagesRouter} nest /> <Route path="/test-pages" component={TestPagesRouter} nest />
<Route path="/" component={PagesRouter} nest /> <Route path="/" component={PagesRouter} nest />
</Switch> </Switch>
</Router> </Router>
// </StrictMode>
) )

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react' import { useEffect, useState } from 'react'
import { Link } from 'wouter' import { Link } from 'wouter'
interface UploadProgress { interface UploadProgress {
@@ -33,7 +33,7 @@ export default function AsyncTestPage() {
isRunning: false, isRunning: false,
progress: 0, progress: 0,
currentStep: '', currentStep: '',
logs: [] logs: [],
}) })
// 模拟实时数据更新 // 模拟实时数据更新
@@ -42,7 +42,7 @@ export default function AsyncTestPage() {
if (isRealTimeActive) { if (isRealTimeActive) {
interval = setInterval(() => { interval = setInterval(() => {
const newData = `数据更新 ${new Date().toLocaleTimeString()}: ${Math.floor(Math.random() * 1000)}` const newData = `数据更新 ${new Date().toLocaleTimeString()}: ${Math.floor(Math.random() * 1000)}`
setRealTimeData(prev => [newData, ...prev.slice(0, 9)]) // 保持最新10条 setRealTimeData((prev) => [newData, ...prev.slice(0, 9)]) // 保持最新10条
}, 2000) }, 2000)
} }
return () => { return () => {
@@ -59,50 +59,53 @@ export default function AsyncTestPage() {
progress: 0, progress: 0,
status: 'uploading', status: 'uploading',
speed: '0 KB/s', speed: '0 KB/s',
timeRemaining: '计算中...' timeRemaining: '计算中...',
} }
setUploads(prev => [...prev, newUpload]) setUploads((prev) => [...prev, newUpload])
// 模拟上传进度 // 模拟上传进度
const interval = setInterval(() => { const interval = setInterval(() => {
setUploads(prev => prev.map(upload => { setUploads((prev) =>
if (upload.id === uploadId) { prev.map((upload) => {
const newProgress = Math.min(upload.progress + Math.random() * 15, 100) if (upload.id === uploadId) {
const speed = `${(Math.random() * 500 + 100).toFixed(0)} KB/s` const newProgress = Math.min(upload.progress + Math.random() * 15, 100)
const timeRemaining = newProgress >= 100 ? '完成' : `${Math.ceil((100 - newProgress) / 10)}` const speed = `${(Math.random() * 500 + 100).toFixed(0)} KB/s`
const timeRemaining =
// 模拟随机失败 newProgress >= 100 ? '完成' : `${Math.ceil((100 - newProgress) / 10)}`
if (newProgress > 50 && Math.random() < 0.1) {
clearInterval(interval) // 模拟随机失败
return { if (newProgress > 50 && Math.random() < 0.1) {
...upload, clearInterval(interval)
status: 'error' as const, return {
speed: '0 KB/s', ...upload,
timeRemaining: '失败' status: 'error' as const,
} speed: '0 KB/s',
} timeRemaining: '失败',
}
}
if (newProgress >= 100) {
clearInterval(interval)
return {
...upload,
progress: 100,
status: 'completed' as const,
speed,
timeRemaining,
}
}
if (newProgress >= 100) {
clearInterval(interval)
return { return {
...upload, ...upload,
progress: 100, progress: newProgress,
status: 'completed' as const,
speed, speed,
timeRemaining timeRemaining,
} }
} }
return upload
return { })
...upload, )
progress: newProgress,
speed,
timeRemaining
}
}
return upload
}))
}, 500) }, 500)
} }
@@ -117,36 +120,38 @@ export default function AsyncTestPage() {
title: '', title: '',
content: '', content: '',
timestamp: '', timestamp: '',
status: 'loading' status: 'loading',
})) }))
setDataItems(skeletonItems) setDataItems(skeletonItems)
// 逐个加载数据项 // 逐个加载数据项
for (let i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 1000)) await new Promise((resolve) => setTimeout(resolve, 800 + Math.random() * 1000))
setDataItems(prev => prev.map(item => { setDataItems((prev) =>
if (item.id === i) { prev.map((item) => {
// 模拟随机加载失败 if (item.id === i) {
if (Math.random() < 0.15) { // 模拟随机加载失败
if (Math.random() < 0.15) {
return {
...item,
status: 'error',
title: '加载失败',
content: '数据加载失败,请重试',
}
}
return { return {
...item, ...item,
status: 'error', status: 'loaded',
title: '加载失败', title: `数据项 ${i + 1}`,
content: '数据加载失败,请重试' content: `这是第 ${i + 1} 个数据项的内容,包含了一些示例文本用于展示加载效果。`,
timestamp: new Date().toLocaleString(),
} }
} }
return item
return { })
...item, )
status: 'loaded',
title: `数据项 ${i + 1}`,
content: `这是第 ${i + 1} 个数据项的内容,包含了一些示例文本用于展示加载效果。`,
timestamp: new Date().toLocaleString()
}
}
return item
}))
} }
setIsLoadingData(false) setIsLoadingData(false)
@@ -158,7 +163,7 @@ export default function AsyncTestPage() {
isRunning: true, isRunning: true,
progress: 0, progress: 0,
currentStep: '初始化任务...', currentStep: '初始化任务...',
logs: ['任务开始'] logs: ['任务开始'],
}) })
const steps = [ const steps = [
@@ -169,55 +174,55 @@ export default function AsyncTestPage() {
{ name: '处理数据...', duration: 2500 }, { name: '处理数据...', duration: 2500 },
{ name: '生成报告...', duration: 2000 }, { name: '生成报告...', duration: 2000 },
{ name: '保存结果...', duration: 1000 }, { name: '保存结果...', duration: 1000 },
{ name: '清理资源...', duration: 500 } { name: '清理资源...', duration: 500 },
] ]
for (let i = 0; i < steps.length; i++) { for (let i = 0; i < steps.length; i++) {
const step = steps[i] const step = steps[i]
setLongRunningTask(prev => ({ setLongRunningTask((prev) => ({
...prev, ...prev,
currentStep: step.name, currentStep: step.name,
logs: [...prev.logs, `开始: ${step.name}`] logs: [...prev.logs, `开始: ${step.name}`],
})) }))
// 模拟步骤执行时间 // 模拟步骤执行时间
const startTime = Date.now() const startTime = Date.now()
while (Date.now() - startTime < step.duration) { while (Date.now() - startTime < step.duration) {
await new Promise(resolve => setTimeout(resolve, 100)) await new Promise((resolve) => setTimeout(resolve, 100))
const elapsed = Date.now() - startTime const elapsed = Date.now() - startTime
const stepProgress = Math.min((elapsed / step.duration) * 100, 100) const stepProgress = Math.min((elapsed / step.duration) * 100, 100)
const totalProgress = ((i + stepProgress / 100) / steps.length) * 100 const totalProgress = ((i + stepProgress / 100) / steps.length) * 100
setLongRunningTask(prev => ({ setLongRunningTask((prev) => ({
...prev, ...prev,
progress: totalProgress progress: totalProgress,
})) }))
} }
setLongRunningTask(prev => ({ setLongRunningTask((prev) => ({
...prev, ...prev,
logs: [...prev.logs, `完成: ${step.name}`] logs: [...prev.logs, `完成: ${step.name}`],
})) }))
// 模拟随机失败 // 模拟随机失败
if (i === 3 && Math.random() < 0.2) { if (i === 3 && Math.random() < 0.2) {
setLongRunningTask(prev => ({ setLongRunningTask((prev) => ({
...prev, ...prev,
isRunning: false, isRunning: false,
currentStep: '任务失败', currentStep: '任务失败',
logs: [...prev.logs, '错误: 数据下载失败,请重试'] logs: [...prev.logs, '错误: 数据下载失败,请重试'],
})) }))
return return
} }
} }
setLongRunningTask(prev => ({ setLongRunningTask((prev) => ({
...prev, ...prev,
isRunning: false, isRunning: false,
progress: 100, progress: 100,
currentStep: '任务完成', currentStep: '任务完成',
logs: [...prev.logs, '任务成功完成!'] logs: [...prev.logs, '任务成功完成!'],
})) }))
} }
@@ -226,34 +231,38 @@ export default function AsyncTestPage() {
} }
const retryFailedUpload = (uploadId: string) => { const retryFailedUpload = (uploadId: string) => {
const failedUpload = uploads.find(u => u.id === uploadId) const failedUpload = uploads.find((u) => u.id === uploadId)
if (failedUpload) { if (failedUpload) {
setUploads(prev => prev.filter(u => u.id !== uploadId)) setUploads((prev) => prev.filter((u) => u.id !== uploadId))
simulateFileUpload(failedUpload.name) simulateFileUpload(failedUpload.name)
} }
} }
const retryDataLoad = (itemId: number) => { const retryDataLoad = (itemId: number) => {
setDataItems(prev => prev.map(item => { setDataItems((prev) =>
if (item.id === itemId) { prev.map((item) => {
return { ...item, status: 'loading', title: '', content: '', timestamp: '' }
}
return item
}))
setTimeout(() => {
setDataItems(prev => prev.map(item => {
if (item.id === itemId) { if (item.id === itemId) {
return { return { ...item, status: 'loading', title: '', content: '', timestamp: '' }
...item,
status: 'loaded',
title: `数据项 ${itemId + 1}`,
content: `这是重新加载的第 ${itemId + 1} 个数据项的内容。`,
timestamp: new Date().toLocaleString()
}
} }
return item return item
})) })
)
setTimeout(() => {
setDataItems((prev) =>
prev.map((item) => {
if (item.id === itemId) {
return {
...item,
status: 'loaded',
title: `数据项 ${itemId + 1}`,
content: `这是重新加载的第 ${itemId + 1} 个数据项的内容。`,
timestamp: new Date().toLocaleString(),
}
}
return item
})
)
}, 1000) }, 1000)
} }
@@ -261,9 +270,7 @@ export default function AsyncTestPage() {
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8"> <div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-6xl mx-auto px-4"> <div className="max-w-6xl mx-auto px-4">
<div className="mb-8"> <div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2"> <h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2"></h1>
</h1>
<p className="text-gray-600 dark:text-gray-300"> <p className="text-gray-600 dark:text-gray-300">
</p> </p>
@@ -301,34 +308,45 @@ export default function AsyncTestPage() {
"开始上传" "开始上传"
</div> </div>
) : ( ) : (
uploads.map(upload => ( uploads.map((upload) => (
<div key={upload.id} className="border border-gray-200 dark:border-gray-600 rounded-lg p-4"> <div
key={upload.id}
className="border border-gray-200 dark:border-gray-600 rounded-lg p-4"
>
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<span className="font-medium text-gray-900 dark:text-white"> <span className="font-medium text-gray-900 dark:text-white">
{upload.name} {upload.name}
</span> </span>
<span className={`text-sm ${ <span
upload.status === 'completed' ? 'text-green-600 dark:text-green-400' : className={`text-sm ${
upload.status === 'error' ? 'text-red-600 dark:text-red-400' : upload.status === 'completed'
'text-blue-600 dark:text-blue-400' ? 'text-green-600 dark:text-green-400'
}`}> : upload.status === 'error'
{upload.status === 'completed' ? '✓ 完成' : ? 'text-red-600 dark:text-red-400'
upload.status === 'error' ? '✗ 失败' : : 'text-blue-600 dark:text-blue-400'
'上传中...'} }`}
>
{upload.status === 'completed'
? '✓ 完成'
: upload.status === 'error'
? '✗ 失败'
: '上传中...'}
</span> </span>
</div> </div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2"> <div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
<div <div
className={`h-2 rounded-full transition-all duration-300 ${ className={`h-2 rounded-full transition-all duration-300 ${
upload.status === 'completed' ? 'bg-green-500' : upload.status === 'completed'
upload.status === 'error' ? 'bg-red-500' : ? 'bg-green-500'
'bg-blue-500' : upload.status === 'error'
? 'bg-red-500'
: 'bg-blue-500'
}`} }`}
style={{ width: `${upload.progress}%` }} style={{ width: `${upload.progress}%` }}
/> />
</div> </div>
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-300"> <div className="flex justify-between text-sm text-gray-600 dark:text-gray-300">
<span>{upload.progress.toFixed(1)}%</span> <span>{upload.progress.toFixed(1)}%</span>
<span>{upload.speed}</span> <span>{upload.speed}</span>
@@ -375,18 +393,16 @@ export default function AsyncTestPage() {
"开始更新" "开始更新"
</div> </div>
) : ( ) : (
realTimeData.map((data, index) => ( realTimeData.map((data) => (
<div <div
key={index} key={data}
className={`p-3 rounded-lg border transition-all duration-300 ${ className={`p-3 rounded-lg border transition-all duration-300 ${
index === 0 data === realTimeData[0]
? 'bg-blue-50 dark:bg-blue-900 border-blue-200 dark:border-blue-700' ? 'bg-blue-50 dark:bg-blue-900 border-blue-200 dark:border-blue-700'
: 'bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600' : 'bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600'
}`} }`}
> >
<span className="text-sm text-gray-900 dark:text-white"> <span className="text-sm text-gray-900 dark:text-white">{data}</span>
{data}
</span>
</div> </div>
)) ))
)} )}
@@ -413,8 +429,11 @@ export default function AsyncTestPage() {
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{dataItems.map(item => ( {dataItems.map((item) => (
<div key={item.id} className="border border-gray-200 dark:border-gray-600 rounded-lg p-4"> <div
key={item.id}
className="border border-gray-200 dark:border-gray-600 rounded-lg p-4"
>
{item.status === 'loading' ? ( {item.status === 'loading' ? (
<div className="animate-pulse"> <div className="animate-pulse">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4 mb-2"></div> <div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4 mb-2"></div>
@@ -458,9 +477,7 @@ export default function AsyncTestPage() {
{/* 长时间运行任务 */} {/* 长时间运行任务 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"> <h2 className="text-xl font-semibold text-gray-900 dark:text-white"></h2>
</h2>
<button <button
type="button" type="button"
onClick={startLongRunningTask} onClick={startLongRunningTask}
@@ -496,11 +513,17 @@ export default function AsyncTestPage() {
: :
</h4> </h4>
<div className="space-y-1"> <div className="space-y-1">
{longRunningTask.logs.map((log, index) => ( {longRunningTask.logs.map((log, logIdx) => {
<div key={index} className="text-sm text-gray-600 dark:text-gray-300 font-mono"> const logKey = `${logIdx + 1}-${log.substring(0, 30)}`
{log} return (
</div> <div
))} key={logKey}
className="text-sm text-gray-600 dark:text-gray-300 font-mono"
>
{log}
</div>
)
})}
</div> </div>
</div> </div>
)} )}

View File

@@ -10,7 +10,7 @@ export default function NavigationTestPage() {
const [notifications, setNotifications] = useState([ const [notifications, setNotifications] = useState([
{ id: 1, title: '新消息', content: '您有一条新的私信', time: '2分钟前', unread: true }, { id: 1, title: '新消息', content: '您有一条新的私信', time: '2分钟前', unread: true },
{ id: 2, title: '系统通知', content: '系统将于今晚维护', time: '1小时前', unread: true }, { id: 2, title: '系统通知', content: '系统将于今晚维护', time: '1小时前', unread: true },
{ id: 3, title: '订单更新', content: '您的订单已发货', time: '3小时前', unread: false } { id: 3, title: '订单更新', content: '您的订单已发货', time: '3小时前', unread: false },
]) ])
const handleBreadcrumbClick = (index: number) => { const handleBreadcrumbClick = (index: number) => {
@@ -19,14 +19,12 @@ export default function NavigationTestPage() {
} }
const markNotificationAsRead = (id: number) => { const markNotificationAsRead = (id: number) => {
setNotifications(prev => setNotifications((prev) =>
prev.map(notif => prev.map((notif) => (notif.id === id ? { ...notif, unread: false } : notif))
notif.id === id ? { ...notif, unread: false } : notif
)
) )
} }
const unreadCount = notifications.filter(n => n.unread).length const unreadCount = notifications.filter((n) => n.unread).length
return ( return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900"> <div className="min-h-screen bg-gray-50 dark:bg-gray-900">
@@ -36,17 +34,18 @@ export default function NavigationTestPage() {
<div className="flex justify-between items-center h-16"> <div className="flex justify-between items-center h-16">
{/* Logo */} {/* Logo */}
<div className="flex items-center"> <div className="flex items-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400"> <div className="text-2xl font-bold text-blue-600 dark:text-blue-400">TestNav</div>
TestNav
</div>
</div> </div>
{/* 主导航菜单 */} {/* 主导航菜单 */}
<div className="hidden md:flex space-x-8"> <div className="hidden md:flex space-x-8">
<a href="#" className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"> <a
href="#"
className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"
>
</a> </a>
{/* 产品下拉菜单 */} {/* 产品下拉菜单 */}
<div className="relative"> <div className="relative">
<button <button
@@ -54,25 +53,47 @@ export default function NavigationTestPage() {
className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center" className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center"
> >
<svg className="ml-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> className="ml-1 h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg> </svg>
</button> </button>
{isDropdownOpen && ( {isDropdownOpen && (
<div className="absolute top-full left-0 mt-1 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50"> <div className="absolute top-full left-0 mt-1 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50">
<div className="py-1"> <div className="py-1">
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<div className="border-t border-gray-200 dark:border-gray-600 my-1"></div> <div className="border-t border-gray-200 dark:border-gray-600 my-1"></div>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
</div> </div>
@@ -80,10 +101,16 @@ export default function NavigationTestPage() {
)} )}
</div> </div>
<a href="#" className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"> <a
href="#"
className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"
>
</a> </a>
<a href="#" className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"> <a
href="#"
className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors"
>
</a> </a>
</div> </div>
@@ -94,7 +121,12 @@ export default function NavigationTestPage() {
<div className="relative"> <div className="relative">
<button className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 p-2 rounded-full transition-colors"> <button className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 p-2 rounded-full transition-colors">
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-5 5v-5zM10.5 3.75a6 6 0 0 1 6 6v2.25l2.25 2.25v2.25H2.25V14.25L4.5 12V9.75a6 6 0 0 1 6-6z" /> <path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 17h5l-5 5v-5zM10.5 3.75a6 6 0 0 1 6 6v2.25l2.25 2.25v2.25H2.25V14.25L4.5 12V9.75a6 6 0 0 1 6-6z"
/>
</svg> </svg>
{unreadCount > 0 && ( {unreadCount > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center"> <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
@@ -114,24 +146,36 @@ export default function NavigationTestPage() {
U U
</div> </div>
</button> </button>
{isUserMenuOpen && ( {isUserMenuOpen && (
<div className="absolute top-full right-0 mt-1 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50"> <div className="absolute top-full right-0 mt-1 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50">
<div className="py-1"> <div className="py-1">
<div className="px-4 py-2 text-sm text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-600"> <div className="px-4 py-2 text-sm text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-600">
user@example.com user@example.com
</div> </div>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
</a> </a>
<div className="border-t border-gray-200 dark:border-gray-600 my-1"></div> <div className="border-t border-gray-200 dark:border-gray-600 my-1"></div>
<a href="#" className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"> <a
href="#"
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
退 退
</a> </a>
</div> </div>
@@ -148,25 +192,39 @@ export default function NavigationTestPage() {
<div className="max-w-7xl mx-auto px-4 py-3"> <div className="max-w-7xl mx-auto px-4 py-3">
<nav className="flex" aria-label="Breadcrumb"> <nav className="flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-2"> <ol className="flex items-center space-x-2">
{breadcrumbs.map((crumb, index) => ( {breadcrumbs.map((crumb, crumbIdx) => {
<li key={index} className="flex items-center"> const isLast = crumbIdx === breadcrumbs.length - 1
{index > 0 && ( const showSeparator = crumbIdx > 0
<svg className="h-4 w-4 text-gray-400 mx-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> return (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> <li key={`${crumb}-${crumbIdx + 1}`} className="flex items-center">
</svg> {showSeparator && (
)} <svg
<button className="h-4 w-4 text-gray-400 mx-2"
onClick={() => handleBreadcrumbClick(index)} fill="none"
className={`text-sm font-medium transition-colors ${ stroke="currentColor"
index === breadcrumbs.length - 1 viewBox="0 0 24 24"
? 'text-gray-500 dark:text-gray-400 cursor-default' >
: 'text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300' <path
}`} strokeLinecap="round"
> strokeLinejoin="round"
{crumb} strokeWidth={2}
</button> d="M9 5l7 7-7 7"
</li> />
))} </svg>
)}
<button
onClick={() => handleBreadcrumbClick(crumbIdx)}
className={`text-sm font-medium transition-colors ${
isLast
? 'text-gray-500 dark:text-gray-400 cursor-default'
: 'text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300'
}`}
>
{crumb}
</button>
</li>
)
})}
</ol> </ol>
</nav> </nav>
</div> </div>
@@ -183,7 +241,7 @@ export default function NavigationTestPage() {
{ id: 'products', label: '产品列表', icon: '📱' }, { id: 'products', label: '产品列表', icon: '📱' },
{ id: 'orders', label: '订单管理', icon: '📦' }, { id: 'orders', label: '订单管理', icon: '📦' },
{ id: 'analytics', label: '数据分析', icon: '📊' }, { id: 'analytics', label: '数据分析', icon: '📊' },
{ id: 'settings', label: '设置', icon: '⚙️' } { id: 'settings', label: '设置', icon: '⚙️' },
].map((tab) => ( ].map((tab) => (
<button <button
key={tab.id} key={tab.id}
@@ -231,8 +289,11 @@ export default function NavigationTestPage() {
<div> <div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2> <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2>
<div className="space-y-4"> <div className="space-y-4">
{['iPhone 15 Pro', 'MacBook Air', 'iPad Pro', 'Apple Watch'].map((product, index) => ( {['iPhone 15 Pro', 'MacBook Air', 'iPad Pro', 'Apple Watch'].map((product) => (
<div key={index} className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-600 rounded-lg"> <div
key={product}
className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-600 rounded-lg"
>
<div> <div>
<h3 className="font-medium text-gray-900 dark:text-white">{product}</h3> <h3 className="font-medium text-gray-900 dark:text-white">{product}</h3>
<p className="text-gray-500 dark:text-gray-400">...</p> <p className="text-gray-500 dark:text-gray-400">...</p>
@@ -253,31 +314,49 @@ export default function NavigationTestPage() {
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-600"> <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
<thead className="bg-gray-50 dark:bg-gray-700"> <thead className="bg-gray-50 dark:bg-gray-700">
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"></th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"></th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"></th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"></th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600"> <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600">
{[ {[
{ id: '#001', customer: '张三', status: '已发货', amount: '¥1,299' }, { id: '#001', customer: '张三', status: '已发货', amount: '¥1,299' },
{ id: '#002', customer: '李四', status: '处理中', amount: '¥2,599' }, { id: '#002', customer: '李四', status: '处理中', amount: '¥2,599' },
{ id: '#003', customer: '王五', status: '已完成', amount: '¥899' } { id: '#003', customer: '王五', status: '已完成', amount: '¥899' },
].map((order, index) => ( ].map((order) => (
<tr key={index}> <tr key={order.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{order.id}</td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{order.customer}</td> {order.id}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{order.customer}
</td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 py-1 text-xs font-medium rounded-full ${ <span
order.status === '已完成' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : className={`px-2 py-1 text-xs font-medium rounded-full ${
order.status === '已发货' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : order.status === '已完成'
'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
}`}> : order.status === '已发货'
? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
}`}
>
{order.status} {order.status}
</span> </span>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{order.amount}</td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{order.amount}
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@@ -291,13 +370,17 @@ export default function NavigationTestPage() {
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2> <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg"> <div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4"></h3> <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
</h3>
<div className="h-32 bg-gradient-to-r from-blue-400 to-purple-500 rounded-lg flex items-center justify-center text-white"> <div className="h-32 bg-gradient-to-r from-blue-400 to-purple-500 rounded-lg flex items-center justify-center text-white">
📈 📈
</div> </div>
</div> </div>
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg"> <div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4"></h3> <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
</h3>
<div className="h-32 bg-gradient-to-r from-green-400 to-blue-500 rounded-lg flex items-center justify-center text-white"> <div className="h-32 bg-gradient-to-r from-green-400 to-blue-500 rounded-lg flex items-center justify-center text-white">
🗺 🗺
</div> </div>
@@ -311,27 +394,46 @@ export default function NavigationTestPage() {
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2> <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"></h2>
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2"></h3> <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
</h3>
<div className="space-y-2"> <div className="space-y-2">
<label className="flex items-center"> <label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" defaultChecked /> <input
type="checkbox"
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
defaultChecked
/>
<span className="ml-2 text-gray-700 dark:text-gray-300"></span> <span className="ml-2 text-gray-700 dark:text-gray-300"></span>
</label> </label>
<label className="flex items-center"> <label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" /> <input
type="checkbox"
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="ml-2 text-gray-700 dark:text-gray-300"></span> <span className="ml-2 text-gray-700 dark:text-gray-300"></span>
</label> </label>
</div> </div>
</div> </div>
<div> <div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2"></h3> <h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
</h3>
<div className="space-y-2"> <div className="space-y-2">
<label className="flex items-center"> <label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" defaultChecked /> <input
type="checkbox"
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
defaultChecked
/>
<span className="ml-2 text-gray-700 dark:text-gray-300"></span> <span className="ml-2 text-gray-700 dark:text-gray-300"></span>
</label> </label>
<label className="flex items-center"> <label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" defaultChecked /> <input
type="checkbox"
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
defaultChecked
/>
<span className="ml-2 text-gray-700 dark:text-gray-300"></span> <span className="ml-2 text-gray-700 dark:text-gray-300"></span>
</label> </label>
</div> </div>
@@ -362,9 +464,9 @@ export default function NavigationTestPage() {
title: '新通知', title: '新通知',
content: `这是第 ${notifications.length + 1} 条通知`, content: `这是第 ${notifications.length + 1} 条通知`,
time: '刚刚', time: '刚刚',
unread: true unread: true,
} }
setNotifications(prev => [newNotif, ...prev]) setNotifications((prev) => [newNotif, ...prev])
}} }}
className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-md transition-colors" className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-md transition-colors"
> >
@@ -389,11 +491,17 @@ export default function NavigationTestPage() {
> >
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900 dark:text-white">{notification.title}</h4> <h4 className="font-medium text-gray-900 dark:text-white">
<p className="text-gray-600 dark:text-gray-300 text-sm">{notification.content}</p> {notification.title}
</h4>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{notification.content}
</p>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="text-xs text-gray-500 dark:text-gray-400">{notification.time}</span> <span className="text-xs text-gray-500 dark:text-gray-400">
{notification.time}
</span>
{notification.unread && ( {notification.unread && (
<div className="w-2 h-2 bg-blue-500 rounded-full"></div> <div className="w-2 h-2 bg-blue-500 rounded-full"></div>
)} )}
@@ -417,7 +525,12 @@ export default function NavigationTestPage() {
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
> >
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg> </svg>
</button> </button>
</div> </div>

View File

@@ -84,12 +84,11 @@ export class LLM {
usage: LanguageModelUsage usage: LanguageModelUsage
}> { }> {
const isClaude = this.config.modelName.slice(0, 8).includes('claude') const isClaude = this.config.modelName.slice(0, 8).includes('claude')
const isQwen = this.config.modelName.slice(0, 6).includes('qwen') // const isQwen = this.config.modelName.slice(0, 6).includes('qwen')
const isGPT = this.config.modelName.slice(0, 5).includes('gpt') // const isGPT = this.config.modelName.slice(0, 5).includes('gpt')
return await withRetry( return await withRetry(
async () => { async () => {
// try {
const result = await generateText({ const result = await generateText({
model: this.#model, model: this.#model,
messages, messages,
@@ -165,16 +164,6 @@ export class LLM {
toolResult, toolResult,
usage, usage,
} }
// } catch (error) {
// // handle ai-sdk internal error here
// // currently useless since we bypassed most of ai-sdk logic
// console.log('generateText error', error)
// console.log('APICallError', APICallError.isInstance(error))
// console.log('isNoSuchModelError', NoSuchModelError.isInstance(error))
// throw error
// }
}, },
// retry settings // retry settings
{ {