fs-lawrisk/static/db_admin.html

897 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据库维护页面 - LawRisk</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 30px;
}
.header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 3px solid #667eea;
}
.header h1 {
color: #333;
font-size: 28px;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 14px;
}
.step-indicator {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 30px;
gap: 10px;
}
.step {
display: flex;
align-items: center;
gap: 10px;
}
.step-number {
width: 36px;
height: 36px;
border-radius: 50%;
background: #e0e7ff;
color: #667eea;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
}
.step.active .step-number {
background: #667eea;
color: white;
}
.step-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.step.active .step-label {
color: #667eea;
font-weight: bold;
}
.arrow {
color: #ccc;
font-size: 24px;
}
.content-area {
display: grid;
grid-template-columns: 350px 1fr;
gap: 30px;
min-height: 600px;
}
.panel {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
border: 1px solid #e0e0e0;
}
.panel h2 {
color: #333;
font-size: 18px;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-controls {
display: flex;
gap: 8px;
}
.back-button {
padding: 4px 12px;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
color: #666;
cursor: pointer;
transition: all 0.3s;
}
.back-button:hover:not(:disabled) {
background: #e0e0e0;
border-color: #999;
}
.back-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.breadcrumb {
background: white;
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
font-size: 14px;
color: #666;
}
.breadcrumb-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.breadcrumb-item a {
color: #667eea;
text-decoration: none;
cursor: pointer;
transition: color 0.3s;
}
.breadcrumb-item a:hover {
color: #5568d3;
text-decoration: underline;
}
.breadcrumb-separator {
color: #ccc;
margin: 0 4px;
}
.breadcrumb-current {
color: #333;
font-weight: 500;
}
.selection-area {
background: white;
border-radius: 8px;
padding: 20px;
max-height: 600px;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
/* 自定义滚动条样式 */
.selection-area::-webkit-scrollbar {
width: 8px;
}
.selection-area::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.selection-area::-webkit-scrollbar-thumb {
background: #c0c0c0;
border-radius: 4px;
transition: background 0.3s;
}
.selection-area::-webkit-scrollbar-thumb:hover {
background: #a0a0a0;
}
.item-list {
list-style: none;
margin-top: 10px;
}
.item-list li {
padding: 12px 16px;
margin-bottom: 8px;
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
display: flex;
justify-content: space-between;
align-items: center;
}
.item-list li:hover {
background: #e0e7ff;
border-color: #667eea;
transform: translateX(5px);
}
.item-list li.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.item-list li.active:hover {
background: #5568d3;
}
.item-name {
font-size: 15px;
font-weight: 500;
}
.item-count {
font-size: 12px;
background: rgba(102, 126, 234, 0.1);
padding: 4px 10px;
border-radius: 12px;
color: #667eea;
}
.item-list li.active .item-count {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.details-area {
background: white;
border-radius: 8px;
padding: 20px;
min-height: 500px;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
}
.empty-state svg {
width: 80px;
height: 80px;
margin-bottom: 15px;
opacity: 0.3;
}
.empty-state p {
font-size: 16px;
}
.details-content {
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.detail-section {
margin-bottom: 25px;
}
.detail-section h3 {
color: #667eea;
font-size: 16px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.detail-section h3::before {
content: '';
width: 4px;
height: 16px;
background: #667eea;
border-radius: 2px;
}
.detail-content {
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
border-left: 3px solid #667eea;
line-height: 1.8;
color: #444;
}
.risk-item {
background: white;
padding: 15px;
margin-bottom: 12px;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.risk-item h4 {
color: #d32f2f;
font-size: 15px;
margin-bottom: 10px;
}
.risk-field {
margin-bottom: 10px;
line-height: 1.6;
}
.risk-field strong {
color: #333;
display: inline-block;
min-width: 80px;
}
.risk-field p {
color: #555;
display: inline;
}
.scope-item {
background: white;
padding: 10px 15px;
margin-bottom: 8px;
border-radius: 4px;
border-left: 3px solid #667eea;
}
.permit-status {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
margin-right: 8px;
}
.status-active {
background: #e8f5e9;
color: #2e7d32;
}
.status-inactive {
background: #ffebee;
color: #c62828;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
background: #ffebee;
color: #c62828;
padding: 15px;
border-radius: 6px;
margin: 15px 0;
border-left: 4px solid #c62828;
}
@media (max-width: 1024px) {
.content-area {
grid-template-columns: 1fr;
}
.panel:first-child {
max-height: 300px;
overflow-y: auto;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🗃️ 数据库维护系统</h1>
<p>LawRisk 法律风险提示系统 - 数据库维护与查询工具</p>
</div>
<div class="step-indicator">
<div class="step active" id="step1">
<div class="step-number">1</div>
<div class="step-label">选择地区</div>
</div>
<div class="arrow"></div>
<div class="step" id="step2">
<div class="step-number">2</div>
<div class="step-label">选择主题</div>
</div>
<div class="arrow"></div>
<div class="step" id="step3">
<div class="step-number">3</div>
<div class="step-label">选择许可</div>
</div>
<div class="arrow"></div>
<div class="step" id="step4">
<div class="step-number">4</div>
<div class="step-label">查看详情</div>
</div>
</div>
<div class="content-area">
<div class="panel">
<h2 id="navTitle">
<span>选择区域</span>
<div class="nav-controls">
<button class="back-button" id="backButton" onclick="goBack()" disabled>← 上一步</button>
</div>
</h2>
<div class="breadcrumb" id="breadcrumb"></div>
<div class="selection-area">
<div id="navList" class="item-list"></div>
</div>
</div>
<div class="panel">
<h2 id="detailsTitle">详情内容</h2>
<div class="details-area" id="detailsArea">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
<p id="emptyMessage">请选择区域开始导航</p>
</div>
</div>
</div>
</div>
</div>
<script>
// 导航状态管理
let currentStep = 1; // 1=区域, 2=主题, 3=许可, 4=详情
let historyStack = []; // 历史记录栈
let currentRegion = null;
let currentTheme = null;
let currentPermit = null;
// 步骤配置
const steps = {
1: { title: '选择区域', loadData: loadRegions },
2: { title: '选择主题', loadData: (region) => loadThemes(region.id, region.name) },
3: { title: '选择许可', loadData: (theme) => loadPermits(theme.id, theme.name) },
4: { title: '许可详情', loadData: null }
};
// 加载地区列表
async function loadRegions() {
const navList = document.getElementById('navList');
navList.innerHTML = '<div class="loading"></div>加载地区列表...';
try {
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/regions');
const data = await response.json();
if (data.success) {
navList.innerHTML = '';
if (data.data.regions.length === 0) {
navList.innerHTML = '<div class="error">未找到地区数据</div>';
return;
}
data.data.regions.forEach(region => {
const li = document.createElement('li');
li.innerHTML = `
<span class="item-name">${region.name}</span>
<span class="item-count">点击选择</span>
`;
li.onclick = () => selectRegion(region.id, region.name);
navList.appendChild(li);
});
} else {
navList.innerHTML = `<div class="error">加载地区失败:${data.message}</div>`;
}
} catch (error) {
navList.innerHTML = `<div class="error">网络错误:${error.message}</div>`;
}
}
// 加载主题列表
async function loadThemes(regionId, regionName) {
const navList = document.getElementById('navList');
navList.innerHTML = '<div class="loading"></div>加载主题列表...';
try {
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/themes?region=${regionId}`);
const data = await response.json();
if (data.success) {
const themes = data.data.themes;
if (themes.length === 0) {
navList.innerHTML = `<div class="error">地区 "${regionName}" 下没有可用的主题</div>`;
return;
}
let html = '<div class="item-list">';
themes.forEach(theme => {
html += `
<li onclick="selectTheme('${theme.id}', '${theme.name.replace(/'/g, "\\'")}')">
<span class="item-name">${theme.name}</span>
<span class="item-count">点击选择</span>
</li>
`;
});
html += '</div>';
navList.innerHTML = html;
} else {
navList.innerHTML = `<div class="error">加载主题失败:${data.message}</div>`;
}
} catch (error) {
navList.innerHTML = `<div class="error">网络错误:${error.message}</div>`;
}
}
// 加载许可列表
async function loadPermits(themeId, themeName) {
const navList = document.getElementById('navList');
navList.innerHTML = '<div class="loading"></div>加载许可列表...';
try {
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/permits?region=${currentRegion.id}&theme=${themeId}`);
const data = await response.json();
if (data.success) {
const permits = data.data.permits;
if (permits.length === 0) {
navList.innerHTML = `<div class="error">主题 "${themeName}" 下没有可用的许可</div>`;
return;
}
let html = '<div class="item-list">';
permits.forEach(permit => {
const riskCount = permit.risks ? permit.risks.length : 0;
html += `
<li onclick="selectPermit('${permit.id}', '${permit.name.replace(/'/g, "\\'")}', '${themeId}')">
<span class="item-name">${permit.name}</span>
<span class="item-count">${riskCount} 个风险</span>
</li>
`;
});
html += '</div>';
navList.innerHTML = html;
} else {
navList.innerHTML = `<div class="error">加载许可失败:${data.message}</div>`;
}
} catch (error) {
navList.innerHTML = `<div class="error">网络错误:${error.message}</div>`;
}
}
// 选择地区
async function selectRegion(regionId, regionName) {
// 保存到历史栈
historyStack.push({ step: currentStep, region: currentRegion });
currentRegion = { id: regionId, name: regionName };
currentTheme = null;
currentPermit = null;
// 更新步骤
goToStep(2);
}
// 选择主题
async function selectTheme(themeId, themeName) {
// 保存到历史栈
historyStack.push({ step: currentStep, theme: currentTheme });
currentTheme = { id: themeId, name: themeName };
currentPermit = null;
// 更新步骤
goToStep(3);
}
// 选择许可
async function selectPermit(permitId, permitName, themeId) {
// 保存到历史栈
historyStack.push({ step: currentStep, permit: currentPermit });
currentPermit = { id: permitId, name: permitName, themeId: themeId };
// 更新步骤
goToStep(4);
}
// 跳转到指定步骤
async function goToStep(step) {
currentStep = step;
// 更新导航标题
document.getElementById('navTitle').querySelector('span').textContent = steps[step].title;
// 更新上一步按钮
const backButton = document.getElementById('backButton');
backButton.disabled = historyStack.length === 0;
// 更新步骤指示器
updateStepIndicator(step);
// 更新面包屑导航
updateBreadcrumb();
// 清空详情区域
const detailsArea = document.getElementById('detailsArea');
if (step === 1) {
detailsArea.innerHTML = `
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
<p id="emptyMessage">请选择区域开始导航</p>
</div>
`;
}
// 加载数据
if (step === 1) {
await loadRegions();
} else if (step === 2) {
await loadThemes(currentRegion.id, currentRegion.name);
} else if (step === 3) {
await loadPermits(currentTheme.id, currentTheme.name);
} else if (step === 4) {
await showPermitDetails();
}
}
// 更新面包屑导航
function updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
let html = '';
// 总是显示"首页"
html += `
<span class="breadcrumb-item">
<a onclick="goHome()">首页</a>
</span>
`;
// 显示当前选择的路径
if (currentRegion) {
html += '<span class="breadcrumb-separator"></span>';
if (currentStep > 2) {
html += `
<span class="breadcrumb-item">
<a onclick="quickJump(2)">${currentRegion.name}</a>
</span>
`;
} else {
html += `
<span class="breadcrumb-item">
<span class="breadcrumb-current">${currentRegion.name}</span>
</span>
`;
}
}
if (currentTheme) {
html += '<span class="breadcrumb-separator"></span>';
if (currentStep > 3) {
html += `
<span class="breadcrumb-item">
<a onclick="quickJump(3)">${currentTheme.name}</a>
</span>
`;
} else {
html += `
<span class="breadcrumb-item">
<span class="breadcrumb-current">${currentTheme.name}</span>
</span>
`;
}
}
if (currentPermit) {
html += '<span class="breadcrumb-separator"></span>';
if (currentStep > 4) {
html += `
<span class="breadcrumb-item">
<a onclick="quickJump(4)">${currentPermit.name}</a>
</span>
`;
} else {
html += `
<span class="breadcrumb-item">
<span class="breadcrumb-current">${currentPermit.name}</span>
</span>
`;
}
}
breadcrumb.innerHTML = html;
}
// 快速跳转到指定步骤
function quickJump(targetStep) {
// 清空历史栈中比目标步骤更晚的记录
while (historyStack.length > 0 && historyStack[historyStack.length - 1].step >= targetStep) {
historyStack.pop();
}
// 清理后续状态
if (targetStep <= 2) {
currentTheme = null;
currentPermit = null;
}
if (targetStep <= 3) {
currentPermit = null;
}
goToStep(targetStep);
}
// 返回首页
function goHome() {
currentRegion = null;
currentTheme = null;
currentPermit = null;
historyStack = [];
goToStep(1);
}
// 显示许可详情
async function showPermitDetails() {
const detailsArea = document.getElementById('detailsArea');
detailsArea.innerHTML = '<div class="loading"></div>加载许可详情...';
try {
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/permit-details?region=${currentRegion.id}&theme=${currentPermit.themeId}&permit=${currentPermit.id}`);
const data = await response.json();
if (data.success) {
renderPermitDetails(data.data.permit);
} else {
detailsArea.innerHTML = `<div class="error">加载详情失败:${data.message}</div>`;
}
} catch (error) {
detailsArea.innerHTML = `<div class="error">网络错误:${error.message}</div>`;
}
}
// 回退到上一步
function goBack() {
if (historyStack.length === 0) return;
const prev = historyStack.pop();
// 恢复状态
if (prev.region) currentRegion = prev.region;
if (prev.theme) currentTheme = prev.theme;
if (prev.permit) currentPermit = prev.permit;
// 跳转到上一步
goToStep(prev.step);
}
// 渲染许可详情
function renderPermitDetails(permit) {
const detailsArea = document.querySelector('.details-area');
let html = '<div class="details-content">';
// 许可基本信息
html += `
<div class="detail-section">
<h3>许可信息</h3>
<div class="detail-content">
<p><strong>许可名称:</strong>${permit.name}</p>
${permit.permit_status ? `<p style="margin-top: 10px;"><strong>许可状态:</strong><span class="permit-status ${permit.permit_status === 'active' ? 'status-active' : 'status-inactive'}">${permit.permit_status}</span></p>` : ''}
${permit.subitem_summary ? `<p style="margin-top: 10px;"><strong>子项说明:</strong>${permit.subitem_summary}</p>` : ''}
${permit.responsible_contact ? `<p style="margin-top: 10px;"><strong>负责部门:</strong>${permit.responsible_contact}</p>` : ''}
${permit.jurisdiction_scope ? `<p style="margin-top: 10px;"><strong>管辖范围:</strong>${permit.jurisdiction_scope}</p>` : ''}
</div>
</div>
`;
// 经营范围
if (permit.business_scopes && permit.business_scopes.length > 0) {
html += '<div class="detail-section"><h3>经营范围</h3><div class="detail-content">';
permit.business_scopes.forEach(scope => {
html += `<div class="scope-item">${scope.description}</div>`;
});
html += '</div></div>';
}
// 法律风险
if (permit.risks && permit.risks.length > 0) {
html += '<div class="detail-section"><h3>法律风险</h3><div class="detail-content">';
permit.risks.forEach(risk => {
html += `
<div class="risk-item">
<h4>风险 ${risk.id}</h4>
${risk.risk_content ? `<div class="risk-field"><strong>风险内容:</strong><p>${risk.risk_content}</p></div>` : ''}
${risk.legal_basis ? `<div class="risk-field"><strong>法律依据:</strong><p>${risk.legal_basis}</p></div>` : ''}
${risk.document_no ? `<div class="risk-field"><strong>文件编号:</strong><p>${risk.document_no}</p></div>` : ''}
${risk.summary ? `<div class="risk-field"><strong>摘要:</strong><div style="margin-top: 5px;">${risk.summary}</div></div>` : ''}
</div>
`;
});
html += '</div></div>';
} else {
html += `
<div class="detail-section">
<h3>法律风险</h3>
<div class="detail-content">
<p style="color: #999;">暂无法律风险信息</p>
</div>
</div>
`;
}
html += '</div>';
detailsArea.innerHTML = html;
}
// 更新步骤指示器
function updateStepIndicator(step) {
for (let i = 1; i <= 4; i++) {
const stepElement = document.getElementById(`step${i}`);
if (i <= step) {
stepElement.classList.add('active');
} else {
stepElement.classList.remove('active');
}
}
}
// 页面加载时初始化
window.addEventListener('DOMContentLoaded', () => {
goToStep(1);
});
</script>
</body>
</html>