aa7099ba94
将单一 CI/CD 流水线拆成三条互不影响的发布流水线,各有 tag 前缀、独立版本
序列与独立 CHANGELOG:
- client(client-v*):Flutter 全平台 + version.yaml 自更新清单
- site(site-v*):web/ Eleventy 营销站,不含 web 版 app
- server(server-v*):backend Go 服务 + 共享基建 nginx/systemd
新增 3 个 workflow(deploy-client/site/server.yml)替换 deploy.yml;CI 脚本
按 part 拆分为 compile-/release-/deploy-{client,site,server}.sh,抽出公共函数
lib-forgejo.sh;compile-{macos,android,ios,windows}.sh 改去 client-v 前缀;
manual.yml 按前缀路由回滚。
跨流水线解耦:version.yaml 归 client,后端每请求实时读取(不重启、不触发
server 流水线);官网下载页的版本徽章/下载链接/更新日志时间线运行时经
/api/v1/public/release 动态拉取(API 不可达回退构建时静态内容)。为此一次性
扩展后端 changelog 字段(version.go/public.go)与 download.njk 动态渲染。
CHANGELOG.md 重命名为 CHANGELOG-client.md,新增 CHANGELOG-site/server.md;
重写 /release 命令为 /release <part> [version];同步更新 CLAUDE.md 与部署文档。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
433 lines
19 KiB
HTML
433 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<meta name="color-scheme" content="light" />
|
||
<title>注册新门店 · 岩美酒库管理系统</title>
|
||
<meta name="description" content="填写门店信息,创建岩美酒库管理账户,注册完成后获取门店编码用于登录客户端。" />
|
||
<link rel="stylesheet" href="/assets/color.css" />
|
||
<link rel="stylesheet" href="/assets/style.css" />
|
||
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<script src="https://unpkg.com/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
||
<style>.reg-page { padding: var(--space-16) 0 var(--space-24); }
|
||
.reg-wrap { max-width: 640px; margin: 0 auto; padding: 0 var(--space-4); }
|
||
.reg-hero { text-align: center; margin-bottom: var(--space-8); }
|
||
.reg-hero h1 { font-size: var(--text-3xl); font-weight: 700; margin-bottom: var(--space-2); }
|
||
.reg-hero p { color: var(--neutral-500); font-size: var(--text-md); max-width: 480px; margin: 0 auto; }
|
||
.reg-card { background: var(--neutral-0); border: 1px solid var(--neutral-200); border-radius: var(--radius-xl); padding: var(--space-8); }
|
||
.reg-fieldset { border: none; padding: 0; margin: 0 0 var(--space-8); }
|
||
.reg-legend { font-size: var(--text-sm); font-weight: 600; color: var(--neutral-500); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: var(--space-5); padding-bottom: var(--space-3); border-bottom: 1px solid var(--neutral-100); display: block; width: 100%; }
|
||
.form-row { margin-bottom: var(--space-5); }
|
||
.form-label { display: block; font-size: var(--text-sm); font-weight: 500; color: var(--neutral-700); margin-bottom: var(--space-1-5); }
|
||
.form-label .req { color: var(--error-500); margin-left: 2px; }
|
||
.form-input, .form-textarea { width: 100%; padding: var(--space-2-5) var(--space-3); border: 1.5px solid var(--neutral-300); border-radius: var(--radius-md); font-size: var(--text-md); font-family: inherit; background: var(--neutral-0); color: var(--neutral-900); transition: border-color .15s, box-shadow .15s; box-sizing: border-box; }
|
||
.form-input:focus, .form-textarea:focus { outline: none; border-color: var(--brand-500); box-shadow: 0 0 0 3px var(--brand-100); }
|
||
.form-input.error, .form-textarea.error { border-color: var(--error-500); box-shadow: 0 0 0 3px rgba(239,68,68,.12); }
|
||
.form-textarea { resize: vertical; min-height: 80px; }
|
||
.form-helper { font-size: var(--text-xs); color: var(--neutral-400); margin-top: var(--space-1); }
|
||
.field-error { font-size: var(--text-xs); color: var(--error-600); margin-top: 4px; display: none; }
|
||
.field-error.visible { display: block; }
|
||
.reg-submit { width: 100%; padding: var(--space-3) var(--space-4); font-size: var(--text-md); font-weight: 600; margin-top: var(--space-2); }
|
||
.reg-login-hint { text-align: center; margin-top: var(--space-4); font-size: var(--text-sm); color: var(--neutral-500); }
|
||
.reg-login-hint a { color: var(--brand-600); }
|
||
.reg-error { display: none; background: var(--error-50); border: 1px solid var(--error-200); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-5); font-size: var(--text-sm); color: var(--error-700); }
|
||
.reg-error.visible { display: block; }
|
||
.success-panel { display: none; text-align: center; }
|
||
.success-panel.visible { display: block; }
|
||
.success-icon { color: var(--success-500); margin-bottom: var(--space-4); }
|
||
.success-panel h2 { font-size: var(--text-2xl); font-weight: 700; margin-bottom: var(--space-2); }
|
||
.success-panel > p { color: var(--neutral-500); margin-bottom: var(--space-6); }
|
||
.shop-code-box { background: var(--brand-50); border: 1px solid var(--brand-200); border-radius: var(--radius-lg); padding: var(--space-5) var(--space-6); margin-bottom: var(--space-6); display: flex; align-items: center; justify-content: center; gap: var(--space-3); }
|
||
.shop-code-text { font-size: var(--text-3xl); font-weight: 700; font-family: 'JetBrains Mono', monospace; color: var(--brand-700); letter-spacing: 0.05em; }
|
||
.copy-btn { display: inline-flex; align-items: center; gap: var(--space-1-5); padding: var(--space-2) var(--space-3); background: var(--brand-600); color: #fff; border: none; border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: 500; cursor: pointer; transition: background .15s; }
|
||
.copy-btn:hover { background: var(--brand-700); }
|
||
.login-hint-card { background: var(--neutral-50); border: 1px solid var(--neutral-200); border-radius: var(--radius-md); padding: var(--space-4) var(--space-5); margin-bottom: var(--space-6); text-align: left; }
|
||
.login-hint-card p { font-size: var(--text-sm); font-weight: 600; color: var(--neutral-600); margin-bottom: var(--space-3); }
|
||
.login-hint-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
|
||
.login-hint-table td { padding: var(--space-1-5) var(--space-2); }
|
||
.login-hint-table td:first-child { color: var(--neutral-500); width: 80px; }
|
||
.login-hint-table td:last-child { font-weight: 500; color: var(--neutral-800); font-family: 'JetBrains Mono', monospace; }
|
||
.success-note { font-size: var(--text-xs); color: var(--neutral-400); margin-bottom: var(--space-6); }
|
||
.btn-loading { opacity: .7; cursor: not-allowed; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="topnav">
|
||
<div class="topnav-inner">
|
||
<a href="/" class="topnav-brand">
|
||
<img src="/assets/logo-full.svg" alt="岩美" />
|
||
</a>
|
||
<div class="topnav-links">
|
||
<a href="/#modules">产品</a>
|
||
<a href="/features/inventory.html">库存</a>
|
||
<a href="/features/approval.html">审核流</a>
|
||
<a href="/#pricing">价格</a>
|
||
<a href="/download/">下载</a>
|
||
<a href="/docs/">文档</a>
|
||
<a href="/#faq">支持</a>
|
||
</div>
|
||
<div class="topnav-cta" id="nav-cta">
|
||
<a href="/register/" class="btn btn-primary btn-sm">注册门店</a>
|
||
</div>
|
||
<button class="nav-hamburger" id="nav-hamburger" aria-label="菜单" aria-expanded="false">
|
||
<span></span><span></span><span></span>
|
||
</button>
|
||
</div>
|
||
<div class="nav-mobile-menu" id="nav-mobile-menu">
|
||
<a href="/#modules">产品</a>
|
||
<a href="/features/inventory.html">库存管理</a>
|
||
<a href="/features/approval.html">审核流</a>
|
||
<a href="/#pricing">价格</a>
|
||
<a href="/download/">下载</a>
|
||
<a href="/docs/">文档</a>
|
||
<a href="/#faq">支持</a>
|
||
<div class="nav-mobile-cta" id="nav-mobile-cta">
|
||
<a href="/register/" class="btn btn-primary">注册门店</a>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
|
||
|
||
<section class="reg-page">
|
||
<div class="reg-wrap">
|
||
|
||
<div class="reg-hero">
|
||
<h1>注册新门店</h1>
|
||
<p>填写以下信息,创建您的专属酒库管理账户。注册完成后系统自动分配门店编码,用于登录客户端。</p>
|
||
</div>
|
||
|
||
<!-- 注册表单 -->
|
||
<div class="reg-card" id="reg-form-card">
|
||
<div class="reg-error" id="reg-error"></div>
|
||
|
||
<!-- 门店信息 -->
|
||
<fieldset class="reg-fieldset">
|
||
<legend class="reg-legend">门店信息</legend>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="shop_name">店铺名称<span class="req">*</span></label>
|
||
<input class="form-input" type="text" id="shop_name" placeholder="例:强朋友名酒行" autocomplete="organization" />
|
||
<div class="field-error" id="err-shop_name"></div>
|
||
<div class="form-helper">您的酒行正式名称,将显示在所有单据和商品标签上</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="address">门店地址<span class="req">*</span></label>
|
||
<input class="form-input" type="text" id="address" placeholder="例:贵州省贵阳市云岩区中华北路 88 号" autocomplete="street-address" />
|
||
<div class="field-error" id="err-address"></div>
|
||
<div class="form-helper">实际经营地址,打印标签和单据时展示</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="manager_name">负责人姓名<span class="req">*</span></label>
|
||
<input class="form-input" type="text" id="manager_name" placeholder="例:张三" autocomplete="name" />
|
||
<div class="field-error" id="err-manager_name"></div>
|
||
<div class="form-helper">主要负责人真实姓名,也是首个管理员账号的姓名</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="phone">联系电话<span class="req">*</span></label>
|
||
<input class="form-input" type="tel" id="phone" placeholder="例:0851-12345678" autocomplete="tel" />
|
||
<div class="field-error" id="err-phone"></div>
|
||
<div class="form-helper">对外联系电话,打印商品标签时会展示</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="description">店铺简介</label>
|
||
<textarea class="form-textarea" id="description" placeholder="简短介绍您的门店,例:专注高端白酒批发零售,主营茅台系列" maxlength="200"></textarea>
|
||
<div class="form-helper">选填,最多 200 字;客户扫商品码时可见</div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<!-- 管理员账号 -->
|
||
<fieldset class="reg-fieldset" style="margin-bottom:0">
|
||
<legend class="reg-legend">管理员账号</legend>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="username">用户名<span class="req">*</span></label>
|
||
<input class="form-input" type="text" id="username" placeholder="例:admin" autocomplete="username" />
|
||
<div class="field-error" id="err-username"></div>
|
||
<div class="form-helper">登录账号(3–30 位,字母/数字/下划线),创建后不可修改</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="password">密码<span class="req">*</span></label>
|
||
<input class="form-input" type="password" id="password" placeholder="至少 6 位" autocomplete="new-password" />
|
||
<div class="field-error" id="err-password"></div>
|
||
<div class="form-helper">至少 6 位,建议字母数字组合</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label class="form-label" for="password_confirm">确认密码<span class="req">*</span></label>
|
||
<input class="form-input" type="password" id="password_confirm" placeholder="再次输入密码" autocomplete="new-password" />
|
||
<div class="field-error" id="err-password_confirm"></div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<button class="btn btn-primary reg-submit" id="reg-btn">
|
||
<i data-lucide="store" class="icon"></i>
|
||
立即注册
|
||
</button>
|
||
<p class="reg-login-hint">已有账号?<a href="/app/">直接登录</a></p>
|
||
</div>
|
||
|
||
<!-- 注册成功面板 -->
|
||
<div class="reg-card success-panel" id="success-panel">
|
||
<div class="success-icon">
|
||
<i data-lucide="check-circle" style="width:56px;height:56px;"></i>
|
||
</div>
|
||
<h2>注册成功!</h2>
|
||
<p>您的门店编码已生成,请妥善保存:</p>
|
||
|
||
<div class="shop-code-box">
|
||
<span class="shop-code-text" id="result-shop-code">—</span>
|
||
<button class="copy-btn" id="copy-code-btn">
|
||
<i data-lucide="copy" class="icon"></i>复制
|
||
</button>
|
||
</div>
|
||
|
||
<div class="login-hint-card">
|
||
<p>登录客户端时请输入:</p>
|
||
<table class="login-hint-table">
|
||
<tr>
|
||
<td>门店编码</td>
|
||
<td id="hint-code">—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>用户名</td>
|
||
<td id="hint-username">—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>密码</td>
|
||
<td>您设置的密码</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="success-note">如忘记门店编码,登录后可在「系统设置」中查看。</p>
|
||
<a href="/app/" class="btn btn-primary" style="display:inline-flex;align-items:center;gap:8px;">
|
||
<i data-lucide="log-in" class="icon"></i>前往登录
|
||
</a>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
(function() {
|
||
var API_URL = "https://jiu.51yanmei.com/api/v1/public/register";
|
||
|
||
function $(id) { return document.getElementById(id); }
|
||
|
||
function getVal(id) { return $(id).value.trim(); }
|
||
|
||
function setFieldError(id, msg) {
|
||
var input = $(id);
|
||
var errEl = $('err-' + id);
|
||
if (msg) {
|
||
input.classList.add('error');
|
||
if (errEl) { errEl.textContent = msg; errEl.classList.add('visible'); }
|
||
} else {
|
||
input.classList.remove('error');
|
||
if (errEl) { errEl.textContent = ''; errEl.classList.remove('visible'); }
|
||
}
|
||
}
|
||
|
||
function clearAllErrors() {
|
||
['shop_name','address','manager_name','phone','username','password','password_confirm'].forEach(function(id) {
|
||
setFieldError(id, '');
|
||
});
|
||
}
|
||
|
||
// 用户修改字段后清除该字段的错误提示
|
||
['shop_name','address','manager_name','phone','username','password','password_confirm'].forEach(function(id) {
|
||
var el = $(id);
|
||
if (el) el.addEventListener('input', function() { setFieldError(id, ''); });
|
||
});
|
||
|
||
function validate() {
|
||
clearAllErrors();
|
||
var ok = true;
|
||
var firstErr = null;
|
||
|
||
function require(id, msg) {
|
||
if (!getVal(id)) { setFieldError(id, msg); if (!firstErr) firstErr = id; ok = false; }
|
||
}
|
||
|
||
require('shop_name', '请填写店铺名称');
|
||
require('address', '请填写门店地址');
|
||
require('manager_name', '请填写负责人姓名');
|
||
require('phone', '请填写联系电话');
|
||
|
||
var username = getVal('username');
|
||
if (!username) {
|
||
setFieldError('username', '请填写用户名'); if (!firstErr) firstErr = 'username'; ok = false;
|
||
} else if (username.length < 3 || username.length > 30) {
|
||
setFieldError('username', '用户名长度须在 3–30 位之间'); if (!firstErr) firstErr = 'username'; ok = false;
|
||
} else if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
||
setFieldError('username', '用户名只能包含字母、数字和下划线'); if (!firstErr) firstErr = 'username'; ok = false;
|
||
}
|
||
|
||
var password = $('password').value;
|
||
if (!password) {
|
||
setFieldError('password', '请填写密码'); if (!firstErr) firstErr = 'password'; ok = false;
|
||
} else if (password.length < 6) {
|
||
setFieldError('password', '密码至少 6 位'); if (!firstErr) firstErr = 'password'; ok = false;
|
||
}
|
||
|
||
var confirm = $('password_confirm').value;
|
||
if (!confirm) {
|
||
setFieldError('password_confirm', '请再次输入密码'); if (!firstErr) firstErr = 'password_confirm'; ok = false;
|
||
} else if (password && password !== confirm) {
|
||
setFieldError('password_confirm', '两次输入的密码不一致'); if (!firstErr) firstErr = 'password_confirm'; ok = false;
|
||
}
|
||
|
||
if (firstErr) { $(firstErr).focus(); $(firstErr).scrollIntoView({ behavior: 'smooth', block: 'center' }); }
|
||
return ok;
|
||
}
|
||
|
||
function showTopError(msg) {
|
||
var el = $('reg-error');
|
||
el.textContent = msg;
|
||
el.classList.add('visible');
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
|
||
$('reg-btn').addEventListener('click', function() {
|
||
$('reg-error').classList.remove('visible');
|
||
if (!validate()) return;
|
||
|
||
var btn = $('reg-btn');
|
||
btn.disabled = true;
|
||
btn.classList.add('btn-loading');
|
||
btn.textContent = '注册中…';
|
||
|
||
var payload = {
|
||
shop_name: getVal('shop_name'),
|
||
address: getVal('address'),
|
||
manager_name: getVal('manager_name'),
|
||
phone: getVal('phone'),
|
||
description: getVal('description'),
|
||
username: getVal('username'),
|
||
password: $('password').value
|
||
};
|
||
|
||
fetch(API_URL, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(function(res) { return res.json().then(function(data) { return { ok: res.ok, data: data }; }); })
|
||
.then(function(r) {
|
||
if (!r.ok) {
|
||
var msg = (r.data && r.data.error) ? r.data.error : '注册失败,请稍后重试';
|
||
if (msg.indexOf('Duplicate') !== -1 || msg.indexOf('duplicate') !== -1) {
|
||
msg = '该用户名在此门店已存在,请换一个用户名';
|
||
}
|
||
showTopError(msg);
|
||
btn.disabled = false;
|
||
btn.classList.remove('btn-loading');
|
||
btn.innerHTML = '<i data-lucide="store" class="icon"></i>立即注册';
|
||
if (window.lucide) lucide.createIcons();
|
||
return;
|
||
}
|
||
var d = r.data.data;
|
||
$('result-shop-code').textContent = d.shop_code;
|
||
$('hint-code').textContent = d.shop_code;
|
||
$('hint-username').textContent = d.username;
|
||
$('reg-form-card').style.display = 'none';
|
||
var panel = $('success-panel');
|
||
panel.classList.add('visible');
|
||
if (window.lucide) lucide.createIcons();
|
||
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
})
|
||
.catch(function() {
|
||
showTopError('网络异常,请检查网络连接后重试');
|
||
btn.disabled = false;
|
||
btn.classList.remove('btn-loading');
|
||
btn.innerHTML = '<i data-lucide="store" class="icon"></i>立即注册';
|
||
if (window.lucide) lucide.createIcons();
|
||
});
|
||
});
|
||
|
||
$('copy-code-btn').addEventListener('click', function() {
|
||
var code = $('result-shop-code').textContent;
|
||
navigator.clipboard.writeText(code).then(function() {
|
||
var btn = $('copy-code-btn');
|
||
btn.innerHTML = '<i data-lucide="check" class="icon"></i>已复制';
|
||
if (window.lucide) lucide.createIcons();
|
||
setTimeout(function() {
|
||
btn.innerHTML = '<i data-lucide="copy" class="icon"></i>复制';
|
||
if (window.lucide) lucide.createIcons();
|
||
}, 2000);
|
||
});
|
||
});
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
if (window.lucide) lucide.createIcons();
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
|
||
<footer>
|
||
<div class="container">
|
||
<div class="footer-grid">
|
||
<div class="footer-brand">
|
||
<img src="/assets/logo-full.svg" alt="岩美" />
|
||
<p>为酒行与酒店设计的库存、审核、财务一体化管理平台。</p>
|
||
<div class="footer-contact">
|
||
|
||
<div><a href="mailto:yammy2023@163.com">yammy2023@163.com</a></div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5>产品</h5>
|
||
<ul>
|
||
<li><a href="/#modules">核心模块</a></li>
|
||
<li><a href="/#platforms">多端覆盖</a></li>
|
||
<li><a href="/#reports">数据洞察</a></li>
|
||
<li><a href="/#pricing">价格方案</a></li>
|
||
<li><a href="/download/">版本更新</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5>资源</h5>
|
||
<ul>
|
||
<li><a href="/docs/">使用手册</a></li>
|
||
<li><a href="/download/">下载客户端</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5>客户支持</h5>
|
||
<ul>
|
||
<li><a href="/#faq">常见问题</a></li>
|
||
<li><a href="mailto:yammy2023@163.com">联系我们</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="footer-col">
|
||
<h5>公司</h5>
|
||
<ul>
|
||
<li><a href="/register/">注册门店</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="footer-bottom">
|
||
<div>© 2026 岩美科技 · 保留所有权利</div>
|
||
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
|
||
<script src="/assets/nav.js"></script>
|
||
</body>
|
||
</html>
|