Files
CNWei e522a98f6f refactor: 合并为单 crate,前端改为内嵌 React 控制台
合并 stash-cli / stash-core 为根目录单 crate(lib + bin),新增 React + Vite + TS 控制台替换 Askama 模板。
build.rs 用 walkdir 把 stash-web/dist 同步到 $OUT_DIR/web,由 include_dir! 编译期内嵌,产出单文件二进制。
路由改为 / 与 /assets/** 走内嵌前端,其余交给 ServeDir,缺失路径返回内置 404。同步更新依赖、.gitignore 与 README。
2026-10-21 17:59:08 +08:00

745 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Git 规范标准化与工作流生成器</title>
<style>
:root {
--bg-color: #f8fafc;
--card-bg: #ffffff;
--text-main: #1e293b;
--text-muted: #64748b;
--border-color: #e2e8f0;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-light: #eff6ff;
--success: #10b981;
--radius: 8px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
line-height: 1.5;
padding: 24px 16px;
}
.container {
max-width: 1040px;
margin: 0 auto;
}
header {
margin-bottom: 24px;
text-align: center;
}
header h1 {
font-size: 1.75rem;
font-weight: 700;
color: var(--text-main);
margin-bottom: 6px;
}
header p {
color: var(--text-muted);
font-size: 0.95rem;
}
/* Tabs */
.nav-tabs {
display: flex;
gap: 8px;
border-bottom: 2px solid var(--border-color);
margin-bottom: 20px;
overflow-x: auto;
}
.tab-btn {
padding: 10px 20px;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-muted);
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s ease;
}
.tab-btn:hover {
color: var(--primary);
}
.tab-btn.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.section-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
/* 完美的图片展示容器 */
.img-viewport {
background: #0f172a;
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 20px;
margin-bottom: 20px;
text-align: center;
overflow-x: auto;
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
}
.img-viewport img {
max-width: 100%;
height: auto;
border-radius: 4px;
display: inline-block;
}
/* Grid layout for types */
.type-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
margin-bottom: 16px;
}
.type-card {
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 10px 12px;
cursor: pointer;
transition: all 0.2s ease;
background: #fff;
}
.type-card:hover {
border-color: var(--primary);
background: var(--primary-light);
}
.type-card.selected {
border-color: var(--primary);
background: var(--primary-light);
box-shadow: 0 0 0 2px var(--primary);
}
.type-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 2px;
}
.type-tag {
font-family: monospace;
color: var(--primary);
}
.type-desc {
font-size: 0.8rem;
color: var(--text-muted);
}
/* Form Styles */
.form-group {
margin-bottom: 16px;
}
.form-row {
display: flex;
gap: 16px;
}
.form-row .form-group {
flex: 1;
}
label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 6px;
color: var(--text-main);
}
label span.opt {
color: var(--text-muted);
font-weight: normal;
font-size: 0.8rem;
}
input[type="text"],
textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
font-size: 0.9rem;
color: var(--text-main);
outline: none;
transition: border-color 0.2s;
background: #fff;
}
input[type="text"]:focus,
textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
}
textarea {
resize: vertical;
min-height: 80px;
font-family: inherit;
}
/* Output Box */
.output-card {
background: #1e293b;
color: #f8fafc;
border-radius: var(--radius);
padding: 16px;
position: relative;
}
.output-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #334155;
}
.output-title {
font-size: 0.85rem;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.copy-btn {
background: var(--primary);
color: #fff;
border: none;
padding: 6px 14px;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.copy-btn:hover {
background: var(--primary-hover);
}
.copy-btn.copied {
background: var(--success);
}
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.9rem;
white-space: pre-wrap;
word-break: break-all;
line-height: 1.4;
}
/* Template Cards for Branch */
.template-selector {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.tpl-btn {
flex: 1;
min-width: 140px;
padding: 10px 14px;
border: 1px solid var(--border-color);
background: #fff;
border-radius: var(--radius);
cursor: pointer;
font-size: 0.88rem;
font-weight: 500;
text-align: center;
transition: all 0.2s;
}
.tpl-btn.active {
border-color: var(--primary);
background: var(--primary-light);
color: var(--primary);
}
.rules-box {
background: #f1f5f9;
border-left: 4px solid var(--primary);
padding: 12px;
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 16px;
border-radius: 0 var(--radius) var(--radius) 0;
}
.rules-box ul {
padding-left: 18px;
margin-top: 4px;
}
@media (max-width: 640px) {
.form-row {
flex-direction: column;
gap: 0;
}
.type-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Git 规范标准化生成器</h1>
<p>企业级 Git 分支管理与 Commit 标准化生成工具</p>
</header>
<!-- Nav Tabs -->
<div class="nav-tabs">
<button class="tab-btn active" onclick="switchTab('init-tab')">项目 Init 提交</button>
<button class="tab-btn" onclick="switchTab('commit-tab')">常规 Commit 提交</button>
<button class="tab-btn" onclick="switchTab('branch-tab')">分支命名与流转图</button>
</div>
<!-- TAB 1: 项目 Init 提交 -->
<div id="init-tab" class="tab-content active">
<div class="card">
<div class="section-title">初始化项目 Commit 模版</div>
<div class="form-group">
<label for="init-proj-name">项目名称</label>
<input type="text" id="init-proj-name" placeholder="例: git_helper" oninput="updateInitOutput()">
</div>
<div class="form-group">
<label for="init-proj-desc">项目描述</label>
<input type="text" id="init-proj-desc" placeholder="例: 基于 Spring Boot 的企业级后台" oninput="updateInitOutput()">
</div>
<div class="form-group">
<label for="init-env">开发环境 / 技术栈</label>
<input type="text" id="init-env" placeholder="例: JDK 17, Spring Boot 3.x, MySQL" oninput="updateInitOutput()">
</div>
<div class="form-group">
<label for="init-items">初始化完成工作 <span class="opt">(按行分隔)</span></label>
<textarea id="init-items" rows="5" oninput="updateInitOutput()">创建了基本的项目结构
添加了初始的 README 文件
添加了 .gitignore 文件
配置了基本的开发环境(如 ESLint, Prettier)
设置了基础文件夹结构(src, public, config)</textarea>
</div>
<div class="form-group">
<label for="init-next">下一步计划 <span class="opt">(按行分隔)</span></label>
<textarea id="init-next" rows="3" oninput="updateInitOutput()">填充 README 文件
添加项目基本设计文档
配置 CI/CD 自动化部署流水线</textarea>
</div>
</div>
<div class="output-card">
<div class="output-header">
<span class="output-title">生成的 Init Commit Message</span>
<button class="copy-btn" onclick="copyText('init-result', this)">复制初始化信息</button>
</div>
<pre id="init-result"></pre>
</div>
</div>
<!-- TAB 2: 常规 Commit -->
<div id="commit-tab" class="tab-content">
<div class="card">
<div class="section-title">1. 选择提交类型 (Type)</div>
<div class="type-grid" id="commit-type-grid"></div>
<div class="section-title" style="margin-top:20px;">2. 填写提交内容</div>
<div class="form-row">
<div class="form-group">
<label for="commit-scope">影响范围 Scope <span class="opt">(可选,如: auth, db)</span></label>
<input type="text" id="commit-scope" placeholder="例: auth" oninput="updateCommitOutput()">
</div>
<div class="form-group">
<label for="commit-subject">简明描述 Description <span class="opt">(必填,建议主动语态)</span></label>
<input type="text" id="commit-subject" placeholder="例: add user login functionality" oninput="updateCommitOutput()">
</div>
</div>
<div class="form-group">
<label for="commit-body">详细说明 Body <span class="opt">(可选,补充变动原因及细节)</span></label>
<textarea id="commit-body" placeholder="- Implemented login endpoint with JWT authentication&#10;- Added login form to frontend" oninput="updateCommitOutput()"></textarea>
</div>
<div class="section-title" style="margin-top:20px;">3. 脚注选项 (Footer)</div>
<div class="form-row">
<div class="form-group">
<label for="commit-footer">关联 Issue / 脚注 <span class="opt">(可选,例: Closes #45)</span></label>
<input type="text" id="commit-footer" placeholder="例: Closes #45" oninput="updateCommitOutput()">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="commit-wip">WIP 状态 <span class="opt">(可选,正在进行中的工作)</span></label>
<input type="text" id="commit-wip" placeholder="例: ing 登录模块UI正在联调" oninput="updateCommitOutput()">
</div>
<div class="form-group">
<label for="commit-fixup">FIXUP 目标 <span class="opt">(可选,修复先前提交的错误)</span></label>
<input type="text" id="commit-fixup" placeholder="例: 修正上一个提交的拼写错误" oninput="updateCommitOutput()">
</div>
</div>
<div class="form-group">
<label class="checkbox-group" style="display:flex; align-items:center; gap:8px; cursor:pointer;">
<input type="checkbox" id="commit-breaking" onchange="updateCommitOutput()">
<span>包含破坏性变动 (BREAKING CHANGE)</span>
</label>
</div>
</div>
<div class="output-card">
<div class="output-header">
<span class="output-title">生成的 Commit Message</span>
<button class="copy-btn" onclick="copyText('commit-result', this)">复制提交信息</button>
</div>
<pre id="commit-result"></pre>
</div>
</div>
<!-- TAB 3: 分支命名与高清图片流转图 -->
<div id="branch-tab" class="tab-content">
<div class="card">
<div class="section-title">企业敏捷 Git 演进架构图 (原生高清预览)</div>
<!-- 使用图片完美还原 Obsidian 效果 -->
<div class="img-viewport">
<img src="git-flow.png" alt="企业敏捷 Git 拓扑流转图">
</div>
<div class="section-title">选择分支类型生成 Git 命令</div>
<div class="template-selector">
<button class="tpl-btn active" onclick="selectBranchType('develop', this)">创建 develop 分支</button>
<button class="tpl-btn" onclick="selectBranchType('feature', this)">迭代功能分支 (SNAPSHOT)</button>
<button class="tpl-btn" onclick="selectBranchType('personal', this)">组员个人开发分支</button>
<button class="tpl-btn" onclick="selectBranchType('staging', this)">预生产分支 (Release)</button>
<button class="tpl-btn" onclick="selectBranchType('hotfix', this)">Hotfix 修复分支</button>
</div>
<div class="section-title" style="margin-top:16px;">填写项目与人员配置</div>
<div class="form-row" id="proj-row">
<div class="form-group">
<label for="branch-proj">项目名称</label>
<input type="text" id="branch-proj" value="git_helper" oninput="updateBranchOutput()">
</div>
<div class="form-group" id="version-group">
<label for="branch-version">版本号</label>
<input type="text" id="branch-version" value="v2.1.1" oninput="updateBranchOutput()">
</div>
<div class="form-group" id="date-group">
<label for="branch-date">日期</label>
<input type="text" id="branch-date" oninput="updateBranchOutput()">
</div>
</div>
<div class="form-row" id="personal-fields" style="display:none;">
<div class="form-group">
<label for="branch-author">组员姓名/拼音</label>
<input type="text" id="branch-author" value="zhangsan" oninput="updateBranchOutput()">
</div>
<div class="form-group">
<label for="branch-seq">同名序号 <span class="opt">(可选,如: 2)</span></label>
<input type="text" id="branch-seq" placeholder="例: 2" oninput="updateBranchOutput()">
</div>
</div>
<div class="rules-box">
<strong>当前分支说明与规则:</strong>
<ul id="branch-rules-list">
<li>从 <code>main</code> 拉取并创建 <code>develop</code> 分支作为团队日常开发基线。</li>
<li>执行命令:<code>git checkout -b develop main</code></li>
</ul>
</div>
</div>
<div class="output-card">
<div class="output-header">
<span class="output-title">生成的 Git 创建/切换分支命令</span>
<button class="copy-btn" onclick="copyText('branch-result', this)">复制 Git 命令</button>
</div>
<pre id="branch-result"></pre>
</div>
</div>
</div>
<script>
// 1. Commit Types Definition
const commitTypes = [
{ type: 'feat', label: '✨ feat', desc: '新功能 / 新特性' },
{ type: 'fix', label: '🐛 fix', desc: '修复 bug' },
{ type: 'docs', label: '📚 docs', desc: '文档更新' },
{ type: 'style', label: '💄 style', desc: '代码格式(不影响功能)' },
{ type: 'refactor', label: '💎 refactor', desc: '重构(既不修复bug也不加功能)' },
{ type: 'perf', label: '⚡ perf', desc: '性能提升' },
{ type: 'test', label: '🚨 test', desc: '添加或修改测试' },
{ type: 'chore', label: '🔧 chore', desc: '构建/工具等杂项变动' },
{ type: 'build', label: '📦 build', desc: '影响构建系统或依赖的变动' },
{ type: 'security', label: '🛡️ security', desc: '安全性修复或增强' }
];
let currentCommitType = 'feat';
let currentBranchType = 'develop';
// Initialize Page
window.onload = () => {
renderCommitTypes();
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
document.getElementById('branch-date').value = `${yyyy}${mm}${dd}`;
updateCommitOutput();
updateInitOutput();
updateBranchOutput();
};
// Tab Switching
function switchTab(tabId) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tabId).classList.add('active');
}
// Render Commit Type Cards
function renderCommitTypes() {
const grid = document.getElementById('commit-type-grid');
grid.innerHTML = commitTypes.map(t => `
<div class="type-card ${t.type === currentCommitType ? 'selected' : ''}" onclick="selectCommitType('${t.type}', this)">
<div class="type-header">
<span class="type-tag">${t.label}</span>
</div>
<div class="type-desc">${t.desc}</div>
</div>
`).join('');
}
function selectCommitType(type, el) {
currentCommitType = type;
document.querySelectorAll('#commit-type-grid .type-card').forEach(c => c.classList.remove('selected'));
el.classList.add('selected');
updateCommitOutput();
}
// Update Commit Output
function updateCommitOutput() {
const scope = document.getElementById('commit-scope').value.trim();
const subject = document.getElementById('commit-subject').value.trim();
const body = document.getElementById('commit-body').value.trim();
const footer = document.getElementById('commit-footer').value.trim();
const wip = document.getElementById('commit-wip').value.trim();
const fixup = document.getElementById('commit-fixup').value.trim();
const isBreaking = document.getElementById('commit-breaking').checked;
let header = currentCommitType;
if (scope) {
header += `(${scope})`;
}
header += `: ${subject || '<描述>'}`;
let result = header;
if (body) {
result += `\n\n${body}`;
}
let footers = [];
if (isBreaking) {
footers.push(`BREAKING CHANGE: 包含破坏性变更,可能不向下兼容。`);
}
if (wip) {
footers.push(`WIP: ${wip}`);
}
if (fixup) {
footers.push(`FIXUP: ${fixup}`);
}
if (footer) {
footers.push(footer);
}
if (footers.length > 0) {
result += `\n\n${footers.join('\n')}`;
}
document.getElementById('commit-result').textContent = result;
}
// Update Init Output
function updateInitOutput() {
const projName = document.getElementById('init-proj-name').value.trim() || '[项目名称]';
const projDesc = document.getElementById('init-proj-desc').value.trim() || '[项目描述]';
const env = document.getElementById('init-env').value.trim() || '[开发环境]';
const items = document.getElementById('init-items').value.trim().split('\n').filter(i => i).map(i => `- ${i}`).join('\n');
const nexts = document.getElementById('init-next').value.trim().split('\n').filter(i => i).map(i => `- ${i}`).join('\n');
const result = `init: 初始化项目\n\n${items}\n\n项目说明:\n- 项目名称:${projName}\n- 项目描述:${projDesc}\n- 开发环境:${env}\n\n下一步:\n${nexts}\n\n感谢您的贡献!`;
document.getElementById('init-result').textContent = result;
}
// Branch Type Selection
function selectBranchType(type, btn) {
currentBranchType = type;
document.querySelectorAll('.template-selector .tpl-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const personalFields = document.getElementById('personal-fields');
const versionGroup = document.getElementById('version-group');
const dateGroup = document.getElementById('date-group');
const rulesList = document.getElementById('branch-rules-list');
if (type === 'develop') {
versionGroup.style.display = 'none';
dateGroup.style.display = 'none';
personalFields.style.display = 'none';
rulesList.innerHTML = `
<li>从 <code>main</code> 拉取并创建 <code>develop</code> 分支作为团队日常开发基线。</li>
<li>执行命令:<code>git checkout -b develop main</code></li>
`;
} else {
versionGroup.style.display = 'block';
dateGroup.style.display = 'block';
}
if (type === 'feature') {
personalFields.style.display = 'none';
rulesList.innerHTML = `
<li>开发前从 <code>develop</code> 分支拉取,保持起始点一致。用于自动化部署测试环境。</li>
<li>规范格式:<code>feature-{项目名}-{版本}-SNAPSHOT-{日期}</code></li>
`;
} else if (type === 'personal') {
personalFields.style.display = 'flex';
rulesList.innerHTML = `
<li>开发中组员基于<b>迭代功能分支</b>拉取个人临时分支,提测时合并回功能分支。</li>
<li>规范格式:<code>feature-{项目名}-{版本}-SNAPSHOT-{日期}-{姓名}</code></li>
`;
} else if (type === 'staging') {
personalFields.style.display = 'none';
rulesList.innerHTML = `
<li>开发环境测试结束后,从功能分支拉出去掉 <code>SNAPSHOT</code> 标识的预生产分支。</li>
<li>上线验证通过后,将预生产分支分别合并回 <code>main</code> 和 <code>develop</code>。</li>
<li>规范格式:<code>feature-{项目名}-{版本}-{日期}</code></li>
`;
} else if (type === 'hotfix') {
personalFields.style.display = 'none';
rulesList.innerHTML = `
<li>线上紧急修复,从 <code>main</code> 拉取,修复验证后同时合并回 <code>main</code> 与 <code>develop</code>。</li>
<li>规范格式:<code>hotfix-{项目名}-{版本}-{日期}</code></li>
`;
}
updateBranchOutput();
}
// Update Branch Output
function updateBranchOutput() {
const proj = document.getElementById('branch-proj').value.trim() || 'git_helper';
const version = document.getElementById('branch-version').value.trim() || 'v2.1.1';
const date = document.getElementById('branch-date').value.trim() || '20240703';
const author = document.getElementById('branch-author').value.trim() || 'zhangsan';
const seq = document.getElementById('branch-seq').value.trim();
let command = '';
if (currentBranchType === 'develop') {
command = `git checkout -b develop main`;
} else if (currentBranchType === 'feature') {
const branchName = `feature-${proj}-${version}-SNAPSHOT-${date}`;
command = `git checkout -b ${branchName} develop`;
} else if (currentBranchType === 'personal') {
const seqStr = seq ? `-${seq}` : '';
const baseBranch = `feature-${proj}-${version}-SNAPSHOT-${date}`;
const branchName = `${baseBranch}-${author}${seqStr}`;
command = `git checkout -b ${branchName} ${baseBranch}`;
} else if (currentBranchType === 'staging') {
const baseBranch = `feature-${proj}-${version}-SNAPSHOT-${date}`;
const branchName = `feature-${proj}-${version}-${date}`;
command = `git checkout -b ${branchName} ${baseBranch}`;
} else if (currentBranchType === 'hotfix') {
const branchName = `hotfix-${proj}-${version}-${date}`;
command = `git checkout -b ${branchName} main`;
}
document.getElementById('branch-result').textContent = command;
}
// Copy helper
function copyText(elementId, btn) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = btn.textContent;
btn.textContent = '已复制!';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('copied');
}, 2000);
});
}
</script>
</body>
</html>