Просмотр исходного кода

✨ feat(system-config): 重构广告管理为系统配置管理

- 新增系统配置模块的 schema 导出文件
- 重构广告管理 UI 为系统配置管理 UI
- 更新依赖包,移除广告相关模块,添加核心模块依赖
- 重构客户端管理器,从 AdvertisementClientManager 改为 SystemConfigClientManager
- 重构组件逻辑,移除广告相关字段,添加系统配置字段(配置键、配置值、描述)
- 更新表单验证和数据类型定义
- 优化界面布局,移除图片、状态、排序等广告相关字段
- 更新搜索和表格列配置,适配系统配置数据结构
yourname 1 месяц назад
Родитель
Сommit
1c2e0fd6d0

+ 1 - 0
packages/core-module-mt/system-config-module-mt/src/schemas/index.mt.ts

@@ -0,0 +1 @@
+export * from './system-config.schema.mt';

+ 3 - 5
packages/system-config-management-ui-mt/package.json

@@ -42,9 +42,7 @@
   "dependencies": {
     "@d8d/shared-types": "workspace:*",
     "@d8d/shared-ui-components": "workspace:*",
-    "@d8d/advertisements-module-mt": "workspace:*",
-    "@d8d/file-management-ui-mt": "workspace:*",
-    "@d8d/advertisement-type-management-ui-mt": "workspace:*",
+    "@d8d/core-module-mt": "workspace:*",
     "@hookform/resolvers": "^5.2.1",
     "@tanstack/react-query": "^5.90.9",
     "class-variance-authority": "^0.7.1",
@@ -79,13 +77,13 @@
     "react-dom": "^19.1.0"
   },
   "keywords": [
-    "advertisement",
+    "system-config",
+    "configuration",
     "management",
     "admin",
     "ui",
     "react",
     "crud",
-    "banner",
     "multi-tenant",
     "mt"
   ],

+ 14 - 14
packages/system-config-management-ui-mt/src/api/systemConfigClient.ts

@@ -1,26 +1,26 @@
-import { advertisementRoutes } from '@d8d/advertisements-module-mt';
+import { systemConfigRoutesMt } from '@d8d/core-module-mt/system-config-module-mt';
 import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
 
-export class AdvertisementClientManager {
-  private static instance: AdvertisementClientManager;
-  private client: ReturnType<typeof rpcClient<typeof advertisementRoutes>> | null = null;
+export class SystemConfigClientManager {
+  private static instance: SystemConfigClientManager;
+  private client: ReturnType<typeof rpcClient<typeof systemConfigRoutesMt>> | null = null;
 
   private constructor() {}
 
-  public static getInstance(): AdvertisementClientManager {
-    if (!AdvertisementClientManager.instance) {
-      AdvertisementClientManager.instance = new AdvertisementClientManager();
+  public static getInstance(): SystemConfigClientManager {
+    if (!SystemConfigClientManager.instance) {
+      SystemConfigClientManager.instance = new SystemConfigClientManager();
     }
-    return AdvertisementClientManager.instance;
+    return SystemConfigClientManager.instance;
   }
 
   // 初始化客户端
-  public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof advertisementRoutes>> {
-    return this.client = rpcClient<typeof advertisementRoutes>(baseUrl);
+  public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof systemConfigRoutesMt>> {
+    return this.client = rpcClient<typeof systemConfigRoutesMt>(baseUrl);
   }
 
   // 获取客户端实例
-  public get(): ReturnType<typeof rpcClient<typeof advertisementRoutes>> {
+  public get(): ReturnType<typeof rpcClient<typeof systemConfigRoutesMt>> {
     if (!this.client) {
       return this.init()
     }
@@ -34,11 +34,11 @@ export class AdvertisementClientManager {
 }
 
 // 导出单例实例
-const advertisementClientManager = AdvertisementClientManager.getInstance();
+const systemConfigClientManager = SystemConfigClientManager.getInstance();
 
 // 导出默认客户端实例(延迟初始化)
-export const advertisementClient = advertisementClientManager.get()
+export const systemConfigClient = systemConfigClientManager.get()
 
 export {
-  advertisementClientManager
+  systemConfigClientManager
 }

+ 144 - 381
packages/system-config-management-ui-mt/src/components/SystemConfigManagement.tsx

@@ -6,7 +6,6 @@ import { Input } from '@d8d/shared-ui-components/components/ui/input';
 import { Button } from '@d8d/shared-ui-components/components/ui/button';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@d8d/shared-ui-components/components/ui/card';
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
-import { Badge } from '@d8d/shared-ui-components/components/ui/badge';
 import { Skeleton } from '@d8d/shared-ui-components/components/ui/skeleton';
 import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@d8d/shared-ui-components/components/ui/dialog';
 import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@d8d/shared-ui-components/components/ui/form';
@@ -14,118 +13,111 @@ import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { toast } from 'sonner';
 import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
-import { FileSelector } from '@d8d/file-management-ui-mt';
-import { AdvertisementTypeSelector } from '@d8d/advertisement-type-management-ui-mt';
-import { advertisementClientManager } from '../api/advertisementClient';
-import { CreateAdvertisementDto, UpdateAdvertisementDto } from '@d8d/advertisements-module-mt/schemas';
-import type { AdvertisementSearchParams, CreateAdvertisementRequest, UpdateAdvertisementRequest, AdvertisementResponse } from '../types';
+import { systemConfigClientManager } from '../api/systemConfigClient';
+import { CreateSystemConfigSchema, UpdateSystemConfigSchema } from '@d8d/core-module-mt/system-config-module-mt';
+import type { SystemConfigFormData, SystemConfigSearchParams, CreateSystemConfigRequest, UpdateSystemConfigRequest, SystemConfigResponse } from '../types';
 
-type CreateRequest = CreateAdvertisementRequest;
-type UpdateRequest = UpdateAdvertisementRequest;
+type CreateRequest = CreateSystemConfigRequest;
+type UpdateRequest = UpdateSystemConfigRequest;
 
-const createFormSchema = CreateAdvertisementDto;
-const updateFormSchema = UpdateAdvertisementDto;
+const createFormSchema = CreateSystemConfigSchema;
+const updateFormSchema = UpdateSystemConfigSchema;
 
-export const AdvertisementManagement: React.FC = () => {
-  const [searchParams, setSearchParams] = useState<AdvertisementSearchParams>({ page: 1, limit: 10, search: '' });
+export const SystemConfigManagement: React.FC = () => {
+  const [searchParams, setSearchParams] = useState<SystemConfigSearchParams>({ page: 1, limit: 10, search: '' });
   const [isModalOpen, setIsModalOpen] = useState(false);
-  const [editingAdvertisement, setEditingAdvertisement] = useState<AdvertisementResponse | null>(null);
+  const [editingSystemConfig, setEditingSystemConfig] = useState<SystemConfigResponse | null>(null);
   const [isCreateForm, setIsCreateForm] = useState(true);
   const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
-  const [advertisementToDelete, setAdvertisementToDelete] = useState<number | null>(null);
+  const [systemConfigToDelete, setSystemConfigToDelete] = useState<number | null>(null);
 
   // 表单实例
-  const createForm = useForm<CreateRequest>({
+  const createForm = useForm<SystemConfigFormData>({
     resolver: zodResolver(createFormSchema),
     defaultValues: {
-      title: '',
-      typeId: 1,
-      code: '',
-      url: '',
-      imageFileId: undefined,
-      sort: 0,
-      status: 1,
-      actionType: 1
+      configKey: '',
+      configValue: '',
+      description: ''
     }
   });
 
-  const updateForm = useForm<UpdateRequest>({
+  const updateForm = useForm<SystemConfigFormData>({
     resolver: zodResolver(updateFormSchema),
     defaultValues: {}
   });
 
   // 数据查询
   const { data, isLoading, refetch } = useQuery({
-    queryKey: ['advertisements', searchParams],
+    queryKey: ['system-configs', searchParams],
     queryFn: async () => {
-      const res = await advertisementClientManager.get().index.$get({
+      const res = await systemConfigClientManager.get().index.$get({
         query: {
           page: searchParams.page,
           pageSize: searchParams.limit,
           keyword: searchParams.search
         }
       });
-      if (res.status !== 200) throw new Error('获取广告列表失败');
+      if (res.status !== 200) throw new Error('获取系统配置列表失败');
       return await res.json();
     }
   });
 
-  // 创建广告
+  // 创建系统配置
   const createMutation = useMutation({
     mutationFn: async (data: CreateRequest) => {
-      const res = await advertisementClientManager.get().index.$post({ json: data });
-      if (res.status !== 201) throw new Error('创建广告失败');
+      const res = await systemConfigClientManager.get().index.$post({ json: data });
+      if (res.status !== 201) throw new Error('创建系统配置失败');
       return await res.json();
     },
     onSuccess: () => {
-      toast.success('广告创建成功');
+      toast.success('系统配置创建成功');
       setIsModalOpen(false);
       createForm.reset();
       refetch();
     },
     onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '创建广告失败');
+      toast.error(error instanceof Error ? error.message : '创建系统配置失败');
     }
   });
 
-  // 更新广告
+  // 更新系统配置
   const updateMutation = useMutation({
     mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
-      const res = await advertisementClientManager.get()[':id']['$put']({
+      const res = await systemConfigClientManager.get()[':id']['$put']({
         param: { id },
         json: data
       });
-      if (res.status !== 200) throw new Error('更新广告失败');
+      if (res.status !== 200) throw new Error('更新系统配置失败');
       return await res.json();
     },
     onSuccess: () => {
-      toast.success('广告更新成功');
+      toast.success('系统配置更新成功');
       setIsModalOpen(false);
-      setEditingAdvertisement(null);
+      setEditingSystemConfig(null);
       refetch();
     },
     onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '更新广告失败');
+      toast.error(error instanceof Error ? error.message : '更新系统配置失败');
     }
   });
 
-  // 删除广告
+  // 删除系统配置
   const deleteMutation = useMutation({
     mutationFn: async (id: number) => {
-      const res = await advertisementClientManager.get()[':id']['$delete']({
+      const res = await systemConfigClientManager.get()[':id']['$delete']({
         param: { id }
       });
-      if (res.status !== 204) throw new Error('删除广告失败');
+      if (res.status !== 204) throw new Error('删除系统配置失败');
       return await res.json();
     },
     onSuccess: () => {
-      toast.success('广告删除成功');
+      toast.success('系统配置删除成功');
       setDeleteDialogOpen(false);
-      setAdvertisementToDelete(null);
+      setSystemConfigToDelete(null);
       refetch();
     },
     onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '删除广告失败');
+      toast.error(error instanceof Error ? error.message : '删除系统配置失败');
     }
   });
 
@@ -136,46 +128,41 @@ export const AdvertisementManagement: React.FC = () => {
     refetch();
   };
 
-  // 处理创建广告
-  const handleCreateAdvertisement = () => {
+  // 处理创建系统配置
+  const handleCreateSystemConfig = () => {
     setIsCreateForm(true);
-    setEditingAdvertisement(null);
+    setEditingSystemConfig(null);
     createForm.reset();
     setIsModalOpen(true);
   };
 
-  // 处理编辑广告
-  const handleEditAdvertisement = (advertisement: AdvertisementResponse) => {
+  // 处理编辑系统配置
+  const handleEditSystemConfig = (systemConfig: SystemConfigResponse) => {
     setIsCreateForm(false);
-    setEditingAdvertisement(advertisement);
+    setEditingSystemConfig(systemConfig);
     updateForm.reset({
-      title: advertisement.title || undefined,
-      typeId: advertisement.typeId || undefined,
-      code: advertisement.code || undefined,
-      url: advertisement.url || undefined,
-      imageFileId: advertisement.imageFileId || undefined,
-      sort: advertisement.sort || undefined,
-      status: advertisement.status || undefined,
-      actionType: advertisement.actionType || undefined
+      configKey: systemConfig.configKey || undefined,
+      configValue: systemConfig.configValue || undefined,
+      description: systemConfig.description || undefined
     });
     setIsModalOpen(true);
   };
 
-  // 处理删除广告
-  const handleDeleteAdvertisement = (id: number) => {
-    setAdvertisementToDelete(id);
+  // 处理删除系统配置
+  const handleDeleteSystemConfig = (id: number) => {
+    setSystemConfigToDelete(id);
     setDeleteDialogOpen(true);
   };
 
   // 确认删除
   const confirmDelete = () => {
-    if (advertisementToDelete) {
-      deleteMutation.mutate(advertisementToDelete);
+    if (systemConfigToDelete) {
+      deleteMutation.mutate(systemConfigToDelete);
     }
   };
 
   // 处理创建表单提交
-  const handleCreateSubmit = async (data: CreateRequest) => {
+  const handleCreateSubmit = async (data: SystemConfigFormData) => {
     try {
       await createMutation.mutateAsync(data);
     } catch (error) {
@@ -184,12 +171,12 @@ export const AdvertisementManagement: React.FC = () => {
   };
 
   // 处理编辑表单提交
-  const handleUpdateSubmit = async (data: UpdateRequest) => {
-    if (!editingAdvertisement) return;
+  const handleUpdateSubmit = async (data: SystemConfigFormData) => {
+    if (!editingSystemConfig) return;
 
     try {
       await updateMutation.mutateAsync({
-        id: editingAdvertisement.id,
+        id: editingSystemConfig.id,
         data
       });
     } catch (error) {
@@ -200,17 +187,17 @@ export const AdvertisementManagement: React.FC = () => {
   return (
     <div className="space-y-4">
       <div className="flex justify-between items-center">
-        <h1 className="text-2xl font-bold">广告管理</h1>
-        <Button onClick={handleCreateAdvertisement}>
+        <h1 className="text-2xl font-bold">系统配置管理</h1>
+        <Button onClick={handleCreateSystemConfig}>
           <Plus className="mr-2 h-4 w-4" />
-          创建广告
+          创建配置
         </Button>
       </div>
 
       <Card>
         <CardHeader>
-          <CardTitle>广告列表</CardTitle>
-          <CardDescription>管理网站的所有广告内容</CardDescription>
+          <CardTitle>系统配置列表</CardTitle>
+          <CardDescription>管理系统所有配置项,包括小程序配置、支付参数等</CardDescription>
         </CardHeader>
         <CardContent>
           <div className="mb-4">
@@ -218,7 +205,7 @@ export const AdvertisementManagement: React.FC = () => {
               <div className="relative flex-1 max-w-sm">
                 <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
                 <Input
-                  placeholder="搜索广告标题或别名..."
+                  placeholder="搜索配置键或描述..."
                   value={searchParams.search}
                   onChange={(e) => setSearchParams(prev => ({ ...prev, search: e.target.value }))}
                   className="pl-8"
@@ -237,13 +224,12 @@ export const AdvertisementManagement: React.FC = () => {
                 <TableHeader>
                   <TableRow>
                     <TableHead>ID</TableHead>
-                    <TableHead>标题</TableHead>
-                    <TableHead>类型</TableHead>
-                    <TableHead>别名</TableHead>
-                    <TableHead>图片</TableHead>
-                    <TableHead>状态</TableHead>
-                    <TableHead>排序</TableHead>
+                    <TableHead>租户ID</TableHead>
+                    <TableHead>配置键</TableHead>
+                    <TableHead>配置值</TableHead>
+                    <TableHead>描述</TableHead>
                     <TableHead>创建时间</TableHead>
+                    <TableHead>更新时间</TableHead>
                     <TableHead className="text-right">操作</TableHead>
                   </TableRow>
                 </TableHeader>
@@ -255,22 +241,19 @@ export const AdvertisementManagement: React.FC = () => {
                           <Skeleton className="h-4 w-8" />
                         </TableCell>
                         <TableCell>
-                          <Skeleton className="h-4 w-32" />
-                        </TableCell>
-                        <TableCell>
-                          <Skeleton className="h-4 w-20" />
+                          <Skeleton className="h-4 w-12" />
                         </TableCell>
                         <TableCell>
-                          <Skeleton className="h-4 w-24" />
+                          <Skeleton className="h-4 w-32" />
                         </TableCell>
                         <TableCell>
-                          <Skeleton className="h-8 w-8 rounded" />
+                          <Skeleton className="h-4 w-40" />
                         </TableCell>
                         <TableCell>
-                          <Skeleton className="h-6 w-12 rounded-full" />
+                          <Skeleton className="h-4 w-24" />
                         </TableCell>
                         <TableCell>
-                          <Skeleton className="h-4 w-8" />
+                          <Skeleton className="h-4 w-24" />
                         </TableCell>
                         <TableCell>
                           <Skeleton className="h-4 w-24" />
@@ -284,54 +267,42 @@ export const AdvertisementManagement: React.FC = () => {
                       </TableRow>
                     ))
                   ) : data?.data && data.data.length > 0 ? (
-                    data.data.map((advertisement) => (
-                      <TableRow key={advertisement.id}>
-                        <TableCell>{advertisement.id}</TableCell>
-                        <TableCell>{advertisement.title || '-'}</TableCell>
+                    data.data.map((systemConfig) => (
+                      <TableRow key={systemConfig.id}>
+                        <TableCell>{systemConfig.id}</TableCell>
+                        <TableCell>{systemConfig.tenantId}</TableCell>
                         <TableCell>
-                          {advertisement.advertisementType?.name || '-'}
+                          <code className="text-xs bg-muted px-1 rounded">{systemConfig.configKey}</code>
                         </TableCell>
                         <TableCell>
-                          <code className="text-xs bg-muted px-1 rounded">{advertisement.code || '-'}</code>
+                          <div className="max-w-xs truncate">
+                            {systemConfig.configValue}
+                          </div>
                         </TableCell>
                         <TableCell>
-                          {advertisement.imageFile?.fullUrl ? (
-                            <img
-                              src={advertisement.imageFile.fullUrl}
-                              alt={advertisement.title || '广告图片'}
-                              className="w-16 h-10 object-cover rounded"
-                              onError={(e) => {
-                                e.currentTarget.src = '/placeholder.png';
-                              }}
-                            />
-                          ) : (
-                            <span className="text-muted-foreground text-xs">无图片</span>
-                          )}
+                          {systemConfig.description || '-'}
                         </TableCell>
                         <TableCell>
-                          <Badge variant={advertisement.status === 1 ? 'default' : 'secondary'}>
-                            {advertisement.status === 1 ? '启用' : '禁用'}
-                          </Badge>
+                          {systemConfig.createdAt ? format(new Date(systemConfig.createdAt), 'yyyy-MM-dd HH:mm') : '-'}
                         </TableCell>
-                        <TableCell>{advertisement.sort}</TableCell>
                         <TableCell>
-                          {advertisement.createdAt ? format(new Date(advertisement.createdAt), 'yyyy-MM-dd HH:mm') : '-'}
+                          {systemConfig.updatedAt ? format(new Date(systemConfig.updatedAt), 'yyyy-MM-dd HH:mm') : '-'}
                         </TableCell>
                         <TableCell className="text-right">
                           <div className="flex justify-end gap-2">
                             <Button
                               variant="ghost"
                               size="icon"
-                              onClick={() => handleEditAdvertisement(advertisement)}
-                              data-testid={`edit-button-${advertisement.id}`}
+                              onClick={() => handleEditSystemConfig(systemConfig)}
+                              data-testid={`edit-button-${systemConfig.id}`}
                             >
                               <Edit className="h-4 w-4" />
                             </Button>
                             <Button
                               variant="ghost"
                               size="icon"
-                              onClick={() => handleDeleteAdvertisement(advertisement.id)}
-                              data-testid={`delete-button-${advertisement.id}`}
+                              onClick={() => handleDeleteSystemConfig(systemConfig.id)}
+                              data-testid={`delete-button-${systemConfig.id}`}
                             >
                               <Trash2 className="h-4 w-4" />
                             </Button>
@@ -341,8 +312,8 @@ export const AdvertisementManagement: React.FC = () => {
                     ))
                   ) : (
                     <TableRow>
-                      <TableCell colSpan={9} className="text-center py-8">
-                        <p className="text-muted-foreground">暂无广告数据</p>
+                      <TableCell colSpan={8} className="text-center py-8">
+                        <p className="text-muted-foreground">暂无系统配置数据</p>
                       </TableCell>
                     </TableRow>
                   )}
@@ -351,23 +322,22 @@ export const AdvertisementManagement: React.FC = () => {
             </div>
           </div>
 
-
-        <DataTablePagination
-          currentPage={searchParams.page}
-          pageSize={searchParams.limit}
-          totalCount={data?.pagination.total || 0}
-          onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
-        />
-      </CardContent>
-    </Card>
-
-    {/* 创建/编辑对话框 */}
-    <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
+          <DataTablePagination
+            currentPage={searchParams.page}
+            pageSize={searchParams.limit}
+            totalCount={data?.pagination.total || 0}
+            onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
+          />
+        </CardContent>
+      </Card>
+
+      {/* 创建/编辑对话框 */}
+      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
         <DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
           <DialogHeader>
-            <DialogTitle>{isCreateForm ? '创建广告' : '编辑广告'}</DialogTitle>
+            <DialogTitle>{isCreateForm ? '创建系统配置' : '编辑系统配置'}</DialogTitle>
             <DialogDescription>
-              {isCreateForm ? '创建一个新的广告' : '编辑现有广告信息'}
+              {isCreateForm ? '创建一个新的系统配置项' : '编辑现有系统配置信息'}
             </DialogDescription>
           </DialogHeader>
 
@@ -377,37 +347,20 @@ export const AdvertisementManagement: React.FC = () => {
               <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
                 <FormField
                   control={createForm.control}
-                  name="title"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        标题 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入广告标题" {...field} data-testid="title-input" />
-                      </FormControl>
-                      <FormDescription>广告显示的标题文本,最多30个字符</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="typeId"
+                  name="configKey"
                   render={({ field }) => (
                     <FormItem>
                       <FormLabel className="flex items-center">
-                        广告类型 <span className="text-red-500 ml-1">*</span>
+                        配置键 <span className="text-red-500 ml-1">*</span>
                       </FormLabel>
                       <FormControl>
-                        <AdvertisementTypeSelector
-                          value={field.value}
-                          onChange={field.onChange}
-                          placeholder="请选择广告类型"
-                          testId="type-selector"
+                        <Input
+                          placeholder="请输入配置键,如:app.login.enabled"
+                          {...field}
+                          data-testid="config-key-input"
                         />
                       </FormControl>
+                      <FormDescription>配置项的唯一标识,使用点分隔符命名,如:app.login.enabled</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
@@ -415,41 +368,20 @@ export const AdvertisementManagement: React.FC = () => {
 
                 <FormField
                   control={createForm.control}
-                  name="code"
+                  name="configValue"
                   render={({ field }) => (
                     <FormItem>
                       <FormLabel className="flex items-center">
-                        调用别名 <span className="text-red-500 ml-1">*</span>
+                        配置值 <span className="text-red-500 ml-1">*</span>
                       </FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入调用别名" {...field} data-testid="code-input" />
-                      </FormControl>
-                      <FormDescription>用于程序调用的唯一标识,最多20个字符</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="imageFileId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>广告图片</FormLabel>
-                      <FormControl>
-                        <FileSelector
-                          value={field.value || undefined}
-                          onChange={field.onChange}
-                          maxSize={2}
-                          uploadPath="/advertisements"
-                          previewSize="medium"
-                          placeholder="选择广告图片"
-                          title="选择广告图片"
-                          description="上传新图片或从已有图片中选择"
-                          filterType="image"
+                        <Input
+                          placeholder="请输入配置值"
+                          {...field}
+                          data-testid="config-value-input"
                         />
                       </FormControl>
-                      <FormDescription>推荐尺寸:1200x400px,支持jpg、png格式</FormDescription>
+                      <FormDescription>配置项的具体值,可以是字符串、数字或布尔值</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
@@ -457,84 +389,18 @@ export const AdvertisementManagement: React.FC = () => {
 
                 <FormField
                   control={createForm.control}
-                  name="url"
+                  name="description"
                   render={({ field }) => (
                     <FormItem>
-                      <FormLabel>跳转链接</FormLabel>
+                      <FormLabel>配置描述</FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入跳转链接" {...field} data-testid="url-input" />
-                      </FormControl>
-                      <FormDescription>点击广告后跳转的URL地址</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <div className="grid grid-cols-2 gap-4">
-                  <FormField
-                    control={createForm.control}
-                    name="actionType"
-                    render={({ field }) => (
-                      <FormItem>
-                        <FormLabel>跳转类型</FormLabel>
-                        <FormControl>
-                          <select
-                            {...field}
-                            className="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
-                            value={field.value || 1}
-                            onChange={(e) => field.onChange(parseInt(e.target.value))}
-                            data-testid="action-type-select"
-                          >
-                            <option value={0}>不跳转</option>
-                            <option value={1}>Web页面</option>
-                            <option value={2}>小程序页面</option>
-                          </select>
-                        </FormControl>
-                        <FormMessage />
-                      </FormItem>
-                    )}
-                  />
-
-                  <FormField
-                    control={createForm.control}
-                    name="sort"
-                    render={({ field }) => (
-                      <FormItem>
-                        <FormLabel>排序值</FormLabel>
-                        <FormControl>
-                          <Input
-                            type="number"
-                            placeholder="排序值"
-                            {...field}
-                            onChange={(e) => field.onChange(parseInt(e.target.value))}
-                            data-testid="sort-input"
-                          />
-                        </FormControl>
-                        <FormDescription>数值越大排序越靠前</FormDescription>
-                        <FormMessage />
-                      </FormItem>
-                    )}
-                  />
-                </div>
-
-                <FormField
-                  control={createForm.control}
-                  name="status"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>状态</FormLabel>
-                      <FormControl>
-                        <select
+                        <Input
+                          placeholder="请输入配置描述"
                           {...field}
-                          className="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
-                          value={field.value || 1}
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                          data-testid="status-select"
-                        >
-                          <option value={1}>启用</option>
-                          <option value={0}>禁用</option>
-                        </select>
+                          data-testid="description-input"
+                        />
                       </FormControl>
+                      <FormDescription>配置项的详细说明和用途描述</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
@@ -556,36 +422,20 @@ export const AdvertisementManagement: React.FC = () => {
               <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
                 <FormField
                   control={updateForm.control}
-                  name="title"
+                  name="configKey"
                   render={({ field }) => (
                     <FormItem>
                       <FormLabel className="flex items-center">
-                        标题 <span className="text-red-500 ml-1">*</span>
+                        配置键 <span className="text-red-500 ml-1">*</span>
                       </FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入广告标题" {...field} data-testid="title-input" />
-                      </FormControl>
-                      <FormDescription>广告显示的标题文本,最多30个字符</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="typeId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        广告类型 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <AdvertisementTypeSelector
-                          value={field.value}
-                          onChange={field.onChange}
-                          testId="type-selector"
+                        <Input
+                          placeholder="请输入配置键"
+                          {...field}
+                          data-testid="config-key-input"
                         />
                       </FormControl>
+                      <FormDescription>配置项的唯一标识,使用点分隔符命名</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
@@ -593,126 +443,39 @@ export const AdvertisementManagement: React.FC = () => {
 
                 <FormField
                   control={updateForm.control}
-                  name="code"
+                  name="configValue"
                   render={({ field }) => (
                     <FormItem>
                       <FormLabel className="flex items-center">
-                        调用别名 <span className="text-red-500 ml-1">*</span>
+                        配置值 <span className="text-red-500 ml-1">*</span>
                       </FormLabel>
                       <FormControl>
-                        <Input placeholder="请输入调用别名" {...field} data-testid="code-input" />
-                      </FormControl>
-                      <FormDescription>用于程序调用的唯一标识,最多20个字符</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="imageFileId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>广告图片</FormLabel>
-                      <FormControl>
-                        <FileSelector
-                          value={field.value || undefined}
-                          onChange={field.onChange}
-                          maxSize={2}
-                          uploadPath="/advertisements"
-                          previewSize="medium"
-                          placeholder="选择广告图片"
-                          title="选择广告图片"
-                          description="上传新图片或从已有图片中选择"
-                          filterType="image"
+                        <Input
+                          placeholder="请输入配置值"
+                          {...field}
+                          data-testid="config-value-input"
                         />
                       </FormControl>
-                      <FormDescription>推荐尺寸:1200x400px,支持jpg、png格式</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="url"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>跳转链接</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入跳转链接" {...field} data-testid="url-input" />
-                      </FormControl>
-                      <FormDescription>点击广告后跳转的URL地址</FormDescription>
+                      <FormDescription>配置项的具体值,可以是字符串、数字或布尔值</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
                 />
 
-                <div className="grid grid-cols-2 gap-4">
-                  <FormField
-                    control={updateForm.control}
-                    name="actionType"
-                    render={({ field }) => (
-                      <FormItem>
-                        <FormLabel>跳转类型</FormLabel>
-                        <FormControl>
-                          <select
-                            {...field}
-                            className="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
-                            value={field.value || 1}
-                            onChange={(e) => field.onChange(parseInt(e.target.value))}
-                            data-testid="action-type-select"
-                          >
-                            <option value={0}>不跳转</option>
-                            <option value={1}>Web页面</option>
-                            <option value={2}>小程序页面</option>
-                          </select>
-                        </FormControl>
-                        <FormMessage />
-                      </FormItem>
-                    )}
-                  />
-
-                  <FormField
-                    control={updateForm.control}
-                    name="sort"
-                    render={({ field }) => (
-                      <FormItem>
-                        <FormLabel>排序值</FormLabel>
-                        <FormControl>
-                          <Input
-                            type="number"
-                            placeholder="排序值"
-                            {...field}
-                            onChange={(e) => field.onChange(parseInt(e.target.value))}
-                            data-testid="sort-input"
-                          />
-                        </FormControl>
-                        <FormDescription>数值越大排序越靠前</FormDescription>
-                        <FormMessage />
-                      </FormItem>
-                    )}
-                  />
-                </div>
-
                 <FormField
                   control={updateForm.control}
-                  name="status"
+                  name="description"
                   render={({ field }) => (
                     <FormItem>
-                      <FormLabel>状态</FormLabel>
+                      <FormLabel>配置描述</FormLabel>
                       <FormControl>
-                        <select
+                        <Input
+                          placeholder="请输入配置描述"
                           {...field}
-                          className="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
-                          value={field.value || 1}
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                          data-testid="status-select"
-                        >
-                          <option value={1}>启用</option>
-                          <option value={0}>禁用</option>
-                        </select>
+                          data-testid="description-input"
+                        />
                       </FormControl>
+                      <FormDescription>配置项的详细说明和用途描述</FormDescription>
                       <FormMessage />
                     </FormItem>
                   )}
@@ -738,7 +501,7 @@ export const AdvertisementManagement: React.FC = () => {
           <DialogHeader>
             <DialogTitle>确认删除</DialogTitle>
             <DialogDescription>
-              确定要删除这个广告吗?此操作无法撤销。
+              确定要删除这个系统配置吗?此操作无法撤销。
             </DialogDescription>
           </DialogHeader>
           <DialogFooter>
@@ -760,4 +523,4 @@ export const AdvertisementManagement: React.FC = () => {
   );
 };
 
-export default AdvertisementManagement;
+export default SystemConfigManagement;

+ 10 - 15
packages/system-config-management-ui-mt/src/types/systemConfig.ts

@@ -1,23 +1,18 @@
 import type { InferRequestType, InferResponseType } from 'hono/client';
-import { advertisementClient } from '../api/advertisementClient';
+import { systemConfigClient } from '../api/systemConfigClient';
 
-export type CreateAdvertisementRequest = InferRequestType<typeof advertisementClient.index.$post>['json'];
-export type UpdateAdvertisementRequest = InferRequestType<typeof advertisementClient[':id']['$put']>['json'];
-export type AdvertisementResponse = InferResponseType<typeof advertisementClient.index.$get, 200>['data'][0];
-export type AdvertisementListResponse = InferResponseType<typeof advertisementClient.index.$get, 200>;
+export type CreateSystemConfigRequest = InferRequestType<typeof systemConfigClient.index.$post>['json'];
+export type UpdateSystemConfigRequest = InferRequestType<typeof systemConfigClient[':id']['$put']>['json'];
+export type SystemConfigResponse = InferResponseType<typeof systemConfigClient.index.$get, 200>['data'][0];
+export type SystemConfigListResponse = InferResponseType<typeof systemConfigClient.index.$get, 200>;
 
-export interface AdvertisementFormData {
-  title: string;
-  typeId: number;
-  code: string;
-  url?: string;
-  imageFileId?: number;
-  sort: number;
-  status: number;
-  actionType: number;
+export interface SystemConfigFormData {
+  configKey: string;
+  configValue: string;
+  description?: string;
 }
 
-export interface AdvertisementSearchParams {
+export interface SystemConfigSearchParams {
   page: number;
   limit: number;
   search: string;

+ 91 - 0
pnpm-lock.yaml

@@ -4000,6 +4000,97 @@ importers:
         specifier: ^3.2.4
         version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
 
+  packages/system-config-management-ui-mt:
+    dependencies:
+      '@d8d/core-module-mt':
+        specifier: workspace:*
+        version: link:../core-module-mt
+      '@d8d/shared-types':
+        specifier: workspace:*
+        version: link:../shared-types
+      '@d8d/shared-ui-components':
+        specifier: workspace:*
+        version: link:../shared-ui-components
+      '@hookform/resolvers':
+        specifier: ^5.2.1
+        version: 5.2.2(react-hook-form@7.65.0(react@19.2.0))
+      '@tanstack/react-query':
+        specifier: ^5.90.9
+        version: 5.90.9(react@19.2.0)
+      class-variance-authority:
+        specifier: ^0.7.1
+        version: 0.7.1
+      clsx:
+        specifier: ^2.1.1
+        version: 2.1.1
+      date-fns:
+        specifier: ^4.1.0
+        version: 4.1.0
+      hono:
+        specifier: ^4.8.5
+        version: 4.8.5
+      lucide-react:
+        specifier: ^0.536.0
+        version: 0.536.0(react@19.2.0)
+      react:
+        specifier: ^19.1.0
+        version: 19.2.0
+      react-dom:
+        specifier: ^19.1.0
+        version: 19.2.0(react@19.2.0)
+      react-hook-form:
+        specifier: ^7.61.1
+        version: 7.65.0(react@19.2.0)
+      sonner:
+        specifier: ^2.0.7
+        version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      tailwind-merge:
+        specifier: ^3.3.1
+        version: 3.3.1
+      zod:
+        specifier: ^4.0.15
+        version: 4.1.12
+    devDependencies:
+      '@testing-library/jest-dom':
+        specifier: ^6.8.0
+        version: 6.9.1
+      '@testing-library/react':
+        specifier: ^16.3.0
+        version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      '@testing-library/user-event':
+        specifier: ^14.6.1
+        version: 14.6.1(@testing-library/dom@10.4.1)
+      '@types/node':
+        specifier: ^22.10.2
+        version: 22.19.0
+      '@types/react':
+        specifier: ^19.2.2
+        version: 19.2.2
+      '@types/react-dom':
+        specifier: ^19.2.3
+        version: 19.2.3(@types/react@19.2.2)
+      '@typescript-eslint/eslint-plugin':
+        specifier: ^8.18.1
+        version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      '@typescript-eslint/parser':
+        specifier: ^8.18.1
+        version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      eslint:
+        specifier: ^9.17.0
+        version: 9.38.0(jiti@2.6.1)
+      jsdom:
+        specifier: ^26.0.0
+        version: 26.1.0
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      unbuild:
+        specifier: ^3.4.0
+        version: 3.6.1(sass@1.93.2)(typescript@5.8.3)(vue@3.5.22(typescript@5.8.3))
+      vitest:
+        specifier: ^4.0.9
+        version: 4.0.9(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
+
   packages/tenant-management-ui:
     dependencies:
       '@d8d/shared-ui-components':