Files
database_render/web/templates/list.html
2025-08-06 17:21:27 +08:00

399 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.TableAlias}} - 数据管理系统</title>
<link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/components/sidebar.css">
<link rel="stylesheet" href="/static/css/components/content.css">
<link rel="stylesheet" href="/static/css/responsive.css">
</head>
<body>
<div class="app-container">
<!-- 左侧导航栏 -->
<aside class="sidebar" role="navigation" aria-label="主导航">
<!-- Logo/标题区域 -->
<div class="sidebar-header">
<div class="brand">
<svg class="brand-icon" width="32" height="32" viewBox="0 0 24 24" fill="none">
<path d="M4 6h16M4 12h16M4 18h7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<div class="brand-text">
<h1 class="brand-title">数据管理系统</h1>
<p class="brand-subtitle">{{.TableAlias}}</p>
</div>
</div>
<!-- 主题切换 -->
<button class="theme-toggle"
aria-label="切换主题"
title="切换明暗主题">
<svg class="sun-icon" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z"/>
</svg>
<svg class="moon-icon" width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
<path d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z"/>
</svg>
</button>
</div>
<!-- 智能搜索框 -->
<div class="sidebar-search">
<div class="search-container">
<svg class="search-icon" width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2"/>
</svg>
<input type="search"
class="sidebar-search-input"
placeholder="快速查找表格..."
aria-label="搜索数据表"
oninput="filterTables(this.value)">
</div>
</div>
<!-- 分组导航 -->
<nav class="sidebar-nav" aria-label="数据表导航">
<h2 class="nav-section-title">数据表</h2>
<ul class="nav-list" role="list">
{{range .Tables}}
<li class="nav-item">
<a href="/?table={{.Name}}"
class="nav-link {{if eq .Name $.Table}}active{{end}}"
role="menuitem"
aria-current="{{if eq .Name $.Table}}page{{end}}"
title="{{.Alias}} ({{.Name}}) - {{.Description}}">
<div class="nav-link-content">
<svg class="nav-icon" width="18" height="18" fill="none" viewBox="0 0 24 24">
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2z" stroke="currentColor" stroke-width="2"/>
<path d="M8 5v14M16 5v14" stroke="currentColor" stroke-width="2"/>
</svg>
<div class="nav-text-content">
<span class="nav-title">{{.Alias}}</span>
<span class="nav-subtitle">{{.Name}}</span>
<span class="nav-description">{{.RowCount}} 条记录</span>
</div>
{{if eq .Name $.Table}}
<div class="nav-indicator" aria-hidden="true"></div>
{{end}}
</div>
</a>
</li>
{{end}}
</ul>
</nav>
</aside>
<!-- 移动端遮罩层 -->
<div class="sidebar-backdrop" onclick="closeSidebar()" aria-hidden="true"></div>
<!-- 右侧主内容区 -->
<main class="main-content" role="main">
<!-- 移动端菜单按钮 -->
<header class="content-header" role="banner">
<div class="header-toolbar">
<!-- 移动端菜单 -->
<button class="mobile-menu-toggle"
onclick="toggleSidebar()"
aria-label="打开导航菜单"
aria-controls="sidebar"
aria-expanded="false">
<svg class="menu-icon" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
<!-- 面包屑导航 -->
<nav class="breadcrumb" aria-label="当前位置">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item" role="listitem">
<a href="/" class="breadcrumb-link">首页</a>
</li>
<li class="breadcrumb-item" role="listitem" aria-current="page">
<span class="breadcrumb-current">{{.TableAlias}}</span>
</li>
</ol>
</nav>
<!-- 工具栏 -->
<div class="header-tools">
<!-- 高级搜索 -->
<div class="search-container">
<div class="search-box">
<svg class="search-icon" width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2"/>
</svg>
<input type="search"
id="searchInput"
class="search-input"
placeholder="在 {{.TableAlias}} 中搜索..."
value="{{.Search}}"
aria-label="搜索数据"
oninput="debounceSearch(this.value)"
onkeydown="handleSearchKeydown(event)">
<button class="search-clear"
onclick="clearSearch()"
aria-label="清除搜索"
style="display: {{if .Search}}block{{else}}none{{end}}">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M6 18L18 6M6 6l12 12" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
</div>
<button class="search-button" onclick="performSearch()" aria-label="搜索">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
</div>
<!-- 视图控制 -->
<div class="view-controls">
<button class="view-button active"
onclick="setViewMode('table')"
aria-label="表格视图"
title="表格视图">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2z" stroke="currentColor" stroke-width="2"/>
<path d="M8 5v14M16 5v14" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
<button class="view-button"
onclick="setViewMode('cards')"
aria-label="卡片视图"
title="卡片视图">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
</div>
<!-- 卡片布局控制 -->
<div class="cards-layout-controls" id="cardsLayoutControls" style="display: none;">
<select class="cards-per-row-select"
onchange="changeCardsPerRow(this.value)"
aria-label="每行卡片数量">
<option value="1">1 列</option>
<option value="2" selected>2 列</option>
<option value="3">3 列</option>
<option value="4">4 列</option>
<option value="6">6 列</option>
</select>
</div>
<!-- 导出功能 -->
<div class="export-dropdown">
<button class="export-button"
onclick="toggleExportMenu()"
aria-label="导出数据"
aria-haspopup="true"
aria-expanded="false">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24">
<path d="M12 10v6m0 0l-3-3m3 3l3-3m2-8H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V4a2 2 0 00-2-2z" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
<div class="export-menu" role="menu" aria-orientation="vertical">
<button class="export-option" role="menuitem" onclick="exportData('csv')">导出 CSV</button>
<button class="export-option" role="menuitem" onclick="exportData('json')">导出 JSON</button>
<button class="export-option" role="menuitem" onclick="exportData('excel')">导出 Excel</button>
</div>
</div>
</div>
</div>
<!-- 分页信息 -->
<div class="pagination-info">
<span class="pagination-text">
显示 {{.StartRecord}} - {{.EndRecord}} / {{.Total}} 条记录
</span>
<select class="per-page-select"
onchange="changePerPage(this.value)"
aria-label="每页显示条数">
<option value="10" {{if eq .PerPage 10}}selected{{end}}>10 条/页</option>
<option value="25" {{if eq .PerPage 25}}selected{{end}}>25 条/页</option>
<option value="50" {{if eq .PerPage 50}}selected{{end}}>50 条/页</option>
<option value="100" {{if eq .PerPage 100}}selected{{end}}>100 条/页</option>
</select>
</div>
</header>
<!-- 主内容区域 -->
<div class="content-main">
<div class="content-container">
<!-- 数据展示 -->
<div class="data-display">
<!-- 表格视图 -->
<div class="data-table-container">
<table class="data-table">
<thead>
<tr>
{{range $col := .Columns}}
{{if $col.ShowInList}}
<th>{{$col.Alias}}</th>
{{end}}
{{end}}
<th>操作</th>
</tr>
</thead>
<tbody>
{{range $row := .Data}}
<tr>
{{range $col := $.Columns}}
{{if $col.ShowInList}}
<td>
{{if eq $col.RenderType "text"}}
{{truncate (index $row $col.Name) 50}}
{{else}}
{{index $row $col.Name}}
{{end}}
</td>
{{end}}
{{end}}
<td>
<button class="action-button" onclick="showDetail('{{index $row "id"}}')">
查看详情
</button>
</td>
</tr>
{{end}}
</tbody>
</table>
<!-- 空状态 -->
{{if eq (len .Data) 0}}
<div class="empty-state">
<svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<div class="empty-state-title">暂无数据</div>
<div class="empty-state-description">
{{if .Search}}
没有找到与"{{.Search}}"相关的记录
{{else}}
当前表格暂无数据
{{end}}
</div>
</div>
{{end}}
</div>
<!-- 卡片视图 -->
<div class="data-cards-container" style="display: none;">
<div class="data-cards" data-columns="2">
{{range $row := .Data}}
<div class="data-card">
<div class="data-card-title">
{{index $row (index $.Columns 0).Name}}
</div>
{{range $i, $col := $.Columns}}
{{if and $col.ShowInList (gt $i 0) (lt $i 4)}}
<div class="data-card-item">
<span class="data-card-label">{{$col.Alias}}:</span>
<span class="data-card-value">
{{if eq $col.RenderType "text"}}
{{truncate (index $row $col.Name) 30}}
{{else}}
{{index $row $col.Name}}
{{end}}
</span>
</div>
{{end}}
{{end}}
<div style="margin-top: 12px;">
<button class="action-button" onclick="showDetail('{{index $row "id"}}')">
查看详情
</button>
</div>
</div>
{{end}}
</div>
<!-- 空状态 -->
{{if eq (len .Data) 0}}
<div class="empty-state">
<svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
<div class="empty-state-title">暂无数据</div>
<div class="empty-state-description">
{{if .Search}}
没有找到与"{{.Search}}"相关的记录
{{else}}
当前表格暂无数据
{{end}}
</div>
</div>
{{end}}
</div>
</div>
<!-- 分页 - 固定在底部 -->
{{if gt .Pages 1}}
<div class="pagination">
<div class="pagination-info">
显示 {{.StartRecord}} - {{.EndRecord}} / {{.Total}} 条记录
</div>
<div class="pagination-controls">
{{if gt .Page 1}}
<a href="?table={{.Table}}&page={{sub .Page 1}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
class="pagination-button">上一页</a>
{{end}}
{{$start := sub .Page 2}}
{{$end := add .Page 2}}
{{if lt $start 1}}
{{$start = 1}}
{{$end = min 5 .Pages}}
{{end}}
{{if gt $end .Pages}}
{{$end = .Pages}}
{{$start = max 1 (sub .Pages 4)}}
{{end}}
{{range $i := until $end}}
{{if ge $i $start}}
{{if eq $i $.Page}}
<span class="pagination-button active">{{$i}}</span>
{{else}}
<a href="?table={{$.Table}}&page={{$i}}&per_page={{$.PerPage}}&search={{$.Search}}&sort={{$.SortField}}&order={{$.SortOrder}}"
class="pagination-button">{{$i}}</a>
{{end}}
{{end}}
{{end}}
{{if lt .Page .Pages}}
<a href="?table={{.Table}}&page={{add .Page 1}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
class="pagination-button">下一页</a>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
</main>
</div>
<!-- Detail Modal -->
<div id="detailModal" class="modal hidden">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">详情信息</h3>
<button onclick="closeModal()" class="modal-close" aria-label="关闭">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="modal-body">
<div id="detailContent" class="detail-content">
<!-- Content will be loaded here -->
</div>
</div>
</div>
</div>
<script src="/static/js/app.js"></script>
</body>
</html>