fs-lawrisk/static/db_admin.html

1498 lines
47 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;
}
.checkpoint-nav-item {
background: #fff3cd;
border: 2px solid #ffc107;
}
.checkpoint-nav-item:hover {
background: #ffe69c;
border-color: #ff9800;
}
.checkpoint-section {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #e0e0e0;
}
.checkpoint-section h3 {
color: #667eea;
font-size: 18px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.checkpoint-section h3::before {
content: '🔒';
font-size: 20px;
}
.checkpoint-form {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
}
.form-group input[type="text"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input[type="text"]:focus {
outline: none;
border-color: #667eea;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #c82333;
}
.btn-warning {
background: #ffc107;
color: #333;
}
.btn-warning:hover:not(:disabled) {
background: #e0a800;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.checkpoint-list {
margin-top: 20px;
}
.checkpoint-item {
background: #f8f9fa;
padding: 15px;
margin-bottom: 12px;
border-radius: 6px;
border: 1px solid #e0e0e0;
transition: all 0.3s;
}
.checkpoint-item:hover {
background: #e9ecef;
border-color: #667eea;
}
.checkpoint-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.checkpoint-id {
font-weight: bold;
color: #333;
font-size: 15px;
}
.checkpoint-timestamp {
color: #666;
font-size: 13px;
}
.checkpoint-description {
color: #555;
margin-bottom: 10px;
line-height: 1.6;
}
.checkpoint-stats {
display: flex;
gap: 15px;
margin-bottom: 10px;
font-size: 13px;
color: #666;
}
.checkpoint-stats span {
display: inline-flex;
align-items: center;
gap: 5px;
}
.checkpoint-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.btn-sm {
padding: 6px 12px;
font-size: 13px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
border-radius: 12px;
padding: 30px;
max-width: 500px;
width: 90%;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
animation: modalFadeIn 0.3s;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
text-align: center;
margin-bottom: 20px;
}
.modal-header h3 {
color: #dc3545;
font-size: 20px;
margin-bottom: 10px;
}
.modal-header .warning-icon {
font-size: 48px;
margin-bottom: 15px;
}
.modal-body {
margin-bottom: 20px;
}
.modal-body p {
color: #555;
line-height: 1.6;
margin-bottom: 10px;
}
.modal-footer {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.danger-text {
color: #dc3545;
font-weight: bold;
}
.success-text {
color: #28a745;
font-weight: bold;
}
.checkpoint-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
}
.checkpoint-toolbar .btn {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.checkpoint-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
padding: 20px;
}
.checkpoint-modal.show {
display: flex;
}
.checkpoint-modal-content {
background: white;
border-radius: 12px;
padding: 0;
max-width: 900px;
width: 100%;
max-height: 90vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
animation: modalFadeIn 0.3s;
}
.checkpoint-modal-header {
padding: 20px 30px;
border-bottom: 2px solid #667eea;
display: flex;
justify-content: space-between;
align-items: center;
}
.checkpoint-modal-header h2 {
color: #333;
font-size: 20px;
margin: 0;
}
.checkpoint-modal-close {
background: none;
border: none;
font-size: 28px;
color: #999;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s;
}
.checkpoint-modal-close:hover {
background: #f0f0f0;
color: #333;
}
.checkpoint-modal-body {
padding: 30px;
overflow-y: auto;
flex: 1;
}
.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="checkpoint-toolbar">
<button class="btn btn-warning" onclick="openCheckpointModal()">
<span>🔒</span> 检查点管理
</button>
</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>
<!-- 危险操作确认模态框 -->
<div class="modal" id="dangerModal">
<div class="modal-content">
<div class="modal-header">
<div class="warning-icon">⚠️</div>
<h3 id="dangerModalTitle">危险操作确认</h3>
</div>
<div class="modal-body">
<p id="dangerModalMessage"></p>
<p class="danger-text" id="dangerModalWarning"></p>
</div>
<div class="modal-footer">
<button class="btn" onclick="closeDangerModal()">取消</button>
<button class="btn btn-danger" id="dangerModalConfirmBtn" onclick="confirmDangerOperation()">确认执行</button>
</div>
</div>
</div>
<!-- 检查点管理模态窗口 -->
<div class="checkpoint-modal" id="checkpointModal">
<div class="checkpoint-modal-content">
<div class="checkpoint-modal-header">
<h2>🔒 数据库检查点管理</h2>
<button class="checkpoint-modal-close" onclick="closeCheckpointModal()">&times;</button>
</div>
<div class="checkpoint-modal-body" id="checkpointModalBody">
<!-- 检查点管理内容将在这里动态加载 -->
</div>
</div>
</div>
<script>
// 导航状态管理
let currentStep = 1; // 1=区域, 2=主题, 3=许可, 4=详情
let historyStack = []; // 历史记录栈
let currentRegion = null;
let currentTheme = null;
let currentPermit = null;
let pendingDangerOperation = 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');
}
}
}
// ================ 检查点管理功能 ================
// 打开检查点模态窗口
async function openCheckpointModal() {
const modal = document.getElementById('checkpointModal');
const modalBody = document.getElementById('checkpointModalBody');
modalBody.innerHTML = '<div class="loading"></div>加载检查点列表...';
modal.classList.add('show');
try {
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/checkpoints');
const data = await response.json();
if (data.success) {
renderCheckpointManager(data.data.checkpoints);
} else {
modalBody.innerHTML = `<div class="error">加载检查点失败:${data.message}</div>`;
}
} catch (error) {
modalBody.innerHTML = `<div class="error">网络错误:${error.message}</div>`;
}
}
// 关闭检查点模态窗口
function closeCheckpointModal() {
const modal = document.getElementById('checkpointModal');
modal.classList.remove('show');
}
// 点击模态窗口外部关闭
document.getElementById('checkpointModal').addEventListener('click', function(e) {
if (e.target === this) {
closeCheckpointModal();
}
});
// 加载检查点列表(保持兼容性)
async function loadCheckpoints() {
await openCheckpointModal();
}
// 渲染检查点管理界面
function renderCheckpointManager(checkpoints) {
const modalBody = document.getElementById('checkpointModalBody');
let html = '<div class="checkpoint-content">';
// 创建检查点表单
html += `
<div class="checkpoint-section">
<h3>创建新检查点</h3>
<div class="checkpoint-form">
<div class="form-group">
<label for="checkpointDescription">检查点描述(可选):</label>
<input type="text" id="checkpointDescription" placeholder="例如:修改风险提示前的备份">
</div>
<button class="btn btn-primary" onclick="createCheckpoint()">
<span>📸</span> 创建检查点
</button>
</div>
</div>
`;
// 检查点列表
html += '<div class="checkpoint-section">';
html += '<h3>已有检查点</h3>';
if (checkpoints.length === 0) {
html += '<p style="color: #999; text-align: center; padding: 20px;">暂无检查点</p>';
} else {
html += '<div class="checkpoint-list">';
checkpoints.forEach(cp => {
const formattedTime = formatTimestamp(cp.timestamp);
html += `
<div class="checkpoint-item">
<div class="checkpoint-header">
<span class="checkpoint-id">${cp.checkpoint_id}</span>
<span class="checkpoint-timestamp">${formattedTime}</span>
</div>
${cp.description ? `<div class="checkpoint-description">${cp.description}</div>` : ''}
<div class="checkpoint-stats">
<span>📊 总行数:<strong>${cp.total_rows}</strong></span>
<span>📋 表数:<strong>${Object.keys(cp.table_counts).length}</strong></span>
</div>
<div class="checkpoint-actions">
<button class="btn btn-danger btn-sm" onclick="confirmRestoreCheckpoint('${cp.checkpoint_id}')">
<span>🔄</span> 恢复
</button>
<button class="btn btn-warning btn-sm" onclick="deleteCheckpoint('${cp.checkpoint_id}')">
<span>🗑️</span> 删除
</button>
</div>
</div>
`;
});
html += '</div>';
}
html += '</div>';
html += '</div>';
modalBody.innerHTML = html;
}
// 创建检查点
async function createCheckpoint() {
const description = document.getElementById('checkpointDescription').value;
const btn = event.target;
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<div class="loading"></div>创建中...';
try {
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/checkpoints', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ description })
});
const data = await response.json();
if (data.success) {
alert('检查点创建成功!');
document.getElementById('checkpointDescription').value = '';
await openCheckpointModal();
} else {
alert(`创建失败:${data.message}`);
}
} catch (error) {
alert(`网络错误:${error.message}`);
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
}
}
// 确认恢复检查点
function confirmRestoreCheckpoint(checkpointId) {
showDangerModal(
'恢复检查点',
`您确定要恢复检查点 "${checkpointId}" 吗?`,
'此操作将覆盖当前数据库中的所有数据,且无法撤销!请确保您已经创建了新的检查点作为备份。',
() => restoreCheckpoint(checkpointId)
);
}
// 恢复检查点
async function restoreCheckpoint(checkpointId) {
closeDangerModal();
try {
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/checkpoints/${checkpointId}/restore`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
alert(`检查点恢复成功!\n恢复了 ${data.data.total_rows_restored} 行数据,覆盖了 ${data.data.tables_restored} 个表。`);
await openCheckpointModal();
} else {
alert(`恢复失败:${data.message}`);
}
} catch (error) {
alert(`网络错误:${error.message}`);
}
}
// 删除检查点
async function deleteCheckpoint(checkpointId) {
if (!confirm(`确定要删除检查点 "${checkpointId}" 吗?此操作无法撤销。`)) {
return;
}
try {
const response = await fetch(`/fs-ai-asistant/api/workflow/lawrisk/admin/checkpoints/${checkpointId}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
alert('检查点已删除');
await openCheckpointModal();
} else {
alert(`删除失败:${data.message}`);
}
} catch (error) {
alert(`网络错误:${error.message}`);
}
}
// 格式化时间戳
function formatTimestamp(timestamp) {
if (!timestamp) return '';
const year = timestamp.substring(0, 4);
const month = timestamp.substring(4, 6);
const day = timestamp.substring(6, 8);
const hour = timestamp.substring(9, 11);
const minute = timestamp.substring(11, 13);
const second = timestamp.substring(13, 15);
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
// ================ 危险操作确认模态框 ================
// 显示危险操作确认模态框
function showDangerModal(title, message, warning, callback) {
pendingDangerOperation = callback;
document.getElementById('dangerModalTitle').textContent = title;
document.getElementById('dangerModalMessage').textContent = message;
document.getElementById('dangerModalWarning').textContent = warning;
document.getElementById('dangerModal').classList.add('show');
}
// 关闭危险操作确认模态框
function closeDangerModal() {
document.getElementById('dangerModal').classList.remove('show');
pendingDangerOperation = null;
}
// 确认执行危险操作
function confirmDangerOperation() {
if (pendingDangerOperation && typeof pendingDangerOperation === 'function') {
pendingDangerOperation();
}
closeDangerModal();
}
// 页面加载时初始化
window.addEventListener('DOMContentLoaded', () => {
goToStep(1);
});
</script>
</body>
</html>