This commit is contained in:
龟男日记\www 2026-02-12 02:36:01 +08:00
parent 249423d73d
commit 07d1c2274b
7 changed files with 0 additions and 357 deletions

View File

@ -1,46 +0,0 @@
import { getPayload } from 'payload'
import config from '@payload-config'
import { NextResponse } from 'next/server'
/**
*
* GET /api/fix-database
*/
export async function GET(request: Request) {
try {
const payload = await getPayload({ config })
const db = payload.db
console.log('🔧 开始修复数据库字段类型...')
// 修复 products 表的 thumbnail 字段
await db.execute({
raw: `
ALTER TABLE "products"
ALTER COLUMN "thumbnail" TYPE varchar
USING CASE
WHEN "thumbnail" IS NULL THEN NULL
ELSE "thumbnail"::varchar
END;
`,
})
console.log('✅ Products.thumbnail 字段已修复为 varchar 类型')
return NextResponse.json({
success: true,
message: '数据库字段类型修复成功!',
fixes: ['products.thumbnail: integer → varchar'],
})
} catch (error) {
console.error('❌ 修复数据库出错:', error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
message: '数据库修复失败,请查看控制台日志',
},
{ status: 500 },
)
}
}

View File

@ -1,7 +0,0 @@
'use client'
import type { SelectFieldClientComponent } from 'payload'
// 隐藏字段因为在ThumbnailAndStatusField中已经显示和编辑
export const HiddenField: SelectFieldClientComponent = () => {
return null
}

View File

@ -1,60 +0,0 @@
'use client'
import { useFormFields, useField } from '@payloadcms/ui'
import type { TextFieldClientComponent } from 'payload'
// 并排显示缩略图和状态(带状态选择器)
export const ThumbnailAndStatusField: TextFieldClientComponent = ({ path }) => {
// 获取thumbnail值
const fields = useFormFields(([fields]) => fields)
const thumbnail = fields.thumbnail?.value
// 获取status字段的值和setter
const { value: status, setValue: setStatus } = useField({ path: 'status' })
const isImage = typeof thumbnail === 'string' && thumbnail.match(/^https?:\/\/.+/)
return (
<div>
<label style={{ display: 'block', marginBottom: 8, fontWeight: 500 }}></label>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
<div
style={{
width: 100,
height: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
flexShrink: 0,
}}
>
{isImage ? (
<img
src={thumbnail}
alt="商品缩略图"
style={{ maxWidth: '100%', maxHeight: '100%', borderRadius: 6 }}
/>
) : (
<span style={{ color: '#bbb' }}></span>
)}
</div>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: 4, fontSize: 14 }}></label>
<select
value={(status as string) || 'draft'}
onChange={(e) => setStatus(e.target.value)}
style={{
padding: '8px 12px',
borderRadius: 4,
fontSize: 14,
minWidth: 120,
}}
>
<option value="draft">稿</option>
<option value="published"></option>
</select>
</div>
</div>
</div>
)
}

View File

@ -1,45 +0,0 @@
'use client'
import React from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
/**
*
*/
export function AdminPanelNavLink() {
const pathname = usePathname()
const isActive = pathname === '/admin/admin-panel'
return (
<Link
href="/admin/admin-panel"
style={{
display: 'block',
padding: '0.75rem 1rem',
textDecoration: 'none',
color: isActive ? 'var(--theme-text)' : 'var(--theme-elevation-800)',
backgroundColor: isActive ? 'var(--theme-elevation-100)' : 'transparent',
borderRadius: '4px',
transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.backgroundColor = 'var(--theme-elevation-50)'
}
}}
onMouseLeave={(e) => {
if (!isActive) {
e.currentTarget.style.backgroundColor = 'transparent'
}
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<span style={{ fontSize: '1.25rem' }}>🛠</span>
<span style={{ fontSize: '0.875rem', fontWeight: isActive ? '600' : '400' }}>
</span>
</div>
</Link>
)
}

View File

@ -1,107 +0,0 @@
'use client'
import { useState } from 'react'
import { Button } from '@payloadcms/ui'
/**
*
* Products, Announcements, Articles
*/
export function ClearDataButton() {
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState('')
const [showConfirm, setShowConfirm] = useState(false)
const handleClearData = () => {
setShowConfirm(true)
setMessage('')
}
const handleConfirm = async () => {
setLoading(true)
setMessage('')
setShowConfirm(false)
try {
const response = await fetch('/api/clear-data?confirm=true', {
method: 'GET',
})
const data = await response.json()
if (data.success) {
setMessage(data.message || '数据清理成功!')
setTimeout(() => window.location.reload(), 2000)
} else {
setMessage(`清理失败: ${data.error}`)
}
} catch (error) {
setMessage(`清理出错: ${error instanceof Error ? error.message : '未知错误'}`)
} finally {
setLoading(false)
}
}
const handleCancel = () => {
setShowConfirm(false)
setMessage('')
}
return (
<div style={{ padding: '1rem', borderBottom: '1px solid var(--theme-elevation-100)' }}>
<h3 style={{ marginBottom: '0.5rem', fontSize: '1rem' }}>🗑 </h3>
<p style={{ marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--theme-elevation-500)' }}>
</p>
{showConfirm ? (
<div>
<div
style={{
marginBottom: '1rem',
padding: '0.75rem',
backgroundColor: 'var(--theme-error-50)',
borderRadius: '4px',
border: '1px solid var(--theme-error-500)',
}}
>
<p style={{ margin: '0', fontWeight: 'bold', color: 'var(--theme-error-700)' }}>
</p>
<p style={{ margin: '0.5rem 0 0 0', fontSize: '0.875rem' }}>
</p>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<Button onClick={handleConfirm} disabled={loading} buttonStyle="error">
{loading ? '清理中...' : '确认清理'}
</Button>
<Button onClick={handleCancel} disabled={loading} buttonStyle="secondary">
</Button>
</div>
</div>
) : (
<Button onClick={handleClearData} disabled={loading} buttonStyle="error">
</Button>
)}
{message && (
<div
style={{
marginTop: '0.75rem',
padding: '0.75rem',
backgroundColor: message.includes('失败') || message.includes('出错')
? 'var(--theme-error-50)'
: 'var(--theme-success-50)',
borderRadius: '4px',
fontSize: '0.875rem',
}}
>
{message}
</div>
)}
</div>
)
}

View File

@ -12,9 +12,6 @@ export default function AdminPanel() {
const [clearMessage, setClearMessage] = useState('') const [clearMessage, setClearMessage] = useState('')
const [showClearConfirm, setShowClearConfirm] = useState(false) const [showClearConfirm, setShowClearConfirm] = useState(false)
const [fixLoading, setFixLoading] = useState(false)
const [fixMessage, setFixMessage] = useState('')
const handleClearData = () => { const handleClearData = () => {
setShowClearConfirm(true) setShowClearConfirm(true)
setClearMessage('') setClearMessage('')
@ -49,33 +46,6 @@ export default function AdminPanel() {
setClearMessage('') setClearMessage('')
} }
const handleFixDatabase = async () => {
if (!confirm('确定要修复数据库字段类型吗?这会修改 products 表的 thumbnail 字段。')) {
return
}
setFixLoading(true)
setFixMessage('')
try {
const response = await fetch('/api/fix-database', {
method: 'GET',
})
const data = await response.json()
if (data.success) {
setFixMessage(data.message || '数据库修复成功!')
} else {
setFixMessage(`修复失败: ${data.error}`)
}
} catch (error) {
setFixMessage(`修复出错: ${error instanceof Error ? error.message : '未知错误'}`)
} finally {
setFixLoading(false)
}
}
return ( return (
<div style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}> <div style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '2rem', fontSize: '2rem', fontWeight: 'bold' }}> <h1 style={{ marginBottom: '2rem', fontSize: '2rem', fontWeight: 'bold' }}>
@ -179,56 +149,6 @@ export default function AdminPanel() {
</div> </div>
)} )}
</div> </div>
{/* 修复数据库区域 */}
<div
style={{
backgroundColor: 'var(--theme-elevation-0)',
borderRadius: '6px',
padding: '1.5rem',
marginTop: '1rem',
border: '1px solid var(--theme-elevation-150)',
}}
>
<h3 style={{ marginBottom: '0.5rem', fontSize: '1rem', fontWeight: '600' }}>
🔧
</h3>
<p
style={{
marginBottom: '1rem',
fontSize: '0.875rem',
color: 'var(--theme-elevation-600)',
}}
>
products.thumbnail integer varchar
</p>
<Button onClick={handleFixDatabase} disabled={fixLoading} buttonStyle="primary">
{fixLoading ? '修复中...' : '修复字段类型'}
</Button>
{fixMessage && (
<div
style={{
marginTop: '1rem',
padding: '1rem',
backgroundColor:
fixMessage.includes('失败') || fixMessage.includes('出错')
? 'var(--theme-error-50)'
: 'var(--theme-success-50)',
borderRadius: '6px',
fontSize: '0.875rem',
border: `1px solid ${
fixMessage.includes('失败') || fixMessage.includes('出错')
? 'var(--theme-error-500)'
: 'var(--theme-success-500)'
}`,
}}
>
{fixMessage}
</div>
)}
</div>
</div> </div>
{/* 系统信息区域 */} {/* 系统信息区域 */}

View File

@ -1,6 +1,4 @@
import * as migration_20260208_171142 from './20260208_171142' import * as migration_20260208_171142 from './20260208_171142'
import * as migration_20260211_180407 from './20260211_180407'
import * as migration_20260211_180500_fix_thumbnail from './20260211_180500_fix_thumbnail'
export const migrations = [ export const migrations = [
{ {
@ -8,14 +6,4 @@ export const migrations = [
down: migration_20260208_171142.down, down: migration_20260208_171142.down,
name: '20260208_171142', name: '20260208_171142',
}, },
{
up: migration_20260211_180407.up,
down: migration_20260211_180407.down,
name: '20260211_180407',
},
{
up: migration_20260211_180500_fix_thumbnail.up,
down: migration_20260211_180500_fix_thumbnail.down,
name: '20260211_180500_fix_thumbnail',
},
] ]