个人整改后做的html版网页密码管理系统

2026-09-03 0 268

文章摘要

## 简要总结 该文章介绍了一款**密码管理系统**的整改方案,系统支持**指定浏览器**访问客户ERP,自动检测浏览器安装状态。管理员后台新增**浏览器选择**功能,数据自动保存至**localStorage**,支持Excel/JSON导入导出及数据库备份。系统提供**8种颜色主题**切换,管理员默认密码admin123,员工查看密码employee2024。系统采用**员工门户**与**管理后台**双界面设计,支持凭证管理、密码修改、自动备份等核心功能。

主要整改:
网址指定浏览器访问:
用于销售助理平时点击添加后客户的ERP的网页进入客户的ERP系统,可以根据自己在后台添加的网址指定浏览器来实现。
点击访问客户ERP 系统的时候,会自动检测指定浏览器是否已经安装,如未安装,会弹窗提示安装浏览器。
管理员后台整改:
主要整改添加客户ERP系统是选择使用什么浏览器。数据自动保存,管理员后台还修改了数据导入及导出JSON功能。
管理员后台密码默认: admin123,员工查看密码默认: employee2024
支持 Excel (.xls, .xlsx) 导入导出
所有数据存储方式改成了 localStorage
可点击「备份数据库」下载完整数据库文件
界面主题增加:
增加有8种颜色主题切换
个人整改后做的html版网页密码管理系统 个人整改后做的html版网页密码管理系统
html代码:

  1. <!DOCTYPE html>
  2. <html lang=”zh-CN”>
  3. <head>
  4.     <meta charset=”UTF-8″>
  5.     <meta name=”viewport” content=”width=device-width, initial-scale=1.0, user-scalable=yes”>
  6.     <title>企业密码管理系统</title>
  7.     <script src=”https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js”></script>
  8.     <script src=”https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/xlsx.full.min.js”></script>
  9.     <script src=”https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js”></script>
  10.     <style id=”theme-style”>
  11.         :root {
  12.             –bg: #f1f5f9;
  13.             –card: #ffffff;
  14.             –border: #e9edf2;
  15.             –text: #1e293b;
  16.             –text2: #475569;
  17.             –shadow: rgba(0,0,0,0.05);
  18.             –primary: #1e3c72;
  19.             –primary-hover: #0f2b4f;
  20.             –input: #ffffff;
  21.             –input-border: #cfdfed;
  22.             –table-header: #fafcff;
  23.             –badge-default: #e2e8f0;
  24.             –badge-edge: #0078d4;
  25.             –badge-chrome: #4285f4;
  26.             –badge-firefox: #ff9400;
  27.             –success: #10b981;
  28.             –warning: #f59e0b;
  29.             –danger: #ef4444;
  30.             –info: #3b82f6;
  31.         }
  32.         .theme-red { –bg: #fef2f2; –card: #ffffff; –primary: #b91c1c; –primary-hover: #7f1d1d; –border: #fecaca; –table-header: #fef2f2; }
  33.         .theme-orange { –bg: #fff7ed; –card: #ffffff; –primary: #c2410c; –primary-hover: #9a3412; –border: #fed7aa; –table-header: #fff7ed; }
  34.         .theme-yellow { –bg: #fefce8; –card: #ffffff; –primary: #a16207; –primary-hover: #854d0e; –border: #fde68a; –table-header: #fefce8; }
  35.         .theme-green { –bg: #f0fdf4; –card: #ffffff; –primary: #15803d; –primary-hover: #166534; –border: #bbf7d0; –table-header: #f0fdf4; }
  36.         .theme-blue { –bg: #eff6ff; –card: #ffffff; –primary: #1d4ed8; –primary-hover: #1e40af; –border: #bfdbfe; –table-header: #eff6ff; }
  37.         .theme-purple { –bg: #faf5ff; –card: #ffffff; –primary: #7e22ce; –primary-hover: #6b21a8; –border: #e9d5ff; –table-header: #faf5ff; }
  38.         .theme-pink { –bg: #fdf2f8; –card: #ffffff; –primary: #be185d; –primary-hover: #9d174d; –border: #fbcfe8; –table-header: #fdf2f8; }
  39.         * { margin: 0; padding: 0; box-sizing: border-box; }
  40.         body { font-family: ‘Segoe UI’, system-ui; background: var(–bg); padding: 20px 16px; transition: 0.3s; }
  41.         .app { max-width: 1400px; margin: 0 auto; }
  42.         .header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
  43.         .header h1 { font-size: 1.4rem; font-weight: 600; color: var(–text); }
  44.         .theme-bar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
  45.         .theme-bar span { font-size: 0.75rem; color: var(–text2); margin-right: 2px; }
  46.         .theme-dot { width: 28px; height: 28px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; transition: 0.2s; }
  47.         .theme-dot:hover { transform: scale(1.1); }
  48.         .theme-dot.active { border-color: var(–text); box-shadow: 0 0 0 2px var(–bg); }
  49.         .tab-bar { display: flex; gap: 8px; margin-bottom: 20px; background: var(–card); padding: 6px 16px; border-radius: 40px; width: fit-content; flex-wrap: wrap; border: 1px solid var(–border); }
  50.         .tab-btn { border: none; background: transparent; padding: 10px 24px; font-size: 0.95rem; font-weight: 500; border-radius: 30px; cursor: pointer; color: var(–text2); transition: 0.2s; }
  51.         .tab-btn.active { background: var(–primary); color: #fff; }
  52.         .tab-btn:hover:not(.active) { background: var(–bg); }
  53.         .card { background: var(–card); border-radius: 20px; border: 1px solid var(–border); overflow: hidden; margin-bottom: 20px; box-shadow: 0 2px 8px var(–shadow); }
  54.         .card-header { padding: 16px 20px; border-bottom: 1px solid var(–border); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
  55.         .card-header h2 { font-size: 1.2rem; font-weight: 600; color: var(–text); }
  56.         .btn { border: none; background: var(–bg); padding: 6px 18px; border-radius: 30px; font-weight: 500; cursor: pointer; transition: 0.2s; font-size: 0.85rem; color: var(–text); }
  57.         .btn-primary { background: var(–primary); color: #fff; }
  58.         .btn-primary:hover { background: var(–primary-hover); }
  59.         .btn-outline { background: transparent; border: 1px solid var(–border); }
  60.         .btn-outline:hover { background: var(–bg); }
  61.         .btn-success { background: var(–success); color: #fff; }
  62.         .btn-success:hover { background: #059669; }
  63.         .btn-warning { background: var(–warning); color: #fff; }
  64.         .btn-warning:hover { background: #d97706; }
  65.         .btn-danger { background: var(–danger); color: #fff; }
  66.         .btn-danger:hover { background: #dc2626; }
  67.         .btn-info { background: var(–info); color: #fff; }
  68.         .btn-info:hover { background: #2563eb; }
  69.         .btn-sm { padding: 2px 10px; font-size: 0.7rem; }
  70.         .btn-open { background: #22d3ee; color: #0e7490; }
  71.         .btn-open:hover { background: #67e8f9; }
  72.         .btn-auto-backup { background: #8b5cf6; color: #fff; }
  73.         .btn-auto-backup:hover { background: #7c3aed; }
  74.         table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
  75.         th, td { text-align: left; padding: 12px 14px; border-bottom: 1px solid var(–border); vertical-align: middle; }
  76.         th { background: var(–table-header); font-weight: 600; color: var(–text); }
  77.         .url-link { color: var(–primary); text-decoration: none; font-weight: 500; }
  78.         .url-link:hover { text-decoration: underline; }
  79.         .url-cell { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  80.         .password-masked { font-family: monospace; background: var(–bg); padding: 2px 12px; border-radius: 20px; display: inline-block; font-size: 0.85rem; }
  81.         .password-plain { font-family: monospace; background: #dbeafe; padding: 2px 12px; border-radius: 20px; display: inline-block; font-size: 0.85rem; }
  82.         .input-group { margin-bottom: 14px; display: flex; flex-direction: column; gap: 4px; }
  83.         .input-group label { font-weight: 600; font-size: 0.8rem; color: var(–text2); }
  84.         input, select { padding: 8px 14px; border-radius: 16px; border: 1px solid var(–input-border); font-family: inherit; background: var(–input); color: var(–text); }
  85.         input:focus, select:focus { outline: none; border-color: var(–primary); }
  86.         .modal { display: none; position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.4); align-items: center; justify-content: center; z-index: 1000; }
  87.         .modal-content { background: var(–card); max-width: 500px; width: 92%; border-radius: 24px; padding: 24px; }
  88.         .toast { position: fixed; bottom: 24px; right: 24px; background: var(–text); color: #fff; padding: 10px 20px; border-radius: 30px; opacity: 0; transition: 0.25s; z-index: 1100; font-size: 0.85rem; pointer-events: none; max-width: 90%; }
  89.         .toast.show { opacity: 1; }
  90.         .toast.error { background: var(–danger); }
  91.         .toast.success { background: var(–success); }
  92.         .hidden { display: none; }
  93.         .flex-btns { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
  94.         .verify-panel { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
  95.         .file-input-label { background: var(–bg); border: 1px solid var(–border); padding: 6px 16px; border-radius: 30px; cursor: pointer; font-size: 0.85rem; }
  96.         input[type=”file”] { display: none; }
  97.         .settings-group { background: var(–bg); border-radius: 16px; padding: 14px 18px; margin: 12px 20px 16px 20px; border: 1px solid var(–border); }
  98.         .settings-group h4 { margin-bottom: 10px; color: var(–text); font-size: 0.9rem; }
  99.         .settings-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }
  100.         .settings-row span { font-size: 0.85rem; color: var(–text2); min-width: 130px; }
  101.         .browser-badge { display: inline-block; padding: 2px 10px; border-radius: 16px; font-size: 0.7rem; margin-right: 6px; }
  102.         .browser-default { background: var(–badge-default); color: var(–text2); }
  103.         .browser-edge { background: var(–badge-edge); color: #fff; }
  104.         .browser-chrome { background: var(–badge-chrome); color: #fff; }
  105.         .browser-firefox { background: var(–badge-firefox); color: #fff; }
  106.         .copy-url-btn { background: var(–bg); border: none; padding: 2px 8px; border-radius: 14px; cursor: pointer; font-size: 0.6rem; color: var(–text2); }
  107.         .copy-url-btn:hover { background: var(–border); }
  108.         .db-status { background: #d1fae5; padding: 6px 16px; border-radius: 10px; font-size: 0.75rem; color: #065f46; margin: 0 20px 12px 20px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 6px; }
  109.         .open-btn-group { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
  110.         .open-btn-group .btn { padding: 2px 10px; font-size: 0.65rem; }
  111.         .empty-row td { text-align: center; padding: 40px; color: var(–text2); }
  112.         .admin-auth-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 2000; }
  113.         .admin-auth-box { background: var(–card); border-radius: 24px; padding: 28px; width: 92%; max-width: 380px; text-align: center; }
  114.         .admin-auth-box input { width: 100%; margin: 10px 0; }
  115.         .admin-auth-box .error { color: var(–danger); font-size: 0.8rem; }
  116.         .scroll-x { overflow-x: auto; }
  117.         .browser-hint-overlay {
  118.             position: fixed;
  119.             top: 0;
  120.             left: 0;
  121.             width: 100%;
  122.             height: 100%;
  123.             background: rgba(0,0,0,0.55);
  124.             display: none;
  125.             align-items: center;
  126.             justify-content: center;
  127.             z-index: 3000;
  128.         }
  129.         .browser-hint-overlay.show { display: flex; }
  130.         .browser-hint-box {
  131.             background: var(–card);
  132.             border-radius: 24px;
  133.             padding: 32px 28px;
  134.             max-width: 460px;
  135.             width: 92%;
  136.             text-align: center;
  137.             box-shadow: 0 20px 60px rgba(0,0,0,0.3);
  138.             animation: fadeIn 0.3s ease;
  139.         }
  140.         @keyframes fadeIn {
  141.             from { opacity: 0; transform: scale(0.95); }
  142.             to { opacity: 1; transform: scale(1); }
  143.         }
  144.         .browser-hint-box .icon { font-size: 3.2rem; margin-bottom: 12px; }
  145.         .browser-hint-box h3 { font-size: 1.3rem; color: var(–text); margin-bottom: 10px; }
  146.         .browser-hint-box p { font-size: 0.9rem; color: var(–text2); line-height: 1.6; margin-bottom: 18px; }
  147.         .browser-hint-box .btn-group { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
  148.         .browser-hint-box .btn-group .btn { min-width: 100px; padding: 10px 24px; font-size: 0.9rem; }
  149.         .browser-hint-box .checkbox-row { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 12px 0 16px 0; font-size: 0.8rem; color: var(–text2); }
  150.         .browser-hint-box .checkbox-row input[type=”checkbox”] { width: 16px; height: 16px; cursor: pointer; accent-color: var(–primary); }
  151.         .storage-info {
  152.             background: var(–bg);
  153.             border-radius: 12px;
  154.             padding: 12px 18px;
  155.             margin: 8px 20px 12px 20px;
  156.             border: 1px solid var(–border);
  157.             font-size: 0.8rem;
  158.             color: var(–text2);
  159.             display: flex;
  160.             justify-content: space-between;
  161.             align-items: center;
  162.             flex-wrap: wrap;
  163.             gap: 8px;
  164.         }
  165.         .storage-info .label { font-weight: 600; color: var(–text); }
  166.         .storage-info .status { color: var(–success); }
  167.         .storage-info .backup-status { color: var(–info); font-size: 0.75rem; }
  168.         .backup-settings {
  169.             background: var(–bg);
  170.             border-radius: 12px;
  171.             padding: 12px 18px;
  172.             margin: 8px 20px 12px 20px;
  173.             border: 1px solid var(–border);
  174.             display: flex;
  175.             justify-content: space-between;
  176.             align-items: center;
  177.             flex-wrap: wrap;
  178.             gap: 10px;
  179.         }
  180.         .backup-settings .label { font-weight: 600; color: var(–text); font-size: 0.85rem; }
  181.         .backup-settings .desc { font-size: 0.75rem; color: var(–text2); }
  182.         .backup-settings .toggle { display: flex; align-items: center; gap: 8px; }
  183.         .toggle-switch {
  184.             width: 40px;
  185.             height: 22px;
  186.             background: #cbd5e1;
  187.             border-radius: 12px;
  188.             cursor: pointer;
  189.             position: relative;
  190.             transition: 0.3s;
  191.             border: none;
  192.             padding: 0;
  193.         }
  194.         .toggle-switch.active {
  195.             background: var(–success);
  196.         }
  197.         .toggle-switch::after {
  198.             content: ”;
  199.             width: 18px;
  200.             height: 18px;
  201.             background: white;
  202.             border-radius: 50%;
  203.             position: absolute;
  204.             top: 2px;
  205.             left: 2px;
  206.             transition: 0.3s;
  207.             box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  208.         }
  209.         .toggle-switch.active::after {
  210.             left: 20px;
  211.         }
  212.         [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 600px) {
  213.             th, td { padding: 8px 10px; font-size: 0.8rem; }
  214.             .card-header { padding: 12px 14px; }
  215.             .browser-hint-box { padding: 24px 18px; }
  216.             .browser-hint-box .btn-group .btn { min-width: 80px; padding: 8px 16px; font-size: 0.8rem; }
  217.             .storage-info, .backup-settings { flex-direction: column; align-items: flex-start; }
  218.         }
  219.     </style>
  220. </head>
  221. <body>
  222. <div class=”app”>
  223.     <div class=”header”>
  224.         <h1>密码管理系统</h1>
  225.         <div class=”theme-bar”>
  226.             <span>主题</span>
  227.             <span class=”theme-dot active” data-theme=”default” style=”background:#64748b;” title=”默认”></span>
  228.             <span class=”theme-dot” data-theme=”red” style=”background:#dc2626;” title=”红”></span>
  229.             <span class=”theme-dot” data-theme=”orange” style=”background:#ea580c;” title=”橙”></span>
  230.             <span class=”theme-dot” data-theme=”yellow” style=”background:#ca8a04;” title=”黄”></span>
  231.             <span class=”theme-dot” data-theme=”green” style=”background:#16a34a;” title=”绿”></span>
  232.             <span class=”theme-dot” data-theme=”blue” style=”background:#2563eb;” title=”蓝”></span>
  233.             <span class=”theme-dot” data-theme=”purple” style=”background:#9333ea;” title=”紫”></span>
  234.             <span class=”theme-dot” data-theme=”pink” style=”background:#db2777;” title=”粉”></span>
  235.         </div>
  236.     </div>
  237.     <div class=”tab-bar”>
  238.         <button class=”tab-btn active” data-tab=”employee”>员工门户</button>
  239.         <button class=”tab-btn” data-tab=”admin”>管理后台</button>
  240.     </div>
  241.     <div id=”employeePanel”>
  242.         <div class=”card”>
  243.             <div class=”card-header”>
  244.                 <h2>快捷访问</h2>
  245.                 <div class=”verify-panel”>
  246.                     <input type=”password” id=”empViewPwd” placeholder=”查看密码” style=”width:150px;” autocomplete=”off”>
  247.                     <button class=”btn btn-primary” id=”verifyEmpBtn”>显示密码</button>
  248.                 </div>
  249.             </div>
  250.             <div class=”scroll-x”>
  251.                 <table>
  252.                     <thead><tr><th>网址</th><th>账号</th><th>密码</th><th>打开方式</th></tr></thead>
  253.                     <tbody id=”employeeTableBody”></tbody>
  254.                 </table>
  255.             </div>
  256.             <div style=”padding:10px 20px;border-top:1px solid var(–border);font-size:0.7rem;color:var(–text2);”>
  257.                 <span>查看密码: <strong>employee2024</strong></span>
  258.             </div>
  259.         </div>
  260.     </div>
  261.     <div id=”adminPanel” class=”hidden”>
  262.         <div class=”card”>
  263.             <div class=”card-header”>
  264.                 <h2>凭证管理</h2>
  265.                 <div class=”flex-btns”>
  266.                     <button class=”btn btn-primary” id=”openAddCredBtn”>新增</button>
  267.                     <button class=”btn btn-success” id=”exportExcelBtn”>导出 Excel</button>
  268.                     <label class=”file-input-label” for=”importExcelInput”>导入 Excel</label>
  269.                     <input type=”file” id=”importExcelInput” accept=”.xls,.xlsx”>
  270.                     <button class=”btn btn-info” id=”exportJsonBtn”>&#128229; 导出 JSON</button>
  271.                     <label class=”file-input-label” for=”importJsonInput” style=”background:var(–primary);color:#fff;”>&#128228; 导入 JSON</label>
  272.                     <input type=”file” id=”importJsonInput” accept=”.json”>
  273.                 </div>
  274.             </div>
  275.             <div class=”db-status”>
  276.                 <span>&#128192; 数据库已连接</span>
  277.                 <span id=”dbStatusText”>localStorage 持久化存储</span>
  278.             </div>
  279.             <!– 自动备份设置 –>
  280.             <div class=”backup-settings”>
  281.                 <div>
  282.                     <span class=”label”>&#128190; 自动备份到下载文件夹</span>
  283.                     <span class=”desc”>每次修改数据后自动导出 JSON 文件到电脑下载目录</span>
  284.                 </div>
  285.                 <div class=”toggle”>
  286.                     <span id=”autoBackupStatus” style=”font-size:0.75rem;color:var(–text2);”>已关闭</span>
  287.                     <button class=”toggle-switch” id=”autoBackupToggle” title=”点击切换自动备份”></button>
  288.                 </div>
  289.             </div>
  290.             <!– 存储信息 –>
  291.             <div class=”storage-info”>
  292.                 <span><span class=”label”>&#128190; 存储位置:</span>浏览器 localStorage</span>
  293.                 <span><span class=”label”>&#128202; 记录数:</span><span id=”recordCount”>0</span> 条</span>
  294.                 <span><span class=”label”>&#128204; 持久化:</span><span class=”status”>&#9989; 刷新/关闭不丢失</span></span>
  295.                 <span class=”backup-status” id=”lastBackupTime”>&#128203; 最后备份:无</span>
  296.             </div>
  297.             <div class=”scroll-x”>
  298.                 <table>
  299.                     <thead><tr><th>网址</th><th>账号</th><th>密码</th><th>浏览器</th><th>操作</th></tr></thead>
  300.                     <tbody id=”adminTableBody”></tbody>
  301.                 </table>
  302.             </div>
  303.             <div class=”settings-group”>
  304.                 <h4>&#9881;&#65039; 安全设置</h4>
  305.                 <div class=”settings-row”><span>员工查看密码</span><input type=”password” id=”newEmployeePwd” placeholder=”新密码” style=”width:160px;”><button class=”btn btn-warning” id=”updateEmployeePwdBtn”>修改</button></div>
  306.                 <div class=”settings-row”><span>管理员密码</span><input type=”password” id=”newAdminPwd” placeholder=”新密码” style=”width:160px;”><button class=”btn btn-warning” id=”updateAdminPwdBtn”>修改</button></div>
  307.             </div>
  308.             <div style=”padding:14px 20px;border-top:1px solid var(–border);font-size:0.75rem;color:var(–text2);”>
  309.                 <span>管理员: <strong>admin123</strong></span>
  310.                 <span style=”margin-left:16px;”>员工: <strong>employee2024</strong></span>
  311.                 <span style=”margin-left:16px;”>&#128161; 开启「自动备份」后每次修改自动导出 JSON 到下载文件夹</span>
  312.             </div>
  313.         </div>
  314.     </div>
  315. </div>
  316. <!– 浏览器提示弹窗 –>
  317. <div class=”browser-hint-overlay” id=”browserHintOverlay”>
  318.     <div class=”browser-hint-box”>
  319.         <div class=”icon” id=”hintIcon”>&#127760;</div>
  320.         <h3 id=”hintTitle”>浏览器</h3>
  321.         <p id=”hintDesc”>请先确认电脑已安装该浏览器,如未安装请先下载。</p>
  322.         <div class=”checkbox-row”>
  323.             <input type=”checkbox” id=”hintDontShowAgain”>
  324.             <label for=”hintDontShowAgain”>下次不再提示</label>
  325.         </div>
  326.         <div class=”btn-group”>
  327.             <button class=”btn btn-outline” id=”hintCancel”>取消</button>
  328.             <button class=”btn btn-warning” id=”hintDownload”>&#11015; 下载浏览器</button>
  329.             <button class=”btn btn-primary” id=”hintConfirm”>确定,已安装</button>
  330.         </div>
  331.     </div>
  332. </div>
  333. <div id=”credModal” class=”modal”>
  334.     <div class=”modal-content”>
  335.         <h3 id=”modalTitle”>添加凭证</h3>
  336.         <div class=”input-group”><label>网址</label><input type=”text” id=”credUrl” placeholder=”https://…”></div>
  337.         <div class=”input-group”><label>账号</label><input type=”text” id=”credUsername” placeholder=”账号”></div>
  338.         <div class=”input-group”><label>密码</label><input type=”text” id=”credPassword” placeholder=”密码”></div>
  339.         <div class=”input-group”>
  340.             <label>浏览器</label>
  341.             <select id=”credBrowser”>
  342.                 <option value=”default”>默认</option>
  343.                 <option value=”edge”>Edge</option>
  344.                 <option value=”chrome”>Chrome</option>
  345.                 <option value=”firefox”>Firefox</option>
  346.             </select>
  347.         </div>
  348.         <input type=”hidden” id=”editId” value=””>
  349.         <div style=”display:flex;gap:10px;justify-content:flex-end;margin-top:16px;”>
  350.             <button class=”btn btn-outline” id=”closeModalBtn”>取消</button>
  351.             <button class=”btn btn-primary” id=”saveCredBtn”>保存</button>
  352.         </div>
  353.     </div>
  354. </div>
  355. <div id=”toast” class=”toast”>操作成功</div>
  356. <script>
  357.     function h(s) { return CryptoJS.SHA256(s).toString(); }
  358.     // ===== 浏览器配置 =====
  359.     var BROWSER_CONFIG = {
  360.         edge: {
  361.             icon: ‘&#128311;’,
  362.             name: ‘Edge 浏览器’,
  363.             desc: ‘使用前提:已安装Edge浏览器。(未安装请先下载安装)。’,
  364.             download: ‘https://www.microsoft.com/zh-cn/edge/download’,
  365.             protocol: ‘microsoft-edge:’
  366.         },
  367.         chrome: {
  368.             icon: ‘&#128308;’,
  369.             name: ‘谷歌浏览器’,
  370.             desc: ‘使用前提:已安装谷歌浏览器。(未安装请先下载安装)。’,
  371.             download: ‘https://sw.pcmgr.qq.com/62a4683d5d6deb5f13daf8e29b0d90dd/6a962b0c/spcmgr/download/152.0.7977.65_chrome_installer_uncompressed-win64.exe’,
  372.             protocol: ‘googlechrome:’
  373.         },
  374.         firefox: {
  375.             icon: ‘&#129418;’,
  376.             name: ‘火狐浏览器’,
  377.             desc: ‘使用前提:已安装火狐浏览器。(未安装请先下载安装)。’,
  378.             download: ‘https://sw.pcmgr.qq.com/e38a6e930fd7892da4c3d82d122394cb/6a962af4/spcmgr/download/22931-110819-Firefox-setup-win64_116.0.3.8627.exe’,
  379.             protocol: ‘firefox:’
  380.         }
  381.     };
  382.     // ===== 浏览器提示弹窗控制 =====
  383.     var hintOverlay = document.getElementById(‘browserHintOverlay’);
  384.     var hintDontShowAgain = document.getElementById(‘hintDontShowAgain’);
  385.     var hintIcon = document.getElementById(‘hintIcon’);
  386.     var hintTitle = document.getElementById(‘hintTitle’);
  387.     var hintDesc = document.getElementById(‘hintDesc’);
  388.     var hintDownload = document.getElementById(‘hintDownload’);
  389.     var hintConfirm = document.getElementById(‘hintConfirm’);
  390.     var hintCancel = document.getElementById(‘hintCancel’);
  391.     var pendingBrowser = null;
  392.     var pendingUrl = null;
  393.     function getHintSetting(browser) {
  394.         return localStorage.getItem(‘hint_dismissed_’ + browser) === ‘true’;
  395.     }
  396.     function setHintSetting(browser, dismissed) {
  397.         localStorage.setItem(‘hint_dismissed_’ + browser, dismissed ? ‘true’ : ‘false’);
  398.     }
  399.     function showBrowserHint(browser, url) {
  400.         if (getHintSetting(browser)) {
  401.             doOpenBrowser(browser, url);
  402.             return;
  403.         }
  404.         var config = BROWSER_CONFIG[browser];
  405.         if (!config) { window.open(url, ‘_blank’); return; }
  406.         pendingBrowser = browser;
  407.         pendingUrl = url;
  408.         hintIcon.textContent = config.icon;
  409.         hintTitle.textContent = config.name;
  410.         hintDesc.textContent = config.desc;
  411.         hintDownload.textContent = ‘&#11015; 下载 ‘ + config.name;
  412.         hintOverlay.classList.add(‘show’);
  413.         hintDontShowAgain.checked = false;
  414.     }
  415.     function hideBrowserHint() {
  416.         hintOverlay.classList.remove(‘show’);
  417.     }
  418.     function doOpenBrowser(b, u) {
  419.         var config = BROWSER_CONFIG[b];
  420.         if (!config) { window.open(u, ‘_blank’); return; }
  421.         var url = u;
  422.         if (!url.startsWith(‘http://’) && !url.startsWith(‘https://’)) {
  423.             url = ‘https://’ + url;
  424.         }
  425.         var fullUrl = config.protocol + url;
  426.         try {
  427.             var currentUrl = window.location.href;
  428.             window.location.href = fullUrl;
  429.             setTimeout(function() {
  430.                 if (window.location.href === currentUrl) {
  431.                     tryMethod2(b, url);
  432.                 }
  433.             }, 800);
  434.         } catch(e) {
  435.             tryMethod2(b, url);
  436.         }
  437.     }
  438.     function tryMethod2(b, u) {
  439.         var config = BROWSER_CONFIG[b];
  440.         var url = u;
  441.         if (!url.startsWith(‘http://’) && !url.startsWith(‘https://’)) {
  442.             url = ‘https://’ + url;
  443.         }
  444.         var protocols = [
  445.             config.protocol + url,
  446.             config.protocol + ‘//open?url=’ + encodeURIComponent(url),
  447.             config.protocol + ‘//’ + url
  448.         ];
  449.         for (var i = 0; i < protocols.length; i++) {
  450.             try {
  451.                 var w = window.open(protocols[i], ‘_blank’);
  452.                 if (w) {
  453.                     setTimeout(function(win) {
  454.                         try { if (win && win.closed) { tryMethod3(b, u); } } catch(e) { tryMethod3(b, u); }
  455.                     }, 1500, w);
  456.                     return;
  457.                 }
  458.             } catch(e) {}
  459.         }
  460.         tryMethod3(b, u);
  461.     }
  462.     function tryMethod3(b, u) {
  463.         var config = BROWSER_CONFIG[b];
  464.         var url = u;
  465.         if (!url.startsWith(‘http://’) && !url.startsWith(‘https://’)) {
  466.             url = ‘https://’ + url;
  467.         }
  468.         var fullUrl = config.protocol + url;
  469.         try {
  470.             var iframe = document.createElement(‘iframe’);
  471.             iframe.style.display = ‘none’;
  472.             iframe.src = fullUrl;
  473.             document.body.appendChild(iframe);
  474.             setTimeout(function() {
  475.                 try { document.body.removeChild(iframe); } catch(e) {}
  476.                 showDownloadDialog(b, u);
  477.             }, 2000);
  478.         } catch(e) {
  479.             showDownloadDialog(b, u);
  480.         }
  481.     }
  482.     function showDownloadDialog(b, u) {
  483.         var config = BROWSER_CONFIG[b];
  484.         var name = config ? config.name : ‘该浏览器’;
  485.         var url = config ? config.download : null;
  486.         if (url) {
  487.             if (confirm(‘未检测到 ‘ + name + ‘,是否下载?\n\n点击「确定」前往下载\n点击「取消」使用默认浏览器打开’)) {
  488.                 window.open(url, ‘_blank’);
  489.             } else {
  490.                 window.open(u, ‘_blank’);
  491.             }
  492.         } else {
  493.             window.open(u, ‘_blank’);
  494.         }
  495.     }
  496.     hintConfirm.addEventListener(‘click’, function() {
  497.         if (hintDontShowAgain.checked && pendingBrowser) {
  498.             setHintSetting(pendingBrowser, true);
  499.         }
  500.         hideBrowserHint();
  501.         if (pendingUrl && pendingBrowser) {
  502.             setTimeout(function() { doOpenBrowser(pendingBrowser, pendingUrl); }, 150);
  503.         }
  504.     });
  505.     hintCancel.addEventListener(‘click’, function() {
  506.         hideBrowserHint();
  507.         pendingBrowser = null;
  508.         pendingUrl = null;
  509.     });
  510.     hintDownload.addEventListener(‘click’, function() {
  511.         if (pendingBrowser) {
  512.             var config = BROWSER_CONFIG[pendingBrowser];
  513.             if (config && config.download) {
  514.                 window.open(config.download, ‘_blank’);
  515.             }
  516.         }
  517.     });
  518.     hintOverlay.addEventListener(‘click’, function(e) {
  519.         if (e.target === hintOverlay) {
  520.             hideBrowserHint();
  521.             pendingBrowser = null;
  522.             pendingUrl = null;
  523.         }
  524.     });
  525.     // ===== 浏览器打开入口 =====
  526.     function bn(b) {
  527.         var map = { edge:’Edge浏览器打开’, chrome:’谷歌浏览器打开’, firefox:’火狐浏览器打开’ };
  528.         return map[b] || ‘默认浏览器打开’;
  529.     }
  530.     function bc(b) {
  531.         var map = { edge:’browser-edge’, chrome:’browser-chrome’, firefox:’browser-firefox’ };
  532.         return map[b] || ‘browser-default’;
  533.     }
  534.     function openWith(b, u) {
  535.         if (b === ‘default’ || !b) { window.open(u, ‘_blank’); return; }
  536.         showBrowserHint(b, u);
  537.     }
  538.     function openHTML(b, u) {
  539.         var name = bn(b);
  540.         var cls = bc(b);
  541.         var su = u.replace(/’/g, “\\'”);
  542.         var html = ‘<div class=”open-btn-group”>’;
  543.         if (b === ‘default’) {
  544.             html += ‘<button class=”btn btn-open btn-sm”>打开</button>’;
  545.         } else {
  546.             html += ‘<button class=”btn btn-open btn-sm”>’ + name + ‘</button>’;
  547.             html += ‘<button class=”btn btn-outline btn-sm”>默认浏览器打开</button>’;
  548.         }
  549.         html += ‘</div>’;
  550.         return html;
  551.     }
  552.     function copyText(t, msg) {
  553.         try {
  554.             navigator.clipboard.writeText(t);
  555.             toast(msg);
  556.         } catch(e) {
  557.             var ta = document.createElement(‘textarea’);
  558.             ta.value = t;
  559.             document.body.appendChild(ta);
  560.             ta.select();
  561.             document.execCommand(‘copy’);
  562.             document.body.removeChild(ta);
  563.             toast(msg);
  564.         }
  565.     }
  566.     // ============================================================
  567.     // ===== 数据存储层 – localStorage 持久化存储 =====
  568.     // ============================================================
  569.     var STORAGE_KEY = ‘password_manager_data’;
  570.     var CONFIG_KEY = ‘password_manager_config’;
  571.     var empVisible = false;
  572.     var adminAuth = false;
  573.     var empHash = ”;
  574.     var adminHash = ”;
  575.     var autoBackupEnabled = false;
  576.     var lastBackupTime = null;
  577.     var credentials = [];
  578.     var nextId = 1;
  579.     // 默认密码
  580.     var DEF_EMP = h(’employee2024′);
  581.     var DEF_ADMIN = h(‘admin123’);
  582.     // ===== 数据操作 =====
  583.     function loadData() {
  584.         try {
  585.             var saved = localStorage.getItem(STORAGE_KEY);
  586.             if (saved) {
  587.                 var parsed = JSON.parse(saved);
  588.                 credentials = parsed.credentials || [];
  589.                 nextId = parsed.nextId || 1;
  590.                 return true;
  591.             }
  592.         } catch(e) {
  593.             console.error(‘加载数据失败:’, e);
  594.         }
  595.         return false;
  596.     }
  597.     function saveData() {
  598.         try {
  599.             var data = {
  600.                 credentials: credentials,
  601.                 nextId: nextId
  602.             };
  603.             localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
  604.             return true;
  605.         } catch(e) {
  606.             console.error(‘保存数据失败:’, e);
  607.             return false;
  608.         }
  609.     }
  610.     function loadConfig() {
  611.         try {
  612.             var saved = localStorage.getItem(CONFIG_KEY);
  613.             if (saved) {
  614.                 var parsed = JSON.parse(saved);
  615.                 empHash = parsed.empHash || DEF_EMP;
  616.                 adminHash = parsed.adminHash || DEF_ADMIN;
  617.                 autoBackupEnabled = parsed.autoBackupEnabled || false;
  618.                 return true;
  619.             }
  620.         } catch(e) {
  621.             console.error(‘加载配置失败:’, e);
  622.         }
  623.         return false;
  624.     }
  625.     function saveConfig() {
  626.         try {
  627.             var data = {
  628.                 empHash: empHash,
  629.                 adminHash: adminHash,
  630.                 autoBackupEnabled: autoBackupEnabled
  631.             };
  632.             localStorage.setItem(CONFIG_KEY, JSON.stringify(data));
  633.             return true;
  634.         } catch(e) {
  635.             console.error(‘保存配置失败:’, e);
  636.             return false;
  637.         }
  638.     }
  639.     function initData() {
  640.         var loaded = loadData();
  641.         var configLoaded = loadConfig();
  642.         if (!loaded) {
  643.             credentials = [
  644.                 { id: 1, url: ‘https://mail.company.com’, username: ‘zhang.san@company.com’, password: ‘Mail@2024Secure’, browser: ‘default’ },
  645.                 { id: 2, url: ‘https://hrsystem.company.com’, username: ‘hr_admin’, password: ‘HrP@ss#2024’, browser: ‘edge’ },
  646.                 { id: 3, url: ‘https://gitlab.company.com’, username: ‘dev_lihua’, password: ‘GitLab!7890’, browser: ‘chrome’ },
  647.                 { id: 4, url: ‘https://jira.company.com’, username: ‘project_manager’, password: ‘Jira@2025’, browser: ‘firefox’ }
  648.             ];
  649.             nextId = 5;
  650.             saveData();
  651.         }
  652.         if (!configLoaded) {
  653.             empHash = DEF_EMP;
  654.             adminHash = DEF_ADMIN;
  655.             autoBackupEnabled = false;
  656.             saveConfig();
  657.         }
  658.         updateStorageInfo();
  659.         updateLastBackupTime();
  660.         updateAutoBackupUI();
  661.     }
  662.     // ===== 自动备份 =====
  663.     function getAutoBackupSetting() {
  664.         return autoBackupEnabled;
  665.     }
  666.     function setAutoBackupSetting(enabled) {
  667.         autoBackupEnabled = enabled;
  668.         saveConfig();
  669.         updateAutoBackupUI();
  670.     }
  671.     function updateAutoBackupUI() {
  672.         var toggle = document.getElementById(‘autoBackupToggle’);
  673.         var status = document.getElementById(‘autoBackupStatus’);
  674.         if (toggle) {
  675.             toggle.classList.toggle(‘active’, autoBackupEnabled);
  676.         }
  677.         if (status) {
  678.             status.textContent = autoBackupEnabled ? ‘&#9989; 已开启’ : ‘&#9940; 已关闭’;
  679.             status.style.color = autoBackupEnabled ? ‘var(–success)’ : ‘var(–text2)’;
  680.         }
  681.     }
  682.     function exportToDownloadFolder() {
  683.         try {
  684.             var data = {
  685.                 credentials: credentials,
  686.                 nextId: nextId,
  687.                 exportTime: new Date().toISOString(),
  688.                 version: ‘1.0’
  689.             };
  690.             var json = JSON.stringify(data, null, 2);
  691.             var blob = new Blob([json], { type: ‘application/json’ });
  692.             var url = URL.createObjectURL(blob);
  693.             var a = document.createElement(‘a’);
  694.             a.href = url;
  695.             var now = new Date();
  696.             var dateStr = now.getFullYear() + ‘-‘ +
  697.                 String(now.getMonth()+1).padStart(2,’0′) + ‘-‘ +
  698.                 String(now.getDate()).padStart(2,’0′) + ‘_’ +
  699.                 String(now.getHours()).padStart(2,’0′) + ‘-‘ +
  700.                 String(now.getMinutes()).padStart(2,’0′);
  701.             a.download = ‘password_backup_’ + dateStr + ‘.json’;
  702.             document.body.appendChild(a);
  703.             a.click();
  704.             document.body.removeChild(a);
  705.             setTimeout(function() {
  706.                 URL.revokeObjectURL(url);
  707.             }, 5000);
  708.             lastBackupTime = now;
  709.             updateLastBackupTime();
  710.             return true;
  711.         } catch(e) {
  712.             console.error(‘自动备份失败:’, e);
  713.             return false;
  714.         }
  715.     }
  716.     function updateLastBackupTime() {
  717.         var el = document.getElementById(‘lastBackupTime’);
  718.         if (!el) return;
  719.         if (lastBackupTime) {
  720.             var str = lastBackupTime.getFullYear() + ‘-‘ +
  721.                 String(lastBackupTime.getMonth()+1).padStart(2,’0′) + ‘-‘ +
  722.                 String(lastBackupTime.getDate()).padStart(2,’0′) + ‘ ‘ +
  723.                 String(lastBackupTime.getHours()).padStart(2,’0′) + ‘:’ +
  724.                 String(lastBackupTime.getMinutes()).padStart(2,’0′);
  725.             el.textContent = ‘&#128203; 最后备份:’ + str;
  726.         } else {
  727.             el.textContent = ‘&#128203; 最后备份:无’;
  728.         }
  729.     }
  730.     // ===== 增删改查 =====
  731.     function allCreds() {
  732.         return credentials.slice().sort(function(a, b) { return b.id – a.id; });
  733.     }
  734.     function getCredById(id) {
  735.         for (var i = 0; i < credentials.length; i++) {
  736.             if (credentials[i].id === id) return credentials[i];
  737.         }
  738.         return null;
  739.     }
  740.     function upsert(id, url, username, password, browser) {
  741.         if (!url || !username || !password) return false;
  742.         if (id === null || id === undefined || id === ”) {
  743.             var newCred = {
  744.                 id: nextId++,
  745.                 url: url,
  746.                 username: username,
  747.                 password: password,
  748.                 browser: browser || ‘default’
  749.             };
  750.             credentials.push(newCred);
  751.         } else {
  752.             var found = false;
  753.             for (var i = 0; i < credentials.length; i++) {
  754.                 if (credentials[i].id === id) {
  755.                     credentials[i].url = url;
  756.                     credentials[i].username = username;
  757.                     credentials[i].password = password;
  758.                     credentials[i].browser = browser || ‘default’;
  759.                     found = true;
  760.                     break;
  761.                 }
  762.             }
  763.             if (!found) return false;
  764.         }
  765.         saveData();
  766.         updateStorageInfo();
  767.         if (autoBackupEnabled) {
  768.             exportToDownloadFolder();
  769.         }
  770.         return true;
  771.     }
  772.     function delCred(id) {
  773.         for (var i = 0; i < credentials.length; i++) {
  774.             if (credentials[i].id === id) {
  775.                 credentials.splice(i, 1);
  776.                 saveData();
  777.                 updateStorageInfo();
  778.                 if (autoBackupEnabled) {
  779.                     exportToDownloadFolder();
  780.                 }
  781.                 return true;
  782.             }
  783.         }
  784.         return false;
  785.     }
  786.     function batchUpsert(records) {
  787.         var cnt = 0;
  788.         for (var i = 0; i < records.length; i++) {
  789.             var rec = records[i];
  790.             if (rec.url && rec.username && rec.password) {
  791.                 var exists = false;
  792.                 for (var j = 0; j < credentials.length; j++) {
  793.                     if (credentials[j].url === rec.url) {
  794.                         credentials[j].username = rec.username;
  795.                         credentials[j].password = rec.password;
  796.                         credentials[j].browser = rec.browser || ‘default’;
  797.                         exists = true;
  798.                         cnt++;
  799.                         break;
  800.                     }
  801.                 }
  802.                 if (!exists) {
  803.                     credentials.push({
  804.                         id: nextId++,
  805.                         url: rec.url,
  806.                         username: rec.username,
  807.                         password: rec.password,
  808.                         browser: rec.browser || ‘default’
  809.                     });
  810.                     cnt++;
  811.                 }
  812.             }
  813.         }
  814.         saveData();
  815.         updateStorageInfo();
  816.         if (autoBackupEnabled) {
  817.             exportToDownloadFolder();
  818.         }
  819.         return cnt;
  820.     }
  821.     function updateStorageInfo() {
  822.         var el = document.getElementById(‘recordCount’);
  823.         if (el) el.textContent = credentials.length;
  824.     }
  825.     // ===== 密码配置 =====
  826.     function getEmpHash() { return empHash; }
  827.     function getAdminHash() { return adminHash; }
  828.     function setEmpHash(newHash) {
  829.         empHash = newHash;
  830.         saveConfig();
  831.     }
  832.     function setAdminHash(newHash) {
  833.         adminHash = newHash;
  834.         saveConfig();
  835.     }
  836.     // ===== 导出/导入 JSON =====
  837.     function exportJson() {
  838.         try {
  839.             var data = {
  840.                 credentials: credentials,
  841.                 nextId: nextId,
  842.                 exportTime: new Date().toISOString(),
  843.                 version: ‘1.0’
  844.             };
  845.             var json = JSON.stringify(data, null, 2);
  846.             var blob = new Blob([json], { type: ‘application/json’ });
  847.             var url = URL.createObjectURL(blob);
  848.             var a = document.createElement(‘a’);
  849.             a.href = url;
  850.             var now = new Date();
  851.             var dateStr = now.getFullYear() + ‘-‘ +
  852.                 String(now.getMonth()+1).padStart(2,’0′) + ‘-‘ +
  853.                 String(now.getDate()).padStart(2,’0′) + ‘_’ +
  854.                 String(now.getHours()).padStart(2,’0′) + ‘-‘ +
  855.                 String(now.getMinutes()).padStart(2,’0′);
  856.             a.download = ‘password_data_’ + dateStr + ‘.json’;
  857.             document.body.appendChild(a);
  858.             a.click();
  859.             document.body.removeChild(a);
  860.             setTimeout(function() {
  861.                 URL.revokeObjectURL(url);
  862.             }, 5000);
  863.             toast(‘数据已导出到下载目录’, ‘success’);
  864.         } catch(e) {
  865.             toast(‘导出失败: ‘ + e.message, true);
  866.         }
  867.     }
  868.     function importJson(file) {
  869.         var reader = new FileReader();
  870.         reader.onload = function(e) {
  871.             try {
  872.                 var data = JSON.parse(e.target.result);
  873.                 if (!data.credentials || !Array.isArray(data.credentials)) {
  874.                     toast(‘无效的数据文件’, true);
  875.                     return;
  876.                 }
  877.                 if (!confirm(‘确定要恢复此数据吗?当前数据将被覆盖!’)) {
  878.                     return;
  879.                 }
  880.                 credentials = data.credentials;
  881.                 nextId = data.nextId || (credentials.length > 0 ? Math.max.apply(null, credentials.map(function(c) { return c.id; })) + 1 : 1);
  882.                 saveData();
  883.                 updateStorageInfo();
  884.                 if (autoBackupEnabled) {
  885.                     exportToDownloadFolder();
  886.                 }
  887.                 renderEmp();
  888.                 renderAdmin();
  889.                 toast(‘数据恢复成功’, ‘success’);
  890.             } catch(err) {
  891.                 toast(‘无效的数据文件: ‘ + err.message, true);
  892.             }
  893.         };
  894.         reader.readAsText(file);
  895.     }
  896.     // ===== 渲染 =====
  897.     function renderEmp() {
  898.         var tbody = document.getElementById(’employeeTableBody’);
  899.         if (!tbody) return;
  900.         var list = allCreds();
  901.         if (!list.length) {
  902.             tbody.innerHTML = ‘<tr class=”empty-row”><td colspan=”4″>暂无数据</td></tr>’;
  903.             return;
  904.         }
  905.         var html = ”;
  906.         for (var i = 0; i < list.length; i++) {
  907.             var item = list[i];
  908.             var name = bn(item.browser);
  909.             var cls = bc(item.browser);
  910.             var pwd = empVisible ?
  911.                 ‘<span class=”password-plain”>’ + esc(item.password) + ‘</span>’ :
  912.                 ‘<span class=”password-masked”>●●●●●●●●</span>’;
  913.             html += ‘<tr><td><div class=”url-cell”><a href=”‘ + esc(item.url) + ‘” target=”_blank” class=”url-link”>’ + esc(item.url) + ‘</a><button class=”copy-url-btn” data-url=”‘ + esc(item.url) + ‘”>复制</button></div></td>’;
  914.             html += ‘<td>’ + esc(item.username) + ‘</td><td>’ + pwd + ‘</td>’;
  915.             html += ‘<td><span class=”browser-badge ‘ + cls + ‘”>’ + name + ‘</span><br>’ + openHTML(item.browser, item.url) + ‘</td></tr>’;
  916.         }
  917.         tbody.innerHTML = html;
  918.         var btns = document.querySelectorAll(‘.copy-url-btn’);
  919.         for (var j = 0; j < btns.length; j++) {
  920.             btns[j].addEventListener(‘click’, function() { copyText(this.dataset.url, ‘网址已复制’); });
  921.         }
  922.     }
  923.     function renderAdmin() {
  924.         var tbody = document.getElementById(‘adminTableBody’);
  925.         if (!tbody) return;
  926.         if (!adminAuth) {
  927.             tbody.innerHTML = ‘<tr class=”empty-row”><td colspan=”5″>请验证管理员权限</td></tr>’;
  928.             return;
  929.         }
  930.         var list = allCreds();
  931.         if (!list.length) {
  932.             tbody.innerHTML = ‘<tr class=”empty-row”><td colspan=”5″>暂无数据,点击”新增”添加</td></tr>’;
  933.             return;
  934.         }
  935.         var html = ”;
  936.         for (var i = 0; i < list.length; i++) {
  937.             var item = list[i];
  938.             var name = bn(item.browser);
  939.             var cls = bc(item.browser);
  940.             html += ‘<tr><td><a href=”‘ + esc(item.url) + ‘” target=”_blank” class=”url-link”>’ + esc(item.url) + ‘</a></td>’;
  941.             html += ‘<td>’ + esc(item.username) + ‘</td>’;
  942.             html += ‘<td><span class=”password-plain”>’ + esc(item.password) + ‘</span></td>’;
  943.             html += ‘<td><span class=”browser-badge ‘ + cls + ‘”>’ + name + ‘</span><br>’ + openHTML(item.browser, item.url) + ‘</td>’;
  944.             html += ‘<td class=”flex-btns”><button class=”btn btn-outline edit-btn” data-id=”‘ + item.id + ‘”>编辑</button><button class=”btn btn-danger delete-btn” data-id=”‘ + item.id + ‘”>删除</button></td></tr>’;
  945.         }
  946.         tbody.innerHTML = html;
  947.         var eds = document.querySelectorAll(‘.edit-btn’);
  948.         for (var j = 0; j < eds.length; j++) {
  949.             eds[j].addEventListener(‘click’, function() { openEdit(parseInt(this.dataset.id)); });
  950.         }
  951.         var dels = document.querySelectorAll(‘.delete-btn’);
  952.         for (var k = 0; k < dels.length; k++) {
  953.             dels[k].addEventListener(‘click’, function() {
  954.                 if (confirm(‘确定删除?’)) {
  955.                     delCred(parseInt(this.dataset.id));
  956.                     renderAdmin();
  957.                     renderEmp();
  958.                     toast(‘已删除’);
  959.                 }
  960.             });
  961.         }
  962.         updateStorageInfo();
  963.     }
  964.     function esc(s) { if (!s) return ”; return s.replace(/[&<>]/g, function(m) { return { ‘&’:’&’, ‘<‘:'<‘, ‘>’:’>’ }[m]; }); }
  965.     function verifyEmp() {
  966.         var inp = document.getElementById(’empViewPwd’);
  967.         if (!inp) return;
  968.         if (h(inp.value) === empHash) {
  969.             empVisible = true;
  970.             toast(‘验证成功’, ‘success’);
  971.             renderEmp();
  972.         } else {
  973.             empVisible = false;
  974.             renderEmp();
  975.             toast(‘密码错误’, true);
  976.         }
  977.         inp.value = ”;
  978.     }
  979.     function openEdit(id) {
  980.         var item = getCredById(id);
  981.         if (item) openModal(item);
  982.     }
  983.     function toast(msg, err) {
  984.         var t = document.getElementById(‘toast’);
  985.         if (!t) return;
  986.         t.textContent = msg;
  987.         t.className = ‘toast’;
  988.         if (err === true) t.classList.add(‘error’);
  989.         else if (err === ‘success’) t.classList.add(‘success’);
  990.         t.classList.add(‘show’);
  991.         clearTimeout(t._timer);
  992.         t._timer = setTimeout(function() { t.classList.remove(‘show’); }, 3000);
  993.     }
  994.     // ===== 模态框 =====
  995.     var modal = document.getElementById(‘credModal’);
  996.     var urlInp = document.getElementById(‘credUrl’);
  997.     var userInp = document.getElementById(‘credUsername’);
  998.     var pwdInp = document.getElementById(‘credPassword’);
  999.     var browserSel = document.getElementById(‘credBrowser’);
  1000.     var editIdF = document.getElementById(‘editId’);
  1001.     var modalTitle = document.getElementById(‘modalTitle’);
  1002.     function openModal(entry) {
  1003.         if (!adminAuth) { toast(‘请先登录’, true); return; }
  1004.         if (entry) {
  1005.             modalTitle.textContent = ‘编辑凭证’;
  1006.             urlInp.value = entry.url;
  1007.             userInp.value = entry.username;
  1008.             pwdInp.value = entry.password;
  1009.             browserSel.value = entry.browser || ‘default’;
  1010.             editIdF.value = entry.id;
  1011.         } else {
  1012.             modalTitle.textContent = ‘添加凭证’;
  1013.             urlInp.value = ”;
  1014.             userInp.value = ”;
  1015.             pwdInp.value = ”;
  1016.             browserSel.value = ‘default’;
  1017.             editIdF.value = ”;
  1018.         }
  1019.         modal.style.display = ‘flex’;
  1020.     }
  1021.     function closeModal() { modal.style.display = ‘none’; }
  1022.     function saveCred() {
  1023.         if (!adminAuth) { toast(‘未授权’, true); return; }
  1024.         var url = urlInp.value.trim();
  1025.         var user = userInp.value.trim();
  1026.         var pwd = pwdInp.value.trim();
  1027.         var browser = browserSel.value;
  1028.         if (!url || !user || !pwd) { toast(‘请填完整’, true); return; }
  1029.         if (!url.startsWith(‘http://’) && !url.startsWith(‘https://’)) {
  1030.             toast(‘网址需 http:// 或 https:// 开头’, true);
  1031.             return;
  1032.         }
  1033.         var id = editIdF.value;
  1034.         if (upsert(id ? parseInt(id) : null, url, user, pwd, browser)) {
  1035.             toast(id ? ‘已修改’ : ‘已添加’, ‘success’);
  1036.             closeModal();
  1037.             renderAdmin();
  1038.             renderEmp();
  1039.         } else { toast(‘操作失败’, true); }
  1040.     }
  1041.     // ===== 导出导入 Excel =====
  1042.     function exportExcel() {
  1043.         if (!adminAuth) { toast(‘请先登录’, true); return; }
  1044.         var list = allCreds();
  1045.         if (!list.length) { toast(‘无数据’, true); return; }
  1046.         var data = [[‘网址’, ‘账号’, ‘密码’, ‘浏览器’]];
  1047.         for (var i = 0; i < list.length; i++) {
  1048.             data.push([list[i].url, list[i].username, list[i].password, list[i].browser]);
  1049.         }
  1050.         var ws = XLSX.utils.aoa_to_sheet(data);
  1051.         var wb = XLSX.utils.book_new();
  1052.         XLSX.utils.book_append_sheet(wb, ws, ‘Credentials’);
  1053.         XLSX.writeFile(wb, ‘password_export_’ + Date.now() + ‘.xls’, { bookType: ‘xls’ });
  1054.         toast(‘导出成功’, ‘success’);
  1055.     }
  1056.     function importExcel(file) {
  1057.         if (!adminAuth) { toast(‘请先登录’, true); return; }
  1058.         var reader = new FileReader();
  1059.         reader.onload = function(e) {
  1060.             var data = new Uint8Array(e.target.result);
  1061.             var workbook = XLSX.read(data, { type: ‘array’ });
  1062.             var sheet = workbook.Sheets[workbook.SheetNames[0]];
  1063.             var rows = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: ” });
  1064.             if (!rows || rows.length < 2) { toast(‘无效文件’, true); return; }
  1065.             var headers = rows[0];
  1066.             var ui = -1;
  1067.             var ui2 = -1;
  1068.             var pi = -1;
  1069.             var bi = -1;
  1070.             for (var i = 0; i < headers.length; i++) {
  1071.                 var s = String(headers[i]).toLowerCase();
  1072.                 if (s.includes(‘网址’) || s.includes(‘url’)) ui = i;
  1073.                 if (s.includes(‘账号’) || s.includes(‘用户名’)) ui2 = i;
  1074.                 if (s.includes(‘密码’)) pi = i;
  1075.                 if (s.includes(‘浏览器’)) bi = i;
  1076.             }
  1077.             if (ui === -1 || ui2 === -1 || pi === -1) { toast(‘缺少必要列’, true); return; }
  1078.             var records = [];
  1079.             for (var j = 1; j < rows.length; j++) {
  1080.                 var row = rows[j];
  1081.                 if (row && row.length > Math.max(ui, ui2, pi)) {
  1082.                     var url = row[ui] ? String(row[ui]).trim() : ”;
  1083.                     var user = row[ui2] ? String(row[ui2]).trim() : ”;
  1084.                     var pwd = row[pi] ? String(row[pi]).trim() : ”;
  1085.                     var browser = (bi !== -1 && row[bi]) ? String(row[bi]).trim() : ‘default’;
  1086.                     if (url && user && pwd && (url.startsWith(‘http://’) || url.startsWith(‘https://’))) {
  1087.                         records.push({ url: url, username: user, password: pwd, browser: browser });
  1088.                     }
  1089.                 }
  1090.             }
  1091.             if (!records.length) { toast(‘无有效数据’, true); return; }
  1092.             var cnt = batchUpsert(records);
  1093.             renderAdmin();
  1094.             renderEmp();
  1095.             toast(‘导入 ‘ + cnt + ‘ 条’, ‘success’);
  1096.         };
  1097.         reader.readAsArrayBuffer(file);
  1098.     }
  1099.     // ===== 管理员登录 =====
  1100.     function showAdminLogin() {
  1101.         if (adminAuth) return;
  1102.         var overlay = document.createElement(‘div’);
  1103.         overlay.className = ‘admin-auth-overlay’;
  1104.         overlay.id = ‘adminOverlay’;
  1105.         overlay.innerHTML = ‘<div class=”admin-auth-box”><h3>管理员验证</h3><input type=”password” id=”adminLoginPwd” placeholder=”管理员密码” autocomplete=”off”><div id=”adminLoginError” class=”error”></div><div style=”display:flex;gap:10px;margin-top:16px;justify-content:center;”><button class=”btn btn-outline” id=”adminLoginCancel”>取消</button><button class=”btn btn-primary” id=”adminLoginConfirm”>确认</button></div></div>’;
  1106.         document.body.appendChild(overlay);
  1107.         var inp = document.getElementById(‘adminLoginPwd’);
  1108.         inp.focus();
  1109.         var doAuth = function() {
  1110.             if (h(inp.value) === adminHash) {
  1111.                 adminAuth = true;
  1112.                 document.body.removeChild(overlay);
  1113.                 toast(‘验证成功’, ‘success’);
  1114.                 renderAdmin();
  1115.                 var panel = document.getElementById(‘adminPanel’);
  1116.                 if (!panel.classList.contains(‘hidden’)) renderAdmin();
  1117.             } else {
  1118.                 document.getElementById(‘adminLoginError’).textContent = ‘密码错误’;
  1119.                 inp.value = ”;
  1120.                 inp.focus();
  1121.             }
  1122.         };
  1123.         var cancel = function() {
  1124.             document.body.removeChild(overlay);
  1125.             if (!adminAuth) { switchTab(’employee’);
  1126.                 toast(‘已取消’, true); }
  1127.         };
  1128.         document.getElementById(‘adminLoginConfirm’).addEventListener(‘click’, doAuth);
  1129.         document.getElementById(‘adminLoginCancel’).addEventListener(‘click’, cancel);
  1130.         inp.addEventListener(‘keypress’, function(e) { if (e.key === ‘Enter’) doAuth(); });
  1131.     }
  1132.     // ===== Tab切换 =====
  1133.     function switchTab(tab) {
  1134.         var admin = document.getElementById(‘adminPanel’);
  1135.         var emp = document.getElementById(’employeePanel’);
  1136.         var btns = document.querySelectorAll(‘.tab-btn’);
  1137.         if (tab === ’employee’) {
  1138.             admin.classList.add(‘hidden’);
  1139.             emp.classList.remove(‘hidden’);
  1140.             renderEmp();
  1141.             btns.forEach(function(b) { b.classList.remove(‘active’); });
  1142.             document.querySelector(‘.tab-btn[data-tab=”employee”]’).classList.add(‘active’);
  1143.         } else {
  1144.             if (!adminAuth) {
  1145.                 showAdminLogin();
  1146.                 var iv = setInterval(function() {
  1147.                     if (adminAuth) {
  1148.                         clearInterval(iv);
  1149.                         admin.classList.remove(‘hidden’);
  1150.                         emp.classList.add(‘hidden’);
  1151.                         renderAdmin();
  1152.                         btns.forEach(function(b) { b.classList.remove(‘active’); });
  1153.                         document.querySelector(‘.tab-btn[data-tab=”admin”]’).classList.add(‘active’);
  1154.                     }
  1155.                 }, 200);
  1156.                 setTimeout(function() { clearInterval(iv); }, 30000);
  1157.             } else {
  1158.                 admin.classList.remove(‘hidden’);
  1159.                 emp.classList.add(‘hidden’);
  1160.                 renderAdmin();
  1161.                 btns.forEach(function(b) { b.classList.remove(‘active’); });
  1162.                 document.querySelector(‘.tab-btn[data-tab=”admin”]’).classList.add(‘active’);
  1163.             }
  1164.         }
  1165.     }
  1166.     // ===== 主题切换 =====
  1167.     function setTheme(name) {
  1168.         var root = document.documentElement;
  1169.         var prev = document.querySelector(‘.theme-dot.active’);
  1170.         if (prev) prev.classList.remove(‘active’);
  1171.         var dot = document.querySelector(‘.theme-dot[data-theme=”‘ + name + ‘”]’);
  1172.         if (dot) dot.classList.add(‘active’);
  1173.         root.className = ”;
  1174.         if (name !== ‘default’) root.className = ‘theme-‘ + name;
  1175.         localStorage.setItem(‘pwd_theme’, name);
  1176.     }
  1177.     // ===== 事件绑定 =====
  1178.     document.addEventListener(‘DOMContentLoaded’, function() {
  1179.         var dots = document.querySelectorAll(‘.theme-dot’);
  1180.         dots.forEach(function(d) {
  1181.             d.addEventListener(‘click’, function() { setTheme(this.dataset.theme); });
  1182.         });
  1183.         var saved = localStorage.getItem(‘pwd_theme’);
  1184.         if (saved) setTheme(saved);
  1185.         // 自动备份开关
  1186.         var toggleBtn = document.getElementById(‘autoBackupToggle’);
  1187.         if (toggleBtn) {
  1188.             toggleBtn.addEventListener(‘click’, function() {
  1189.                 autoBackupEnabled = !autoBackupEnabled;
  1190.                 setAutoBackupSetting(autoBackupEnabled);
  1191.                 toast(autoBackupEnabled ? ‘自动备份已开启’ : ‘自动备份已关闭’, ‘success’);
  1192.                 if (autoBackupEnabled && credentials.length > 0) {
  1193.                     exportToDownloadFolder();
  1194.                     toast(‘已备份到下载文件夹’, ‘success’);
  1195.                 }
  1196.             });
  1197.         }
  1198.         var btnOpenAdd = document.getElementById(‘openAddCredBtn’);
  1199.         var btnCloseModal = document.getElementById(‘closeModalBtn’);
  1200.         var btnSaveCred = document.getElementById(‘saveCredBtn’);
  1201.         var btnVerifyEmp = document.getElementById(‘verifyEmpBtn’);
  1202.         var inpEmpPwd = document.getElementById(’empViewPwd’);
  1203.         var btnExportExcel = document.getElementById(‘exportExcelBtn’);
  1204.         var btnExportJson = document.getElementById(‘exportJsonBtn’);
  1205.         var inpImportExcel = document.getElementById(‘importExcelInput’);
  1206.         var inpImportJson = document.getElementById(‘importJsonInput’);
  1207.         var btnUpdateEmpPwd = document.getElementById(‘updateEmployeePwdBtn’);
  1208.         var btnUpdateAdminPwd = document.getElementById(‘updateAdminPwdBtn’);
  1209.         if (btnOpenAdd) btnOpenAdd.addEventListener(‘click’, function() { openModal(null); });
  1210.         if (btnCloseModal) btnCloseModal.addEventListener(‘click’, closeModal);
  1211.         if (btnSaveCred) btnSaveCred.addEventListener(‘click’, saveCred);
  1212.         if (btnVerifyEmp) btnVerifyEmp.addEventListener(‘click’, verifyEmp);
  1213.         if (inpEmpPwd) inpEmpPwd.addEventListener(‘keypress’, function(e) { if (e.key === ‘Enter’) verifyEmp(); });
  1214.         if (btnExportExcel) btnExportExcel.addEventListener(‘click’, exportExcel);
  1215.         if (btnExportJson) btnExportJson.addEventListener(‘click’, exportJson);
  1216.         if (inpImportExcel) inpImportExcel.addEventListener(‘change’, function(e) { if (this.files.length) importExcel(this.files[0]);
  1217.             this.value = ”; });
  1218.         if (inpImportJson) inpImportJson.addEventListener(‘change’, function(e) { if (this.files.length) importJson(this.files[0]);
  1219.             this.value = ”; });
  1220.         if (btnUpdateEmpPwd) {
  1221.             btnUpdateEmpPwd.addEventListener(‘click’, function() {
  1222.                 var inp = document.getElementById(‘newEmployeePwd’);
  1223.                 var p = inp.value;
  1224.                 if (!p || p.length < 4) { toast(‘密码至少4位’, true);
  1225.                     inp.value = ”; return; }
  1226.                 setEmpHash(h(p));
  1227.                 empVisible = false;
  1228.                 renderEmp();
  1229.                 toast(‘员工密码已修改’, ‘success’);
  1230.                 inp.value = ”;
  1231.             });
  1232.         }
  1233.         if (btnUpdateAdminPwd) {
  1234.             btnUpdateAdminPwd.addEventListener(‘click’, function() {
  1235.                 var inp = document.getElementById(‘newAdminPwd’);
  1236.                 var p = inp.value;
  1237.                 if (!p || p.length < 4) { toast(‘密码至少4位’, true);
  1238.                     inp.value = ”; return; }
  1239.                 setAdminHash(h(p));
  1240.                 toast(‘管理员密码已修改’, ‘success’);
  1241.                 inp.value = ”;
  1242.             });
  1243.         }
  1244.         document.querySelectorAll(‘.tab-btn’).forEach(function(btn) {
  1245.             btn.addEventListener(‘click’, function() { switchTab(this.dataset.tab); });
  1246.         });
  1247.         window.onclick = function(e) { if (e.target === modal) closeModal(); };
  1248.     });
  1249.     // ===== 启动 =====
  1250.     function boot() {
  1251.         try {
  1252.             initData();
  1253.             empVisible = false;
  1254.             adminAuth = false;
  1255.             renderEmp();
  1256.             renderAdmin();
  1257.             switchTab(’employee’);
  1258.             updateStorageInfo();
  1259.             updateLastBackupTime();
  1260.             toast(‘系统已启动,数据永久保存在 localStorage’, ‘success’);
  1261.         } catch(err) {
  1262.             toast(‘初始化失败: ‘ + err.message, true);
  1263.             console.error(err);
  1264.         }
  1265.     }
  1266.     if (document.readyState === ‘loading’) {
  1267.         document.addEventListener(‘DOMContentLoaded’, boot);
  1268.     } else {
  1269.         boot();
  1270.     }
  1271. </script>
  1272. </body>
  1273. </html>
复制代码

由于个人对HTML网页代码的理解还是欠缺,大家有什么新的建议可提出,或有什么错误的地方,麻烦大家告知,我虚心求教。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

本站所有资源来源于网络,仅限用于学习研究;无任何技术支持!不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除内容。如果您喜欢,请支持正版。如有侵权请邮件与我们联系处理。

个人整改后做的html版网页密码管理系统
下一篇:

已经没有下一篇了!

常见问题
  • 网盘有时候会因为名字 关键词导致失效 大家可以给管理员提供失效信息,我们会给大家适当积分进行奖励 我们会第一时间进行补充修正 感谢大家的配合 让我们共同努力 打造良好的资源分享平台
查看详情

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务