mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
48d414e197
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
231 lines
8.7 KiB
TypeScript
231 lines
8.7 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Switch } from "@/components/ui/switch"
|
|
import { ManagePage, ManageDetailPanel } from "@/components/common/general/manage-pannel"
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
|
|
import { formatDateTime } from "@/lib/utils"
|
|
import type { SystemConfig } from "@/lib/services"
|
|
import { useAdmin } from "@/contexts/admin-context"
|
|
|
|
|
|
/**
|
|
* 系统配置
|
|
* 显示系统配置的详细信息和编辑面板
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <SystemConfigDetailPanel
|
|
* config={config}
|
|
* editData={editData}
|
|
* onEditDataChange={onEditDataChange}
|
|
* onSave={onSave}
|
|
* saving={saving}
|
|
* />
|
|
* ```
|
|
* @param {SystemConfig} config - 系统配置
|
|
* @param {Partial<SystemConfig>} editData - 编辑数据
|
|
* @param {function} onEditDataChange - 编辑数据改变回调
|
|
* @param {function} onSave - 保存回调
|
|
* @param {boolean} saving - 是否正在保存
|
|
* @returns {React.ReactNode} 系统配置详情面板组件
|
|
*/
|
|
function SystemConfigDetailPanel({
|
|
config,
|
|
editData,
|
|
onEditDataChange,
|
|
onSave,
|
|
saving
|
|
}: {
|
|
config: SystemConfig | null
|
|
editData: Partial<SystemConfig>
|
|
onEditDataChange: (field: keyof SystemConfig, value: SystemConfig[keyof SystemConfig]) => void
|
|
onSave: () => void
|
|
saving: boolean
|
|
}) {
|
|
|
|
return (
|
|
<ManageDetailPanel
|
|
isEmpty={!config}
|
|
onSave={onSave}
|
|
saving={saving}
|
|
>
|
|
<div className="grid grid-cols-1 gap-0">
|
|
<div className="border border-dashed rounded-lg">
|
|
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
|
|
<label className="text-xs font-medium text-muted-foreground">配置键</label>
|
|
<p className="text-xs text-muted-foreground font-mono">{config?.key}</p>
|
|
</div>
|
|
|
|
<div className="pl-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
|
|
<label className="text-xs font-medium text-muted-foreground">配置值</label>
|
|
<div className="flex gap-1 w-[90%] justify-end items-center pr-3">
|
|
{config?.key.endsWith('_enabled') ? (
|
|
<Switch
|
|
checked={editData.value !== undefined ? editData.value === 'true' : (config?.value === 'true')}
|
|
onCheckedChange={(checked) => {
|
|
onEditDataChange('value', checked ? 'true' : 'false')
|
|
}}
|
|
/>
|
|
) : (
|
|
<Input
|
|
type={
|
|
config?.key && (
|
|
config.key.endsWith('_limit') ||
|
|
config.key.endsWith('_minutes') ||
|
|
config.key.endsWith('_days') ||
|
|
config.key.endsWith('_seconds') ||
|
|
config.key.endsWith('_hours') ||
|
|
config.key.endsWith('_ttl') ||
|
|
config.key.endsWith('_max') ||
|
|
config.key.endsWith('_min') ||
|
|
config.key.includes('max_') ||
|
|
config.key.includes('min_') ||
|
|
config.key.includes('limit') ||
|
|
config.key.includes('count')
|
|
) ? "number" : "text"
|
|
}
|
|
step="1"
|
|
min="0"
|
|
value={editData.value !== undefined ? editData.value : (config?.value || '')}
|
|
placeholder={editData.value === undefined && !config?.value ? '必需' : ''}
|
|
onChange={(e) => {
|
|
const value = e.target.value
|
|
if (value === '') {
|
|
onEditDataChange('value', '')
|
|
return
|
|
}
|
|
onEditDataChange('value', value)
|
|
}}
|
|
className="!text-[12px] text-right h-4 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:!text-[12px] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]"
|
|
style={{
|
|
MozAppearance: 'textfield'
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pl-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
|
|
<label className="text-xs font-medium text-muted-foreground">配置描述</label>
|
|
<div className="flex gap-1 w-[90%]">
|
|
<Input
|
|
type="text"
|
|
value={editData.description !== undefined ? editData.description : (config?.description || '')}
|
|
placeholder="可选描述"
|
|
onChange={(e) => {
|
|
const value = e.target.value
|
|
onEditDataChange('description', value)
|
|
}}
|
|
className="!text-[12px] text-right h-4 rounded-none border-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:!text-[12px]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="px-3 py-2 flex items-center justify-between border-b border-dashed last:border-b-0">
|
|
<label className="text-xs font-medium text-muted-foreground">配置类型</label>
|
|
<span className={`text-[11px] px-1.5 py-0.5 rounded font-medium ${
|
|
config?.type === 'system'
|
|
? 'bg-blue-500/10 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400'
|
|
: 'bg-indigo-500/10 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400'
|
|
}`}>
|
|
{config?.type === 'system' ? '系统配置' : '业务配置'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="px-3 py-2 flex items-center justify-between">
|
|
<label className="text-xs font-medium text-muted-foreground">创建时间</label>
|
|
<p className="text-xs text-muted-foreground">{config ? formatDateTime(config.created_at) : ''}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ManageDetailPanel>
|
|
)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 系统配置管理组件
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <SystemConfigs />
|
|
* ```
|
|
* @returns {React.ReactNode} 系统配置管理组件
|
|
*/
|
|
export function SystemConfigs() {
|
|
const {
|
|
systemConfigs: configs,
|
|
systemConfigsLoading: loading,
|
|
systemConfigsError: error,
|
|
refetchSystemConfigs,
|
|
updateSystemConfig,
|
|
deleteSystemConfig
|
|
} = useAdmin()
|
|
|
|
const [activeTab, setActiveTab] = React.useState<'system' | 'business'>('business')
|
|
|
|
React.useEffect(() => {
|
|
refetchSystemConfigs(activeTab)
|
|
}, [activeTab, refetchSystemConfigs])
|
|
|
|
const getInitialEditData = (config: SystemConfig) => ({
|
|
value: config.value,
|
|
description: config.description
|
|
})
|
|
|
|
const handleSave = async (config: SystemConfig, editData: Partial<SystemConfig>) => {
|
|
if (!config) return
|
|
|
|
await updateSystemConfig(config.key, {
|
|
value: editData.value ?? config.value,
|
|
description: editData.description ?? config.description
|
|
})
|
|
}
|
|
|
|
const handleDelete = async (config: SystemConfig) => {
|
|
await deleteSystemConfig(config.key)
|
|
}
|
|
|
|
return (
|
|
<ManagePage<SystemConfig>
|
|
title="系统配置"
|
|
data={configs}
|
|
loading={loading}
|
|
error={error}
|
|
onReload={() => refetchSystemConfigs(activeTab)}
|
|
getInitialEditData={getInitialEditData}
|
|
onSave={handleSave}
|
|
onDelete={handleDelete}
|
|
getId={(config) => config.key}
|
|
emptyDescription="未发现系统配置"
|
|
loadingDescription="配置加载中"
|
|
headerExtra={
|
|
<Tabs value={activeTab} onValueChange={(val) => setActiveTab(val as 'system' | 'business')} className="w-[180px]">
|
|
<TabsList className="grid w-full grid-cols-2 h-8">
|
|
<TabsTrigger value="business" className="text-[11px] h-7">业务配置</TabsTrigger>
|
|
<TabsTrigger value="system" className="text-[11px] h-7">系统配置</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
}
|
|
columns={[
|
|
{ header: "配置键", cell: (item) => <span className="font-mono font-medium">{item.key}</span>, width: "200px" },
|
|
{ header: "配置值", cell: (item) => <span className="truncate max-w-[120px] inline-block" title={item.value}>{item.value}</span>, width: "120px" },
|
|
{ header: "描述", cell: (item) => <span className="truncate max-w-[200px] inline-block text-muted-foreground" title={item.description}>{item.description}</span>, width: "200px" },
|
|
]}
|
|
renderDetail={({ selected, hovered, editData, onEditDataChange, onSave, saving }) => (
|
|
<SystemConfigDetailPanel
|
|
config={selected || hovered}
|
|
editData={editData}
|
|
onEditDataChange={onEditDataChange}
|
|
onSave={onSave}
|
|
saving={saving}
|
|
/>
|
|
)}
|
|
/>
|
|
)
|
|
}
|