This commit is contained in:
2025-10-20 20:53:14 +08:00
parent e2e1e9e9e9
commit b5430eb8d0
7 changed files with 230 additions and 29 deletions

View File

@@ -0,0 +1,127 @@
import React, { useEffect, useRef, useState } from 'react';
import Graph from 'graphology';
import Sigma from 'sigma';
import { Mark } from '../../../modules/mark';
type Props = {
dataSource?: Mark[];
}
export const SigmaGraph = (props: Props) => {
const { dataSource = [] } = props;
const containerRef = useRef<HTMLDivElement>(null);
const sigmaRef = useRef<Sigma | null>(null);
const graphRef = useRef<Graph | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// 创建图实例
const graph = new Graph();
graphRef.current = graph;
// 创建 Sigma 实例
const sigma = new Sigma(graph, containerRef.current, {
renderLabels: true,
labelRenderedSizeThreshold: 8,
labelDensity: 8,
});
sigmaRef.current = sigma;
return () => {
sigma.kill();
};
}, []);
useEffect(() => {
if (!graphRef.current || !dataSource.length) return;
const graph = graphRef.current;
// 清空现有图数据
graph.clear();
// 添加节点
dataSource.forEach((mark, index) => {
graph.addNode(`node-${index}`, {
x: Math.random() * 800,
y: Math.random() * 600,
size: Math.random() * 20 + 5,
label: mark.title || `Mark ${index}`,
color: `#${Math.floor(Math.random() * 16777215).toString(16)}`,
});
});
// // 添加一些随机边(基于数据关系或随机生成)
// for (let i = 0; i < Math.min(dataSource.length - 1, 20); i++) {
// const sourceIndex = Math.floor(Math.random() * dataSource.length);
// const targetIndex = Math.floor(Math.random() * dataSource.length);
// if (sourceIndex !== targetIndex) {
// try {
// graph.addEdge(`node-${sourceIndex}`, `node-${targetIndex}`, {
// size: Math.random() * 5 + 1,
// color: '#999',
// });
// } catch (error) {
// // 边已存在,忽略错误
// }
// }
// }
// 刷新 Sigma 渲染
if (sigmaRef.current) {
sigmaRef.current.refresh();
}
}, [dataSource]);
// 添加交互事件处理
useEffect(() => {
if (!sigmaRef.current) return;
const sigma = sigmaRef.current;
// 节点点击事件
const handleNodeClick = (event: any) => {
console.log('Node clicked:', event.node);
};
// 节点悬停事件
const handleNodeHover = (event: any) => {
console.log('Node hovered:', event.node);
};
sigma.on('clickNode', handleNodeClick);
sigma.on('enterNode', handleNodeHover);
return () => {
sigma.off('clickNode', handleNodeClick);
sigma.off('enterNode', handleNodeHover);
};
}, []);
return (
<div style={{ width: '100%', height: '100vh', position: 'relative' }}>
<div
ref={containerRef}
style={{
width: '100%',
height: '100%',
background: '#f5f5f5'
}}
/>
<div style={{
position: 'absolute',
top: 10,
left: 10,
background: 'rgba(255,255,255,0.8)',
padding: '10px',
borderRadius: '4px'
}}>
<p>: {dataSource.length}</p>
<p>使</p>
</div>
</div>
);
};

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { Base } from "./table/index";
import { markService } from "../modules/mark-service";
import { SigmaGraph } from "./graph/sigma/index";
const tabs = [
{
key: 'table',
@@ -22,15 +23,22 @@ const tabs = [
export const BaseApp = () => {
const [activeTab, setActiveTab] = useState('table');
const [dataSource, setDataSource] = useState<any[]>([]);
useEffect(() => {
getMarks();
}, []);
const getMarks = async () => {
const marks = await markService.getAllMarks();
setDataSource(marks);
}
const renderContent = () => {
switch (activeTab) {
case 'table':
return <Base />;
return <Base dataSource={dataSource} />;
case 'graph':
return (
<div className="flex items-center justify-center h-96 text-gray-500">
<div className="w-full h-96">
<SigmaGraph dataSource={dataSource} />
</div>
);
case 'world':

View File

@@ -1,17 +1,26 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useEffect } from 'react';
import { Table } from './Table';
import { DetailModal } from './DetailModal';
import { mockMarks, Mark } from '../mock/collection';
import { Mark } from '../mock/collection';
import { TableColumn, ActionButton } from './types';
export const Base = () => {
type Props = {
dataSource?: Mark[];
}
export const Base = (props: Props) => {
const { dataSource = [] } = props;
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [data, setData] = useState<Mark[]>(mockMarks);
const [data, setData] = useState<Mark[]>([]);
const [detailModalVisible, setDetailModalVisible] = useState(false);
const [currentRecord, setCurrentRecord] = useState<Mark | null>(null);
useEffect(() => {
if (dataSource) {
setData(dataSource);
}
}, [dataSource]);
// 表格列配置
const columns: TableColumn<Mark>[] = [
{
@@ -193,7 +202,7 @@ export const Base = () => {
// 排序处理
const handleSort = (field: string, order: 'asc' | 'desc' | null) => {
if (!order) {
setData(mockMarks); // 重置为原始顺序
setData(dataSource); // 重置为原始顺序
return;
}

View File

@@ -29,27 +29,42 @@ export class MarkDB {
// 创建索引以支持查询
async createIndexes() {
// 检查索引是否已存在,而不是尝试创建
if (!this.db) {
throw new Error('数据库未初始化');
}
const transaction = this.db.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
// 检查索引是否存在
const indexNames = ['uid', 'markType', 'tags', 'createdAt', 'title'];
const existingIndexes = Array.from(store.indexNames);
console.log('现有索引:', existingIndexes);
const missingIndexes = indexNames.filter(name => !existingIndexes.includes(name));
if (missingIndexes.length > 0) {
console.warn('缺少索引:', missingIndexes);
console.log('请删除数据库并重新初始化以创建缺少的索引');
} else {
console.log('所有索引都已存在');
try {
// PouchDB 创建索引的正确方式
const indexes = [
{ index: { fields: ['uid'] } },
{ index: { fields: ['markType'] } },
{ index: { fields: ['tags'] } },
{ index: { fields: ['createdAt'] } },
{ index: { fields: ['title'] } },
{ index: { fields: ['uid', 'markType'] } },
{ index: { fields: ['createdAt', 'uid'] } }
];
const results = await Promise.allSettled(
indexes.map(indexDef => this.db.createIndex(indexDef))
);
// 检查索引创建结果
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`索引 ${index + 1} 创建成功:`, result.value);
} else {
// 如果索引已存在PouchDB 会返回错误,这是正常的
if (result.reason?.status !== 409) {
console.warn(`索引 ${index + 1} 创建失败:`, result.reason);
}
}
});
console.log('索引初始化完成');
} catch (error) {
console.error('创建索引失败:', error);
throw error;
}
}

View File

@@ -84,7 +84,7 @@ export const ChatInterface: React.FC = () => {
<Bot className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-xl font-semibold text-gray-900">AI </h1>
<h1 className="text-xl font-semibold text-gray-900"></h1>
<p className="text-sm text-gray-500">线 · </p>
</div>
</div>