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。
This commit is contained in:
2026-10-21 17:59:08 +08:00
parent 92c510e959
commit e522a98f6f
48 changed files with 5113 additions and 273 deletions
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

+745
View File
@@ -0,0 +1,745 @@
<!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>