feat: 新增用户自助注册功能
后端新增 POST /api/v1/public/register 接口,支持门店自助注册并自动生成门店编码(S000001 格式);shops 表加 description 字段。营销站新增 /register/ 注册页,含门店信息和管理员账号表单,注册成功后展示门店编码和登录提示。Flutter 登录页底部加「前往官网注册」跳转链接。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,23 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// Register POST /api/v1/public/register
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req service.RegisterInput
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.svc.Register(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
// Refresh POST /api/v1/auth/refresh
|
||||
func (h *AuthHandler) Refresh(c *gin.Context) {
|
||||
var req struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ type Shop struct {
|
||||
Base
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Code string `gorm:"size:50;uniqueIndex" json:"code"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
Address string `gorm:"size:255" json:"address"`
|
||||
Phone string `gorm:"size:30" json:"phone"`
|
||||
BusinessHours string `gorm:"size:100" json:"business_hours"`
|
||||
|
||||
@@ -58,6 +58,7 @@ func Setup(r *gin.Engine, db *gorm.DB) {
|
||||
public.GET("/products/:public_id", publicH.GetProduct)
|
||||
public.GET("/release", publicH.GetRelease)
|
||||
public.POST("/errors", errorReportH.Submit)
|
||||
public.POST("/register", authH.Register)
|
||||
}
|
||||
|
||||
// 需要 JWT 的路由(ReadOnly 中间件:只读用户不可执行写操作)
|
||||
|
||||
@@ -2,9 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -61,6 +63,76 @@ func (s *AuthService) Login(shopCode, username, password string) (*TokenPair, *m
|
||||
return pair, &user, nil
|
||||
}
|
||||
|
||||
// RegisterInput 注册新门店所需参数
|
||||
type RegisterInput struct {
|
||||
ShopName string `json:"shop_name" binding:"required"`
|
||||
Address string `json:"address" binding:"required"`
|
||||
ManagerName string `json:"manager_name" binding:"required"`
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
// RegisterResult 注册成功后返回的数据
|
||||
type RegisterResult struct {
|
||||
ShopCode string `json:"shop_code"`
|
||||
ShopName string `json:"shop_name"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// Register 自助注册新门店(公开接口,无需认证)
|
||||
func (s *AuthService) Register(in RegisterInput) (*RegisterResult, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result RegisterResult
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 先用 UUID 占位创建门店,得到真实 ID
|
||||
shop := model.Shop{
|
||||
Name: in.ShopName,
|
||||
Code: uuid.New().String(),
|
||||
Address: in.Address,
|
||||
Phone: in.Phone,
|
||||
ManagerName: in.ManagerName,
|
||||
Description: in.Description,
|
||||
}
|
||||
if err := tx.Create(&shop).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 用自增 ID 生成正式门店编码
|
||||
shop.Code = fmt.Sprintf("S%06d", shop.ID)
|
||||
if err := tx.Model(&shop).Update("code", shop.Code).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建管理员用户
|
||||
user := model.User{
|
||||
ShopID: shop.ID,
|
||||
Username: in.Username,
|
||||
PasswordHash: string(hash),
|
||||
RealName: in.ManagerName,
|
||||
Phone: in.Phone,
|
||||
Role: "admin",
|
||||
IsActive: true,
|
||||
}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result = RegisterResult{
|
||||
ShopCode: shop.Code,
|
||||
ShopName: shop.Name,
|
||||
Username: user.Username,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// HashPassword 生成 bcrypt 哈希
|
||||
func HashPassword(plain string) (string, error) {
|
||||
b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost)
|
||||
|
||||
@@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `shops` (
|
||||
`address` VARCHAR(255) DEFAULT NULL,
|
||||
`phone` VARCHAR(30) DEFAULT NULL,
|
||||
`manager_name` VARCHAR(50) DEFAULT NULL COMMENT '负责人',
|
||||
`description` TEXT DEFAULT NULL COMMENT '店铺简介',
|
||||
`logo_url` VARCHAR(500) DEFAULT '' COMMENT '门店 logo URL',
|
||||
`business_license` VARCHAR(500) DEFAULT NULL COMMENT '营业执照照片URL',
|
||||
`shop_photos` JSON DEFAULT NULL COMMENT '门店照片URL数组',
|
||||
|
||||
@@ -53,6 +53,7 @@ func SetupTestDB() *gorm.DB {
|
||||
deleted_at DATETIME,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT UNIQUE,
|
||||
description TEXT,
|
||||
address TEXT,
|
||||
phone TEXT,
|
||||
manager_name TEXT,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../core/auth/auth_state.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/storage/login_history.dart';
|
||||
@@ -484,6 +485,20 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
letterSpacing: 2)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextButton(
|
||||
onPressed: () => launchUrl(
|
||||
Uri.parse('https://jiu.51yanmei.com/register/'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: const Text(
|
||||
'还没有门店账号?前往官网注册',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
<a href="/docs/">文档</a>
|
||||
<a href="/#faq">支持</a>
|
||||
</div>
|
||||
<div class="topnav-cta" id="nav-cta"></div>
|
||||
<div class="topnav-cta" id="nav-cta">
|
||||
<a href="/register/" class="btn btn-primary btn-sm">注册门店</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
layout: base.njk
|
||||
title: 注册新门店
|
||||
description: 填写门店信息,创建岩美酒库管理账户,注册完成后获取门店编码用于登录客户端。
|
||||
pageStyle: |
|
||||
.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: 1px 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-textarea { resize: vertical; min-height: 80px; }
|
||||
.form-helper { font-size: var(--text-xs); color: var(--neutral-400); margin-top: var(--space-1); }
|
||||
.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; }
|
||||
---
|
||||
|
||||
<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="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="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="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="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="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="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>
|
||||
</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="https://jiu.51yanmei.com/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="https://jiu.51yanmei.com/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 showError(msg) {
|
||||
var el = $('reg-error');
|
||||
el.textContent = msg;
|
||||
el.classList.add('visible');
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function clearError() {
|
||||
var el = $('reg-error');
|
||||
el.textContent = '';
|
||||
el.classList.remove('visible');
|
||||
}
|
||||
|
||||
function getVal(id) { return $(id).value.trim(); }
|
||||
|
||||
function validate() {
|
||||
var shopName = getVal('shop_name');
|
||||
var address = getVal('address');
|
||||
var manager = getVal('manager_name');
|
||||
var phone = getVal('phone');
|
||||
var username = getVal('username');
|
||||
var password = getVal('password');
|
||||
var confirm = getVal('password_confirm');
|
||||
|
||||
if (!shopName) { showError('请填写店铺名称'); $('shop_name').focus(); return false; }
|
||||
if (!address) { showError('请填写门店地址'); $('address').focus(); return false; }
|
||||
if (!manager) { showError('请填写负责人姓名'); $('manager_name').focus(); return false; }
|
||||
if (!phone) { showError('请填写联系电话'); $('phone').focus(); return false; }
|
||||
if (!username) { showError('请填写用户名'); $('username').focus(); return false; }
|
||||
if (username.length < 3 || username.length > 30) { showError('用户名长度须在 3–30 位之间'); $('username').focus(); return false; }
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(username)) { showError('用户名只能包含字母、数字和下划线'); $('username').focus(); return false; }
|
||||
if (!password) { showError('请填写密码'); $('password').focus(); return false; }
|
||||
if (password.length < 6) { showError('密码至少 6 位'); $('password').focus(); return false; }
|
||||
if (password !== confirm) { showError('两次输入的密码不一致'); $('password_confirm').focus(); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
$('reg-btn').addEventListener('click', function() {
|
||||
clearError();
|
||||
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 = '该用户名在此门店已存在,请换一个用户名';
|
||||
}
|
||||
showError(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() {
|
||||
showError('网络异常,请检查网络连接后重试');
|
||||
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>
|
||||
Reference in New Issue
Block a user