前端:优化 Manifest 视图文件显示

- 文件名区域改为左右布局
- 左侧显示文件名(如 000001.sst)
- 右侧显示级别标签(如 L0、L1)
- 添加级别标签样式,使用主题色背景
This commit is contained in:
2025-10-09 20:03:53 +08:00
parent c4d79bc54b
commit dd8a534931
43 changed files with 3142 additions and 761 deletions

179
table.go
View File

@@ -5,7 +5,9 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
@@ -203,8 +205,181 @@ func OpenTable(opts *TableOptions) (*Table, error) {
return table, nil
}
// Insert 插入数据
func (t *Table) Insert(data map[string]any) error {
// Insert 插入数据(支持单条或批量)
// 支持的类型:
// - map[string]any: 单条数据
// - []map[string]any: 批量数据
// - *struct{}: 单个结构体指针
// - []struct{}: 结构体切片
// - []*struct{}: 结构体指针切片
func (t *Table) Insert(data any) error {
// 1. 将输入转换为 []map[string]any
rows, err := t.normalizeInsertData(data)
if err != nil {
return err
}
// 2. 批量插入
return t.insertBatch(rows)
}
// normalizeInsertData 将各种输入格式转换为 []map[string]any
func (t *Table) normalizeInsertData(data any) ([]map[string]any, error) {
// 处理 nil
if data == nil {
return nil, fmt.Errorf("data cannot be nil")
}
// 获取反射值
val := reflect.ValueOf(data)
typ := reflect.TypeOf(data)
// 如果是指针,解引用
if typ.Kind() == reflect.Ptr {
if val.IsNil() {
return nil, fmt.Errorf("data pointer cannot be nil")
}
val = val.Elem()
typ = val.Type()
}
switch typ.Kind() {
case reflect.Map:
// map[string]any - 单条
m, ok := data.(map[string]any)
if !ok {
return nil, fmt.Errorf("expected map[string]any, got %T", data)
}
return []map[string]any{m}, nil
case reflect.Slice:
// 检查切片元素类型
elemType := typ.Elem()
// []map[string]any
if elemType.Kind() == reflect.Map {
maps, ok := data.([]map[string]any)
if !ok {
return nil, fmt.Errorf("expected []map[string]any, got %T", data)
}
return maps, nil
}
// []*struct{} 或 []struct{}
if elemType.Kind() == reflect.Ptr {
elemType = elemType.Elem()
}
if elemType.Kind() == reflect.Struct {
// 将每个结构体转换为 map
var rows []map[string]any
for i := 0; i < val.Len(); i++ {
elem := val.Index(i)
// 如果是指针,解引用
if elem.Kind() == reflect.Ptr {
if elem.IsNil() {
continue // 跳过 nil 指针
}
elem = elem.Elem()
}
m, err := t.structToMap(elem.Interface())
if err != nil {
return nil, fmt.Errorf("convert struct at index %d: %w", i, err)
}
rows = append(rows, m)
}
return rows, nil
}
return nil, fmt.Errorf("unsupported slice element type: %s", elemType.Kind())
case reflect.Struct:
// struct{} - 单个结构体
m, err := t.structToMap(data)
if err != nil {
return nil, err
}
return []map[string]any{m}, nil
default:
return nil, fmt.Errorf("unsupported data type: %T (kind: %s)", data, typ.Kind())
}
}
// structToMap 将结构体转换为 map[string]any
func (t *Table) structToMap(v any) (map[string]any, error) {
val := reflect.ValueOf(v)
typ := reflect.TypeOf(v)
if typ.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
if typ.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct, got %s", typ.Kind())
}
result := make(map[string]any)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// 跳过未导出的字段
if !field.IsExported() {
continue
}
// 获取字段名
fieldName := field.Name
tag := field.Tag.Get("srdb")
// 跳过忽略的字段
if tag == "-" {
continue
}
// 解析 tag 获取字段名
if tag != "" {
parts := strings.Split(tag, ";")
if parts[0] != "" {
fieldName = parts[0]
} else {
// 使用 snake_case 转换
fieldName = camelToSnake(field.Name)
}
} else {
// 没有 tag使用 snake_case 转换
fieldName = camelToSnake(field.Name)
}
// 获取字段值
fieldVal := val.Field(i)
result[fieldName] = fieldVal.Interface()
}
return result, nil
}
// insertBatch 批量插入数据
func (t *Table) insertBatch(rows []map[string]any) error {
if len(rows) == 0 {
return nil
}
// 逐条插入
for _, data := range rows {
if err := t.insertSingle(data); err != nil {
return err
}
}
return nil
}
// insertSingle 插入单条数据
func (t *Table) insertSingle(data map[string]any) error {
// 1. 验证 Schema
if err := t.schema.Validate(data); err != nil {
return NewError(ErrCodeSchemaValidationFailed, err)