核心生成逻辑:UUID v7 的高 48 位基于 Date.now() 获取的毫秒级 Unix 时间戳,剩余位则通过 crypto.getRandomValues() 填充加密级随机熵,确保了唯一性和安全性。
便捷操作方式:您可以通过三种方式复制当前 UUID:直接点击显示区域、点击"复制"按钮,或在焦点位于 UUID 框时使用 Ctrl/Cmd + C 快捷键。复制成功时,界面会给出视觉和文字反馈。
交互反馈与信息:页面会实时显示生成时的时间戳(毫秒),并明确标注熵源为 crypto.getRandomValues。您还可以使用键盘的"空格键"快速生成新的 UUID。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UUID v7 生成器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(145deg, #f6f9fc 0%, #e9f1f8 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 32px;
padding: 40px 48px;
max-width: 640px;
width: 100%;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.5) inset;
transition: all 0.2s ease;
}
h1 {
font-size: 28px;
font-weight: 700;
color: #0b1e33;
letter-spacing: -0.5px;
display: flex;
align-items: center;
gap: 10px;
}
h1 span {
background: #2563eb;
color: white;
font-size: 14px;
font-weight: 600;
padding: 2px 12px;
border-radius: 20px;
letter-spacing: 0.3px;
}
.sub {
color: #4b5b6e;
font-size: 15px;
font-weight: 400;
margin-top: 6px;
margin-bottom: 28px;
border-left: 3px solid #2563eb;
padding-left: 14px;
background: rgba(37, 99, 235, 0.06);
border-radius: 0 4px 4px 0;
}
.uuid-box {
background: #ffffff;
padding: 20px 24px;
border-radius: 16px;
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Menlo', monospace;
font-size: 22px;
font-weight: 500;
letter-spacing: 0.5px;
color: #0a1a2b;
border: 2px solid #dce5ef;
transition: all 0.2s ease;
cursor: pointer;
user-select: all;
word-break: break-all;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
position: relative;
}
.uuid-box:hover {
border-color: #2563eb;
background: #f8faff;
box-shadow: 0 8px 20px -8px rgba(37, 99, 235, 0.15);
transform: scale(1.005);
}
.uuid-box:active {
transform: scale(0.98);
}
.uuid-box .hint {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 400;
font-size: 14px;
color: #8a9aa8;
letter-spacing: 0.3px;
}
.actions {
display: flex;
gap: 12px;
margin-top: 24px;
flex-wrap: wrap;
}
.btn-primary {
flex: 1;
min-width: 160px;
background: #2563eb;
color: white;
border: none;
padding: 14px 24px;
border-radius: 14px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
}
.btn-primary:hover {
background: #1d4ed8;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.3);
}
.btn-primary:active {
transform: translateY(0px);
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.2);
}
.btn-secondary {
background: #eef2f6;
color: #1e2b3c;
border: none;
padding: 14px 24px;
border-radius: 14px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex: 0.5;
}
.btn-secondary:hover {
background: #e2e8f0;
transform: translateY(-2px);
}
.btn-secondary:active {
transform: translateY(0px);
}
.feedback {
margin-top: 16px;
height: 28px;
font-size: 15px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
transition: all 0.2s ease;
border-radius: 8px;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
margin-right: 6px;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}
.footer-meta {
margin-top: 20px;
font-size: 13px;
color: #8a9aa8;
display: flex;
justify-content: space-between;
border-top: 1px solid #e4ebf3;
padding-top: 16px;
}
.footer-meta span {
display: flex;
align-items: center;
gap: 4px;
}
@media (max-width: 520px) {
.container {
padding: 28px 20px;
}
.uuid-box {
font-size: 16px;
padding: 16px 14px;
}
h1 {
font-size: 22px;
}
.actions {
flex-direction: column;
}
.btn-primary, .btn-secondary {
flex: 1;
width: 100%;
}
.footer-meta {
flex-direction: column;
align-items: center;
gap: 6px;
text-align: center;
}
}
</style>
</head>
<body>
<div class="container">
<h1>
UUID v7
<span>RFC 9562</span>
</h1>
<div class="sub">
⚡ 基于当前 Unix 时间戳(毫秒)+ 加密级随机熵生成 · 点击下方 ID 可复制
</div>
<div class="uuid-box" id="uuidDisplay" title="点击复制 UUID">
<span class="hint">点击「生成」按钮或刷新页面</span>
</div>
<div class="actions">
<button class="btn-primary" id="generateBtn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.66 0 3-4.03 3-9s-1.34-9-3-9m0 18c-1.66 0-3-4.03-3-9s1.34-9 3-9"/></svg>
生成新的 UUID v7
</button>
<button class="btn-secondary" id="copyBtn" title="复制到剪贴板">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
复制
</button>
</div>
<div id="feedback" class="feedback"></div>
<div class="footer-meta">
<span>🕒 <span id="timestampDisplay">-</span> ms</span>
<span>🔐 熵源: <strong>crypto.getRandomValues</strong></span>
</div>
</div>
<script>
(function() {
'use strict';
const uuidDisplay = document.getElementById('uuidDisplay');
const generateBtn = document.getElementById('generateBtn');
const copyBtn = document.getElementById('copyBtn');
const feedback = document.getElementById('feedback');
const timestampDisplay = document.getElementById('timestampDisplay');
let currentUUID = '';
/**
* 生成符合 RFC 9562 的 UUID v7
* 结构: 时间戳(48位) + 版本(4位) + 随机(12位) + 变体(2位) + 随机(62位)
*/
function generateUUIDv7() {
// 1. 获取当前毫秒时间戳 (Unix 纪元)
const ts = Date.now();
// 2. 创建 16 字节数组 (128位)
const bytes = new Uint8Array(16);
// 3. 使用 BigInt 安全地提取 48 位时间戳 (6 字节, 大端序)
const tsBig = BigInt(ts);
bytes[0] = Number((tsBig >> 40n) & 0xFFn);
bytes[1] = Number((tsBig >> 32n) & 0xFFn);
bytes[2] = Number((tsBig >> 24n) & 0xFFn);
bytes[3] = Number((tsBig >> 16n) & 0xFFn);
bytes[4] = Number((tsBig >> 8n) & 0xFFn);
bytes[5] = Number(tsBig & 0xFFn);
// 4. 填充剩余 10 字节 (80位) 为加密级随机数
crypto.getRandomValues(bytes.subarray(6));
// 5. 设置版本号 (Version 7) -> 高 4 位为 0b0111 (0x7)
// bytes[6] 属于 time_hi_and_version 字段的高字节
bytes[6] = (bytes[6] & 0x0F) | 0x70;
// 6. 设置变体 (Variant RFC 4122) -> 高 2 位为 0b10 (0x8)
// bytes[8] 属于 clock_seq_hi_and_reserved 字段
bytes[8] = (bytes[8] & 0x3F) | 0x80;
// 7. 转换为十六进制字符串
const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
// 8. 按 UUID 格式插入连字符: 8-4-4-4-12
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20,32)}`;
}
function updateUI(uuid) {
if (!uuid) {
uuidDisplay.innerHTML = `<span class="hint">点击「生成」按钮</span>`;
currentUUID = '';
timestampDisplay.textContent = '-';
return;
}
currentUUID = uuid;
uuidDisplay.textContent = uuid;
uuidDisplay.style.color = '#0a1a2b';
// 更新时间戳显示
const ts = Date.now();
timestampDisplay.textContent = ts.toLocaleString();
}
function generateAndDisplay() {
const newUUID = generateUUIDv7();
updateUI(newUUID);
// 清空之前的反馈(保留成功状态短暂显示)
feedback.textContent = '';
feedback.style.color = '';
}
// --- 复制功能 ---
async function copyCurrentUUID() {
if (!currentUUID) {
feedback.textContent = '⚠️ 请先生成一个 UUID';
feedback.style.color = '#f59e0b';
setTimeout(() => { feedback.textContent = ''; }, 2000);
return;
}
try {
await navigator.clipboard.writeText(currentUUID);
feedback.textContent = '✅ 已复制到剪贴板!';
feedback.style.color = '#16a34a';
// 给 UUID 框一个瞬时的闪光反馈
uuidDisplay.style.transition = 'background 0.1s';
uuidDisplay.style.background = '#dcfce7';
setTimeout(() => {
uuidDisplay.style.background = '';
}, 300);
setTimeout(() => {
feedback.textContent = '';
}, 2200);
} catch (err) {
// 降级方案:使用传统方法
try {
const textArea = document.createElement('textarea');
textArea.value = currentUUID;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
feedback.textContent = '✅ 已复制到剪贴板!';
feedback.style.color = '#16a34a';
setTimeout(() => { feedback.textContent = ''; }, 2200);
} catch (fallbackErr) {
feedback.textContent = '❌ 复制失败,请手动选中复制';
feedback.style.color = '#dc2626';
setTimeout(() => { feedback.textContent = ''; }, 3000);
}
}
}
// --- 事件绑定 ---
// 1. 点击 UUID 框复制
uuidDisplay.addEventListener('click', copyCurrentUUID);
// 2. 点击复制按钮复制
copyBtn.addEventListener('click', copyCurrentUUID);
// 3. 点击生成按钮
generateBtn.addEventListener('click', generateAndDisplay);
// 4. 页面加载时自动生成一个
window.addEventListener('load', () => {
generateAndDisplay();
});
// 5. 键盘快捷键:空格键生成 (辅助功能)
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.code === 'Space' && !e.repeat) {
e.preventDefault();
generateAndDisplay();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
// 如果焦点在 UUID 框上,拦截默认行为,使用我们的复制逻辑
if (document.activeElement === uuidDisplay) {
e.preventDefault();
copyCurrentUUID();
}
}
});
// 6. 为 UUID 框添加辅助提示 (title)
uuidDisplay.setAttribute('title', '点击复制到剪贴板');
console.log('✅ UUID v7 生成器已启动 (RFC 9562)');
})();
</script>
</body>
</html>