13 Commits

Author SHA1 Message Date
3e23f08eea chore: 不优化首页头像
Some checks failed
Deploy to K3s / deploy (push) Has been cancelled
2026-01-19 14:19:22 +08:00
88a017d6da feat: 优化控制台登陆深色模式表现
All checks were successful
Deploy to K3s / deploy (push) Successful in 1m27s
2026-01-03 15:10:10 +08:00
a718a5487a feat: 博客页支持深色模式 2026-01-03 15:06:17 +08:00
a04227016e chore: 修复首页图像警告 2026-01-03 14:54:45 +08:00
12724bea7f chore: 修复资源卡片图像的警告 2026-01-03 14:53:58 +08:00
720ca56eb3 feat: 添加资源页深色模式支持 2026-01-03 14:51:37 +08:00
8c01303c6c feat: 优化footer深色模式样式 2026-01-03 14:43:01 +08:00
5e2e18fce6 feat: 添加首页、header/footer组件深色模式支持 2026-01-03 14:41:38 +08:00
33053b4a92 secure: 更新nextjs...
All checks were successful
Deploy to K3s / deploy (push) Successful in 3m34s
2026-01-02 23:55:15 +08:00
1c518b44cc chore: 移除自动迁移...
All checks were successful
Deploy to K3s / deploy (push) Successful in 11s
2025-12-27 14:32:01 +08:00
cd80375cc5 chore: ..
Some checks failed
Deploy to K3s / deploy (push) Failing after 5m6s
2025-12-27 14:26:20 +08:00
c23e822cd6 chore: 尝试用job迁移数据库
Some checks failed
Deploy to K3s / deploy (push) Failing after 35s
2025-12-27 14:22:09 +08:00
375d12ab0f lint: 移除前端未使用的import
Some checks failed
Deploy to K3s / deploy (push) Failing after 2m4s
2025-12-27 14:15:05 +08:00
17 changed files with 605 additions and 366 deletions

View File

@@ -54,55 +54,6 @@ jobs:
-t localhost:5000/frontend:${IMAGE_TAG} .
docker push localhost:5000/frontend:${IMAGE_TAG}
- name: Run database migrations with temporary container
run: |
echo "Running database migrations using backend image: localhost:5000/backend:${IMAGE_TAG}"
echo "Waiting for PostgreSQL service to be ready..."
kubectl wait --for=condition=ready pod -l app=postgres --timeout=30s
# 获取密码等敏感信息
DB_PASSWORD=$(kubectl get secret backend-secret -o jsonpath='{.data.DATABASE_PASSWORD}' | base64 -d)
ALIYUN_ACCESS_KEY_ID=$(kubectl get secret backend-secret -o jsonpath='{.data.ALIYUN_ACCESS_KEY_ID}' | base64 -d)
ALIYUN_ACCESS_KEY_SECRET=$(kubectl get secret backend-secret -o jsonpath='{.data.ALIYUN_ACCESS_KEY_SECRET}' | base64 -d)
ALIYUN_OSS_STS_ROLE_ARN=$(kubectl get secret backend-secret -o jsonpath='{.data.ALIYUN_OSS_STS_ROLE_ARN}' | base64 -d)
JWT_SECRET=$(kubectl get secret backend-secret -o jsonpath='{.data.JWT_SECRET}' | base64 -d)
WEBAUTHN_RP_ID=$(kubectl get secret backend-secret -o jsonpath='{.data.WEBAUTHN_RP_ID}' | base64 -d)
WEBAUTHN_ORIGIN=$(kubectl get secret backend-secret -o jsonpath='{.data.WEBAUTHN_ORIGIN}' | base64 -d)
WEBAUTHN_RP_NAME=$(kubectl get secret backend-secret -o jsonpath='{.data.WEBAUTHN_RP_NAME}' | base64 -d)
# 检查是否成功获取了密码
if [ -z "$DB_PASSWORD" ]; then
echo "Error: Could not retrieve DATABASE_PASSWORD from backend-secret."
exit 1
fi
docker run --rm \
-e NODE_ENV=production \
-e DATABASE_HOST=postgres-service \
-e DATABASE_PORT=5432 \
-e DATABASE_NAME=tone_page \
-e DATABASE_USERNAME=tone_page \
-e DATABASE_PASSWORD="$DB_PASSWORD" \
-e JWT_SECRET="$JWT_SECRET" \
-e JWT_EXPIRES_IN=1d \
-e ALIYUN_ACCESS_KEY_ID="$ALIYUN_ACCESS_KEY_ID" \
-e ALIYUN_ACCESS_KEY_SECRET="$ALIYUN_ACCESS_KEY_SECRET" \
-e ALIYUN_OSS_STS_ROLE_ARN="$ALIYUN_OSS_STS_ROLE_ARN" \
-e WEBAUTHN_RP_ID="$WEBAUTHN_RP_ID" \
-e WEBAUTHN_ORIGIN="$WEBAUTHN_ORIGIN" \
-e WEBAUTHN_RP_NAME="$WEBAUTHN_RP_NAME" \
localhost:5000/backend:${IMAGE_TAG} \
pnpm run migration:run
# 检查上一步命令是否成功
if [ $? -ne 0 ]; then
echo "Database migration failed!"
exit 1
fi
echo "Database migrations completed successfully."
- name: Deploy to K3s
run: |
cd /workspace/tone/tonePage/apps/deploy

View File

@@ -19,7 +19,7 @@ export function BlogContent({ content }: { content?: string }) {
h3: ({ ...props }) => <h4 className="text-xl font-bold py-0.5" {...props} />,
h4: ({ ...props }) => <h5 className="text-lg font-bold" {...props} />,
h5: ({ ...props }) => <h6 className="text-md font-bold" {...props} />,
p: ({ ...props }) => <p className="py-1 text-zinc-700" {...props} />,
p: ({ ...props }) => <p className="py-1 text-zinc-700 dark:text-zinc-300" {...props} />,
img: ({ src }) => (
<PhotoProvider className="w-full">
<PhotoView src={src as string}>
@@ -29,8 +29,8 @@ export function BlogContent({ content }: { content?: string }) {
</PhotoView>
</PhotoProvider>
),
th: ({ ...props }) => <th className="text-ellipsis text-nowrap border border-zinc-300 p-2" {...props} />,
td: ({ ...props }) => <td className="border border-zinc-300 p-1" {...props} />,
th: ({ ...props }) => <th className="text-ellipsis text-nowrap border border-zinc-300 dark:border-zinc-500 p-2" {...props} />,
td: ({ ...props }) => <td className="border border-zinc-300 dark:border-zinc-500 p-1" {...props} />,
table: ({ ...props }) => <div className="overflow-x-auto"><table {...props} /></div>,
pre: ({ ...props }) => <pre className="rounded-sm overflow-hidden shadow" {...props} />,
blockquote: ({ ...props }) => <blockquote className="pl-3 border-l-5" {...props} />,

View File

@@ -37,7 +37,7 @@ export function BlogComments({ blogId }: { blogId: string }) {
handleClearReplayTarget={() => setReplayTarget(null)}
/>
<div className="text-sm text-zinc-600">
<div className="text-sm text-zinc-600 dark:text-zinc-400">
{
user ? (<span>{user.nickname}</span>) : (<span></span>)
}
@@ -47,21 +47,21 @@ export function BlogComments({ blogId }: { blogId: string }) {
{
data.filter(d => !d.parentId)
.map((d) => (
<div key={d.id} className="border-b border-zinc-300 py-2 last:border-none">
<h1 className="text-zinc-500">{d.user ? d.user.nickname : '匿名'}</h1>
<div key={d.id} className="border-b border-zinc-300 dark:border-zinc-500 py-2 last:border-none">
<h1 className="text-zinc-500 dark:text-zinc-200">{d.user ? d.user.nickname : '匿名'}</h1>
<div className="whitespace-pre-wrap break-all">{d.content}</div>
<div className="text-xs text-zinc-500 flex gap-2">
<p>{new Date(d.createdAt).toLocaleString()}</p>
<p>{d.address}</p>
<p className="text-zinc-900 cursor-pointer" onClick={() => setReplayTarget(d)}></p>
<p className="text-zinc-900 dark:text-zinc-200 cursor-pointer" onClick={() => setReplayTarget(d)}></p>
</div>
{
data.filter(c => c.parentId === d.id).length > 0 && (
<div className="flex flex-col ml-5 my-1">
{
data.filter(c => c.parentId === d.id).map(c => (
<div key={c.id} className="border-b border-zinc-300 py-1 last:border-none">
<h1 className="text-zinc-500">{c.user ? c.user.nickname : '匿名'}</h1>
<div key={c.id} className="border-b border-zinc-300 dark:border-zinc-500 py-1 last:border-none">
<h1 className="text-zinc-500 dark:text-zinc-200">{c.user ? c.user.nickname : '匿名'}</h1>
<div className="whitespace-pre-wrap break-all">{c.content}</div>
<div className="text-xs text-zinc-500 flex gap-2">
<p>{new Date().toLocaleString()}</p>

View File

@@ -1,4 +1,3 @@
import { base62 } from "@/lib/utils";
import { BlogContent } from "./BlogContent";
import { BlogAPI } from "@/lib/api/server";
import { handleAPIError } from "@/lib/api/common";
@@ -79,12 +78,12 @@ export default async function Page({ params, searchParams }: PageRouteProps) {
return (
<div className="w-full overflow-x-hidden">
<div className="max-w-200 mx-auto px-5 overflow-x-hidden mb-10">
{errorMsg && <div className="my-20 text-center text-zinc-600">{errorMsg}</div>}
{errorMsg && <div className="my-20 text-center text-zinc-600 dark:text-zinc-400">{errorMsg}</div>}
{data && (
<article className="w-full">
<header className="flex flex-col items-center">
<h1 className="text-center text-2xl sm:text-3xl font-bold mt-10 transition-all duration-500">{data.title}</h1>
<time className="text-sm text-zinc-500 text-center my-2 sm:my-5 mb-5 transition-all duration-500">{new Date(data.createdAt).toLocaleString()}</time>
<time className="text-sm text-zinc-500 dark:text-zinc-300 text-center my-2 sm:my-5 mb-5 transition-all duration-500">{new Date(data.createdAt).toLocaleString()}</time>
</header>
<BlogContent content={data.content} />
</article>

View File

@@ -54,11 +54,11 @@ export default async function Blog() {
className="hover:underline focus:outline-none focus:ring-2 focus:ring-zinc-400 rounded"
href={getBlogDetailUrl(blog.slug)}
rel="noopener noreferrer"
>
>
{blog.title}
</a>
</h2>
<p className="text-sm font-medium text-zinc-600">{blog.description}</p>
<p className="text-sm font-medium text-zinc-600 dark:text-zinc-300">{blog.description}</p>
<footer className="mt-3 text-sm text-zinc-500 flex items-center gap-2">
<time dateTime={blog.createdAt}>
{new Date(blog.createdAt).toLocaleString('zh-CN')}

View File

@@ -9,7 +9,7 @@ export default function LayoutWithHeaderFooter({
return (
<>
<Header />
<main className="flex-1 flex flex-col bg-zinc-50">
<main className="flex-1 flex flex-col bg-zinc-50 dark:bg-zinc-950">
{children}
</main>
<Footer />

View File

@@ -10,12 +10,12 @@ export default function Home() {
width={180}
height={180}
className="rounded-full duration-400 size-35 md:size-45 select-none"
unoptimized
priority
quality={100}
/>
</figure>
<h1 className='text-4xl md:text-5xl font-bold mt-5 md:mt-8 gradient-title duration-400 select-none'>(TONE)</h1>
<p className='text-lg sm:text-xl md:text-2xl mt-3 font-medium text-zinc-400 duration-400 select-none'></p>
<p className='text-lg sm:text-xl md:text-2xl mt-3 font-medium text-zinc-400 dark:text-zinc-200 duration-400 select-none'></p>
<nav className='flex sm:flex-row flex-col gap-2 sm:gap-10 mt-5 md:mt-8 duration-400' aria-label="社交媒体链接">
<a href='https://space.bilibili.com/474156211'
target='_black'

View File

@@ -18,7 +18,7 @@ export function ResourceCard({ r, ...props }: ResourceCardProps) {
</div>
<div className="flex-1 overflow-x-hidden">
<div className="font-bold text-2xl">{r.title}</div>
<div className="font-medium text-sm text-zinc-400 mt-1">{r.description}</div>
<div className="font-medium text-sm text-zinc-400 dark:text-zinc-300 mt-1">{r.description}</div>
<div className="flex gap-2 flex-wrap mt-4">
{
r.tags.map((tag) => (

View File

@@ -16,9 +16,9 @@ export default function ResourceCardImage({ imageUrl }: ResourceCardImage) {
alt="资源图片"
width={90}
height={90}
className="rounded-md shadow"
className="rounded-md shadow w-22.5 h-22.5"
priority
quality={80}
quality={75}
onError={() => setImageError(true)}
/>}
</>

View File

@@ -18,9 +18,9 @@ export default async function Resources() {
return (
<div className="flex-1 flex flex-col items-center">
<h1 className="mt-6 md:mt-20 text-2xl md:text-5xl font-medium text-zinc-800 text-center duration-300"></h1>
<p className="mt-4 md:mt-8 mx-3 text-zinc-400 text-sm text-center duration-300">
<a className="text-zinc-600">使</a>
<h1 className="mt-6 md:mt-20 text-2xl md:text-5xl font-medium text-zinc-800 dark:text-zinc-200 text-center duration-300"></h1>
<p className="mt-4 md:mt-8 mx-3 text-zinc-400 dark:text-zinc-300 text-sm text-center duration-300">
<a className="text-zinc-600 dark:text-zinc-400">使</a>
使</p>
{
errorMsg && (

View File

@@ -97,7 +97,7 @@ export default function Login() {
alt="Image"
width={500}
height={500}
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.6] dark:grayscale"
priority
quality={100}
/>

View File

@@ -10,19 +10,19 @@ const EMAIL = "tonesc.cn@gmail.com";
export default function Footer() {
return (
<footer className="border-t border-zinc-300">
<div className="bg-zinc-50 px-4 py-3 md:py-5 sm:px-10 md:px-20 flex flex-col sm:flex-row justify-between items-center gap-4 transition-all">
<footer className="border-t border-zinc-300 dark:border-zinc-500">
<div className="bg-zinc-50 dark:bg-zinc-950 px-4 py-3 md:py-5 sm:px-10 md:px-20 flex flex-col sm:flex-row justify-between items-center gap-4 transition-all">
{/* 版权与备案信息 */}
<div className="text-center sm:text-left">
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
className="block text-sm text-zinc-500 hover:text-zinc-700 hover:underline focus:outline-none focus:underline"
className="block text-sm text-zinc-500 dark:text-zinc-300 hover:text-zinc-700 dark:hover:text-zinc-100 hover:underline focus:outline-none focus:underline"
>
ICP备2023009516号-1
</a>
<p className="mt-1 text-sm text-zinc-500">
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-300">
© {new Date().getFullYear()} TONE Page. All rights reserved.
</p>
</div>
@@ -32,13 +32,13 @@ export default function Footer() {
<Popover>
<PopoverTrigger asChild>
<Button variant='outline' size='sm' >
<Mail className="text-zinc-600" />
<Mail className="text-zinc-600 dark:text-zinc-300" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-fit">
<a
href={`mailto:${EMAIL}`}
className="text-sm text-zinc-800 hover:underline focus:outline-none focus:underline"
className="text-sm text-zinc-800 dark:text-zinc-200 hover:underline focus:outline-none focus:underline"
>
{EMAIL}
</a>

View File

@@ -48,13 +48,13 @@ export default function Header() {
return (
<>
<header className="sticky top-0 z-50 backdrop-blur-sm bg-white/40 shadow" role="banner" aria-label="网站顶部导航栏">
<header className="sticky top-0 z-50 backdrop-blur-sm bg-white/40 dark:bg-black/40 shadow dark:shadow-zinc-500" role="banner" aria-label="网站顶部导航栏">
<div className="flex items-center justify-between px-10 md:h-18 md:px-20 h-14 duration-300" aria-label="主菜单">
<Link
href="/"
className={cn(
"cursor-pointer font-medium text-zinc-500 hover:text-zinc-800 border-b-4 border-transparent duration-200",
pathname === "/" && "text-zinc-800"
"cursor-pointer font-medium text-zinc-500 dark:text-zinc-300 hover:text-zinc-800 dark:hover:text-zinc-100 border-b-4 border-transparent duration-200",
pathname === "/" && "text-zinc-800 dark:text-zinc-100"
)}
aria-current={pathname === "/" ? "page" : undefined}
>
@@ -72,8 +72,8 @@ export default function Header() {
key={item.name}
href={item.path}
className={cn(
"cursor-pointer md:text-lg font-medium text-zinc-500 hover:text-zinc-800 border-b-4 border-transparent duration-200",
pathname.startsWith(item.path) && "text-zinc-800 border-b-pink-500"
"cursor-pointer md:text-lg font-medium text-zinc-500 dark:text-zinc-300 hover:text-zinc-800 dark:hover:text-zinc-100 border-b-4 border-transparent duration-200",
pathname.startsWith(item.path) && "text-zinc-800 dark:text-zinc-100 border-b-pink-500"
)}
onClick={e => handleClick(e, item.path)}
aria-current={pathname === item.path ? "page" : undefined}

View File

@@ -1,3 +1,4 @@
import { cn } from "@/lib/utils";
import { X } from "lucide-react";
interface ResourceBadgeProps extends React.HTMLProps<HTMLDivElement> {
@@ -10,23 +11,17 @@ export function ResourceBadge({ tag, editMode, onClose, ...props }: ResourceBadg
return (
<div
id={tag.name}
className="text-[10px] text-zinc-500 font-medium py-[1px] px-1.5 rounded-full flex items-center gap-1"
style={{
backgroundColor: (() => {
switch (tag.type) {
case 'os':
return '#dbedfd';
default:
return '#e4e4e7';
}
})()
}}
className={cn(
"text-[10px] text-zinc-500 dark:text-zinc-300 dark:border font-medium py-px px-1.5 rounded-full flex items-center gap-1",
'bg-[#e4e4e7] dark:bg-[#2d2d30]',
tag.type === 'os' || 'bg-[#dbedfd] dark:bg-[#1e3a5f]',
)}
{...props}
>
<span className="text-nowrap">{tag.name}</span>
{
editMode && (
<span onClick={() => onClose?.(tag.name)}><X className="w-3 h-3 text-zinc-800 cursor-pointer" /></span>
<span onClick={() => onClose?.(tag.name)}><X className="w-3 h-3 text-zinc-800 dark:text-zinc-200 cursor-pointer" /></span>
)
}
</div>

View File

@@ -39,8 +39,8 @@
"drawer": "^0.0.2",
"highlight.js": "^11.11.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.503.0",
"next": "15.3.1",
"lucide-react": "^0.562.0",
"next": "16.1.1",
"next-themes": "^0.4.6",
"pagination": "^0.4.6",
"popover": "^2.4.1",
@@ -54,22 +54,22 @@
"remark-gfm": "^4.0.1",
"select": "^1.1.2",
"sonner": "^2.0.7",
"swr": "^2.3.7",
"swr": "^2.3.8",
"tailwind-merge": "^3.4.0",
"textarea": "^0.3.0",
"vaul": "^1.1.2",
"zod": "^3.25.76",
"zod": "^4.3.4",
"zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@tanstack/react-table": "^8.21.3",
"@types/node": "^20.19.26",
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/webappsec-credential-management": "^0.6.9",
"eslint": "^9.39.1",
"eslint-config-next": "15.3.1",
"eslint": "^9.39.2",
"eslint-config-next": "16.1.1",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3"

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -11,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -19,9 +23,19 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}