| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>纪检管理后台 · 安建医院纪检</title>
- <style>
- :root{
- --primary:#a51f23;--primary-dark:#8a1a1d;--primary-soft:#f8ecec;
- --ink:#1f1f1f;--muted:#8a8a8a;--line:#e8e8e8;--bg:#f3f4f6;--card:#fff;
- --radius:10px;--shadow:0 2px 12px rgba(0,0,0,.06);--side:220px;
- }
- *{box-sizing:border-box;margin:0;padding:0}
- body{font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;background:var(--bg);color:var(--ink);min-height:100vh;line-height:1.5}
- .layout{display:flex;min-height:100vh}
- .sidebar{width:var(--side);background:linear-gradient(180deg,#2a1516,#1a1011);color:#fff;position:fixed;inset:0 auto 0 0;display:flex;flex-direction:column;z-index:50}
- .brand{padding:22px 18px 16px;border-bottom:1px solid rgba(255,255,255,.08)}
- .brand img{width:40px;height:40px;border-radius:8px;background:#fff;object-fit:contain;padding:4px;display:block;margin-bottom:10px}
- .brand h1{font-size:15px;font-weight:600}
- .brand p{margin-top:4px;font-size:11px;color:rgba(255,255,255,.5)}
- .nav{padding:12px 10px;flex:1}
- .nav button{width:100%;display:flex;align-items:center;gap:10px;padding:11px 12px;margin-bottom:4px;border:0;border-radius:8px;background:transparent;color:rgba(255,255,255,.72);font:inherit;font-size:14px;cursor:pointer;text-align:left}
- .nav button:hover{background:rgba(255,255,255,.06);color:#fff}
- .nav button.active{background:var(--primary);color:#fff;font-weight:600}
- .nav svg{width:18px;height:18px;fill:currentColor;flex-shrink:0}
- .side-foot{padding:14px 18px 20px;font-size:11px;color:rgba(255,255,255,.4);border-top:1px solid rgba(255,255,255,.06);line-height:1.6}
- .main{margin-left:var(--side);flex:1;min-width:0}
- .topbar{height:56px;background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 24px;position:sticky;top:0;z-index:40}
- .topbar h2{font-size:17px}
- .topbar .user{font-size:13px;color:var(--muted)}
- .topbar .user b{color:var(--ink)}
- .content{padding:20px 24px 40px}
- .tip{margin-bottom:14px;padding:10px 14px;background:#fff8e8;border:1px solid #f0e0b2;border-radius:8px;font-size:12px;color:#8a6d1d}
- .panel{display:none}.panel.active{display:block}
- .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:18px}
- @media(max-width:1100px){.stats{grid-template-columns:1fr 1fr}}
- .stat{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px 20px}
- .stat .label{font-size:13px;color:var(--muted)}
- .stat .value{margin-top:6px;font-size:28px;font-weight:700;color:var(--primary)}
- .stat .sub{margin-top:4px;font-size:12px;color:#aaa}
- .toolbar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:14px}
- .toolbar .spacer{flex:1}
- input[type=search],input[type=text],input[type=email],input[type=url],select,textarea{height:36px;border:1px solid var(--line);border-radius:8px;padding:0 12px;font:inherit;font-size:13px;background:#fff;outline:none;color:var(--ink)}
- input[type=search]{width:220px}
- textarea{height:auto;min-height:88px;padding:10px 12px;width:100%;resize:vertical}
- input:focus,select:focus,textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(165,31,35,.1)}
- .btn{height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--line);background:#fff;font:inherit;font-size:13px;cursor:pointer;color:var(--ink)}
- .btn:hover{background:#fafafa}
- .btn-primary{background:var(--primary);border-color:var(--primary);color:#fff}
- .btn-primary:hover{background:var(--primary-dark)}
- .btn-ghost{color:var(--primary);border-color:rgba(165,31,35,.35);background:var(--primary-soft)}
- .btn-sm{height:28px;padding:0 10px;font-size:12px}
- .card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}
- .card-head{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}
- .card-head h3{font-size:15px}
- .table-wrap{overflow-x:auto}
- table{width:100%;border-collapse:collapse;font-size:13px}
- th,td{padding:12px 14px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top}
- th{background:#fafafa;color:#666;font-weight:600;font-size:12px;white-space:nowrap}
- tr:hover td{background:#fcf8f8}
- .ellipsis{max-width:240px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
- .empty{padding:40px;text-align:center;color:var(--muted)}
- .tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:12px;font-weight:600}
- .tag-warn{background:#fff1f0;color:#cf1322}
- .tag-ok{background:#f6ffed;color:#389e0d}
- .tag-gray{background:#f5f5f5;color:#666}
- .tag-blue{background:#e6f4ff;color:#1677ff}
- .grid-2{display:grid;grid-template-columns:1.2fr .8fr;gap:16px}
- @media(max-width:960px){.grid-2{grid-template-columns:1fr}}
- .form-card,.preview-card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:20px 22px 24px}
- .form-card h3,.preview-card h3{font-size:15px;margin-bottom:4px}
- .form-card .desc,.preview-card .desc{font-size:12px;color:var(--muted);margin-bottom:18px}
- .field{margin-bottom:14px}
- .field label{display:block;font-size:13px;margin-bottom:6px;font-weight:500;color:#444}
- .field input{width:100%;height:40px}
- .actions{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px}
- .hint{margin-top:10px;font-size:12px;color:#999;line-height:1.6}
- .upload{border:1px dashed #ccc;border-radius:8px;padding:16px;text-align:center;color:var(--muted);font-size:13px;cursor:pointer;background:#fafafa;display:block}
- .upload:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}
- .upload input{display:none}
- .qr-box{width:220px;height:220px;margin:0 auto 14px;border:1px solid var(--line);border-radius:10px;display:flex;align-items:center;justify-content:center;background:#fff;position:relative;overflow:hidden}
- .qr-box canvas{width:200px;height:200px}
- .qr-box .logo{position:absolute;width:40px;height:40px;background:#fff;border-radius:6px;padding:3px;box-shadow:0 1px 4px rgba(0,0,0,.1)}
- .qr-box .logo img{width:100%;height:100%;object-fit:contain}
- .preview-card{text-align:center}
- .mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:100}
- .mask.show{display:block}
- .drawer{position:fixed;top:0;right:0;width:min(480px,100%);height:100%;background:#fff;z-index:110;transform:translateX(100%);transition:transform .25s ease;display:flex;flex-direction:column;box-shadow:-8px 0 24px rgba(0,0,0,.12)}
- .drawer.open{transform:translateX(0)}
- .drawer-head{height:56px;padding:0 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
- .drawer-head h3{font-size:16px}
- .drawer-close{border:0;background:transparent;font-size:22px;cursor:pointer;color:#999;width:36px;height:36px}
- .drawer-body{flex:1;overflow-y:auto;padding:18px}
- .block{margin-bottom:16px;padding-bottom:14px;border-bottom:1px solid var(--line)}
- .block:last-child{border-bottom:0}
- .block .dt{font-size:12px;color:var(--muted);margin-bottom:8px;font-weight:600}
- .dgrid{display:grid;grid-template-columns:72px 1fr;gap:8px 10px;font-size:13px}
- .dgrid .k{color:#999}
- .dgrid .v{word-break:break-all}
- .dcontent{font-size:14px;line-height:1.7;white-space:pre-wrap;background:#fafafa;border-radius:8px;padding:12px}
- .files{display:flex;flex-wrap:wrap;gap:8px}
- .file{padding:6px 10px;background:var(--primary-soft);color:var(--primary);border-radius:6px;font-size:12px}
- .toast{position:fixed;left:50%;bottom:40px;transform:translateX(-50%) translateY(12px);background:rgba(0,0,0,.8);color:#fff;padding:10px 18px;border-radius:20px;font-size:13px;opacity:0;pointer-events:none;transition:.25s;z-index:200}
- .toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
- </style>
- </head>
- <body>
- <div class="layout">
- <aside class="sidebar">
- <div class="brand">
- <img src="logo1.png" alt="" onerror="this.style.display='none'" />
- <h1>纪检管理后台</h1>
- <p>安建医院纪检</p>
- </div>
- <nav class="nav">
- <button type="button" class="active" data-panel="dashboard" onclick="goPanel('dashboard')">
- <svg viewBox="0 0 24 24"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/></svg>工作台
- </button>
- <button type="button" data-panel="feedback" onclick="goPanel('feedback')">
- <svg viewBox="0 0 24 24"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-1 6h2v6h-2V7zm0 8h2v2h-2v-2z"/></svg>在线反馈
- </button>
- <button type="button" data-panel="suggest" onclick="goPanel('suggest')">
- <svg viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.42l-2.34-2.34a1 1 0 0 0-1.42 0l-1.83 1.83 3.75 3.75 1.84-1.82z"/></svg>建言献策
- </button>
- <button type="button" data-panel="hotline" onclick="goPanel('hotline')">
- <svg viewBox="0 0 24 24"><path d="M6.62 10.79a15.05 15.05 0 0 0 6.59 6.59l2.2-2.2a1 1 0 0 1 1.01-.24c1.12.37 2.33.57 3.58.57a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1C10.4 21 3 13.6 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1c0 1.25.2 2.46.57 3.58a1 1 0 0 1-.25 1.02l-2.2 2.19z"/></svg>热线配置
- </button>
- <button type="button" data-panel="qrcode" onclick="goPanel('qrcode')">
- <svg viewBox="0 0 24 24"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zm8-2v8h8V3h-8zm6 6h-4V5h4v4zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm10 2h2v2h-2zm4-2h-4v4h2v-2h2v-2zm-2-2h2v2h-2zm4 6h-2v2h4v-4h-2z"/></svg>二维码配置
- </button>
- </nav>
- <div class="side-foot">演示原型 · 非正式业务系统<br/>对接后端后替换为真实数据</div>
- </aside>
- <div class="main">
- <header class="topbar">
- <h2 id="pageTitle">工作台</h2>
- <div class="user">当前账号:<b>纪检管理员</b></div>
- </header>
- <div class="content">
- <div class="tip">本页为建设方案演示用管理端。列表为示例数据;热线与二维码配置写入浏览器本地存储,便于演示。</div>
- <section class="panel active" id="panel-dashboard">
- <div class="stats">
- <div class="stat"><div class="label">在线反馈总数</div><div class="value" id="statFeedback">0</div><div class="sub">含投诉 / 表扬</div></div>
- <div class="stat"><div class="label">投诉待办</div><div class="value" id="statComplaint">0</div><div class="sub">需及时核实办理</div></div>
- <div class="stat"><div class="label">建言献策</div><div class="value" id="statSuggest">0</div><div class="sub">群众建议条目</div></div>
- <div class="stat"><div class="label">本周新增</div><div class="value" id="statWeek">0</div><div class="sub">反馈 + 建议合计</div></div>
- </div>
- <div class="card">
- <div class="card-head"><h3>最近反馈</h3><button class="btn btn-sm btn-ghost" type="button" onclick="goPanel('feedback')">查看全部</button></div>
- <div class="table-wrap"><table><thead><tr><th>编号</th><th>类型</th><th>内容摘要</th><th>提交时间</th><th>状态</th><th>操作</th></tr></thead><tbody id="dashBody"></tbody></table></div>
- </div>
- </section>
- <section class="panel" id="panel-feedback">
- <div class="toolbar">
- <input type="search" id="fbSearch" placeholder="搜索内容 / 姓名 / 单位" oninput="renderFeedback()" />
- <select id="fbType" onchange="renderFeedback()"><option value="">全部类型</option><option value="投诉">投诉</option><option value="表扬">表扬</option></select>
- <select id="fbStatus" onchange="renderFeedback()"><option value="">全部状态</option><option value="待处理">待处理</option><option value="处理中">处理中</option><option value="已办结">已办结</option></select>
- <div class="spacer"></div>
- <button class="btn" type="button" onclick="toast('演示环境:导出功能待对接')">导出</button>
- </div>
- <div class="card"><div class="table-wrap"><table>
- <thead><tr><th>编号</th><th>类型</th><th>被反馈人</th><th>内容摘要</th><th>举报人</th><th>附件</th><th>提交时间</th><th>状态</th><th>操作</th></tr></thead>
- <tbody id="feedbackBody"></tbody>
- </table></div></div>
- </section>
- <section class="panel" id="panel-suggest">
- <div class="toolbar">
- <input type="search" id="sgSearch" placeholder="搜索建议内容 / 联系人" oninput="renderSuggest()" />
- <select id="sgStatus" onchange="renderSuggest()"><option value="">全部状态</option><option value="待查阅">待查阅</option><option value="已采纳">已采纳</option><option value="已回复">已回复</option></select>
- <div class="spacer"></div>
- <button class="btn" type="button" onclick="toast('演示环境:导出功能待对接')">导出</button>
- </div>
- <div class="card"><div class="table-wrap"><table>
- <thead><tr><th>编号</th><th>建议内容</th><th>联系人</th><th>单位 / 职务</th><th>联系方式</th><th>附件</th><th>提交时间</th><th>状态</th><th>操作</th></tr></thead>
- <tbody id="suggestBody"></tbody>
- </table></div></div>
- </section>
- <section class="panel" id="panel-hotline">
- <div class="form-card" style="max-width:640px">
- <h3>监督热线配置</h3>
- <p class="desc">配置后用于移动端「监督热线」页面展示(演示写入本地存储)。</p>
- <div class="field"><label>监督电话</label><input id="cfgPhone" type="text" placeholder="如:0551-12345678" /></div>
- <div class="field"><label>电子邮箱</label><input id="cfgEmail" type="email" placeholder="如:jjb@ajyy.example.com" /></div>
- <div class="field"><label>通讯地址</label><textarea id="cfgAddress" placeholder="请填写纪检室通讯地址"></textarea></div>
- <div class="field"><label>受理时间说明(选填)</label><textarea id="cfgHours" placeholder="如:工作日 08:30–11:30、14:30–17:30"></textarea></div>
- <div class="actions">
- <button class="btn btn-primary" type="button" onclick="saveHotline()">保存配置</button>
- <button class="btn" type="button" onclick="resetHotline()">恢复默认</button>
- </div>
- <p class="hint">正式环境需对接后台接口,同步到移动端热线页。</p>
- </div>
- </section>
- <section class="panel" id="panel-qrcode">
- <div class="grid-2">
- <div class="form-card">
- <h3>二维码配置</h3>
- <p class="desc">设置扫码落地页,或上传自定义二维码图片,供打印张贴与移动端分享使用。</p>
- <div class="field"><label>扫码跳转链接</label><input id="cfgQrUrl" type="url" placeholder="https://jjb.ajyy.example.com" oninput="refreshQr()" /></div>
- <div class="field"><label>二维码说明(选填)</label><input id="cfgQrTitle" type="text" placeholder="如:安建医院纪检 · 扫码反馈" oninput="refreshQr()" /></div>
- <div class="field">
- <label>自定义二维码图片(选填)</label>
- <label class="upload">点击上传 PNG / JPG(上传后优先使用图片)<input type="file" accept="image/png,image/jpeg,image/webp" onchange="onQrUpload(event)" /></label>
- <p class="hint" id="qrUploadName"></p>
- </div>
- <div class="actions">
- <button class="btn btn-primary" type="button" onclick="saveQr()">保存配置</button>
- <button class="btn" type="button" onclick="clearQrImage()">清除自定义图片</button>
- <button class="btn" type="button" onclick="downloadQr()">下载预览图</button>
- </div>
- </div>
- <div class="preview-card">
- <h3>效果预览</h3>
- <p class="desc" id="qrTitle">安建医院纪检 · 扫码反馈</p>
- <div class="qr-box" id="qrBox">
- <canvas id="qrCanvas" width="200" height="200"></canvas>
- <div class="logo" id="qrLogo"><img src="logo1.png" alt="" onerror="this.parentElement.style.display='none'" /></div>
- <img id="qrCustom" alt="" style="display:none;width:100%;height:100%;object-fit:contain" />
- </div>
- <p class="hint" id="qrUrlText" style="word-break:break-all"></p>
- </div>
- </div>
- </section>
- </div>
- </div>
- </div>
- <div class="mask" id="mask" onclick="closeDrawer()"></div>
- <aside class="drawer" id="drawer">
- <div class="drawer-head"><h3 id="drawerTitle">详情</h3><button class="drawer-close" type="button" onclick="closeDrawer()">×</button></div>
- <div class="drawer-body" id="drawerBody"></div>
- </aside>
- <div class="toast" id="toast"></div>
- <script>
- const feedbackList=[
- {id:"FK20260825001",type:"投诉",targetName:"张某某",targetUnit:"门诊部",targetPost:"护士",content:"反映个别窗口服务态度生硬,排队指引不清晰,建议加强窗口文明服务培训。",reporterName:"李某",reporterUnit:"患者家属",reporterPost:"",reporterPhone:"138****6621",files:["现场照片1.jpg","说明.docx"],time:"2026-08-25 09:18",status:"待处理"},
- {id:"FK20260824012",type:"表扬",targetName:"王某某",targetUnit:"急诊科",targetPost:"医生",content:"夜间急诊接诊及时、解释耐心,家属非常感动,特此表扬。",reporterName:"",reporterUnit:"",reporterPost:"",reporterPhone:"",files:[],time:"2026-08-24 21:06",status:"已办结"},
- {id:"FK20260823007",type:"投诉",targetName:"赵某某",targetUnit:"信息科",targetPost:"工程师",content:"反映信息系统升级后挂号偶发失败,影响就医体验,请尽快排查。",reporterName:"匿名",reporterUnit:"",reporterPost:"",reporterPhone:"",files:["截图.png"],time:"2026-08-23 14:42",status:"处理中"},
- {id:"FK20260822003",type:"投诉",targetName:"",targetUnit:"后勤保障部",targetPost:"",content:"住院楼电梯长时间排队,高峰时段拥堵明显,建议优化调度。",reporterName:"周某",reporterUnit:"住院患者",reporterPost:"",reporterPhone:"159****1108",files:["视频.mp4"],time:"2026-08-22 11:05",status:"待处理"},
- {id:"FK20260821009",type:"表扬",targetName:"陈某某",targetUnit:"药剂科",targetPost:"药师",content:"发药窗口核对细致,用药交代清楚,服务规范热情。",reporterName:"吴某",reporterUnit:"",reporterPost:"",reporterPhone:"",files:[],time:"2026-08-21 16:33",status:"已办结"}
- ];
- const suggestList=[
- {id:"JY20260825002",content:"建议在门诊大厅增设纪检宣传展板和扫码反馈指引,方便群众知情监督。",name:"刘某",unit:"行政办",post:"科员",phone:"137****8820",files:["展板示意.pdf"],time:"2026-08-25 10:02",status:"待查阅"},
- {id:"JY20260824005",content:"建议对重点岗位开展廉洁风险排查,并建立季度回头看机制。",name:"",unit:"",post:"",phone:"",files:[],time:"2026-08-24 15:20",status:"已采纳"},
- {id:"JY20260823011",content:"建议优化公车使用审批流程,提高透明度和可追溯性。",name:"孙某",unit:"财务科",post:"会计",phone:"186****3344",files:["流程建议.docx"],time:"2026-08-23 09:47",status:"已回复"},
- {id:"JY20260821004",content:"建议将常见廉政提醒纳入新职工入职培训必修内容。",name:"匿名",unit:"",post:"",phone:"",files:[],time:"2026-08-21 08:15",status:"待查阅"}
- ];
- const defaultHotline={phone:"0551-XXXXXXXX",email:"jjb@ajyy.example.com",address:"安徽省合肥市 · 安徽建工集团医院纪检室(示例地址)",hours:"工作日 08:30–11:30、14:30–17:30\\n紧急事项可通过电话随时联系值班人员。"};
- const defaultQr={url:"https://jjb.ajyy.example.com",title:"安建医院纪检 · 扫码反馈",image:""};
- let qrImage="";
- const titles={dashboard:"工作台",feedback:"在线反馈",suggest:"建言献策",hotline:"热线配置",qrcode:"二维码配置"};
- function toast(msg){const el=document.getElementById("toast");el.textContent=msg;el.classList.add("show");clearTimeout(toast._t);toast._t=setTimeout(()=>el.classList.remove("show"),2200)}
- function esc(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}
- function typeTag(t){return t==="投诉"?'<span class="tag tag-warn">投诉</span>':'<span class="tag tag-ok">表扬</span>'}
- function statusTag(s){if(s==="待处理"||s==="待查阅")return '<span class="tag tag-warn">'+s+"</span>";if(s==="处理中")return '<span class="tag tag-blue">'+s+"</span>";return '<span class="tag tag-gray">'+s+"</span>"}
- function goPanel(name){
- document.querySelectorAll(".panel").forEach(p=>p.classList.remove("active"));
- document.querySelectorAll(".nav button").forEach(b=>b.classList.remove("active"));
- document.getElementById("panel-"+name).classList.add("active");
- document.querySelector('.nav button[data-panel="'+name+'"]').classList.add("active");
- document.getElementById("pageTitle").textContent=titles[name];
- if(name==="qrcode")refreshQr();
- }
- function renderDashboard(){
- document.getElementById("statFeedback").textContent=feedbackList.length;
- document.getElementById("statComplaint").textContent=feedbackList.filter(x=>x.type==="投诉"&&x.status!=="已办结").length;
- document.getElementById("statSuggest").textContent=suggestList.length;
- document.getElementById("statWeek").textContent=feedbackList.length+suggestList.length;
- document.getElementById("dashBody").innerHTML=feedbackList.slice(0,5).map(r=>`<tr>
- <td>${r.id}</td><td>${typeTag(r.type)}</td>
- <td class="ellipsis" title="${esc(r.content)}">${esc(r.content)}</td>
- <td>${r.time}</td><td>${statusTag(r.status)}</td>
- <td><button class="btn btn-sm btn-ghost" type="button" onclick="openFeedback('${r.id}')">查看</button></td>
- </tr>`).join("");
- }
- function renderFeedback(){
- const q=(document.getElementById("fbSearch").value||"").trim();
- const type=document.getElementById("fbType").value;
- const status=document.getElementById("fbStatus").value;
- const list=feedbackList.filter(r=>{
- if(type&&r.type!==type)return false;
- if(status&&r.status!==status)return false;
- if(!q)return true;
- return [r.id,r.content,r.targetName,r.targetUnit,r.reporterName].join(" ").includes(q);
- });
- const body=document.getElementById("feedbackBody");
- if(!list.length){body.innerHTML='<tr><td colspan="9"><div class="empty">暂无匹配数据</div></td></tr>';return}
- body.innerHTML=list.map(r=>`<tr>
- <td>${r.id}</td><td>${typeTag(r.type)}</td>
- <td>${esc(r.targetName||"未填写")}<div style="color:#999;font-size:12px">${esc([r.targetUnit,r.targetPost].filter(Boolean).join(" · ")||"-")}</div></td>
- <td class="ellipsis" title="${esc(r.content)}">${esc(r.content)}</td>
- <td>${esc(r.reporterName||"匿名")}</td>
- <td>${r.files.length?r.files.length+"个":"-"}</td>
- <td>${r.time}</td><td>${statusTag(r.status)}</td>
- <td><button class="btn btn-sm btn-ghost" type="button" onclick="openFeedback('${r.id}')">详情</button></td>
- </tr>`).join("");
- }
- function renderSuggest(){
- const q=(document.getElementById("sgSearch").value||"").trim();
- const status=document.getElementById("sgStatus").value;
- const list=suggestList.filter(r=>{
- if(status&&r.status!==status)return false;
- if(!q)return true;
- return [r.id,r.content,r.name,r.unit,r.phone].join(" ").includes(q);
- });
- const body=document.getElementById("suggestBody");
- if(!list.length){body.innerHTML='<tr><td colspan="9"><div class="empty">暂无匹配数据</div></td></tr>';return}
- body.innerHTML=list.map(r=>`<tr>
- <td>${r.id}</td>
- <td class="ellipsis" title="${esc(r.content)}">${esc(r.content)}</td>
- <td>${esc(r.name||"匿名")}</td>
- <td>${esc([r.unit,r.post].filter(Boolean).join(" / ")||"-")}</td>
- <td>${esc(r.phone||"-")}</td>
- <td>${r.files.length?r.files.length+"个":"-"}</td>
- <td>${r.time}</td><td>${statusTag(r.status)}</td>
- <td><button class="btn btn-sm btn-ghost" type="button" onclick="openSuggest('${r.id}')">详情</button></td>
- </tr>`).join("");
- }
- function openDrawer(){document.getElementById("mask").classList.add("show");document.getElementById("drawer").classList.add("open")}
- function closeDrawer(){document.getElementById("mask").classList.remove("show");document.getElementById("drawer").classList.remove("open")}
- function openFeedback(id){
- const r=feedbackList.find(x=>x.id===id);if(!r)return;
- document.getElementById("drawerTitle").textContent="反馈详情 · "+r.id;
- document.getElementById("drawerBody").innerHTML=`
- <div class="block"><div class="dt">基本信息</div><div class="dgrid">
- <div class="k">类型</div><div class="v">${typeTag(r.type)}</div>
- <div class="k">状态</div><div class="v">${statusTag(r.status)}</div>
- <div class="k">时间</div><div class="v">${r.time}</div></div></div>
- <div class="block"><div class="dt">被反馈人</div><div class="dgrid">
- <div class="k">姓名</div><div class="v">${esc(r.targetName||"未填写")}</div>
- <div class="k">单位</div><div class="v">${esc(r.targetUnit||"-")}</div>
- <div class="k">职务</div><div class="v">${esc(r.targetPost||"-")}</div></div></div>
- <div class="block"><div class="dt">反馈内容</div><div class="dcontent">${esc(r.content)}</div></div>
- <div class="block"><div class="dt">附件</div><div class="files">${r.files.length?r.files.map(f=>'<span class="file">'+esc(f)+"</span>").join(""):'<span style="color:#999;font-size:13px">无</span>'}</div></div>
- <div class="block"><div class="dt">举报人信息</div><div class="dgrid">
- <div class="k">姓名</div><div class="v">${esc(r.reporterName||"匿名")}</div>
- <div class="k">单位</div><div class="v">${esc(r.reporterUnit||"-")}</div>
- <div class="k">职务</div><div class="v">${esc(r.reporterPost||"-")}</div>
- <div class="k">联系方式</div><div class="v">${esc(r.reporterPhone||"-")}</div></div></div>
- <div class="actions">
- <button class="btn btn-primary" type="button" onclick="toast('演示:已标记处理中')">标记处理中</button>
- <button class="btn" type="button" onclick="toast('演示:已办结')">办结</button>
- </div>`;
- openDrawer();
- }
- function openSuggest(id){
- const r=suggestList.find(x=>x.id===id);if(!r)return;
- document.getElementById("drawerTitle").textContent="建议详情 · "+r.id;
- document.getElementById("drawerBody").innerHTML=`
- <div class="block"><div class="dt">基本信息</div><div class="dgrid">
- <div class="k">状态</div><div class="v">${statusTag(r.status)}</div>
- <div class="k">时间</div><div class="v">${r.time}</div></div></div>
- <div class="block"><div class="dt">建议内容</div><div class="dcontent">${esc(r.content)}</div></div>
- <div class="block"><div class="dt">附件</div><div class="files">${r.files.length?r.files.map(f=>'<span class="file">'+esc(f)+"</span>").join(""):'<span style="color:#999;font-size:13px">无</span>'}</div></div>
- <div class="block"><div class="dt">联系人信息</div><div class="dgrid">
- <div class="k">姓名</div><div class="v">${esc(r.name||"匿名")}</div>
- <div class="k">单位</div><div class="v">${esc(r.unit||"-")}</div>
- <div class="k">职务</div><div class="v">${esc(r.post||"-")}</div>
- <div class="k">联系方式</div><div class="v">${esc(r.phone||"-")}</div></div></div>
- <div class="actions">
- <button class="btn btn-primary" type="button" onclick="toast('演示:已标记采纳')">标记采纳</button>
- <button class="btn" type="button" onclick="toast('演示:已回复')">标记已回复</button>
- </div>`;
- openDrawer();
- }
- function loadHotline(){
- let cfg={...defaultHotline};
- try{const raw=localStorage.getItem("ajyy_hotline");if(raw)cfg={...cfg,...JSON.parse(raw)}}catch(e){}
- document.getElementById("cfgPhone").value=cfg.phone;
- document.getElementById("cfgEmail").value=cfg.email;
- document.getElementById("cfgAddress").value=cfg.address;
- document.getElementById("cfgHours").value=cfg.hours.replace(/\\n/g,"\n");
- }
- function saveHotline(){
- const cfg={phone:document.getElementById("cfgPhone").value.trim(),email:document.getElementById("cfgEmail").value.trim(),address:document.getElementById("cfgAddress").value.trim(),hours:document.getElementById("cfgHours").value.trim()};
- localStorage.setItem("ajyy_hotline",JSON.stringify(cfg));
- toast("热线配置已保存");
- }
- function resetHotline(){localStorage.removeItem("ajyy_hotline");loadHotline();toast("已恢复默认配置")}
- function drawQr(canvas,text){
- const ctx=canvas.getContext("2d");const size=canvas.width,cells=25,cell=size/cells;
- ctx.fillStyle="#fff";ctx.fillRect(0,0,size,size);
- let h=0;for(let i=0;i<text.length;i++)h=(h*31+text.charCodeAt(i))>>>0;
- function finder(x,y){return x===0||y===0||x===6||y===6||(x>=2&&x<=4&&y>=2&&y<=4)}
- function bit(x,y){
- if(x<7&&y<7)return finder(x,y);
- if(x>=cells-7&&y<7)return finder(x-(cells-7),y);
- if(x<7&&y>=cells-7)return finder(x,y-(cells-7));
- return ((h^(x*73856093)^(y*19349663))>>>0)%3!==0;
- }
- ctx.fillStyle="#1a1a1a";
- for(let y=0;y<cells;y++)for(let x=0;x<cells;x++)if(bit(x,y))ctx.fillRect(x*cell,y*cell,Math.ceil(cell),Math.ceil(cell));
- }
- function refreshQr(){
- const url=document.getElementById("cfgQrUrl").value.trim()||defaultQr.url;
- const title=document.getElementById("cfgQrTitle").value.trim()||defaultQr.title;
- document.getElementById("qrTitle").textContent=title;
- document.getElementById("qrUrlText").textContent=url;
- const canvas=document.getElementById("qrCanvas");
- const img=document.getElementById("qrCustom");
- const logo=document.getElementById("qrLogo");
- if(qrImage){canvas.style.display="none";logo.style.display="none";img.style.display="block";img.src=qrImage}
- else{canvas.style.display="block";logo.style.display="block";img.style.display="none";drawQr(canvas,url)}
- }
- function loadQr(){
- let cfg={...defaultQr};
- try{const raw=localStorage.getItem("ajyy_qr");if(raw)cfg={...cfg,...JSON.parse(raw)}}catch(e){}
- document.getElementById("cfgQrUrl").value=cfg.url;
- document.getElementById("cfgQrTitle").value=cfg.title;
- qrImage=cfg.image||"";
- document.getElementById("qrUploadName").textContent=qrImage?"已上传自定义二维码图片":"";
- refreshQr();
- }
- function saveQr(){
- const cfg={url:document.getElementById("cfgQrUrl").value.trim()||defaultQr.url,title:document.getElementById("cfgQrTitle").value.trim()||defaultQr.title,image:qrImage||""};
- localStorage.setItem("ajyy_qr",JSON.stringify(cfg));
- toast("二维码配置已保存");refreshQr();
- }
- function onQrUpload(e){
- const file=e.target.files&&e.target.files[0];if(!file)return;
- const reader=new FileReader();
- reader.onload=()=>{qrImage=reader.result;document.getElementById("qrUploadName").textContent="已选择:"+file.name;refreshQr();toast("自定义二维码已加载,请点击保存")};
- reader.readAsDataURL(file);e.target.value="";
- }
- function clearQrImage(){qrImage="";document.getElementById("qrUploadName").textContent="";refreshQr();toast("已清除自定义图片")}
- function downloadQr(){
- const canvas=document.getElementById("qrCanvas");const img=document.getElementById("qrCustom");
- const a=document.createElement("a");
- a.href=qrImage&&img.style.display!=="none"?qrImage:canvas.toDataURL("image/png");
- a.download="ajyy-qrcode.png";a.click();toast("已开始下载");
- }
- renderDashboard();renderFeedback();renderSuggest();loadHotline();loadQr();
- </script>
- </body>
- </html>
|