feat: 初始提交(仅核心代码,已排除大文件)
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
.container { padding: 12px }
|
||||
.tabs { display: flex; gap: 8px; margin-bottom: 8px }
|
||||
.tab { padding: 6px 10px; border-radius: 6px; cursor: pointer }
|
||||
.tab.active { }
|
||||
.status { margin: 8px 0; padding: 8px; border: 1px solid #ffd666; background: #fffbe6; color: #a87000; border-radius: 6px }
|
||||
.panel { border: 1px solid #ddd; border-radius: 8px; padding: 8px; margin-bottom: 8px }
|
||||
.btn { padding: 6px 10px; border: none; background: #222; color: #fff; border-radius: 6px; cursor: pointer }
|
||||
.grid { width: 100%; border-collapse: collapse }
|
||||
.grid th, .grid td { padding: 8px; border-bottom: 1px solid #e6e9ef; text-align: left; vertical-align: top }
|
||||
.grid thead th { position: sticky; top: 0; background: var(--table-header-bg); z-index: 1 }
|
||||
.grid tbody tr:nth-child(odd) { background: #f7f8fb }
|
||||
.grid tbody tr:nth-child(even) { background: #f2f4f8 }
|
||||
.grid tbody tr:hover { background: #e9eef6 }
|
||||
.grid tbody tr { transition: background 120ms ease-in-out }
|
||||
.list-card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: 10px; padding: 6px }
|
||||
|
||||
.action-buttons { display: flex; gap: 8px; margin-bottom: 16px; padding: 0 4px }
|
||||
.action-btn {
|
||||
padding: 10px 20px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-2) 100%);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 12px rgba(37,99,235,0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||
transition: left 0.6s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(37,99,235,0.4);
|
||||
background: linear-gradient(135deg, var(--color-primary-2) 0%, var(--color-primary) 100%);
|
||||
}
|
||||
|
||||
.action-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.action-btn.view {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
box-shadow: 0 4px 12px rgba(16,185,129,0.3);
|
||||
}
|
||||
|
||||
.action-btn.view:hover {
|
||||
background: linear-gradient(135deg, #059669 0%, #10b981 100%);
|
||||
box-shadow: 0 8px 20px rgba(16,185,129,0.4);
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
box-shadow: 0 4px 12px rgba(239,68,68,0.3);
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
|
||||
box-shadow: 0 8px 20px rgba(239,68,68,0.4);
|
||||
}
|
||||
.col-actions { white-space: nowrap }
|
||||
.grid .col-actions { display:flex; flex-direction:column; align-items:center; gap:10px }
|
||||
.grid .col-actions .action-btn { width: 36px; height: 36px; border-radius: 50%; display:flex; align-items:center; justify-content:center; padding:0; font-size:16px }
|
||||
.grid .col-actions .action-btn:hover { filter: brightness(1.06) }
|
||||
.grid .col-actions .action-btn:disabled { opacity: .6; cursor: not-allowed }
|
||||
|
||||
/* 主题适配:暗黑风格(深色圆形按钮) */
|
||||
body.theme-expense[data-theme="dark"] .icon-toggle,
|
||||
body.theme-expense[data-theme="dark"] .icon-theme,
|
||||
body.theme-expense[data-theme="dark"] .font-size-toggle,
|
||||
body.theme-expense[data-theme="dark"] .action-buttons #records-export,
|
||||
body.theme-expense[data-theme="dark"] .action-buttons #stats-export,
|
||||
body.theme-expense[data-theme="dark"] .grid .col-actions .action-btn {
|
||||
border: 1px solid rgba(51,65,85,0.6);
|
||||
background: radial-gradient(ellipse at 30% 30%, #1f2937 0%, #0f172a 70%);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
color: #fbbf24;
|
||||
transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
|
||||
}
|
||||
|
||||
body.theme-expense[data-theme="dark"] .icon-toggle:hover,
|
||||
body.theme-expense[data-theme="dark"] .icon-theme:hover,
|
||||
body.theme-expense[data-theme="dark"] .font-size-toggle:hover,
|
||||
body.theme-expense[data-theme="dark"] .action-buttons #records-export:hover,
|
||||
body.theme-expense[data-theme="dark"] .action-buttons #stats-export:hover,
|
||||
body.theme-expense[data-theme="dark"] .grid .col-actions .action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.15);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* 主题适配:明亮风格(浅色圆形按钮) */
|
||||
body.theme-expense[data-theme="light"] .icon-toggle,
|
||||
body.theme-expense[data-theme="light"] .icon-theme,
|
||||
body.theme-expense[data-theme="light"] .font-size-toggle,
|
||||
body.theme-expense[data-theme="light"] .action-buttons #records-export,
|
||||
body.theme-expense[data-theme="light"] .action-buttons #stats-export,
|
||||
body.theme-expense[data-theme="light"] .grid .col-actions .action-btn {
|
||||
border: 1px solid rgba(229,231,235,0.8);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.9);
|
||||
color: var(--color-primary);
|
||||
transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
|
||||
}
|
||||
|
||||
body.theme-expense[data-theme="light"] .icon-toggle:hover,
|
||||
body.theme-expense[data-theme="light"] .icon-theme:hover,
|
||||
body.theme-expense[data-theme="light"] .font-size-toggle:hover,
|
||||
body.theme-expense[data-theme="light"] .action-buttons #records-export:hover,
|
||||
body.theme-expense[data-theme="light"] .action-buttons #stats-export:hover,
|
||||
body.theme-expense[data-theme="light"] .grid .col-actions .action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.95);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
|
||||
}
|
||||
|
||||
/* 模态框样式 - Windows风格适配 */
|
||||
.expense-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: none; justify-content: center; align-items: center; z-index: 1000; opacity: 0; transition: opacity 0.2s ease }
|
||||
.expense-modal-overlay.active { display: flex; opacity: 1 }
|
||||
|
||||
.expense-modal {
|
||||
background: var(--color-card);
|
||||
border-radius: 8px; /* Windows 11 style */
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0,0,0,0.05); /* Deep shadow + subtle border ring */
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 85vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: modalSlideIn 0.2s cubic-bezier(0.1, 0.9, 0.2, 1);
|
||||
border: 1px solid var(--color-border);
|
||||
overflow: hidden;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
|
||||
color: var(--color-text);
|
||||
position: relative; /* Override common.css .modal fixed position */
|
||||
inset: auto; /* Override common.css .modal inset: 0 */
|
||||
display: flex; /* Override common.css .modal display: grid */
|
||||
place-items: unset; /* Override common.css .modal place-items: center */
|
||||
}
|
||||
@keyframes modalSlideIn { from { opacity: 0; transform: scale(0.96) } to { opacity: 1; transform: scale(1) } }
|
||||
|
||||
.expense-modal-header {
|
||||
padding: 0;
|
||||
height: 44px; /* Windows title bar height */
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--color-card); /* Match body background (not blue) */
|
||||
user-select: none;
|
||||
border-bottom: 1px solid var(--color-border); /* Subtle separator */
|
||||
}
|
||||
.expense-modal-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
padding-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
.expense-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text);
|
||||
width: 46px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.1s;
|
||||
border-radius: 0 8px 0 0;
|
||||
}
|
||||
.expense-modal-close:hover { background: #c42b1c; color: white }
|
||||
.expense-modal-close:active { background: #b3261a; color: white }
|
||||
|
||||
.expense-modal-body {
|
||||
padding: 20px 24px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--color-card);
|
||||
width: auto; /* Override common.css .modal-body width: 520px */
|
||||
}
|
||||
|
||||
.expense-modal-footer {
|
||||
padding: 20px 24px;
|
||||
/* border-top: 1px solid var(--color-border); */ /* Seamless footer */
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
background: var(--color-card);
|
||||
padding-top: 0; /* Move closer to body if seamless */
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.loading-modal { max-width: 360px; padding: 0 } /* No padding on modal itself, handled by children */
|
||||
.loading-body { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 40px 20px }
|
||||
.loading-spinner { width: 40px; height: 40px; border: 3px solid var(--color-border); border-top-color: var(--color-primary); border-radius: 50%; animation: spin 1s linear infinite }
|
||||
@keyframes spin { to { transform: rotate(360deg) } }
|
||||
|
||||
.expense-modal-btn {
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px; /* Windows 11 standard button radius */
|
||||
cursor: pointer;
|
||||
font-family: "Segoe UI", system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
transition: all 0.1s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: none; /* Windows buttons are flat by default */
|
||||
}
|
||||
|
||||
.expense-modal-btn:focus {
|
||||
outline: 2px solid var(--color-text);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.expense-modal-btn.primary {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.expense-modal-btn.primary:hover {
|
||||
filter: brightness(1.1);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.expense-modal-btn.primary:active {
|
||||
filter: brightness(0.9);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.expense-modal-btn.secondary {
|
||||
background: var(--table-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.expense-modal-btn.secondary:hover {
|
||||
background: var(--table-row-hover);
|
||||
border-color: var(--color-muted);
|
||||
}
|
||||
|
||||
.expense-modal-btn.secondary:active {
|
||||
background: var(--table-row-odd);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.expense-modal-form { display: grid; gap: 16px }
|
||||
.expense-modal-form-row { display: grid; grid-template-columns: 100px minmax(0,1fr); gap: 12px; align-items: center }
|
||||
.expense-modal-form-row.full-width { grid-template-columns: minmax(0,1fr); display: flex; flex-direction: column; align-items: flex-start; gap: 8px }
|
||||
.expense-modal-label { font-weight: 400; color: var(--color-text); text-align: right; font-size: 14px }
|
||||
|
||||
.expense-modal-input, .expense-modal-select, .expense-modal-textarea {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px; /* Windows 11 input radius */
|
||||
padding: 0 10px;
|
||||
height: 32px; /* Standard height */
|
||||
font-size: 14px;
|
||||
font-family: "Segoe UI", system-ui, sans-serif;
|
||||
transition: border-color 0.1s, background 0.1s;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: var(--table-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.expense-modal-input:hover, .expense-modal-select:hover, .expense-modal-textarea:hover {
|
||||
background: var(--table-row-hover);
|
||||
}
|
||||
.expense-modal-input:focus, .expense-modal-select:focus, .expense-modal-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
border-bottom-width: 2px; /* Windows input focus indicator */
|
||||
background: var(--table-bg);
|
||||
}
|
||||
|
||||
.expense-modal-textarea {
|
||||
padding: 8px 10px;
|
||||
height: auto;
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
.expense-modal-textarea.large { min-height: 120px }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.expense-modal { width: 95%; height: auto; max-height: 90vh; border-radius: 8px; margin: 0 10px }
|
||||
.expense-modal-body { padding: 16px }
|
||||
.expense-modal-footer { padding: 16px; flex-direction: column-reverse }
|
||||
.expense-modal-btn { width: 100% }
|
||||
.expense-modal-form-row { grid-template-columns: 1fr; gap: 4px; align-items: flex-start }
|
||||
.expense-modal-label { text-align: left; font-size: 13px; color: var(--color-muted) }
|
||||
}
|
||||
@media (min-width: 769px) {
|
||||
.expense-modal { width: 800px; height: auto; max-width: 90vw; max-height: 90vh; overflow: hidden; margin: 0 }
|
||||
}
|
||||
|
||||
/* 字体大小切换按钮与面板定位 */
|
||||
body.theme-expense .panel.card { position: relative }
|
||||
.font-size-toggle { position: absolute; right: 12px; top: 10px; width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; z-index: 2; font-size: 18px; cursor: pointer }
|
||||
.font-size-toggle:hover { filter: brightness(1.06) }
|
||||
.panel .action-buttons { padding-right: 56px }
|
||||
.grid.small { font-size: 10px; line-height: 1.2 }
|
||||
.grid.large { font-size: 14px; line-height: 1.5 }
|
||||
|
||||
@media (min-width: 769px) {
|
||||
/* .modal styles removed as they are replaced by .expense-modal */
|
||||
}
|
||||
|
||||
/* 头部与标签 */
|
||||
.expense-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px }
|
||||
.expense-header h2 { margin: 0; font-size: 18px; line-height: 1.1 }
|
||||
.expense-tabs { flex: 1; margin: 0 12px }
|
||||
.expense-tabs .tabs.horizontal { display:flex; gap:8px; justify-content:center; align-items:center }
|
||||
.expense-tabs .tabs.horizontal .tab { display:flex; align-items:center }
|
||||
.expense-tabs .tabs.horizontal .tab svg { margin-right:8px }
|
||||
.header-actions { display:flex; gap:12px; align-items:center }
|
||||
.icon-toggle, .icon-theme { width: 32px; height: 32px; border-radius: 50%; cursor: pointer; display:flex; align-items:center; justify-content:center }
|
||||
.action-buttons #records-export,
|
||||
.action-buttons #stats-export { width: 36px; height: 36px; border-radius: 50%; cursor: pointer; display:inline-flex; align-items:center; justify-content:center; padding:0 }
|
||||
.action-buttons #records-export:hover,
|
||||
.action-buttons #stats-export:hover { filter: brightness(1.06) }
|
||||
.logo-ico { display:inline-block; width:20px; height:20px; margin-right:8px; background: linear-gradient(135deg,#93c5fd,#60a5fa); border-radius:6px; position:relative }
|
||||
.logo-ico::after { content:''; position:absolute; left:4px; top:3px; width:12px; height:12px; border-radius:50%; background:#fff; opacity:0.2 }
|
||||
|
||||
/* 主题变量桥接(供组件文件使用) */
|
||||
/* :root { --exp-card-bg:#ffffff; --exp-border:#e6e9ef } Removed, use --color-card and --color-border directly */
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
/* .modal styles removed */
|
||||
.action-buttons { flex-wrap: wrap }
|
||||
.action-btn { flex: 1; min-width: 120px; justify-content: center }
|
||||
.font-size-toggle { top: 8px; right: 8px; padding: 4px 8px; font-size: 11px }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>报销系统</title>
|
||||
<link rel="icon" href="/icons/expense.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="/common/common.css" />
|
||||
<link rel="stylesheet" href="/tools/expense/expense.css" />
|
||||
<link rel="stylesheet" href="/tools/expense/theme/tokens.css" />
|
||||
<link rel="stylesheet" href="/tools/expense/theme/components.css" />
|
||||
</head>
|
||||
<body class="theme-expense" data-theme="dark">
|
||||
<div id="topbar"></div>
|
||||
<main class="container">
|
||||
<div class="expense-header">
|
||||
<h2><span class="logo-ico" aria-hidden="true"></span>报销系统</h2>
|
||||
<div class="expense-tabs">
|
||||
<div class="tabs horizontal">
|
||||
<button id="tab-expense" class="tab active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24"><path d="M3 7h18v10H3z" fill="none" stroke="currentColor" stroke-width="2"/><path d="M3 7l9-4 9 4" fill="none" stroke="currentColor" stroke-width="2"/></svg>报销列表
|
||||
</button>
|
||||
<button id="tab-maint" class="tab">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10" stroke="currentColor" stroke-width="2"/></svg>数据维护
|
||||
</button>
|
||||
<button id="tab-stat" class="tab">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24"><path d="M4 18h4V10H4v8zm6 0h4V6h-4v12zm6 0h4V14h-4v4z" fill="currentColor"/></svg>统计分析
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button id="expense-toggle-theme" class="icon-theme" title="深色/浅色切换" aria-label="深色/浅色切换">🌙</button>
|
||||
<button id="expense-toggle-nav" class="icon-toggle" title="显示/隐藏导航" aria-label="显示/隐藏导航"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="expense-layout">
|
||||
<div id="expense-content">
|
||||
<div id="status" class="status" style="display:none"></div>
|
||||
<button id="font-size-toggle" class="font-size-toggle" title="切换字体大小">字体大小</button>
|
||||
|
||||
<section id="panel-expense" class="panel card">
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" onclick="openModal('expense', 'add')">
|
||||
<span>➕</span>新增报销记录
|
||||
</button>
|
||||
<div style="margin-left:16px; display:inline-flex; align-items:center; gap:12px; font-size:14px">
|
||||
<label style="cursor:pointer"><input type="radio" name="filter-cost-type" value="全部" checked> 全部</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="filter-cost-type" value="个人费用"> 个人费用</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="filter-cost-type" value="家庭费用"> 家庭费用</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="filter-cost-type" value="公司费用"> 公司费用</label>
|
||||
</div>
|
||||
<div style="flex:1"></div>
|
||||
<button class="action-btn" id="records-export" title="导出Excel" aria-label="导出Excel">
|
||||
⬇️
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="handleSort('date')" style="cursor:pointer; user-select:none">报销日期 <span id="sort-icon-date" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('cost_type')" style="cursor:pointer; user-select:none">费用类型 <span id="sort-icon-cost_type" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('type')" style="cursor:pointer; user-select:none">报销类型 <span id="sort-icon-type" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('amount')" style="cursor:pointer; user-select:none">报销金额 <span id="sort-icon-amount" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('subject')" style="cursor:pointer; user-select:none">报销主体 <span id="sort-icon-subject" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('content')" style="cursor:pointer; user-select:none">报销内容 <span id="sort-icon-content" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th onclick="handleSort('arrived')" style="cursor:pointer; user-select:none">是否到账 <span id="sort-icon-arrived" style="font-size:12px; margin-left:4px">🔻🔺</span></th>
|
||||
<th class="col-actions">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ex-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="panel-stat" class="panel card" style="display:none">
|
||||
<div class="card" style="margin-bottom:12px">
|
||||
<h3>统计分析</h3>
|
||||
<div class="action-buttons">
|
||||
<label class="modal-label" style="margin-right:8px">开始日期:</label>
|
||||
<input type="date" id="stat-start" class="modal-input" style="max-width:160px">
|
||||
<label class="modal-label" style="margin:0 8px">结束日期:</label>
|
||||
<input type="date" id="stat-end" class="modal-input" style="max-width:160px">
|
||||
<button class="action-btn" id="stat-refresh"><span>🔄</span>刷新</button>
|
||||
<button class="action-btn" id="stats-export" title="导出Excel" aria-label="导出Excel">⬇️</button>
|
||||
</div>
|
||||
<div id="stat-summary" class="list-card" style="padding:12px"></div>
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:12px">
|
||||
<h3>按费用类别统计</h3>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>类别</th><th>金额(元)</th><th>已到账(元)</th><th>未到账(元)</th><th>记录数</th></tr></thead>
|
||||
<tbody id="stat-cost-type-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:12px">
|
||||
<h3>按类型统计</h3>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>类型</th><th>金额(元)</th><th>已到账(元)</th><th>未到账(元)</th><th>记录数</th></tr></thead>
|
||||
<tbody id="stat-type-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>按主体统计</h3>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>主体</th><th>金额(元)</th><th>已到账(元)</th><th>未到账(元)</th><th>记录数</th></tr></thead>
|
||||
<tbody id="stat-subject-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>月度趋势</h3>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>月份</th><th>金额(元)</th><th>已到账(元)</th><th>未到账(元)</th><th>记录数</th></tr></thead>
|
||||
<tbody id="stat-monthly-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="panel-maint" class="panel card" style="display:none">
|
||||
<div class="action-buttons" style="margin-bottom:8px">
|
||||
<div style="display:inline-flex; align-items:center; gap:12px; font-size:14px">
|
||||
<label style="cursor:pointer"><input type="radio" name="maint-cost-type" value="全部" checked> 全部</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="maint-cost-type" value="个人费用"> 个人费用</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="maint-cost-type" value="家庭费用"> 家庭费用</label>
|
||||
<label style="cursor:pointer"><input type="radio" name="maint-cost-type" value="公司费用"> 公司费用</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:12px">
|
||||
<h3>报销类型</h3>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" onclick="openModal('cfgType', 'add')">
|
||||
<span>➕</span>新增类型
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>类型名称</th><th>费用类型</th><th class="col-actions">操作</th></tr></thead>
|
||||
<tbody id="cfg-type-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>报销主体</h3>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn" onclick="openModal('cfgSubject', 'add')">
|
||||
<span>➕</span>新增主体
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<table class="grid">
|
||||
<thead><tr><th>主体名称</th><th>费用类型</th><th class="col-actions">操作</th></tr></thead>
|
||||
<tbody id="cfg-subject-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="expense-modal-overlay" class="expense-modal-overlay">
|
||||
<div id="expense-modal" class="expense-modal">
|
||||
<div class="expense-modal-header">
|
||||
<h3 id="expense-modal-title" class="expense-modal-title">模态框标题</h3>
|
||||
<button class="expense-modal-close" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div id="expense-modal-body" class="expense-modal-body"></div>
|
||||
<div id="expense-modal-footer" class="expense-modal-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading-overlay" class="expense-modal-overlay" style="z-index: 2000;">
|
||||
<div class="expense-modal loading-modal">
|
||||
<div class="expense-modal-header">
|
||||
<h3 id="loading-title" class="expense-modal-title">加载中</h3>
|
||||
<button id="loading-close-btn" class="expense-modal-close" style="display:none" onclick="closeLoading()">×</button>
|
||||
</div>
|
||||
<div class="expense-modal-body loading-body">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div id="loading-spinner" class="loading-spinner"></div>
|
||||
<p id="loading-text" style="margin: 0; font-size: 14px; color: var(--color-text); font-weight: 500;">正在处理...</p>
|
||||
</div>
|
||||
<div id="loading-logs" class="loading-logs"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
||||
|
||||
<section class="debug" id="expense-debug" style="display:none">
|
||||
<div class="debug-actions">
|
||||
<button id="ed-btn-copy" class="btn">复制调试信息</button>
|
||||
<button id="ed-btn-refresh" class="btn">刷新</button>
|
||||
<button id="ed-btn-clear" class="btn">清空</button>
|
||||
</div>
|
||||
<div id="ed-output" class="debug-output"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/common/common.js"></script>
|
||||
<script src="/tools/expense/expense.js"></script>
|
||||
<script src="/tools/expense/theme/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
body.theme-expense { background: var(--color-bg) }
|
||||
body.theme-expense .container { color: var(--color-text) }
|
||||
body.theme-expense h2 { color: var(--color-text) }
|
||||
body.theme-expense .panel.card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius); box-shadow: var(--shadow); padding: var(--gap) }
|
||||
body.theme-expense .expense-layout { display: grid; grid-template-columns: 1fr; gap: var(--gap) }
|
||||
/* 顶部胶囊标签 */
|
||||
body.theme-expense .expense-tabs .tabs { display:flex; gap:10px; padding:0 }
|
||||
body.theme-expense .expense-tabs .tab { display:flex; align-items:center; gap:8px; padding:10px 20px; border-radius:999px; font-size:14px; font-weight:500; cursor:pointer; user-select:none; transition: all .3s cubic-bezier(0.4,0,0.2,1); position:relative; overflow:hidden; backdrop-filter: blur(8px) }
|
||||
/* 暗黑 */
|
||||
body.theme-expense[data-theme="dark"] .expense-tabs .tab { background: rgba(17,24,39,0.8); border:1px solid rgba(31,41,55,0.5); color:#cbd5e1; box-shadow: 0 2px 6px rgba(0,0,0,0.1), inset 0 1px 0 rgba(255,255,255,0.05) }
|
||||
body.theme-expense[data-theme="dark"] .expense-tabs .tab:hover { background: rgba(26,35,52,0.6); border-color: rgba(51,65,85,0.5); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.10) }
|
||||
body.theme-expense[data-theme="dark"] .expense-tabs .tab.active { background: rgba(37,99,235,0.20); border-color: rgba(59,130,246,0.5); color:#fff; box-shadow: 0 8px 24px rgba(37,99,235,0.25) }
|
||||
/* 明亮 */
|
||||
body.theme-expense[data-theme="light"] .expense-tabs .tab { background: #ffffff; border:1px solid var(--color-border); color: var(--color-text); box-shadow: 0 2px 6px rgba(0,0,0,0.06) }
|
||||
body.theme-expense[data-theme="light"] .expense-tabs .tab:hover { background: #f8fafc; border-color: var(--color-border); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08) }
|
||||
body.theme-expense[data-theme="light"] .expense-tabs .tab.active { background: rgba(37,99,235,0.12); border-color: var(--color-primary); color: var(--color-text); box-shadow: 0 8px 24px rgba(37,99,235,0.15) }
|
||||
@media (max-width: 768px) { body.theme-expense .expense-tabs .tabs { flex-wrap: wrap; justify-content: center; gap:8px; padding:6px } body.theme-expense .expense-tabs .tab { padding:8px 16px; font-size:13px } }
|
||||
/* 表格卡片 */
|
||||
body.theme-expense .list-card { background: var(--table-bg); border: 1px solid var(--table-border); border-radius: var(--table-radius); overflow: hidden; box-shadow: var(--shadow-sm) }
|
||||
body.theme-expense .grid { width: 100%; border-collapse: collapse; background: var(--table-bg) }
|
||||
body.theme-expense .grid thead { background: var(--table-header-bg); position: sticky; top: 0 }
|
||||
body.theme-expense .grid th { padding: 10px 12px; text-align: left; font-weight: 600; font-size: inherit; color: var(--text-primary); border-bottom: 2px solid var(--table-border) }
|
||||
body.theme-expense .grid td { padding: 10px 12px; border-bottom: 1px solid var(--table-border); color: var(--color-text); font-size: inherit; transition: all .2s ease }
|
||||
body.theme-expense .grid tbody tr:nth-child(odd) { background: var(--table-row-odd) }
|
||||
body.theme-expense .grid tbody tr:nth-child(even) { background: var(--table-row-even) }
|
||||
body.theme-expense .grid tbody tr:hover { background: var(--table-row-hover); color: var(--table-text-hover) }
|
||||
/* 导航显示控制 */
|
||||
body.theme-expense:not(.nav-visible) #topbar { display: none }
|
||||
/* 头部图标样式(与云产品一致) */
|
||||
body.theme-expense[data-theme="dark"] .icon-toggle, body.theme-expense[data-theme="dark"] .icon-theme { border-color: var(--color-border) }
|
||||
body.theme-expense[data-theme="light"] .icon-toggle, body.theme-expense[data-theme="light"] .icon-theme { border-color: var(--color-border) }
|
||||
@@ -0,0 +1,40 @@
|
||||
;(function(){
|
||||
const ExpenseTheme = {
|
||||
init(){
|
||||
this.observeList()
|
||||
this.initNavToggle()
|
||||
this.initThemeToggle()
|
||||
},
|
||||
observeList(){
|
||||
const el=document.getElementById('ex-body')
|
||||
if(!el) return
|
||||
const mo=new MutationObserver(muts=>{
|
||||
muts.forEach(m=>{
|
||||
m.addedNodes.forEach(n=>{ if(n.nodeType===1) n.classList.add('fade-in') })
|
||||
})
|
||||
})
|
||||
mo.observe(el,{childList:true})
|
||||
},
|
||||
initNavToggle(){
|
||||
const key='expense_nav_visible'
|
||||
const btn=document.getElementById('expense-toggle-nav')
|
||||
const eye=`<svg width="16" height="16" viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12Z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>`
|
||||
const setVisible=(v)=>{ document.body.classList.toggle('nav-visible', !!v); if(btn) { btn.setAttribute('aria-pressed', v?'true':'false'); btn.title=v?'隐藏导航':'显示导航'; btn.innerHTML=eye } }
|
||||
const initVal=localStorage.getItem(key)==='1'
|
||||
setVisible(initVal)
|
||||
if(btn) btn.addEventListener('click',()=>{ const cur=document.body.classList.contains('nav-visible'); const next=!cur; localStorage.setItem(key, next?'1':'0'); setVisible(next) })
|
||||
},
|
||||
initThemeToggle(){
|
||||
const key='expense_theme'
|
||||
const btn=document.getElementById('expense-toggle-theme')
|
||||
const moon=`<svg width="16" height="16" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79Z" fill="currentColor"/></svg>`
|
||||
const sun=`<svg width="16" height="16" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" fill="currentColor"/><path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12" stroke="currentColor" stroke-width="2"/></svg>`
|
||||
const apply=(mode)=>{ document.body.setAttribute('data-theme', mode); if(btn) btn.innerHTML=mode==='dark'?moon:sun }
|
||||
const init=localStorage.getItem(key) || 'dark'
|
||||
apply(init)
|
||||
if(btn) btn.addEventListener('click',()=>{ const cur=document.body.getAttribute('data-theme')||'dark'; const next=cur==='dark'?'light':'dark'; localStorage.setItem(key,next); apply(next) })
|
||||
}
|
||||
}
|
||||
window.ExpenseTheme=ExpenseTheme
|
||||
if(document.body && document.body.classList.contains('theme-expense')) { ExpenseTheme.init() }
|
||||
})();
|
||||
@@ -0,0 +1,6 @@
|
||||
body.theme-expense { --radius:12px; --shadow:0 6px 20px rgba(0,0,0,0.35); --gap:12px; --shadow-sm:0 2px 8px rgba(0,0,0,0.1); --shadow-md:0 4px 12px rgba(0,0,0,0.15); --shadow-lg:0 8px 24px rgba(0,0,0,0.2); --glow:0 0 0 3px rgba(59,130,246,0.2) }
|
||||
body.theme-expense[data-theme="dark"] { --color-bg:#0b1220; --color-card:#111827; --color-border:#1f2937; --color-text:#cbd5e1; --color-muted:#64748b; --color-primary:#2563eb; --color-primary-2:#1d4ed8; --color-accent:#10b981 }
|
||||
body.theme-expense[data-theme="light"] { --color-bg:#f3f4f6; --color-card:#ffffff; --color-border:#e5e7eb; --color-text:#0f172a; --color-muted:#475569; --color-primary:#2563eb; --color-primary-2:#1d4ed8; --color-accent:#059669 }
|
||||
/* table palette */
|
||||
body.theme-expense[data-theme="dark"] { --table-bg:#1e293b; --table-border:#334155; --table-radius:8px; --table-row-odd:#1a2536; --table-row-even:#1e293b; --table-header-bg:#0f172a; --table-row-hover:#2d3748; --table-text-hover:#ffffff; --text-primary:var(--color-text) }
|
||||
body.theme-expense[data-theme="light"] { --table-bg:#ffffff; --table-border:#e2e8f0; --table-radius:8px; --table-row-odd:#f8fafc; --table-row-even:#ffffff; --table-header-bg:#f1f5f9; --table-row-hover:#f1f5f9; --table-text-hover:#1e293b; --text-primary:var(--color-text) }
|
||||
Reference in New Issue
Block a user