|
|
@@ -0,0 +1,664 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh-CN">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8" />
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
+ <title>已办中心 · 银行回执单上传 Demo</title>
|
|
|
+ <style>
|
|
|
+ :root {
|
|
|
+ --primary: #10b981;
|
|
|
+ --primary-dark: #059669;
|
|
|
+ --primary-bg: #ecfdf5;
|
|
|
+ --primary-border: #a7f3d0;
|
|
|
+ --text: #0f172a;
|
|
|
+ --text-2: #334155;
|
|
|
+ --muted: #94a3b8;
|
|
|
+ --border: #e2e8f0;
|
|
|
+ --bg: #f8fafc;
|
|
|
+ --panel: #ffffff;
|
|
|
+ --danger: #ef4444;
|
|
|
+ --shadow: 0 12px 40px rgba(15, 23, 42, 0.12);
|
|
|
+ font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
|
|
|
+ }
|
|
|
+ * { box-sizing: border-box; }
|
|
|
+ body {
|
|
|
+ margin: 0;
|
|
|
+ background: linear-gradient(180deg, #f0fdf4 0%, #f8fafc 40%, #f1f5f9 100%);
|
|
|
+ color: var(--text);
|
|
|
+ min-height: 100vh;
|
|
|
+ padding: 24px;
|
|
|
+ }
|
|
|
+ .page-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 12px;
|
|
|
+ margin: 0 0 16px;
|
|
|
+ }
|
|
|
+ .page-title h1 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+ .page-title h1::before {
|
|
|
+ content: "";
|
|
|
+ width: 4px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 2px;
|
|
|
+ background: var(--primary);
|
|
|
+ }
|
|
|
+ .page-title .hint {
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--muted);
|
|
|
+ }
|
|
|
+ .panel {
|
|
|
+ background: var(--panel);
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: 10px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
|
|
+ }
|
|
|
+ table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ th, td {
|
|
|
+ padding: 12px 14px;
|
|
|
+ text-align: center;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ }
|
|
|
+ th {
|
|
|
+ background: #f8fafc;
|
|
|
+ color: #64748b;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ tbody tr:nth-child(even) { background: #fafafa; }
|
|
|
+ tbody tr:hover { background: #f0fdf4; }
|
|
|
+ .tag {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ .tag.success { background: #d1fae5; color: #047857; }
|
|
|
+ .tag.warning { background: #fef3c7; color: #b45309; }
|
|
|
+ .ops {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 6px;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ .btn {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 10px;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ background: #fff;
|
|
|
+ color: var(--text-2);
|
|
|
+ font-size: 13px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all .15s;
|
|
|
+ }
|
|
|
+ .btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
|
+ .btn.primary {
|
|
|
+ border-color: transparent;
|
|
|
+ background: transparent;
|
|
|
+ color: var(--primary);
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ .btn.primary:hover { background: var(--primary-bg); }
|
|
|
+ .btn.main {
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border: none;
|
|
|
+ background: var(--primary);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ .btn.main:hover { background: var(--primary-dark); }
|
|
|
+ .btn.ghost {
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ .btn svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
|
+ .btn.main svg, .btn.ghost svg { width: 16px; height: 16px; }
|
|
|
+
|
|
|
+ /* Mask + Dialog */
|
|
|
+ .mask {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(15, 23, 42, 0.45);
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 16px;
|
|
|
+ z-index: 100;
|
|
|
+ backdrop-filter: none;
|
|
|
+ }
|
|
|
+ .mask.open { display: flex; }
|
|
|
+ .dialog {
|
|
|
+ width: min(720px, 100%);
|
|
|
+ max-height: calc(100vh - 100px);
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-shadow: var(--shadow);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ animation: pop .18s ease-out;
|
|
|
+ }
|
|
|
+ @keyframes pop {
|
|
|
+ from { transform: translateY(8px); opacity: 0; }
|
|
|
+ to { transform: none; opacity: 1; }
|
|
|
+ }
|
|
|
+ .dialog-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .dialog-header h2 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .dialog-close {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: var(--muted);
|
|
|
+ font-size: 18px;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+ .dialog-close:hover { background: var(--bg); color: var(--text); }
|
|
|
+ .dialog-body {
|
|
|
+ padding: 20px;
|
|
|
+ overflow-y: auto;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ .meta {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 12px 16px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 12px 14px;
|
|
|
+ background: var(--bg);
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .meta .label { color: var(--muted); font-size: 11px; margin-bottom: 4px; }
|
|
|
+ .meta .value { color: var(--text-2); font-weight: 500; }
|
|
|
+ .section-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--text);
|
|
|
+ margin: 0 0 10px;
|
|
|
+ }
|
|
|
+ .attachment-upload.is-dragover {
|
|
|
+ outline: 2px dashed var(--primary);
|
|
|
+ outline-offset: 4px;
|
|
|
+ background: var(--primary-bg);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 8px;
|
|
|
+ }
|
|
|
+ .attachment-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .upload-card {
|
|
|
+ width: 140px;
|
|
|
+ height: 96px;
|
|
|
+ border: 1px dashed var(--primary-border);
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #f0fdf4;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all .15s;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+ .upload-card:hover {
|
|
|
+ border-color: var(--primary);
|
|
|
+ border-style: solid;
|
|
|
+ background: var(--primary-bg);
|
|
|
+ }
|
|
|
+ .upload-card .plus {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ color: var(--primary);
|
|
|
+ }
|
|
|
+ .upload-card-text {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--primary);
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+ .attachment-item {
|
|
|
+ width: 140px;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fff;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all .15s;
|
|
|
+ }
|
|
|
+ .attachment-item:hover {
|
|
|
+ border-color: var(--primary);
|
|
|
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
|
|
|
+ }
|
|
|
+ .attachment-item.is-uploading {
|
|
|
+ opacity: 0.6;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ .attachment-preview {
|
|
|
+ width: 100%;
|
|
|
+ height: 96px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: var(--bg);
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .attachment-preview img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ .doc-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #e0f2fe;
|
|
|
+ color: #0284c7;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+ .attachment-footer {
|
|
|
+ padding: 6px 8px 8px;
|
|
|
+ }
|
|
|
+ .attachment-name {
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ overflow: hidden;
|
|
|
+ font-size: 11px;
|
|
|
+ color: var(--text-2);
|
|
|
+ line-height: 1.35;
|
|
|
+ min-height: 30px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ .attachment-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .action-btn {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ color: #64748b;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ .action-btn:hover { color: var(--primary); background: var(--primary-bg); }
|
|
|
+ .action-btn.danger:hover { color: var(--danger); background: #fef2f2; }
|
|
|
+ .action-btn svg { width: 14px; height: 14px; }
|
|
|
+ .upload-hint {
|
|
|
+ display: block;
|
|
|
+ font-size: 11px;
|
|
|
+ color: var(--muted);
|
|
|
+ margin-top: 8px;
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+ .dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 12px 20px 16px;
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .toast {
|
|
|
+ position: fixed;
|
|
|
+ top: 24px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%) translateY(-12px);
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid var(--primary-border);
|
|
|
+ color: var(--primary-dark);
|
|
|
+ padding: 10px 16px;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ box-shadow: var(--shadow);
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: all .2s;
|
|
|
+ z-index: 200;
|
|
|
+ }
|
|
|
+ .toast.show {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(-50%) translateY(0);
|
|
|
+ }
|
|
|
+ .note {
|
|
|
+ margin-top: 16px;
|
|
|
+ padding: 12px 14px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px dashed var(--primary-border);
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--text-2);
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+ .note strong { color: var(--primary-dark); }
|
|
|
+ #fileInput { display: none; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div class="page-title">
|
|
|
+ <h1>已办中心</h1>
|
|
|
+ <span class="hint">Demo · 点击操作列「上传回执」打开弹窗</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section class="panel">
|
|
|
+ <table>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th style="width:68px">序号</th>
|
|
|
+ <th>流程名称</th>
|
|
|
+ <th>申请人</th>
|
|
|
+ <th>申请时间</th>
|
|
|
+ <th>当前节点</th>
|
|
|
+ <th>流程状态</th>
|
|
|
+ <th style="min-width:320px">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td>1</td>
|
|
|
+ <td>费用报销申请</td>
|
|
|
+ <td>张三</td>
|
|
|
+ <td>2026-10-12 14:22</td>
|
|
|
+ <td>—</td>
|
|
|
+ <td><span class="tag success">已完成</span></td>
|
|
|
+ <td>
|
|
|
+ <div class="ops">
|
|
|
+ <button class="btn primary" type="button">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
|
+ 查看
|
|
|
+ </button>
|
|
|
+ <button class="btn" type="button">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
|
|
+ 打印
|
|
|
+ </button>
|
|
|
+ <button class="btn" type="button" onclick="openDialog()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
|
+ 上传回执
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>2</td>
|
|
|
+ <td>付款申请单</td>
|
|
|
+ <td>李四</td>
|
|
|
+ <td>2026-10-10 09:05</td>
|
|
|
+ <td>财务复核</td>
|
|
|
+ <td><span class="tag warning">进行中</span></td>
|
|
|
+ <td>
|
|
|
+ <div class="ops">
|
|
|
+ <button class="btn primary" type="button">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
|
+ 查看
|
|
|
+ </button>
|
|
|
+ <button class="btn" type="button">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
|
|
+ 打印
|
|
|
+ </button>
|
|
|
+ <button class="btn" type="button" onclick="openDialog()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
|
+ 上传回执
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <div class="note">
|
|
|
+ <strong>交互说明:</strong>
|
|
|
+ 操作列新增「上传回执」→ 弹窗回显该审批实例已上传的银行回执单(多张)→
|
|
|
+ 左侧上传卡片可继续追加 → 支持预览/下载/删除 → 点「确定」保存。
|
|
|
+ 本 Demo 为前端示意,文件不会真正上传到服务器。
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mask" id="mask" onclick="onMaskClick(event)">
|
|
|
+ <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="dlgTitle">
|
|
|
+ <div class="dialog-header">
|
|
|
+ <h2 id="dlgTitle">上传银行回执单</h2>
|
|
|
+ <button class="dialog-close" type="button" aria-label="关闭" onclick="closeDialog()">×</button>
|
|
|
+ </div>
|
|
|
+ <div class="dialog-body">
|
|
|
+ <div class="meta">
|
|
|
+ <div>
|
|
|
+ <div class="label">流程名称</div>
|
|
|
+ <div class="value">费用报销申请</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="label">申请人</div>
|
|
|
+ <div class="value">张三</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="label">申请时间</div>
|
|
|
+ <div class="value">2026-10-12 14:22</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="label">已上传</div>
|
|
|
+ <div class="value"><span id="countText">2</span> 张回执单</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="section-label">银行回执单</p>
|
|
|
+ <div
|
|
|
+ class="attachment-upload"
|
|
|
+ id="dropZone"
|
|
|
+ ondragover="onDragOver(event)"
|
|
|
+ ondragleave="onDragLeave(event)"
|
|
|
+ ondrop="onDrop(event)"
|
|
|
+ >
|
|
|
+ <div class="attachment-list" id="list">
|
|
|
+ <div class="upload-card" onclick="document.getElementById('fileInput').click()" title="点击或拖拽上传">
|
|
|
+ <svg class="plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span class="upload-card-text">上传附件</span>
|
|
|
+ </div>
|
|
|
+ <!-- history items injected by JS -->
|
|
|
+ </div>
|
|
|
+ <span class="upload-hint">支持拖拽或点击上传图片、PDF 等文件,单文件不超过 20MB;可上传多张回执单</span>
|
|
|
+ </div>
|
|
|
+ <input id="fileInput" type="file" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx" />
|
|
|
+ </div>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <button class="btn ghost" type="button" onclick="closeDialog()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ <button class="btn main" type="button" onclick="saveDialog()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
|
|
|
+ 确定
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="toast" id="toast"></div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ const seedFiles = [
|
|
|
+ { id: '1', fileName: '电子回单_工商银行_1012.pdf', kind: 'pdf' },
|
|
|
+ { id: '2', fileName: '银行回执单_微信转账.jpg', kind: 'image',
|
|
|
+ thumb: 'data:image/svg+xml,' + encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="280" height="192"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#ecfdf5"/><stop offset="1" stop-color="#d1fae5"/></linearGradient></defs><rect width="280" height="192" fill="url(#g)"/><rect x="24" y="24" width="232" height="144" rx="8" fill="#fff" stroke="#a7f3d0"/><text x="140" y="90" text-anchor="middle" font-family="sans-serif" font-size="14" fill="#059669">银行回执单</text><text x="140" y="114" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#94a3b8">示意缩略图</text></svg>`)
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ let files = [];
|
|
|
+
|
|
|
+ function openDialog() {
|
|
|
+ files = seedFiles.map(f => ({ ...f }));
|
|
|
+ renderList();
|
|
|
+ document.getElementById('mask').classList.add('open');
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeDialog() {
|
|
|
+ document.getElementById('mask').classList.remove('open');
|
|
|
+ }
|
|
|
+
|
|
|
+ function onMaskClick(e) {
|
|
|
+ if (e.target === document.getElementById('mask')) closeDialog();
|
|
|
+ }
|
|
|
+
|
|
|
+ function saveDialog() {
|
|
|
+ showToast('已保存 ' + files.length + ' 张银行回执单(Demo)');
|
|
|
+ closeDialog();
|
|
|
+ }
|
|
|
+
|
|
|
+ function showToast(msg) {
|
|
|
+ const el = document.getElementById('toast');
|
|
|
+ el.textContent = msg;
|
|
|
+ el.classList.add('show');
|
|
|
+ setTimeout(() => el.classList.remove('show'), 1800);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateCount() {
|
|
|
+ document.getElementById('countText').textContent = String(files.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderList() {
|
|
|
+ const list = document.getElementById('list');
|
|
|
+ const uploadCard = list.querySelector('.upload-card');
|
|
|
+ list.innerHTML = '';
|
|
|
+ list.appendChild(uploadCard);
|
|
|
+
|
|
|
+ files.forEach((file, index) => {
|
|
|
+ const item = document.createElement('div');
|
|
|
+ item.className = 'attachment-item' + (file._uploading ? ' is-uploading' : '');
|
|
|
+ const previewInner = file.kind === 'image' && file.thumb
|
|
|
+ ? `<img src="${file.thumb}" alt="${escapeHtml(file.fileName)}" />`
|
|
|
+ : `<div class="doc-icon">PDF</div>`;
|
|
|
+ item.innerHTML = `
|
|
|
+ <div class="attachment-preview">${previewInner}</div>
|
|
|
+ <div class="attachment-footer">
|
|
|
+ <div class="attachment-name" title="${escapeHtml(file.fileName)}">${escapeHtml(file.fileName)}</div>
|
|
|
+ <div class="attachment-actions">
|
|
|
+ <button class="action-btn" type="button" title="下载" onclick="downloadFile(${index})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
|
+ </button>
|
|
|
+ <button class="action-btn danger" type="button" title="删除" onclick="removeFile(${index})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ list.appendChild(item);
|
|
|
+ });
|
|
|
+ updateCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ function escapeHtml(s) {
|
|
|
+ return String(s).replace(/[&<>"']/g, c => ({
|
|
|
+ '&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
|
+ })[c]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeFile(index) {
|
|
|
+ files.splice(index, 1);
|
|
|
+ renderList();
|
|
|
+ showToast('已移除(未保存前可撤销感)');
|
|
|
+ }
|
|
|
+
|
|
|
+ function downloadFile(index) {
|
|
|
+ showToast('下载:' + files[index].fileName);
|
|
|
+ }
|
|
|
+
|
|
|
+ function addFiles(fileList) {
|
|
|
+ Array.from(fileList || []).forEach(raw => {
|
|
|
+ const kind = raw.type.startsWith('image/') ? 'image' : 'pdf';
|
|
|
+ const id = 'n' + Date.now() + Math.random().toString(16).slice(2, 6);
|
|
|
+ const entry = { id, fileName: raw.name, kind, _uploading: true, thumb: '' };
|
|
|
+ files.push(entry);
|
|
|
+ renderList();
|
|
|
+ // simulate upload
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = () => {
|
|
|
+ const idx = files.findIndex(f => f.id === id);
|
|
|
+ if (idx < 0) return;
|
|
|
+ files[idx]._uploading = false;
|
|
|
+ if (kind === 'image') files[idx].thumb = reader.result;
|
|
|
+ renderList();
|
|
|
+ };
|
|
|
+ if (kind === 'image') reader.readAsDataURL(raw);
|
|
|
+ else setTimeout(() => {
|
|
|
+ const idx = files.findIndex(f => f.id === id);
|
|
|
+ if (idx < 0) return;
|
|
|
+ files[idx]._uploading = false;
|
|
|
+ renderList();
|
|
|
+ }, 500);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('fileInput').addEventListener('change', (e) => {
|
|
|
+ addFiles(e.target.files);
|
|
|
+ e.target.value = '';
|
|
|
+ });
|
|
|
+
|
|
|
+ function onDragOver(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ document.getElementById('dropZone').classList.add('is-dragover');
|
|
|
+ }
|
|
|
+ function onDragLeave(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ document.getElementById('dropZone').classList.remove('is-dragover');
|
|
|
+ }
|
|
|
+ function onDrop(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ document.getElementById('dropZone').classList.remove('is-dragover');
|
|
|
+ addFiles(e.dataTransfer.files);
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'Escape') closeDialog();
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|