|
@@ -0,0 +1,757 @@
|
|
|
|
|
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|
|
|
|
+<%@ include file="/webpage/include/taglib.jsp"%>
|
|
|
|
|
+<html>
|
|
|
|
|
+<head>
|
|
|
|
|
+ <title>审计助手</title>
|
|
|
|
|
+ <meta name="decorator" content="default"/>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ * { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
+ body { font-family: "Microsoft YaHei", Arial, sans-serif; font-size: 13px; color: #333; background: #eef1f5; overflow: hidden; height: 100vh; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Wrapper (fills the framework iframe) ===== */
|
|
|
|
|
+ .aa-wrapper { height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Audit Assistant Area ===== */
|
|
|
|
|
+ .aa-area { flex: 1; display: flex; overflow: hidden; padding: 10px; gap: 10px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* -- Left Panel -- */
|
|
|
|
|
+ .aa-left { width: 260px; flex-shrink: 0; display: flex; flex-direction: column; background: #fff; border-radius: 3px; border: 1px solid #e8e8e8; overflow: hidden; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-left-upload { padding: 12px; border-bottom: 1px solid #f0f0f0; }
|
|
|
|
|
+ .aa-left-upload .uz { border: 2px dashed #d9d9d9; border-radius: 3px; padding: 16px 10px; text-align: center; cursor: pointer; transition: all .2s; background: #fafafa; }
|
|
|
|
|
+ .aa-left-upload .uz:hover { border-color: #1e88e5; background: #f0f7ff; }
|
|
|
|
|
+ .aa-left-upload .uz .uz-icon { font-size: 24px; margin-bottom: 4px; opacity: .4; }
|
|
|
|
|
+ .aa-left-upload .uz .uz-text { font-size: 11px; color: #666; margin-bottom: 2px; }
|
|
|
|
|
+ .aa-left-upload .uz .uz-hint { font-size: 10px; color: #aaa; }
|
|
|
|
|
+ .aa-left-upload .new-btn { margin-top: 8px; }
|
|
|
|
|
+ .aa-left-upload .new-btn .btn { width: 100%; justify-content: center; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-left-tabs { display: flex; border-bottom: 1px solid #e8e8e8; background: #fafafa; flex-shrink: 0; }
|
|
|
|
|
+ .aa-left-tab { flex: 1; padding: 6px 4px; text-align: center; font-size: 11px; cursor: pointer; color: #666; border-bottom: 2px solid transparent; transition: all .15s; }
|
|
|
|
|
+ .aa-left-tab:hover { color: #1e88e5; }
|
|
|
|
|
+ .aa-left-tab.active { color: #1e88e5; border-bottom-color: #1e88e5; font-weight: 600; background: #fff; }
|
|
|
|
|
+ .aa-left-tab .cnt { background: #e0e0e0; color: #666; padding: 0 4px; border-radius: 6px; font-size: 9px; margin-left: 2px; }
|
|
|
|
|
+ .aa-left-tab.active .cnt { background: #e3f2fd; color: #1e88e5; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-left-list { flex: 1; overflow-y: auto; padding: 6px; }
|
|
|
|
|
+ .aa-task { padding: 8px 10px; border-radius: 3px; cursor: pointer; margin-bottom: 3px; transition: all .15s; border: 1px solid transparent; }
|
|
|
|
|
+ .aa-task:hover { background: #f5f9ff; border-color: #e3f2fd; }
|
|
|
|
|
+ .aa-task.active { background: #e3f2fd; border-color: #90caf9; }
|
|
|
|
|
+ .aa-task-hd { display: flex; align-items: center; gap: 5px; margin-bottom: 3px; }
|
|
|
|
|
+ .aa-task-st { font-size: 10px; padding: 1px 5px; border-radius: 2px; font-weight: 500; flex-shrink: 0; }
|
|
|
|
|
+ .aa-task-st.done { background: #e8f5e9; color: #2e7d32; }
|
|
|
|
|
+ .aa-task-st.pending { background: #fff3e0; color: #e65100; }
|
|
|
|
|
+ .aa-task-st.failed { background: #ffebee; color: #c62828; }
|
|
|
|
|
+ .aa-task-st.processing { background: #e3f2fd; color: #1565c0; }
|
|
|
|
|
+ .aa-task-name { font-size: 11px; font-weight: 500; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
|
|
|
+ .aa-task.active .aa-task-name { color: #1e88e5; }
|
|
|
|
|
+ .aa-task-meta { font-size: 10px; color: #999; display: flex; gap: 6px; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-left-bottom { padding: 8px 10px; border-top: 1px solid #f0f0f0; display: flex; gap: 4px; background: #fafafa; flex-shrink: 0; }
|
|
|
|
|
+ .aa-left-bottom .btn { flex: 1; font-size: 10px; padding: 4px 6px; justify-content: center; }
|
|
|
|
|
+
|
|
|
|
|
+ /* -- Right Panel -- */
|
|
|
|
|
+ .aa-right { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #fff; border-radius: 3px; border: 1px solid #e8e8e8; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-right-toolbar { padding: 8px 14px; border-bottom: 1px solid #e8e8e8; display: flex; align-items: center; justify-content: space-between; background: #fafafa; flex-shrink: 0; }
|
|
|
|
|
+ .aa-right-title { font-size: 14px; font-weight: 600; }
|
|
|
|
|
+ .aa-right-actions { display: flex; gap: 4px; }
|
|
|
|
|
+
|
|
|
|
|
+ .aa-right-content { flex: 1; overflow-y: auto; padding: 12px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Buttons ===== */
|
|
|
|
|
+ .btn { display: inline-flex; align-items: center; gap: 3px; padding: 5px 12px; border-radius: 3px; font-size: 12px; border: none; cursor: pointer; transition: all .15s; font-family: inherit; text-decoration: none; }
|
|
|
|
|
+ .btn-primary { background: #1e88e5; color: #fff; }
|
|
|
|
|
+ .btn-primary:hover { background: #1976d2; }
|
|
|
|
|
+ .btn-success { background: #43a047; color: #fff; }
|
|
|
|
|
+ .btn-success:hover { background: #388e3c; }
|
|
|
|
|
+ .btn-danger { background: #e53935; color: #fff; }
|
|
|
|
|
+ .btn-danger:hover { background: #c62828; }
|
|
|
|
|
+ .btn-default { background: #fff; color: #555; border: 1px solid #d9d9d9; }
|
|
|
|
|
+ .btn-default:hover { color: #1e88e5; border-color: #1e88e5; }
|
|
|
|
|
+ .btn-sm { padding: 3px 8px; font-size: 11px; }
|
|
|
|
|
+ .btn-lg { padding: 6px 18px; font-size: 13px; }
|
|
|
|
|
+ .btn[disabled] { opacity: .5; cursor: not-allowed; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Steps ===== */
|
|
|
|
|
+ .steps { display: flex; align-items: center; justify-content: center; padding: 16px 0; }
|
|
|
|
|
+ .st-item { display: flex; align-items: center; gap: 5px; }
|
|
|
|
|
+ .st-dot { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 11px; border: 2px solid #d9d9d9; color: #bbb; background: #fff; }
|
|
|
|
|
+ .st-item.active .st-dot { background: #1e88e5; border-color: #1e88e5; color: #fff; }
|
|
|
|
|
+ .st-item.done .st-dot { background: #43a047; border-color: #43a047; color: #fff; }
|
|
|
|
|
+ .st-lbl { font-size: 11px; color: #bbb; }
|
|
|
|
|
+ .st-item.active .st-lbl { color: #1e88e5; font-weight: 600; }
|
|
|
|
|
+ .st-item.done .st-lbl { color: #43a047; }
|
|
|
|
|
+ .st-line { width: 32px; height: 2px; background: #e8e8e8; margin: 0 3px; }
|
|
|
|
|
+ .st-line.done { background: #43a047; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Cards ===== */
|
|
|
|
|
+ .card { background: #fff; border: 1px solid #e8e8e8; border-radius: 3px; margin-bottom: 10px; }
|
|
|
|
|
+ .card-hd { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
|
|
|
|
|
+ .card-hd .ch-icon { width: 18px; height: 18px; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 11px; }
|
|
|
|
|
+ .card-bd { padding: 12px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Upload Zone (right panel) ===== */
|
|
|
|
|
+ .upload-zone { border: 2px dashed #d9d9d9; border-radius: 3px; padding: 28px 16px; text-align: center; cursor: pointer; transition: all .2s; background: #fafafa; }
|
|
|
|
|
+ .upload-zone:hover { border-color: #1e88e5; background: #f0f7ff; }
|
|
|
|
|
+ .upload-zone .uz-icon { font-size: 28px; margin-bottom: 4px; opacity: .4; }
|
|
|
|
|
+ .upload-zone .uz-text { font-size: 12px; color: #666; margin-bottom: 2px; }
|
|
|
|
|
+ .upload-zone .uz-hint { font-size: 10px; color: #aaa; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== File Row ===== */
|
|
|
|
|
+ .file-row { display: flex; align-items: center; padding: 6px 10px; background: #fafbfc; border: 1px solid #f0f0f0; border-radius: 3px; margin-top: 5px; gap: 8px; }
|
|
|
|
|
+ .file-row .fr-icon { width: 24px; height: 24px; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; flex-shrink: 0; }
|
|
|
|
|
+ .file-row .fr-icon.pdf { background: #ffebee; color: #c62828; }
|
|
|
|
|
+ .file-row .fr-icon.xls { background: #e8f5e9; color: #2e7d32; }
|
|
|
|
|
+ .file-row .fr-icon.doc { background: #e3f2fd; color: #1565c0; }
|
|
|
|
|
+ .file-row .fr-icon.img { background: #fff3e0; color: #e65100; }
|
|
|
|
|
+ .file-row .fr-info { flex: 1; }
|
|
|
|
|
+ .file-row .fr-name { font-size: 12px; font-weight: 500; }
|
|
|
|
|
+ .file-row .fr-size { font-size: 10px; color: #999; }
|
|
|
|
|
+ .file-row .fr-status { font-size: 10px; padding: 1px 6px; border-radius: 8px; background: #e8f5e9; color: #2e7d32; }
|
|
|
|
|
+ .file-row .fr-status.wait { background: #fff3e0; color: #e65100; }
|
|
|
|
|
+ .file-row .fr-rm { cursor: pointer; color: #bbb; font-size: 13px; }
|
|
|
|
|
+ .file-row .fr-rm:hover { color: #e53935; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Form ===== */
|
|
|
|
|
+ .form-row { display: flex; gap: 16px; margin-top: 10px; }
|
|
|
|
|
+ .form-item { flex: 1; }
|
|
|
|
|
+ .form-item label { display: block; font-size: 12px; color: #555; margin-bottom: 4px; font-weight: 500; }
|
|
|
|
|
+ .form-item label .req { color: #e53935; }
|
|
|
|
|
+ .form-item select { width: 100%; padding: 5px 8px; border: 1px solid #d9d9d9; border-radius: 3px; font-size: 12px; outline: none; height: auto; }
|
|
|
|
|
+ .form-item select:focus { border-color: #1e88e5; }
|
|
|
|
|
+ .form-hint { font-size: 10px; color: #aaa; margin-top: 2px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Log ===== */
|
|
|
|
|
+ .log-box { background: #1a1a2e; border-radius: 3px; padding: 10px; font-family: "Consolas", monospace; font-size: 11px; line-height: 1.7; color: #ccc; max-height: 220px; overflow-y: auto; }
|
|
|
|
|
+ .log-line { display: flex; gap: 6px; }
|
|
|
|
|
+ .log-time { color: #666; flex-shrink: 0; }
|
|
|
|
|
+ .log-node { color: #64b5f6; font-weight: 600; flex-shrink: 0; min-width: 80px; }
|
|
|
|
|
+ .log-msg { color: #aaa; }
|
|
|
|
|
+ .log-line.ok .log-msg { color: #81c784; }
|
|
|
|
|
+ .log-line.info .log-msg { color: #64b5f6; }
|
|
|
|
|
+ .log-line.warn .log-msg { color: #ffb74d; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Progress ===== */
|
|
|
|
|
+ .progress-wrap { margin: 8px 0; }
|
|
|
|
|
+ .progress-label { display: flex; justify-content: space-between; font-size: 11px; color: #888; margin-bottom: 3px; }
|
|
|
|
|
+ .progress-track { height: 5px; background: #f0f0f0; border-radius: 3px; overflow: hidden; }
|
|
|
|
|
+ .progress-fill { height: 100%; background: linear-gradient(90deg, #1e88e5, #42a5f5); border-radius: 3px; width: 100%; animation: slide 1.2s infinite linear; }
|
|
|
|
|
+ @keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Stats ===== */
|
|
|
|
|
+ .stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 10px; }
|
|
|
|
|
+ .stat-box { background: #fafbfc; border: 1px solid #f0f0f0; border-radius: 3px; padding: 8px; text-align: center; }
|
|
|
|
|
+ .stat-num { font-size: 20px; font-weight: 700; }
|
|
|
|
|
+ .stat-num.blue { color: #1e88e5; }
|
|
|
|
|
+ .stat-num.red { color: #e53935; }
|
|
|
|
|
+ .stat-num.orange { color: #fb8c00; }
|
|
|
|
|
+ .stat-num.green { color: #43a047; }
|
|
|
|
|
+ .stat-lbl { font-size: 10px; color: #999; margin-top: 2px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Download ===== */
|
|
|
|
|
+ .download-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: linear-gradient(135deg, #e3f2fd, #e8f5e9); border: 1px solid #a5d6a7; border-radius: 3px; }
|
|
|
|
|
+ .download-row .dl-icon { font-size: 22px; }
|
|
|
|
|
+ .download-row .dl-info { flex: 1; }
|
|
|
|
|
+ .download-row .dl-name { font-size: 12px; font-weight: 600; }
|
|
|
|
|
+ .download-row .dl-meta { font-size: 10px; color: #999; margin-top: 1px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Detail ===== */
|
|
|
|
|
+ .detail-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
|
|
|
|
+ .detail-title { font-size: 15px; font-weight: 700; }
|
|
|
|
|
+ .detail-sub { font-size: 11px; color: #999; margin-top: 2px; }
|
|
|
|
|
+ .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
|
|
|
+ .detail-section-title { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 6px; padding-bottom: 3px; border-bottom: 1px solid #f0f0f0; }
|
|
|
|
|
+ .detail-info-row { font-size: 12px; line-height: 1.8; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Alert ===== */
|
|
|
|
|
+ .alert { padding: 6px 10px; border-radius: 3px; font-size: 11px; margin-bottom: 8px; }
|
|
|
|
|
+ .alert-info { background: #e3f2fd; border: 1px solid #90caf9; color: #1565c0; }
|
|
|
|
|
+ .alert-danger { background: #ffebee; border: 1px solid #ef9a9a; color: #c62828; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Spinner ===== */
|
|
|
|
|
+ .spinner { display: inline-flex; align-items: center; gap: 3px; color: #1e88e5; font-size: 11px; }
|
|
|
|
|
+ .spinner .dot { width: 4px; height: 4px; border-radius: 50%; background: #1e88e5; animation: blink 1s infinite; }
|
|
|
|
|
+ .spinner .dot:nth-child(2) { animation-delay: .15s; }
|
|
|
|
|
+ .spinner .dot:nth-child(3) { animation-delay: .3s; }
|
|
|
|
|
+ @keyframes blink { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== View Toggle ===== */
|
|
|
|
|
+ .view { display: none; }
|
|
|
|
|
+ .view.active { display: block; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Button Bar ===== */
|
|
|
|
|
+ .btn-bar { display: flex; justify-content: flex-end; gap: 6px; margin-top: 10px; }
|
|
|
|
|
+
|
|
|
|
|
+ /* ===== Empty State ===== */
|
|
|
|
|
+ .empty-state { text-align: center; padding: 40px 20px; color: #999; }
|
|
|
|
|
+ .empty-icon { font-size: 40px; margin-bottom: 8px; opacity: 0.3; }
|
|
|
|
|
+ .empty-text { font-size: 12px; }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+<div class="aa-wrapper">
|
|
|
|
|
+<div class="aa-area">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Left Panel ===== -->
|
|
|
|
|
+ <div class="aa-left">
|
|
|
|
|
+ <!-- Upload -->
|
|
|
|
|
+ <div class="aa-left-upload">
|
|
|
|
|
+ <div class="uz" id="leftUploadZone">
|
|
|
|
|
+ <div class="uz-icon">📁</div>
|
|
|
|
|
+ <div class="uz-text">拖入文件或点击新建任务</div>
|
|
|
|
|
+ <div class="uz-hint">PDF / Word / Excel / JPG,≤15MB,最多5个</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="new-btn">
|
|
|
|
|
+ <button class="btn btn-primary btn-sm" onclick="showNewAudit()">+ 新建审计</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Tabs -->
|
|
|
|
|
+ <div class="aa-left-tabs">
|
|
|
|
|
+ <div class="aa-left-tab active" data-filter="all" onclick="filterList('all',this)">全部 <span class="cnt" id="cntAll">0</span></div>
|
|
|
|
|
+ <div class="aa-left-tab" data-filter="pending" onclick="filterList('pending',this)">待处理 <span class="cnt" id="cntPending">0</span></div>
|
|
|
|
|
+ <div class="aa-left-tab" data-filter="done" onclick="filterList('done',this)">已完成 <span class="cnt" id="cntDone">0</span></div>
|
|
|
|
|
+ <div class="aa-left-tab" data-filter="failed" onclick="filterList('failed',this)">失败 <span class="cnt" id="cntFailed">0</span></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Task List -->
|
|
|
|
|
+ <div class="aa-left-list" id="taskList"></div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Bottom -->
|
|
|
|
|
+ <div class="aa-left-bottom">
|
|
|
|
|
+ <button class="btn btn-default btn-sm" onclick="alert('该功能暂未开放')">归档已完成</button>
|
|
|
|
|
+ <button class="btn btn-default btn-sm" style="color:#e53935;" onclick="alert('该功能暂未开放')">清理历史</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Right Panel ===== -->
|
|
|
|
|
+ <div class="aa-right">
|
|
|
|
|
+ <!-- Toolbar -->
|
|
|
|
|
+ <div class="aa-right-toolbar">
|
|
|
|
|
+ <div class="aa-right-title" id="rTitle">新建审计任务</div>
|
|
|
|
|
+ <div class="aa-right-actions" id="rActions"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Content -->
|
|
|
|
|
+ <div class="aa-right-content">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- View: New Audit -->
|
|
|
|
|
+ <div class="view active" id="v-new">
|
|
|
|
|
+ <!-- Steps -->
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <div class="card-bd" style="padding:6px 12px;">
|
|
|
|
|
+ <div class="steps">
|
|
|
|
|
+ <div class="st-item active"><div class="st-dot">1</div><div class="st-lbl">上传资料</div></div>
|
|
|
|
|
+ <div class="st-line"></div>
|
|
|
|
|
+ <div class="st-item"><div class="st-dot">2</div><div class="st-lbl">字段识别</div></div>
|
|
|
|
|
+ <div class="st-line"></div>
|
|
|
|
|
+ <div class="st-item"><div class="st-dot">3</div><div class="st-lbl">勾选字段</div></div>
|
|
|
|
|
+ <div class="st-line"></div>
|
|
|
|
|
+ <div class="st-item"><div class="st-dot">4</div><div class="st-lbl">审计处理</div></div>
|
|
|
|
|
+ <div class="st-line"></div>
|
|
|
|
|
+ <div class="st-item"><div class="st-dot">5</div><div class="st-lbl">查看结果</div></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Upload -->
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <div class="card-hd"><span class="ch-icon" style="background:#e3f2fd;color:#1e88e5;">📄</span> 上传送审资料</div>
|
|
|
|
|
+ <div class="card-bd">
|
|
|
|
|
+ <div class="upload-zone" id="uploadZone">
|
|
|
|
|
+ <div class="uz-icon">📁</div>
|
|
|
|
|
+ <div class="uz-text">点击或拖拽文件到此区域上传</div>
|
|
|
|
|
+ <div class="uz-hint">支持 PDF / Word / Excel / JPG / PNG,单个≤15MB,最多5个</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="margin-top:6px;" id="fileRows"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Config -->
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <div class="card-hd"><span class="ch-icon" style="background:#e8f5e9;color:#2e7d32;">⚙</span> 审计配置</div>
|
|
|
|
|
+ <div class="card-bd">
|
|
|
|
|
+ <div class="form-row">
|
|
|
|
|
+ <div class="form-item">
|
|
|
|
|
+ <label><span class="req">*</span> 项目类型</label>
|
|
|
|
|
+ <select id="projectType">
|
|
|
|
|
+ <option value="建设工程">建设工程</option>
|
|
|
|
|
+ <option value="市政工程">市政工程</option>
|
|
|
|
|
+ <option value="供电工程">供电工程</option>
|
|
|
|
|
+ <option value="装修工程">装修工程</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ <div class="form-hint">决定适用的法规和报告模板</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="form-item">
|
|
|
|
|
+ <label><span class="req">*</span> 输出类型</label>
|
|
|
|
|
+ <select id="outputType">
|
|
|
|
|
+ <option value="审计报告">审计报告(Word)</option>
|
|
|
|
|
+ <option value="审定单">审定单(Excel)</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ <div class="form-hint">选择最终生成的文件类型</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="btn-bar">
|
|
|
|
|
+ <button class="btn btn-primary btn-lg" id="startBtn" onclick="startExtract()">开始识别 »</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- View: Detail -->
|
|
|
|
|
+ <div class="view" id="v-detail">
|
|
|
|
|
+ <div id="detailBody"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+</div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<input type="file" id="fileInput" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png" style="display:none;">
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+var CTX = '${ctx}';
|
|
|
|
|
+var allTasks = [];
|
|
|
|
|
+var currentFilter = 'all';
|
|
|
|
|
+var currentTaskId = null;
|
|
|
|
|
+var currentDetail = null;
|
|
|
|
|
+var uploadedFiles = [];
|
|
|
|
|
+var pollTimer = null;
|
|
|
|
|
+
|
|
|
|
|
+$(document).ready(function() {
|
|
|
|
|
+ // 上传区域点击 / 拖拽
|
|
|
|
|
+ $('#uploadZone').on('click', function(){ $('#fileInput').click(); });
|
|
|
|
|
+ $('#leftUploadZone').on('click', function(){ showNewAudit(); $('#fileInput').click(); });
|
|
|
|
|
+
|
|
|
|
|
+ bindDrop(document.getElementById('uploadZone'));
|
|
|
|
|
+ bindDrop(document.getElementById('leftUploadZone'));
|
|
|
|
|
+
|
|
|
|
|
+ $('#fileInput').on('change', function(){ handleFiles(this.files); this.value = ''; });
|
|
|
|
|
+
|
|
|
|
|
+ loadTasks();
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+function bindDrop(zone) {
|
|
|
|
|
+ if (!zone) return;
|
|
|
|
|
+ zone.addEventListener('dragover', function(e){ e.preventDefault(); this.style.borderColor = '#1e88e5'; this.style.background = '#f0f7ff'; });
|
|
|
|
|
+ zone.addEventListener('dragleave', function(e){ e.preventDefault(); this.style.borderColor = '#d9d9d9'; this.style.background = '#fafafa'; });
|
|
|
|
|
+ zone.addEventListener('drop', function(e){
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ this.style.borderColor = '#d9d9d9'; this.style.background = '#fafafa';
|
|
|
|
|
+ showNewAudit();
|
|
|
|
|
+ handleFiles(e.dataTransfer.files);
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 视图切换 ================= */
|
|
|
|
|
+function showNewAudit() {
|
|
|
|
|
+ document.querySelectorAll('.view').forEach(function(v){ v.classList.remove('active'); });
|
|
|
|
|
+ document.getElementById('v-new').classList.add('active');
|
|
|
|
|
+ document.getElementById('rTitle').textContent = '新建审计任务';
|
|
|
|
|
+ document.getElementById('rActions').innerHTML = '';
|
|
|
|
|
+ document.querySelectorAll('.aa-task').forEach(function(t){ t.classList.remove('active'); });
|
|
|
|
|
+ currentTaskId = null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function filterList(type, tabEl) {
|
|
|
|
|
+ document.querySelectorAll('.aa-left-tab').forEach(function(t){ t.classList.remove('active'); });
|
|
|
|
|
+ tabEl.classList.add('active');
|
|
|
|
|
+ currentFilter = type;
|
|
|
|
|
+ renderList();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 任务列表 ================= */
|
|
|
|
|
+function loadTasks() {
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/listData',
|
|
|
|
|
+ type: 'GET',
|
|
|
|
|
+ data: { pageNo: 1, pageSize: 200 },
|
|
|
|
|
+ success: function(page) {
|
|
|
|
|
+ allTasks = page.list || [];
|
|
|
|
|
+ updateTabCounts();
|
|
|
|
|
+ renderList();
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('加载任务列表失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function statusMeta(status) {
|
|
|
|
|
+ var map = {
|
|
|
|
|
+ 'draft': { cls: 'pending', text: '草稿' },
|
|
|
|
|
+ 'pending': { cls: 'pending', text: '待处理' },
|
|
|
|
|
+ 'processing': { cls: 'processing', text: '处理中' },
|
|
|
|
|
+ 'completed': { cls: 'done', text: '已完成' },
|
|
|
|
|
+ 'failed': { cls: 'failed', text: '失败' },
|
|
|
|
|
+ 'cancelled': { cls: 'pending', text: '已取消' }
|
|
|
|
|
+ };
|
|
|
|
|
+ return map[status] || { cls: 'pending', text: status || '未知' };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function matchFilter(status, filter) {
|
|
|
|
|
+ if (filter === 'all') return true;
|
|
|
|
|
+ if (filter === 'pending') return status === 'draft' || status === 'pending' || status === 'processing';
|
|
|
|
|
+ if (filter === 'done') return status === 'completed';
|
|
|
|
|
+ if (filter === 'failed') return status === 'failed';
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateTabCounts() {
|
|
|
|
|
+ var c = { all: 0, pending: 0, done: 0, failed: 0 };
|
|
|
|
|
+ allTasks.forEach(function(t) {
|
|
|
|
|
+ c.all++;
|
|
|
|
|
+ if (matchFilter(t.status, 'pending')) c.pending++;
|
|
|
|
|
+ if (matchFilter(t.status, 'done')) c.done++;
|
|
|
|
|
+ if (matchFilter(t.status, 'failed')) c.failed++;
|
|
|
|
|
+ });
|
|
|
|
|
+ $('#cntAll').text(c.all);
|
|
|
|
|
+ $('#cntPending').text(c.pending);
|
|
|
|
|
+ $('#cntDone').text(c.done);
|
|
|
|
|
+ $('#cntFailed').text(c.failed);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderList() {
|
|
|
|
|
+ var list = allTasks.filter(function(t){ return matchFilter(t.status, currentFilter); });
|
|
|
|
|
+ var $box = $('#taskList');
|
|
|
|
|
+ if (list.length === 0) {
|
|
|
|
|
+ $box.html('<div class="empty-state"><div class="empty-icon">📋</div><div class="empty-text">暂无任务,点击"新建审计"开始</div></div>');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var html = '';
|
|
|
|
|
+ list.forEach(function(t) {
|
|
|
|
|
+ var m = statusMeta(t.status);
|
|
|
|
|
+ html += '<div class="aa-task' + (t.id === currentTaskId ? ' active' : '') + '" onclick="selectTask(\'' + t.id + '\', this)">' +
|
|
|
|
|
+ '<div class="aa-task-hd">' +
|
|
|
|
|
+ '<span class="aa-task-st ' + m.cls + '">' + m.text + '</span>' +
|
|
|
|
|
+ '<span class="aa-task-name" title="' + (t.taskName || '') + '">' + (t.taskName || '未命名任务') + '</span>' +
|
|
|
|
|
+ '</div>' +
|
|
|
|
|
+ '<div class="aa-task-meta"><span>' + (t.fileCount || 0) + ' 份文件</span><span>' + (t.outputType || '-') + '</span><span>' + formatDate(t.createDate) + '</span></div>' +
|
|
|
|
|
+ '</div>';
|
|
|
|
|
+ });
|
|
|
|
|
+ $box.html(html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function selectTask(taskId, el) {
|
|
|
|
|
+ currentTaskId = taskId;
|
|
|
|
|
+ document.querySelectorAll('.aa-task').forEach(function(t){ t.classList.remove('active'); });
|
|
|
|
|
+ if (el) el.classList.add('active');
|
|
|
|
|
+ loadDetail(taskId);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 任务详情 ================= */
|
|
|
|
|
+function loadDetail(taskId) {
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/detailData',
|
|
|
|
|
+ type: 'GET',
|
|
|
|
|
+ data: { taskId: taskId },
|
|
|
|
|
+ success: function(task) {
|
|
|
|
|
+ currentDetail = task;
|
|
|
|
|
+ renderDetail(task);
|
|
|
|
|
+ document.querySelectorAll('.view').forEach(function(v){ v.classList.remove('active'); });
|
|
|
|
|
+ document.getElementById('v-detail').classList.add('active');
|
|
|
|
|
+ document.getElementById('rTitle').textContent = '审计记录详情';
|
|
|
|
|
+ renderToolbarActions(task);
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('加载任务详情失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderToolbarActions(task) {
|
|
|
|
|
+ var html = '';
|
|
|
|
|
+ if (task.resultFileUrl) {
|
|
|
|
|
+ html += '<a class="btn btn-success btn-sm" href="' + CTX + '/audit/assistant/downloadReport?taskId=' + task.id + '">下载报告</a>';
|
|
|
|
|
+ }
|
|
|
|
|
+ html += '<button class="btn btn-default btn-sm" onclick="reExtract()">重新处理</button>';
|
|
|
|
|
+ html += '<button class="btn btn-danger btn-sm" onclick="deleteTask()">删除</button>';
|
|
|
|
|
+ $('#rActions').html(html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderDetail(task) {
|
|
|
|
|
+ var m = statusMeta(task.status);
|
|
|
|
|
+ var fieldCount = 0;
|
|
|
|
|
+ try { fieldCount = JSON.parse(task.selectedFields || '[]').length; } catch (e) {}
|
|
|
|
|
+
|
|
|
|
|
+ var html = '';
|
|
|
|
|
+ html += '<div class="detail-top"><div>' +
|
|
|
|
|
+ '<div class="detail-title">' + (task.taskName || '未命名任务') + '</div>' +
|
|
|
|
|
+ '<div class="detail-sub">创建于 ' + formatDate(task.createDate) + ' · 耗时 ' + fmtDuration(task.durationSeconds) + ' · <span class="aa-task-st ' + m.cls + '" style="vertical-align:middle;">' + m.text + '</span></div>' +
|
|
|
|
|
+ '</div></div>';
|
|
|
|
|
+
|
|
|
|
|
+ // 配置 + 任务信息
|
|
|
|
|
+ html += '<div class="card"><div class="card-bd"><div class="detail-grid">' +
|
|
|
|
|
+ '<div><div class="detail-section-title">审计配置</div><div class="detail-info-row">' +
|
|
|
|
|
+ '<div><strong>项目类型:</strong>' + (task.projectType || '-') + '</div>' +
|
|
|
|
|
+ '<div><strong>输出类型:</strong>' + (task.outputType || '-') + '</div>' +
|
|
|
|
|
+ '<div><strong>审计字段:</strong>' + (fieldCount > 0 ? fieldCount + ' 个' : '待勾选') + '</div>' +
|
|
|
|
|
+ '</div></div>' +
|
|
|
|
|
+ '<div><div class="detail-section-title">任务信息</div><div class="detail-info-row">' +
|
|
|
|
|
+ '<div><strong>送审资料:</strong>' + (task.fileCount || 0) + ' 份文件</div>' +
|
|
|
|
|
+ '<div><strong>所属部门:</strong>' + (task.officeName || '-') + '</div>' +
|
|
|
|
|
+ '<div><strong>任务ID:</strong>' + task.id + '</div>' +
|
|
|
|
|
+ '</div></div>' +
|
|
|
|
|
+ '</div></div></div>';
|
|
|
|
|
+
|
|
|
|
|
+ // 统计
|
|
|
|
|
+ html += '<div class="stats-row">' +
|
|
|
|
|
+ '<div class="stat-box"><div class="stat-num blue">' + (task.fileCount || 0) + '</div><div class="stat-lbl">送审文件</div></div>' +
|
|
|
|
|
+ '<div class="stat-box"><div class="stat-num orange">' + fieldCount + '</div><div class="stat-lbl">审计字段</div></div>' +
|
|
|
|
|
+ '<div class="stat-box"><div class="stat-num ' + (task.status === 'failed' ? 'red' : 'green') + '">' + m.text + '</div><div class="stat-lbl">当前状态</div></div>' +
|
|
|
|
|
+ '<div class="stat-box"><div class="stat-num green">' + fmtDuration(task.durationSeconds) + '</div><div class="stat-lbl">总耗时</div></div>' +
|
|
|
|
|
+ '</div>';
|
|
|
|
|
+
|
|
|
|
|
+ // 状态相关
|
|
|
|
|
+ if (task.status === 'processing') {
|
|
|
|
|
+ html += '<div class="card"><div class="card-bd">' +
|
|
|
|
|
+ '<div class="alert alert-info"><span class="spinner"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span> 正在调用 AI 工作流处理中,请稍候...</div>' +
|
|
|
|
|
+ '<div class="progress-wrap"><div class="progress-track"><div class="progress-fill"></div></div></div>' +
|
|
|
|
|
+ '</div></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (task.status === 'failed') {
|
|
|
|
|
+ html += '<div class="alert alert-danger">处理失败:' + (task.errorMsg || '未知错误') + '</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (task.status === 'completed' && task.resultFileUrl) {
|
|
|
|
|
+ html += '<div class="card"><div class="card-hd"><span class="ch-icon" style="background:#e8f5e9;color:#2e7d32;">📥</span> 生成文件</div><div class="card-bd">' +
|
|
|
|
|
+ '<div class="download-row"><div class="dl-icon">📄</div><div class="dl-info">' +
|
|
|
|
|
+ '<div class="dl-name">' + (task.resultFileName || '审计报告') + '</div>' +
|
|
|
|
|
+ '<div class="dl-meta">生成于 ' + formatDate(task.updateDate) + ' · 需审计人员复核确认</div>' +
|
|
|
|
|
+ '</div><a class="btn btn-success btn-sm" href="' + CTX + '/audit/assistant/downloadReport?taskId=' + task.id + '">下载</a></div>' +
|
|
|
|
|
+ '</div></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (task.status === 'completed' && !task.resultFileUrl) {
|
|
|
|
|
+ html += '<div class="card"><div class="card-bd"><div class="alert alert-info">字段提取已完成,请生成审计报告。</div>' +
|
|
|
|
|
+ '<div class="btn-bar"><button class="btn btn-primary" onclick="generateReport()">生成报告 »</button></div></div></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理日志
|
|
|
|
|
+ html += '<div class="card"><div class="card-hd"><span class="ch-icon" style="background:#1a1a2e;color:#64b5f6;">🖥</span> 处理日志</div><div class="card-bd">' +
|
|
|
|
|
+ renderLogBox(task.processLog) + '</div></div>';
|
|
|
|
|
+
|
|
|
|
|
+ $('#detailBody').html(html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderLogBox(processLog) {
|
|
|
|
|
+ var lines = '';
|
|
|
|
|
+ try {
|
|
|
|
|
+ var logs = JSON.parse(processLog || 'null');
|
|
|
|
|
+ if (logs && logs.length) {
|
|
|
|
|
+ logs.forEach(function(l) {
|
|
|
|
|
+ if (typeof l === 'string') {
|
|
|
|
|
+ lines += '<div class="log-line info"><span class="log-msg">' + l + '</span></div>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ var type = l.type || l.level || 'info';
|
|
|
|
|
+ lines += '<div class="log-line ' + type + '"><span class="log-time">' + (l.time || '') + '</span><span class="log-node">' + (l.node || '') + '</span><span class="log-msg">' + (l.msg || '') + '</span></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (processLog) lines = '<div class="log-line info"><span class="log-msg">' + processLog + '</span></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!lines) lines = '<div class="log-line"><span class="log-msg" style="color:#666;">暂无日志</span></div>';
|
|
|
|
|
+ return '<div class="log-box">' + lines + '</div>';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 文件上传 ================= */
|
|
|
|
|
+function handleFiles(files) {
|
|
|
|
|
+ if (!files || files.length === 0) return;
|
|
|
|
|
+ var allowed = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png'];
|
|
|
|
|
+ for (var i = 0; i < files.length; i++) {
|
|
|
|
|
+ var f = files[i];
|
|
|
|
|
+ var ext = f.name.split('.').pop().toLowerCase();
|
|
|
|
|
+ if (allowed.indexOf(ext) < 0) { alert('文件 "' + f.name + '" 格式不支持'); continue; }
|
|
|
|
|
+ if (f.size > 15 * 1024 * 1024) { alert('文件 "' + f.name + '" 超过15MB限制'); continue; }
|
|
|
|
|
+ if (uploadedFiles.length >= 5) { alert('最多上传 5 个文件'); break; }
|
|
|
|
|
+ uploadedFiles.push(f);
|
|
|
|
|
+ }
|
|
|
|
|
+ renderFileRows();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderFileRows() {
|
|
|
|
|
+ var html = '';
|
|
|
|
|
+ uploadedFiles.forEach(function(f, idx) {
|
|
|
|
|
+ var ext = f.name.split('.').pop().toLowerCase();
|
|
|
|
|
+ var icon = 'pdf', letter = 'P';
|
|
|
|
|
+ if (ext === 'xls' || ext === 'xlsx') { icon = 'xls'; letter = 'X'; }
|
|
|
|
|
+ else if (ext === 'doc' || ext === 'docx') { icon = 'doc'; letter = 'W'; }
|
|
|
|
|
+ else if (ext === 'jpg' || ext === 'jpeg' || ext === 'png') { icon = 'img'; letter = 'J'; }
|
|
|
|
|
+ html += '<div class="file-row">' +
|
|
|
|
|
+ '<div class="fr-icon ' + icon + '">' + letter + '</div>' +
|
|
|
|
|
+ '<div class="fr-info"><div class="fr-name">' + f.name + '</div><div class="fr-size">' + fmtSize(f.size) + '</div></div>' +
|
|
|
|
|
+ '<span class="fr-status wait">待上传</span>' +
|
|
|
|
|
+ '<span class="fr-rm" onclick="removeFile(' + idx + ')">×</span>' +
|
|
|
|
|
+ '</div>';
|
|
|
|
|
+ });
|
|
|
|
|
+ $('#fileRows').html(html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeFile(idx) {
|
|
|
|
|
+ uploadedFiles.splice(idx, 1);
|
|
|
|
|
+ renderFileRows();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 业务流程 ================= */
|
|
|
|
|
+function startExtract() {
|
|
|
|
|
+ if (uploadedFiles.length === 0) { alert('请先上传文件'); return; }
|
|
|
|
|
+ var formData = new FormData();
|
|
|
|
|
+ uploadedFiles.forEach(function(f){ formData.append('files', f); });
|
|
|
|
|
+ formData.append('projectType', $('#projectType').val());
|
|
|
|
|
+ formData.append('outputType', $('#outputType').val());
|
|
|
|
|
+
|
|
|
|
|
+ $('#startBtn').prop('disabled', true).text('上传中...');
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/upload',
|
|
|
|
|
+ type: 'POST',
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ processData: false,
|
|
|
|
|
+ contentType: false,
|
|
|
|
|
+ success: function(result) {
|
|
|
|
|
+ $('#startBtn').prop('disabled', false).html('开始识别 »');
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ uploadedFiles = [];
|
|
|
|
|
+ renderFileRows();
|
|
|
|
|
+ loadTasks();
|
|
|
|
|
+ var taskId = result.taskId;
|
|
|
|
|
+ currentTaskId = taskId;
|
|
|
|
|
+ loadDetail(taskId);
|
|
|
|
|
+ callStartExtract(taskId);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert(result.msg || '创建任务失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() {
|
|
|
|
|
+ $('#startBtn').prop('disabled', false).html('开始识别 »');
|
|
|
|
|
+ alert('网络请求失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function callStartExtract(taskId) {
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/startExtract',
|
|
|
|
|
+ type: 'POST',
|
|
|
|
|
+ data: { taskId: taskId },
|
|
|
|
|
+ success: function(result) {
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ startPolling(taskId);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert(result.msg || '启动处理失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('网络请求失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function reExtract() {
|
|
|
|
|
+ if (!currentTaskId) { alert('请先选择任务'); return; }
|
|
|
|
|
+ if (!confirm('确定要重新处理吗?这将重新调用AI提取字段。')) return;
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/reExtract',
|
|
|
|
|
+ type: 'POST',
|
|
|
|
|
+ data: { taskId: currentTaskId },
|
|
|
|
|
+ success: function(result) {
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ alert('已重新开始处理');
|
|
|
|
|
+ startPolling(currentTaskId);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert(result.msg || '重新处理失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('网络请求失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function deleteTask() {
|
|
|
|
|
+ if (!currentTaskId) { alert('请先选择任务'); return; }
|
|
|
|
|
+ if (!confirm('确定要删除该任务吗?删除后不可恢复。')) return;
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/delete',
|
|
|
|
|
+ type: 'POST',
|
|
|
|
|
+ data: { taskId: currentTaskId },
|
|
|
|
|
+ success: function(result) {
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ currentTaskId = null;
|
|
|
|
|
+ loadTasks();
|
|
|
|
|
+ showNewAudit();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert(result.msg || '删除失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('网络请求失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function generateReport() {
|
|
|
|
|
+ if (!currentDetail) { alert('请先选择任务'); return; }
|
|
|
|
|
+ if (!confirm('确定要生成报告吗?')) return;
|
|
|
|
|
+ var fields = currentDetail.selectedFields || '[]';
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/generateReport',
|
|
|
|
|
+ type: 'POST',
|
|
|
|
|
+ data: { taskId: currentDetail.id, selectedFields: fields },
|
|
|
|
|
+ success: function(result) {
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ alert('报告生成成功!');
|
|
|
|
|
+ loadDetail(currentDetail.id);
|
|
|
|
|
+ loadTasks();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert(result.msg || '生成报告失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function() { alert('网络请求失败'); }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function startPolling(taskId) {
|
|
|
|
|
+ stopPolling();
|
|
|
|
|
+ pollTimer = setInterval(function() {
|
|
|
|
|
+ $.ajax({
|
|
|
|
|
+ url: CTX + '/audit/assistant/getStatus',
|
|
|
|
|
+ type: 'GET',
|
|
|
|
|
+ data: { taskId: taskId },
|
|
|
|
|
+ success: function(task) {
|
|
|
|
|
+ if (currentTaskId === taskId) {
|
|
|
|
|
+ currentDetail = task;
|
|
|
|
|
+ renderDetail(task);
|
|
|
|
|
+ renderToolbarActions(task);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (task.status === 'completed' || task.status === 'failed') {
|
|
|
|
|
+ stopPolling();
|
|
|
|
|
+ loadTasks();
|
|
|
|
|
+ if (task.status === 'completed') alert('处理完成!');
|
|
|
|
|
+ else alert('处理失败:' + (task.errorMsg || '未知错误'));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }, 2500);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function stopPolling() {
|
|
|
|
|
+ if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ================= 工具函数 ================= */
|
|
|
|
|
+function formatDate(dateStr) {
|
|
|
|
|
+ if (!dateStr) return '-';
|
|
|
|
|
+ var d = new Date(dateStr);
|
|
|
|
|
+ if (isNaN(d.getTime())) return '-';
|
|
|
|
|
+ var p = function(n){ return ('0' + n).slice(-2); };
|
|
|
|
|
+ return p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function fmtDuration(sec) {
|
|
|
|
|
+ if (!sec && sec !== 0) return '-';
|
|
|
|
|
+ var m = Math.floor(sec / 60), s = sec % 60;
|
|
|
|
|
+ return m + ':' + ('0' + s).slice(-2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function fmtSize(bytes) {
|
|
|
|
|
+ if (!bytes && bytes !== 0) return '-';
|
|
|
|
|
+ if (bytes < 1024) return bytes + ' B';
|
|
|
|
|
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB';
|
|
|
|
|
+ return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|