注册支持用户名/手机号/邮箱 + 镜像站部署脚本 + AI 助手 Tool Calling 重构

- Prisma User 模型新增 username 字段(唯一索引)
- 注册先查重复再创建,返回友好中文提示(非 500)
- 登录支持用户名/手机号/邮箱三种方式
- 前端注册表单增加用户名输入框,预校验 2-20 位格式
- 新增 scripts/deploy.sh:一键构建并部署主站+镜像站+重启后端+重载 Nginx
- 镜像站 www.yuzhiran.com.cn Nginx 配置与主站同步
- AI 助手架构升级:用户端/管理后台均采用完整 Tool Calling 架构
- 新增 UserAiAssistantService(18 工具)+ AiAssistantController
- admin 助手新增 search + mark-all-notifications-read 工具
- 修复注册 500 错误:catch Prisma P2002 → BadRequestException
- Baidu Analytics Script 注入 root layout
This commit is contained in:
yuzhiran-dev
2026-06-01 23:14:42 +08:00
parent 6f3fe50ee0
commit 538de50bb1
329 changed files with 8777 additions and 868 deletions
+58 -6
View File
@@ -4,12 +4,43 @@ import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useT } from '@/i18n';
import { API_BASE } from '@/lib/config';
import Image from 'next/image';
interface BeianInfo {
icp: string
gongan: string
gonganLink: string
showGongan: boolean
}
function getBeianInfo(): BeianInfo {
if (typeof window === 'undefined') {
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
}
const hostname = window.location.hostname
if (hostname === 'yuzhiran.com' || hostname === 'www.yuzhiran.com') {
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
}
if (hostname === 'yuzhiran.com.cn' || hostname === 'www.yuzhiran.com.cn') {
return { icp: '京ICP备2026007249号-2', gongan: '京公网安备11011502039622号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039622', showGongan: true }
}
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
}
const qrCodes = [
{ src: '/images/yzr/yuzhiran.jpg', alt: '微信公众号', label: '微信公众号' },
{ src: '/images/yzr/yuzhiran-tech.jpg', alt: '微信服务号', label: '微信服务号' },
{ src: '/images/yzr/yuzhiran-yhl.jpg', alt: '小程序', label: '小程序' },
{ src: '/images/yzr/kefu.png', alt: '微信客服', label: '微信客服' },
]
export function Footer() {
const t = useT();
const [config, setConfig] = useState<Record<string, string>>({});
const [beian, setBeian] = useState<BeianInfo>({ icp: '', gongan: '', gonganLink: '', showGongan: false });
useEffect(() => {
setBeian(getBeianInfo());
fetch(`${API_BASE}/public/config`)
.then(r => r.json()).then(data => setConfig(data || {}))
.catch(() => {});
@@ -18,10 +49,25 @@ export function Footer() {
return (
<footer className="border-t border-border bg-muted/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
<div className="col-span-2 md:col-span-1">
<div className="grid grid-cols-2 md:grid-cols-5 gap-8">
<div className="col-span-2 md:col-span-2">
<h3 className="text-lg font-bold bg-gradient-to-r from-brand-600 to-brand-400 bg-clip-text text-transparent mb-4"> AI</h3>
<p className="text-sm text-muted-foreground">{t.footer.tagline}</p>
<p className="text-sm text-muted-foreground mb-4">{t.footer.tagline}</p>
<div className="flex flex-wrap gap-3">
{qrCodes.map((qr) => (
<div key={qr.alt} className="text-center group">
<Image
src={qr.src}
alt={qr.alt}
width={72}
height={72}
className="rounded-lg bg-white transition-transform duration-200 group-hover:scale-150 group-hover:z-10 group-hover:shadow-xl cursor-pointer"
unoptimized
/>
<p className="text-xs text-muted-foreground mt-1">{qr.label}</p>
</div>
))}
</div>
</div>
<div>
<h4 className="text-sm font-semibold mb-3">{t.footer.explore}</h4>
@@ -53,11 +99,17 @@ export function Footer() {
<div className="mt-10 pt-8 border-t border-border">
<div className="flex flex-col md:flex-row items-center justify-between gap-2 text-xs text-muted-foreground">
<p>{t.footer.copyright.replace('{year}', String(new Date().getFullYear()))}</p>
<p>
<div className="flex items-center gap-3">
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors">
{config.icp_number ? `ICP 备案号:${config.icp_number}` : 'ICP 备案号:京ICP备XXXXXXXX号'}
{beian.icp}
</a>
</p>
{beian.showGongan && (
<a href={beian.gonganLink} target="_blank" rel="noreferrer" className="hover:text-foreground transition-colors inline-flex items-center gap-1">
<Image src="/images/beian/gongan-beian.png" alt="公安备案" width={16} height={16} unoptimized />
{beian.gongan}
</a>
)}
</div>
</div>
</div>
</div>