swagger

移除 merchant

swagger

改造首页内容为通用后台管理系统定位

- 修改首页标题从 'LINUX DO Credit' 改为 'Modern Platform'
- 更新副标题为 '为二次开发而生'
- 更新首页描述为通用平台的特点
- 更新首页特性标签为 '开箱即用、高度可扩展、工业级基建'
- 修改展示卡片为技术栈和二次开发相关
- 更新开发者示例代码为通用的注册和 API Key 获取示例
- 更新页脚品牌名为 'Modern Platform'
- 调整页脚导航链接为通用平台相关内容

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

去除遗留

裁剪
移除 /api/v1/user/pay-key 相关代码

- 删除后端 UpdatePayKey 处理器函数和 UpdatePayKeyRequest 结构体
- 删除 User 模型中的 PayKey 字段
- 删除 User.VerifyPayKey 方法
- 删除 EncryptPayKeyFailed 错误常量
- 删除 /api/v1/user/pay-key PUT 路由
- 删除 OAuth 返回中的 IsPayKey 字段
- 删除前端 UserService.updatePayKey 方法
- 删除前端所有支付密钥 UI 和逻辑
- 更新相关的导出和注释

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

去除遗留

api 修正

系统配置

前端裁剪

后端裁剪

init
This commit is contained in:
ryan
2026-06-07 15:32:14 +08:00
parent 8a782525de
commit 48d414e197
180 changed files with 1538 additions and 27463 deletions
+9 -71
View File
@@ -4,24 +4,16 @@ import * as React from "react"
import { createContext, useContext, useState, useRef, useCallback } from "react"
import services from "@/lib/services"
import type { UpdateUserPayConfigRequest } from "@/lib/services/admin/types"
import type { UserPayConfig, SystemConfig, UpdateSystemConfigRequest } from "@/lib/services"
import type { SystemConfig, UpdateSystemConfigRequest } from "@/lib/services"
import { handleContextError } from "@/lib/utils/error-handling"
/** Admin 上下文状态接口 */
export interface AdminContextState {
userPayConfigs: UserPayConfig[]
userPayConfigsLoading: boolean
userPayConfigsError: Error | null
refetchUserPayConfigs: () => Promise<void>
updateUserPayConfig: (id: string, data: UpdateUserPayConfigRequest) => Promise<void>
deleteUserPayConfig: (id: string) => Promise<void>
systemConfigs: SystemConfig[]
systemConfigsLoading: boolean
systemConfigsError: Error | null
refetchSystemConfigs: () => Promise<void>
refetchSystemConfigs: (type?: 'system' | 'business') => Promise<void>
updateSystemConfig: (key: string, data: UpdateSystemConfigRequest) => Promise<void>
deleteSystemConfig: (key: string) => Promise<void>
}
@@ -41,69 +33,22 @@ const AdminContext = createContext<AdminContextState | null>(null)
* @param {React.ReactNode} children - Admin Provider 的子元素
*/
export function AdminProvider({ children }: { children: React.ReactNode }) {
const [userPayConfigs, setUserPayConfigs] = useState<UserPayConfig[]>([])
const [userPayConfigsLoading, setUserPayConfigsLoading] = useState(false)
const [userPayConfigsError, setUserPayConfigsError] = useState<Error | null>(null)
const [systemConfigs, setSystemConfigs] = useState<SystemConfig[]>([])
const [systemConfigsLoading, setSystemConfigsLoading] = useState(false)
const [systemConfigsError, setSystemConfigsError] = useState<Error | null>(null)
const userPayRequestIdRef = useRef(0)
const systemRequestIdRef = useRef(0)
/** 获取用户积分配置列表 */
const refetchUserPayConfigs = useCallback(async () => {
const requestId = ++userPayRequestIdRef.current
try {
setUserPayConfigsLoading(true)
setUserPayConfigsError(null)
const data = await services.admin.listUserPayConfigs()
if (requestId !== userPayRequestIdRef.current) {
return
}
setUserPayConfigs(data)
setUserPayConfigsLoading(false)
} catch (error) {
if (requestId !== userPayRequestIdRef.current) {
return
}
const errorObject = handleContextError(error, '加载积分配置失败', { logError: true })
setUserPayConfigsError(errorObject)
setUserPayConfigsLoading(false)
}
}, [])
/** 更新用户积分配置 */
const updateUserPayConfig = useCallback(async (id: string, data: UpdateUserPayConfigRequest) => {
try {
await services.admin.updateUserPayConfig(id, data)
await refetchUserPayConfigs()
} catch (error) {
handleContextError(error, '更新用户积分配置失败')
throw error
}
}, [refetchUserPayConfigs])
/** 删除用户积分配置 */
const deleteUserPayConfig = useCallback(async (id: string) => {
await services.admin.deleteUserPayConfig(id)
await refetchUserPayConfigs()
}, [refetchUserPayConfigs])
const lastConfigTypeRef = useRef<'system' | 'business' | undefined>(undefined)
/** 获取系统配置列表 */
const refetchSystemConfigs = useCallback(async () => {
const refetchSystemConfigs = useCallback(async (type?: 'system' | 'business') => {
lastConfigTypeRef.current = type
const requestId = ++systemRequestIdRef.current
try {
setSystemConfigsLoading(true)
setSystemConfigsError(null)
const data = await services.admin.listSystemConfigs()
const data = await services.admin.listSystemConfigs(type)
if (requestId !== systemRequestIdRef.current) {
return
@@ -126,7 +71,7 @@ export function AdminProvider({ children }: { children: React.ReactNode }) {
const updateSystemConfig = useCallback(async (key: string, data: UpdateSystemConfigRequest) => {
try {
await services.admin.updateSystemConfig(key, data)
await refetchSystemConfigs()
await refetchSystemConfigs(lastConfigTypeRef.current)
} catch (error) {
handleContextError(error, '更新系统配置失败')
throw error
@@ -136,17 +81,10 @@ export function AdminProvider({ children }: { children: React.ReactNode }) {
/** 删除系统配置 */
const deleteSystemConfig = useCallback(async (key: string) => {
await services.admin.deleteSystemConfig(key)
await refetchSystemConfigs()
await refetchSystemConfigs(lastConfigTypeRef.current)
}, [refetchSystemConfigs])
const value: AdminContextState = {
userPayConfigs,
userPayConfigsLoading,
userPayConfigsError,
refetchUserPayConfigs,
updateUserPayConfig,
deleteUserPayConfig,
systemConfigs,
systemConfigsLoading,
systemConfigsError,
@@ -167,7 +105,7 @@ export function AdminProvider({ children }: { children: React.ReactNode }) {
*
* @example
* ```tsx
* const { userPayConfigs, systemConfigs } = useAdmin()
* const { systemConfigs } = useAdmin()
* ```
* @returns {AdminContextState} Admin 上下文状态
*/
-178
View File
@@ -1,178 +0,0 @@
"use client"
import { createContext, useContext, useState, useRef, useCallback, useEffect } from "react"
import services, { type MerchantAPIKey, type UpdateAPIKeyRequest } from "@/lib/services"
import { handleContextError } from "@/lib/utils/error-handling"
/** 商户数据状态接口 */
interface MerchantDataState {
apiKeys: MerchantAPIKey[]
loading: boolean
error: string | null
}
/** 商户 Context 接口 */
interface MerchantContextType extends MerchantDataState {
loadAPIKeys: () => Promise<void>
createAPIKey: (data: {
app_name: string
app_homepage_url: string
redirect_uri?: string
notify_url: string
}) => Promise<MerchantAPIKey>
updateAPIKey: (id: string, data: UpdateAPIKeyRequest) => Promise<void>
deleteAPIKey: (id: string) => Promise<void>
refresh: () => Promise<void>
}
/** 商户 Context(全局状态管理) */
const MerchantContext = createContext<MerchantContextType | undefined>(undefined)
/** 商户 Provider */
export function MerchantProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<MerchantDataState>({
apiKeys: [],
loading: false,
error: null,
})
/** 使用 ref 来标记是否已经加载过数据(全局级别) */
const hasLoadedRef = useRef(false)
const isMountedRef = useRef(true)
const apiKeysRef = useRef(state.apiKeys)
/** 同步更新 ref */
apiKeysRef.current = state.apiKeys
/** 获取 API Keys */
const loadAPIKeys = useCallback(async () => {
if (hasLoadedRef.current && apiKeysRef.current.length > 0) {
return
}
try {
setState(prev => ({ ...prev, loading: true, error: null }))
const data = await services.merchant.listAPIKeys()
if (!isMountedRef.current) return
const validKeys = Array.isArray(data) ? data.filter(key => key != null) : []
setState(prev => ({
...prev,
apiKeys: validKeys,
loading: false,
error: null
}))
hasLoadedRef.current = true
} catch (error) {
const errorObject = handleContextError(error, '加载 API Keys 失败', { showToast: true })
if (errorObject.message === '请求已取消') {
return
}
if (!isMountedRef.current) return
setState(prev => ({
...prev,
loading: false,
error: errorObject.message
}))
}
}, [])
/** 创建 API Key */
const createAPIKey = useCallback(async (data: {
app_name: string
app_homepage_url: string
redirect_uri?: string
notify_url: string
}): Promise<MerchantAPIKey> => {
const newKey = await services.merchant.createAPIKey(data)
if (!isMountedRef.current) return newKey
setState(prev => ({
...prev,
apiKeys: [newKey, ...prev.apiKeys]
}))
return newKey
}, [])
/** 更新 API Key */
const updateAPIKey = useCallback(async (id: string, data: UpdateAPIKeyRequest): Promise<void> => {
await services.merchant.updateAPIKey(id, data)
if (!isMountedRef.current) return
/** 更新本地状态中的API Key */
setState(prev => ({
...prev,
apiKeys: prev.apiKeys.map(key =>
key.id === id ? { ...key, ...data } : key
)
}))
}, [])
/** 删除 API Key */
const deleteAPIKey = useCallback(async (id: string) => {
await services.merchant.deleteAPIKey(id)
if (!isMountedRef.current) return
setState(prev => ({
...prev,
apiKeys: prev.apiKeys.filter(key => key && key.id !== id)
}))
}, [])
/** 刷新数据 */
const refresh = useCallback(async () => {
hasLoadedRef.current = false
await loadAPIKeys()
}, [loadAPIKeys])
const value: MerchantContextType = {
...state,
loadAPIKeys,
createAPIKey,
updateAPIKey,
deleteAPIKey,
refresh,
}
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
return (
<MerchantContext.Provider value={value}>
{children}
</MerchantContext.Provider>
)
}
/**
* 使用商户数据 Hook
* 用于在组件中使用商户数据状态
*
* @example
* ```tsx
* const { apiKeys, loading, error, loadAPIKeys, createAPIKey, updateAPIKey, deleteAPIKey, refresh } = useMerchant()
* ```
*/
export function useMerchant(): MerchantContextType {
const context = useContext(MerchantContext)
if (context === undefined) {
throw new Error('useMerchant must be used within a MerchantProvider')
}
return context
}
-279
View File
@@ -1,279 +0,0 @@
"use client"
import * as React from "react"
import { createContext, useContext, useCallback, useState, useRef, useEffect } from "react"
import services from "@/lib/services"
import type { Order, TransactionQueryParams } from "@/lib/services"
import { generateTransactionCacheKey } from "@/lib/utils"
/** 交易上下文状态接口 */
interface TransactionContextState {
transactions: Order[]
total: number
currentPage: number
pageSize: number
totalPages: number
loading: boolean
error: Error | null
lastParams: Partial<TransactionQueryParams>
fetchTransactions: (params: Partial<TransactionQueryParams>) => Promise<void>
goToPage: (page: number) => Promise<void>
setPageSize: (size: number) => void
refresh: () => Promise<void>
clearCacheAndRefresh: () => Promise<void>
reset: () => void
updateOrderStatus: (orderId: string, updates: Partial<Pick<Order, 'status' | 'dispute_id'>>) => void
}
const MAX_CACHE_SIZE = 50
const CACHE_DURATION = 5 * 60 * 1000 // 5分钟
/** 交易上下文 */
const TransactionContext = createContext<TransactionContextState | null>(null)
/** 交易 Provider Props 接口 */
interface TransactionProviderProps {
children: React.ReactNode
defaultParams?: Partial<TransactionQueryParams>
}
/**
* 交易 Provider
* 提供交易数据的全局状态管理
*
* @param {React.ReactNode} children - 交易 Provider 的子元素
* @param {Partial<TransactionQueryParams>} defaultParams - 默认查询参数
* @example
* ```tsx
* <TransactionProvider defaultParams={{ type: 'receive', page_size: 20 }}>
* <TransactionList />
* </TransactionProvider>
* ```
*/
export function TransactionProvider({ children, defaultParams = {} }: TransactionProviderProps) {
const [transactions, setTransactions] = useState<Order[]>([])
const [total, setTotal] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(defaultParams.page_size || 20)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const [lastParams, setLastParams] = useState<Partial<TransactionQueryParams>>(defaultParams)
/** 使用 useRef 存储缓存 */
const cacheRef = useRef<Record<string, { data: Order[], total: number, timestamp: number }>>({})
const latestRequestIdRef = useRef(0)
const cleanExpiredCache = useCallback(() => {
const now = Date.now()
Object.keys(cacheRef.current).forEach(key => {
if (now - cacheRef.current[key].timestamp > CACHE_DURATION) {
delete cacheRef.current[key]
}
})
}, [])
const addToCache = useCallback((key: string, value: { data: Order[], total: number, timestamp: number }) => {
const cacheKeys = Object.keys(cacheRef.current)
if (cacheKeys.length >= MAX_CACHE_SIZE) {
const oldestKey = cacheKeys.reduce((oldest, current) => {
return cacheRef.current[current].timestamp < cacheRef.current[oldest].timestamp ? current : oldest
})
delete cacheRef.current[oldestKey]
}
cacheRef.current[key] = value
}, [])
/** 获取交易列表 */
const fetchTransactions = useCallback(async (params: Partial<TransactionQueryParams>) => {
const queryParams: TransactionQueryParams = {
page: params.page || 1,
page_size: params.page_size || pageSize,
...params,
}
/** 生成唯一的请求 ID */
const requestId = ++latestRequestIdRef.current
/** 生成缓存key */
const cacheKey = generateTransactionCacheKey(queryParams as Parameters<typeof generateTransactionCacheKey>[0])
const cached = cacheRef.current[cacheKey]
const now = Date.now()
/** 缓存所有页面,不仅仅第一页 */
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
if (requestId !== latestRequestIdRef.current) {
return
}
/** 使用缓存数据,同步更新状态 */
setTransactions(cached.data)
setTotal(cached.total)
setCurrentPage(queryParams.page)
setPageSize(queryParams.page_size)
setLastParams(prev => ({ ...prev, ...params }))
setError(null)
setLoading(false)
return
}
/** 发起API请求 */
setLoading(true)
setError(null)
// 不清空 total,保持显示之前的总数
// setTransactions([]) - 也不清空,保持之前的数据直到新数据到达
try {
const result = await services.transaction.getTransactions(queryParams)
if (requestId !== latestRequestIdRef.current) {
return
}
/** 总是替换数据,不累加 */
setTransactions(result.orders)
/** 缓存所有页的数据 */
addToCache(cacheKey, {
data: result.orders,
total: result.total,
timestamp: now
})
cleanExpiredCache()
setTotal(result.total)
setCurrentPage(queryParams.page)
setPageSize(queryParams.page_size)
setLastParams(prev => ({ ...prev, ...params }))
} catch (err) {
if (err instanceof Error && err.message === '请求已被取消') {
return
}
if (requestId !== latestRequestIdRef.current) {
return
}
setError(err instanceof Error ? err : new Error('获取交易记录失败'))
console.error('获取交易记录失败:', err)
} finally {
if (requestId === latestRequestIdRef.current) {
setLoading(false)
}
}
}, [pageSize, addToCache, cleanExpiredCache])
/** 跳转到指定页 */
const goToPage = useCallback(async (page: number) => {
if (loading) return
await fetchTransactions({
...lastParams,
page,
})
}, [fetchTransactions, lastParams, loading])
/** 设置分页大小 */
const setPageSizeHandler = useCallback((size: number) => {
setPageSize(size)
fetchTransactions({
...lastParams,
page: 1,
page_size: size,
})
}, [fetchTransactions, lastParams])
const refresh = useCallback(async () => {
const cacheKey = generateTransactionCacheKey({ ...lastParams, page: currentPage, page_size: pageSize } as Parameters<typeof generateTransactionCacheKey>[0])
delete cacheRef.current[cacheKey]
await fetchTransactions({
...lastParams,
page: currentPage,
})
}, [fetchTransactions, lastParams, pageSize, currentPage])
/** 清除所有缓存并刷新 */
const clearCacheAndRefresh = useCallback(async () => {
cacheRef.current = {}
await fetchTransactions({
...lastParams,
page: 1,
})
}, [fetchTransactions, lastParams])
/** 乐观更新订单状态 */
const updateOrderStatus = useCallback((orderId: string, updates: Partial<Pick<Order, 'status' | 'dispute_id'>>) => {
setTransactions(prev =>
prev.map(order =>
order.id === orderId
? { ...order, ...updates }
: order
)
)
cacheRef.current = {}
}, [])
/** 重置状态 */
const reset = useCallback(() => {
setTransactions([])
setTotal(0)
setCurrentPage(1)
setPageSize(defaultParams.page_size || 20)
setError(null)
setLastParams(defaultParams)
}, [defaultParams])
const totalPages = Math.ceil(total / pageSize)
const value: TransactionContextState = {
transactions,
total,
currentPage,
pageSize,
totalPages,
loading,
error,
lastParams,
fetchTransactions,
goToPage,
setPageSize: setPageSizeHandler,
refresh,
clearCacheAndRefresh,
reset,
updateOrderStatus,
}
useEffect(() => {
return () => {
cacheRef.current = {}
}
}, [])
return (
<TransactionContext.Provider value={value}>
{children}
</TransactionContext.Provider>
)
}
/**
* 使用交易上下文
*
* @param {React.ReactNode} children - 交易上下文 Provider 的子元素
* @param {Partial<TransactionQueryParams>} defaultParams - 默认查询参数
*/
export function useTransaction() {
const context = useContext(TransactionContext)
if (!context) {
throw new Error('useTransaction 必须在 TransactionProvider 内部使用')
}
return context
}
-8
View File
@@ -16,7 +16,6 @@ interface UserState {
/** 用户上下文接口 */
interface UserContextValue extends UserState {
refetch: () => Promise<void>
updatePayKey: (payKey: string) => Promise<void>
getTrustLevelLabel: (trustLevel: TrustLevel) => string
getPayLevelLabel: (payLevel: PayLevel) => string
logout: () => Promise<void>
@@ -100,12 +99,6 @@ export function UserProvider({ children }: { children: ReactNode }) {
await fetchUser()
}, [fetchUser])
/** 更新支付密码 */
const updatePayKey = useCallback(async (payKey: string) => {
await services.user.updatePayKey(payKey)
await fetchUser()
}, [fetchUser])
/** 用户登出 */
const logout = useCallback(async () => {
try {
@@ -144,7 +137,6 @@ export function UserProvider({ children }: { children: ReactNode }) {
value={{
...state,
refetch,
updatePayKey,
getTrustLevelLabel,
getPayLevelLabel,
logout,