fs-lawrisk/static/permit_upload.html

769 lines
26 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>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: "Microsoft YaHei", "PingFang SC", -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #f7fafc 0%, #e2e8f0 100%);
min-height: 100vh;
color: #1f2937;
}
.page {
max-width: 1000px;
margin: 0 auto;
padding: 32px 20px 60px;
}
.header {
background: #fff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 20px 60px rgba(79, 70, 229, 0.12);
margin-bottom: 24px;
position: relative;
}
.title {
text-align: center;
}
.title h1 {
font-size: 30px;
margin: 0;
color: #111827;
}
.title p {
margin: 6px 0 0;
color: #4b5563;
font-size: 15px;
}
.upload-section {
background: #fff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 20px 60px rgba(79, 70, 229, 0.12);
margin-bottom: 24px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #374151;
margin: 0 0 20px;
padding-bottom: 12px;
border-bottom: 2px solid #f3f4f6;
}
.upload-area {
border: 2px dashed #d1d5db;
border-radius: 12px;
padding: 48px 20px;
text-align: center;
background: #f9fafb;
transition: all 0.3s;
cursor: pointer;
position: relative;
}
.upload-area:hover {
border-color: #6366f1;
background: #f0f9ff;
}
.upload-area.dragover {
border-color: #6366f1;
background: #eef2ff;
transform: scale(1.02);
}
.upload-icon {
font-size: 48px;
margin-bottom: 12px;
color: #6366f1;
}
.upload-text {
font-size: 16px;
color: #4b5563;
margin-bottom: 8px;
}
.upload-hint {
font-size: 14px;
color: #9ca3af;
}
.upload-input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.file-list {
margin-top: 24px;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 10px;
margin-bottom: 12px;
}
.file-info {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.file-icon {
font-size: 24px;
color: #6366f1;
}
.file-details {
flex: 1;
}
.file-name {
font-weight: 600;
color: #374151;
margin-bottom: 4px;
}
.file-size {
font-size: 13px;
color: #9ca3af;
}
.file-status {
font-size: 13px;
padding: 4px 12px;
border-radius: 999px;
background: #dbeafe;
color: #1e40af;
}
.file-status.uploading {
background: #fef3c7;
color: #92400e;
}
.file-status.success {
background: #d1fae5;
color: #065f46;
}
.file-status.error {
background: #fee2e2;
color: #991b1b;
}
.file-actions {
display: flex;
gap: 8px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-small {
padding: 6px 12px;
font-size: 13px;
}
.btn-danger {
background: #fee2e2;
color: #991b1b;
}
.btn-danger:hover {
background: #fecaca;
}
.btn-primary {
background: #6366f1;
color: #fff;
}
.btn-primary:hover {
background: #4f46e5;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 600;
color: #374151;
margin-bottom: 8px;
}
.form-select, .form-input, .form-textarea {
width: 100%;
padding: 12px 16px;
border: 1px solid #d1d5db;
border-radius: 10px;
font-size: 14px;
background: #fff;
transition: all 0.2s;
}
.form-select:focus, .form-input:focus, .form-textarea:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.form-help {
font-size: 13px;
color: #6b7280;
margin-top: 6px;
}
.binding-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 12px;
}
.binding-card {
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
}
.binding-card:hover {
border-color: #6366f1;
}
.binding-card.selected {
border-color: #6366f1;
background: #eef2ff;
}
.binding-radio {
display: none;
}
.binding-title {
font-weight: 600;
color: #374151;
margin-bottom: 8px;
}
.binding-desc {
font-size: 13px;
color: #6b7280;
}
.submit-section {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 24px;
border-top: 2px solid #f3f4f6;
}
.loading {
text-align: center;
padding: 40px 20px;
color: #6b7280;
}
.loading-spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid #f3f4f6;
border-top-color: #6366f1;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error-message {
background: #fee2e2;
border: 1px solid #fecaca;
color: #991b1b;
padding: 16px;
border-radius: 10px;
margin-bottom: 20px;
}
.success-message {
background: #d1fae5;
border: 1px solid #a7f3d0;
color: #065f46;
padding: 16px;
border-radius: 10px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="page">
<!-- 页面头部 -->
<div class="header">
<div class="title">
<h1>许可文件上传</h1>
<p>上传Excel格式的许可文件可选择绑定到特定单位</p>
</div>
</div>
<!-- 上传区域 -->
<div class="upload-section">
<h2 class="section-title">1. 选择文件</h2>
<div id="uploadArea" class="upload-area">
<div class="upload-icon">📤</div>
<div class="upload-text">点击或拖拽文件到此处上传</div>
<div class="upload-hint">支持 .xlsx 格式,建议文件大小不超过 50MB</div>
<input type="file" id="fileInput" class="upload-input" accept=".xlsx,.xls" multiple />
</div>
<div id="fileList" class="file-list" style="display: none;"></div>
</div>
<!-- 绑定设置 -->
<div class="upload-section">
<h2 class="section-title">2. 绑定设置(可选)</h2>
<div class="form-group">
<label class="form-label">绑定方式</label>
<div class="binding-options">
<label class="binding-card">
<input type="radio" name="bindingType" value="none" class="binding-radio" checked />
<div class="binding-title">不绑定</div>
<div class="binding-desc">文件将只有上传者和上级单位可见</div>
</label>
<label class="binding-card" id="municipalBindingCard">
<input type="radio" name="bindingType" value="municipal" class="binding-radio" />
<div class="binding-title">绑定市级单位</div>
<div class="binding-desc">绑定到选定的市级单位,该单位及其下属可见</div>
</label>
<label class="binding-card" id="districtBindingCard">
<input type="radio" name="bindingType" value="district" class="binding-radio" />
<div class="binding-title">绑定区级单位</div>
<div class="binding-desc">绑定到选定的区级单位,只有该单位可见</div>
</label>
</div>
</div>
<div id="municipalSection" class="form-group" style="display: none;">
<label class="form-label">选择市级单位</label>
<select id="municipalDept" class="form-select">
<option value="">请选择市级单位</option>
</select>
<div class="form-help">市级单位可以看到自身和所有下属区级单位的许可文件</div>
</div>
<div id="districtSection" class="form-group" style="display: none;">
<label class="form-label">选择区级单位</label>
<select id="districtDept" class="form-select" disabled>
<option value="">请先选择市级单位</option>
</select>
<div class="form-help">区级单位只能看到自身绑定的许可文件</div>
</div>
<div class="form-group">
<label class="form-label">上传说明(可选)</label>
<textarea id="uploadNote" class="form-textarea" placeholder="输入本次上传的说明信息..."></textarea>
</div>
</div>
<!-- 提交区域 -->
<div class="upload-section">
<div class="submit-section">
<button id="cancelBtn" class="btn" style="background: #e5e7eb; color: #374151;">取消</button>
<button id="submitBtn" class="btn btn-primary" disabled>开始上传</button>
</div>
</div>
<!-- 状态消息 -->
<div id="errorMessage" class="error-message" style="display: none;"></div>
<div id="successMessage" class="success-message" style="display: none;"></div>
<!-- 上传进度 -->
<div id="uploadProgress" class="upload-section" style="display: none;">
<h2 class="section-title">上传进度</h2>
<div id="progressContent"></div>
</div>
</div>
<script>
// 全局变量
let selectedFiles = [];
let municipalTree = [];
// DOM元素
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const submitBtn = document.getElementById('submitBtn');
const cancelBtn = document.getElementById('cancelBtn');
const errorMessage = document.getElementById('errorMessage');
const successMessage = document.getElementById('successMessage');
const municipalDept = document.getElementById('municipalDept');
const districtDept = document.getElementById('districtDept');
const municipalSection = document.getElementById('municipalSection');
const districtSection = document.getElementById('districtSection');
const uploadProgress = document.getElementById('uploadProgress');
const progressContent = document.getElementById('progressContent');
// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
loadMunicipalDepartments();
bindEvents();
});
// 绑定事件
function bindEvents() {
// 文件选择
fileInput.addEventListener('change', handleFileSelect);
// 拖拽上传
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('dragleave', handleDragLeave);
uploadArea.addEventListener('drop', handleDrop);
// 绑定方式选择
document.querySelectorAll('input[name="bindingType"]').forEach(radio => {
radio.addEventListener('change', handleBindingTypeChange);
});
// 市级单位选择变更
municipalDept.addEventListener('change', handleMunicipalChange);
// 提交/取消
submitBtn.addEventListener('click', handleSubmit);
cancelBtn.addEventListener('click', handleCancel);
}
// 加载市级单位
async function loadMunicipalDepartments() {
try {
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/service-departments/tree');
const data = await response.json();
if (!data.success) {
throw new Error(data.message || '加载市级单位失败');
}
municipalTree = data.data.tree || [];
renderMunicipalOptions(municipalTree);
} catch (error) {
console.error('加载市级单位失败:', error);
showError('加载市级单位失败: ' + error.message);
}
}
// 渲染市级单位选项
function renderMunicipalOptions(tree) {
municipalDept.innerHTML = '<option value="">请选择市级单位</option>';
tree.forEach(dept => {
const option = document.createElement('option');
option.value = dept.id;
option.textContent = `${dept.name} (${dept.code})`;
municipalDept.appendChild(option);
});
}
// 处理文件选择
function handleFileSelect(e) {
const files = Array.from(e.target.files);
addFiles(files);
}
// 处理拖拽悬停
function handleDragOver(e) {
e.preventDefault();
uploadArea.classList.add('dragover');
}
// 处理拖拽离开
function handleDragLeave(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
}
// 处理拖拽放下
function handleDrop(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files);
addFiles(files);
}
// 添加文件
function addFiles(files) {
// 过滤文件类型
const validFiles = files.filter(file => {
const validTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'];
const validExt = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
if (!validTypes.includes(file.type) && !validExt) {
showError(`文件 "${file.name}" 不是有效的Excel格式`);
return false;
}
if (file.size > 50 * 1024 * 1024) {
showError(`文件 "${file.name}" 超过50MB大小限制`);
return false;
}
return true;
});
validFiles.forEach(file => {
selectedFiles.push({
file: file,
id: Date.now() + Math.random(),
status: 'pending'
});
});
renderFileList();
updateSubmitButton();
}
// 渲染文件列表
function renderFileList() {
if (selectedFiles.length === 0) {
fileList.style.display = 'none';
return;
}
fileList.style.display = 'block';
fileList.innerHTML = selectedFiles.map(item => `
<div class="file-item" data-id="${item.id}">
<div class="file-info">
<div class="file-icon">📊</div>
<div class="file-details">
<div class="file-name">${escapeHtml(item.file.name)}</div>
<div class="file-size">${formatFileSize(item.file.size)}</div>
</div>
</div>
<div class="file-status ${item.status}">${getStatusText(item.status)}</div>
<div class="file-actions">
<button class="btn btn-small btn-danger" onclick="removeFile('${item.id}')">移除</button>
</div>
</div>
`).join('');
}
// 移除文件
function removeFile(id) {
selectedFiles = selectedFiles.filter(item => item.id !== id);
renderFileList();
updateSubmitButton();
}
// 处理绑定方式变更
function handleBindingTypeChange(e) {
const value = e.target.value;
// 更新卡片选中状态
document.querySelectorAll('.binding-card').forEach(card => {
card.classList.remove('selected');
});
e.target.closest('.binding-card').classList.add('selected');
// 显示/隐藏对应的选择区域
municipalSection.style.display = value === 'municipal' ? 'block' : 'none';
districtSection.style.display = value === 'district' ? 'block' : 'none';
// 重置区级单位选择
districtDept.innerHTML = '<option value="">请先选择市级单位</option>';
districtDept.disabled = value === 'district' && !municipalDept.value;
}
// 处理市级单位选择变更
async function handleMunicipalChange() {
const municipalId = municipalDept.value;
// 重置区级单位
districtDept.innerHTML = '<option value="">请选择区级单位</option>';
districtDept.disabled = !municipalId;
if (!municipalId) {
return;
}
// 加载区级单位
try {
const response = await fetch(
`/fs-ai-asistant/api/workflow/lawrisk/admin/departments/children?parent_id=${municipalId}`
);
const data = await response.json();
if (!data.success) {
throw new Error(data.message || '加载区级单位失败');
}
const children = data.data.units || [];
districtDept.innerHTML = '<option value="">请选择区级单位</option>';
children.forEach(dept => {
const option = document.createElement('option');
option.value = dept.id;
option.textContent = `${dept.name} (${dept.code})`;
districtDept.appendChild(option);
});
} catch (error) {
console.error('加载区级单位失败:', error);
showError('加载区级单位失败: ' + error.message);
}
}
// 处理提交
async function handleSubmit() {
if (selectedFiles.length === 0) {
showError('请先选择要上传的文件');
return;
}
const bindingType = document.querySelector('input[name="bindingType"]:checked').value;
let boundDepartmentId = null;
if (bindingType === 'municipal' && municipalDept.value) {
boundDepartmentId = municipalDept.value;
} else if (bindingType === 'district' && districtDept.value) {
boundDepartmentId = districtDept.value;
}
// 开始上传第一个文件
await uploadFile(0, boundDepartmentId);
}
// 上传文件
async function uploadFile(index, boundDepartmentId) {
if (index >= selectedFiles.length) {
// 所有文件上传完成
showSuccess(`成功上传 ${selectedFiles.length} 个文件`);
resetForm();
return;
}
const item = selectedFiles[index];
item.status = 'uploading';
renderFileList();
try {
const formData = new FormData();
formData.append('file', item.file);
// 添加绑定信息
if (boundDepartmentId) {
formData.append('bound_department_id', boundDepartmentId);
}
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/permit-import/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (!data.success) {
throw new Error(data.message || '上传失败');
}
item.status = 'success';
renderFileList();
// 显示上传进度
showProgress(index + 1, selectedFiles.length, item.file.name);
// 继续上传下一个文件
setTimeout(() => uploadFile(index + 1, boundDepartmentId), 1000);
} catch (error) {
console.error('上传失败:', error);
item.status = 'error';
renderFileList();
showError(`文件 "${item.file.name}" 上传失败: ` + error.message);
}
}
// 显示上传进度
function showProgress(current, total, filename) {
uploadProgress.style.display = 'block';
progressContent.innerHTML = `
<div style="margin-bottom: 12px;">正在上传: ${escapeHtml(filename)} (${current}/${total})</div>
<div style="width: 100%; height: 8px; background: #f3f4f6; border-radius: 4px; overflow: hidden;">
<div style="width: ${(current / total) * 100}%; height: 100%; background: #6366f1; transition: width 0.3s;"></div>
</div>
`;
}
// 处理取消
function handleCancel() {
if (confirm('确定要取消上传吗?已选择的文件将被清除。')) {
resetForm();
}
}
// 重置表单
function resetForm() {
selectedFiles = [];
fileInput.value = '';
fileList.style.display = 'none';
uploadProgress.style.display = 'none';
submitBtn.disabled = true;
// 重置绑定设置
document.querySelector('input[name="bindingType"][value="none"]').checked = true;
handleBindingTypeChange({ target: document.querySelector('input[name="bindingType"][value="none"]') });
municipalDept.value = '';
districtDept.value = '';
districtDept.disabled = true;
document.getElementById('uploadNote').value = '';
}
// 更新提交按钮状态
function updateSubmitButton() {
submitBtn.disabled = selectedFiles.length === 0;
}
// 获取状态文本
function getStatusText(status) {
const statusMap = {
'pending': '等待上传',
'uploading': '上传中...',
'success': '上传成功',
'error': '上传失败'
};
return statusMap[status] || status;
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
// HTML转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 显示错误信息
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
setTimeout(() => errorMessage.style.display = 'none', 5000);
}
// 显示成功信息
function showSuccess(message) {
successMessage.textContent = message;
successMessage.style.display = 'block';
setTimeout(() => successMessage.style.display = 'none', 5000);
}
</script>
</body>
</html>