|
@@ -0,0 +1,928 @@
|
|
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
|
|
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
+import { toast } from 'sonner';
|
|
|
|
|
+import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
|
|
+import { useForm } from 'react-hook-form';
|
|
|
|
|
+import { z } from 'zod';
|
|
|
|
|
+import {
|
|
|
|
|
+ Settings,
|
|
|
|
|
+ Save,
|
|
|
|
|
+ RefreshCw,
|
|
|
|
|
+ AlertCircle,
|
|
|
|
|
+ CheckCircle,
|
|
|
|
|
+ XCircle,
|
|
|
|
|
+ Clock,
|
|
|
|
|
+ Repeat,
|
|
|
|
|
+ Timer,
|
|
|
|
|
+ FileText,
|
|
|
|
|
+ Truck
|
|
|
|
|
+} from 'lucide-react';
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ FeieConfig,
|
|
|
|
|
+ ConfigKey,
|
|
|
|
|
+ ConfigType,
|
|
|
|
|
+ UpdateConfigRequest,
|
|
|
|
|
+ ConfigListResponse
|
|
|
|
|
+} from '../types/feiePrinter';
|
|
|
|
|
+import { createFeiePrinterClient } from '../api/feiePrinterClient';
|
|
|
|
|
+import {
|
|
|
|
|
+ Button,
|
|
|
|
|
+ Card,
|
|
|
|
|
+ CardContent,
|
|
|
|
|
+ CardDescription,
|
|
|
|
|
+ CardHeader,
|
|
|
|
|
+ CardTitle,
|
|
|
|
|
+ Table,
|
|
|
|
|
+ TableBody,
|
|
|
|
|
+ TableCell,
|
|
|
|
|
+ TableHead,
|
|
|
|
|
+ TableHeader,
|
|
|
|
|
+ TableRow,
|
|
|
|
|
+ Badge,
|
|
|
|
|
+ Dialog,
|
|
|
|
|
+ DialogContent,
|
|
|
|
|
+ DialogDescription,
|
|
|
|
|
+ DialogFooter,
|
|
|
|
|
+ DialogHeader,
|
|
|
|
|
+ DialogTitle,
|
|
|
|
|
+ DialogTrigger,
|
|
|
|
|
+ Form,
|
|
|
|
|
+ FormControl,
|
|
|
|
|
+ FormDescription,
|
|
|
|
|
+ FormField,
|
|
|
|
|
+ FormItem,
|
|
|
|
|
+ FormLabel,
|
|
|
|
|
+ FormMessage,
|
|
|
|
|
+ Input,
|
|
|
|
|
+ Select,
|
|
|
|
|
+ SelectContent,
|
|
|
|
|
+ SelectItem,
|
|
|
|
|
+ SelectTrigger,
|
|
|
|
|
+ SelectValue,
|
|
|
|
|
+ Switch,
|
|
|
|
|
+ Textarea,
|
|
|
|
|
+ Tabs,
|
|
|
|
|
+ TabsContent,
|
|
|
|
|
+ TabsList,
|
|
|
|
|
+ TabsTrigger
|
|
|
|
|
+} from '@d8d/shared-ui-components';
|
|
|
|
|
+
|
|
|
|
|
+// 配置分组
|
|
|
|
|
+enum ConfigGroup {
|
|
|
|
|
+ BASIC = 'basic',
|
|
|
|
|
+ PRINT_POLICY = 'print_policy',
|
|
|
|
|
+ TEMPLATE = 'template'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 配置项定义
|
|
|
|
|
+interface ConfigItemDefinition {
|
|
|
|
|
+ key: ConfigKey;
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ description: string;
|
|
|
|
|
+ type: ConfigType;
|
|
|
|
|
+ defaultValue: string;
|
|
|
|
|
+ group: ConfigGroup;
|
|
|
|
|
+ validation?: z.ZodType<any>;
|
|
|
|
|
+ component?: 'input' | 'textarea' | 'switch' | 'select' | 'number';
|
|
|
|
|
+ options?: { value: string; label: string }[];
|
|
|
|
|
+ min?: number;
|
|
|
|
|
+ max?: number;
|
|
|
|
|
+ step?: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 配置项定义映射
|
|
|
|
|
+const configDefinitions: ConfigItemDefinition[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.ENABLED,
|
|
|
|
|
+ label: '启用飞鹅打印',
|
|
|
|
|
+ description: '是否启用飞鹅打印功能',
|
|
|
|
|
+ type: ConfigType.BOOLEAN,
|
|
|
|
|
+ defaultValue: 'true',
|
|
|
|
|
+ group: ConfigGroup.BASIC,
|
|
|
|
|
+ component: 'switch'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.DEFAULT_PRINTER_SN,
|
|
|
|
|
+ label: '默认打印机序列号',
|
|
|
|
|
+ description: '默认使用的打印机序列号',
|
|
|
|
|
+ type: ConfigType.STRING,
|
|
|
|
|
+ defaultValue: '',
|
|
|
|
|
+ group: ConfigGroup.BASIC,
|
|
|
|
|
+ component: 'input'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.AUTO_PRINT_ON_PAYMENT,
|
|
|
|
|
+ label: '支付成功时自动打印',
|
|
|
|
|
+ description: '订单支付成功后是否自动打印小票',
|
|
|
|
|
+ type: ConfigType.BOOLEAN,
|
|
|
|
|
+ defaultValue: 'true',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'switch'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.AUTO_PRINT_ON_SHIPPING,
|
|
|
|
|
+ label: '发货时自动打印',
|
|
|
|
|
+ description: '订单发货时是否自动打印发货单',
|
|
|
|
|
+ type: ConfigType.BOOLEAN,
|
|
|
|
|
+ defaultValue: 'true',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'switch'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.ANTI_REFUND_DELAY,
|
|
|
|
|
+ label: '防退款延迟时间(秒)',
|
|
|
|
|
+ description: '支付成功后等待确认无退款的时间,默认120秒(2分钟)',
|
|
|
|
|
+ type: ConfigType.NUMBER,
|
|
|
|
|
+ defaultValue: '120',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'number',
|
|
|
|
|
+ min: 0,
|
|
|
|
|
+ max: 600,
|
|
|
|
|
+ step: 10
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.RETRY_MAX_COUNT,
|
|
|
|
|
+ label: '最大重试次数',
|
|
|
|
|
+ description: '打印失败时的最大重试次数',
|
|
|
|
|
+ type: ConfigType.NUMBER,
|
|
|
|
|
+ defaultValue: '3',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'number',
|
|
|
|
|
+ min: 0,
|
|
|
|
|
+ max: 10,
|
|
|
|
|
+ step: 1
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.RETRY_INTERVAL,
|
|
|
|
|
+ label: '重试间隔(秒)',
|
|
|
|
|
+ description: '打印失败后重试的间隔时间',
|
|
|
|
|
+ type: ConfigType.NUMBER,
|
|
|
|
|
+ defaultValue: '30',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'number',
|
|
|
|
|
+ min: 5,
|
|
|
|
|
+ max: 300,
|
|
|
|
|
+ step: 5
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.TASK_TIMEOUT,
|
|
|
|
|
+ label: '任务超时时间(秒)',
|
|
|
|
|
+ description: '打印任务的最大执行时间,超时后自动取消',
|
|
|
|
|
+ type: ConfigType.NUMBER,
|
|
|
|
|
+ defaultValue: '300',
|
|
|
|
|
+ group: ConfigGroup.PRINT_POLICY,
|
|
|
|
|
+ component: 'number',
|
|
|
|
|
+ min: 30,
|
|
|
|
|
+ max: 1800,
|
|
|
|
|
+ step: 30
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.RECEIPT_TEMPLATE,
|
|
|
|
|
+ label: '小票模板',
|
|
|
|
|
+ description: '小票打印的模板内容,支持变量替换',
|
|
|
|
|
+ type: ConfigType.STRING,
|
|
|
|
|
+ defaultValue: `订单号: {orderNo}
|
|
|
|
|
+时间: {orderTime}
|
|
|
|
|
+商品: {goodsList}
|
|
|
|
|
+合计: {totalAmount}
|
|
|
|
|
+地址: {address}
|
|
|
|
|
+联系电话: {phone}`,
|
|
|
|
|
+ group: ConfigGroup.TEMPLATE,
|
|
|
|
|
+ component: 'textarea'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ key: ConfigKey.SHIPPING_TEMPLATE,
|
|
|
|
|
+ label: '发货单模板',
|
|
|
|
|
+ description: '发货单打印的模板内容,支持变量替换',
|
|
|
|
|
+ type: ConfigType.STRING,
|
|
|
|
|
+ defaultValue: `发货单
|
|
|
|
|
+订单号: {orderNo}
|
|
|
|
|
+发货时间: {shippingTime}
|
|
|
|
|
+收货人: {receiver}
|
|
|
|
|
+地址: {address}
|
|
|
|
|
+联系电话: {phone}
|
|
|
|
|
+商品列表:
|
|
|
|
|
+{goodsList}`,
|
|
|
|
|
+ group: ConfigGroup.TEMPLATE,
|
|
|
|
|
+ component: 'textarea'
|
|
|
|
|
+ }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// 配置分组标签映射
|
|
|
|
|
+const groupLabelMap: Record<ConfigGroup, string> = {
|
|
|
|
|
+ [ConfigGroup.BASIC]: '基础配置',
|
|
|
|
|
+ [ConfigGroup.PRINT_POLICY]: '打印策略',
|
|
|
|
|
+ [ConfigGroup.TEMPLATE]: '模板配置'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 配置分组图标映射
|
|
|
|
|
+const groupIconMap: Record<ConfigGroup, React.ReactNode> = {
|
|
|
|
|
+ [ConfigGroup.BASIC]: <Settings className="h-4 w-4" />,
|
|
|
|
|
+ [ConfigGroup.PRINT_POLICY]: <Timer className="h-4 w-4" />,
|
|
|
|
|
+ [ConfigGroup.TEMPLATE]: <FileText className="h-4 w-4" />
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 配置类型标签映射
|
|
|
|
|
+const typeLabelMap: Record<ConfigType, string> = {
|
|
|
|
|
+ [ConfigType.STRING]: '字符串',
|
|
|
|
|
+ [ConfigType.JSON]: 'JSON',
|
|
|
|
|
+ [ConfigType.BOOLEAN]: '布尔值',
|
|
|
|
|
+ [ConfigType.NUMBER]: '数字'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 配置类型颜色映射
|
|
|
|
|
+const typeColorMap: Record<ConfigType, string> = {
|
|
|
|
|
+ [ConfigType.STRING]: 'bg-blue-100 text-blue-800 border-blue-200',
|
|
|
|
|
+ [ConfigType.JSON]: 'bg-purple-100 text-purple-800 border-purple-200',
|
|
|
|
|
+ [ConfigType.BOOLEAN]: 'bg-green-100 text-green-800 border-green-200',
|
|
|
|
|
+ [ConfigType.NUMBER]: 'bg-orange-100 text-orange-800 border-orange-200'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 更新配置表单验证模式
|
|
|
|
|
+const updateConfigSchema = z.object({
|
|
|
|
|
+ configValue: z.string().min(1, '配置值不能为空')
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+type UpdateConfigFormValues = z.infer<typeof updateConfigSchema>;
|
|
|
|
|
+
|
|
|
|
|
+interface PrintConfigManagementProps {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * API基础URL
|
|
|
|
|
+ * @default '/api'
|
|
|
|
|
+ */
|
|
|
|
|
+ baseURL?: string;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 租户ID
|
|
|
|
|
+ */
|
|
|
|
|
+ tenantId?: number;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 认证token
|
|
|
|
|
+ */
|
|
|
|
|
+ authToken?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 打印配置管理组件
|
|
|
|
|
+ * 提供打印配置的查询、编辑和管理功能
|
|
|
|
|
+ */
|
|
|
|
|
+export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
|
|
|
|
|
+ baseURL = '/api',
|
|
|
|
|
+ tenantId,
|
|
|
|
|
+ authToken
|
|
|
|
|
+}) => {
|
|
|
|
|
+ const [activeGroup, setActiveGroup] = useState<ConfigGroup>(ConfigGroup.BASIC);
|
|
|
|
|
+ const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
|
|
|
|
+ const [selectedConfig, setSelectedConfig] = useState<FeieConfig | null>(null);
|
|
|
|
|
+ const [configMap, setConfigMap] = useState<Record<string, FeieConfig>>({});
|
|
|
|
|
+
|
|
|
|
|
+ const queryClient = useQueryClient();
|
|
|
|
|
+ const feieClient = createFeiePrinterClient(baseURL);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置认证和租户信息
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (authToken) {
|
|
|
|
|
+ feieClient.setAuthToken(authToken);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (tenantId) {
|
|
|
|
|
+ feieClient.setTenantId(tenantId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [authToken, tenantId, feieClient]);
|
|
|
|
|
+
|
|
|
|
|
+ // 查询配置列表
|
|
|
|
|
+ const {
|
|
|
|
|
+ data: configList,
|
|
|
|
|
+ isLoading,
|
|
|
|
|
+ isError,
|
|
|
|
|
+ refetch
|
|
|
|
|
+ } = useQuery({
|
|
|
|
|
+ queryKey: ['printConfigs', tenantId],
|
|
|
|
|
+ queryFn: () => feieClient.getPrintConfigs(),
|
|
|
|
|
+ enabled: !!tenantId,
|
|
|
|
|
+ onSuccess: (data) => {
|
|
|
|
|
+ // 将配置列表转换为映射
|
|
|
|
|
+ const map: Record<string, FeieConfig> = {};
|
|
|
|
|
+ data.data.forEach((config) => {
|
|
|
|
|
+ map[config.configKey] = config;
|
|
|
|
|
+ });
|
|
|
|
|
+ setConfigMap(map);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新配置表单
|
|
|
|
|
+ const updateForm = useForm<UpdateConfigFormValues>({
|
|
|
|
|
+ resolver: zodResolver(updateConfigSchema),
|
|
|
|
|
+ defaultValues: {
|
|
|
|
|
+ configValue: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新配置Mutation
|
|
|
|
|
+ const updateConfigMutation = useMutation({
|
|
|
|
|
+ mutationFn: ({ configKey, data }: { configKey: string; data: UpdateConfigRequest }) =>
|
|
|
|
|
+ feieClient.updatePrintConfig(configKey, data),
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ toast.success('配置更新成功');
|
|
|
|
|
+ setIsEditDialogOpen(false);
|
|
|
|
|
+ setSelectedConfig(null);
|
|
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error: Error) => {
|
|
|
|
|
+ toast.error(`更新配置失败: ${error.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 批量更新配置Mutation
|
|
|
|
|
+ const batchUpdateConfigMutation = useMutation({
|
|
|
|
|
+ mutationFn: async (updates: Array<{ configKey: string; data: UpdateConfigRequest }>) => {
|
|
|
|
|
+ const promises = updates.map(({ configKey, data }) =>
|
|
|
|
|
+ feieClient.updatePrintConfig(configKey, data)
|
|
|
|
|
+ );
|
|
|
|
|
+ await Promise.all(promises);
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ toast.success('批量更新配置成功');
|
|
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error: Error) => {
|
|
|
|
|
+ toast.error(`批量更新配置失败: ${error.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 重置配置Mutation
|
|
|
|
|
+ const resetConfigMutation = useMutation({
|
|
|
|
|
+ mutationFn: async (configKey: string) => {
|
|
|
|
|
+ const definition = configDefinitions.find(def => def.key === configKey);
|
|
|
|
|
+ if (!definition) throw new Error('配置定义不存在');
|
|
|
|
|
+
|
|
|
|
|
+ return feieClient.updatePrintConfig(configKey, {
|
|
|
|
|
+ configValue: definition.defaultValue
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ toast.success('配置重置成功');
|
|
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error: Error) => {
|
|
|
|
|
+ toast.error(`重置配置失败: ${error.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 处理更新配置
|
|
|
|
|
+ const handleUpdateConfig = (data: UpdateConfigFormValues) => {
|
|
|
|
|
+ if (!selectedConfig) return;
|
|
|
|
|
+ updateConfigMutation.mutate({
|
|
|
|
|
+ configKey: selectedConfig.configKey,
|
|
|
|
|
+ data
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 打开编辑对话框
|
|
|
|
|
+ const openEditDialog = (config: FeieConfig) => {
|
|
|
|
|
+ setSelectedConfig(config);
|
|
|
|
|
+ updateForm.reset({
|
|
|
|
|
+ configValue: config.configValue
|
|
|
|
|
+ });
|
|
|
|
|
+ setIsEditDialogOpen(true);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理批量保存
|
|
|
|
|
+ const handleBatchSave = () => {
|
|
|
|
|
+ const updates: Array<{ configKey: string; data: UpdateConfigRequest }> = [];
|
|
|
|
|
+
|
|
|
|
|
+ configDefinitions.forEach(definition => {
|
|
|
|
|
+ const currentConfig = configMap[definition.key];
|
|
|
|
|
+ if (currentConfig) {
|
|
|
|
|
+ // 这里可以添加逻辑来获取修改后的值
|
|
|
|
|
+ // 目前只是保存当前值
|
|
|
|
|
+ updates.push({
|
|
|
|
|
+ configKey: definition.key,
|
|
|
|
|
+ data: { configValue: currentConfig.configValue }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (updates.length > 0) {
|
|
|
|
|
+ batchUpdateConfigMutation.mutate(updates);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理重置配置
|
|
|
|
|
+ const handleResetConfig = (configKey: string) => {
|
|
|
|
|
+ resetConfigMutation.mutate(configKey);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 获取配置值显示
|
|
|
|
|
+ const getConfigValueDisplay = (config: FeieConfig): string => {
|
|
|
|
|
+ if (config.configType === ConfigType.BOOLEAN) {
|
|
|
|
|
+ return config.configValue === 'true' ? '是' : '否';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (config.configType === ConfigType.JSON) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const parsed = JSON.parse(config.configValue);
|
|
|
|
|
+ return JSON.stringify(parsed, null, 2);
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return config.configValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return config.configValue;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前分组的配置定义
|
|
|
|
|
+ const currentGroupDefinitions = configDefinitions.filter(
|
|
|
|
|
+ def => def.group === activeGroup
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 获取配置值
|
|
|
|
|
+ const getConfigValue = (configKey: string): string => {
|
|
|
|
|
+ const config = configMap[configKey];
|
|
|
|
|
+ if (config) return config.configValue;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果配置不存在,返回默认值
|
|
|
|
|
+ const definition = configDefinitions.find(def => def.key === configKey);
|
|
|
|
|
+ return definition?.defaultValue || '';
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 渲染配置项组件
|
|
|
|
|
+ const renderConfigItem = (definition: ConfigItemDefinition) => {
|
|
|
|
|
+ const config = configMap[definition.key];
|
|
|
|
|
+ const value = getConfigValue(definition.key);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div key={definition.key} className="space-y-2">
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h4 className="font-medium">{definition.label}</h4>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{definition.description}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Badge variant="outline" className={typeColorMap[definition.type]}>
|
|
|
|
|
+ {typeLabelMap[definition.type]}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => handleResetConfig(definition.key)}
|
|
|
|
|
+ disabled={resetConfigMutation.isPending}
|
|
|
|
|
+ title="重置为默认值"
|
|
|
|
|
+ >
|
|
|
|
|
+ <RefreshCw className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {definition.component === 'switch' ? (
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Switch
|
|
|
|
|
+ checked={value === 'true'}
|
|
|
|
|
+ onCheckedChange={(checked) => {
|
|
|
|
|
+ // 这里可以添加即时保存逻辑
|
|
|
|
|
+ const newConfig = { ...config, configValue: checked.toString() };
|
|
|
|
|
+ setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ <span className="text-sm">{value === 'true' ? '已启用' : '已禁用'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : definition.component === 'textarea' ? (
|
|
|
|
|
+ <Textarea
|
|
|
|
|
+ value={value}
|
|
|
|
|
+ onChange={(e) => {
|
|
|
|
|
+ const newConfig = { ...config, configValue: e.target.value };
|
|
|
|
|
+ setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
|
|
|
|
|
+ }}
|
|
|
|
|
+ rows={6}
|
|
|
|
|
+ className="font-mono text-sm"
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : definition.component === 'number' ? (
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ value={value}
|
|
|
|
|
+ onChange={(e) => {
|
|
|
|
|
+ const newConfig = { ...config, configValue: e.target.value };
|
|
|
|
|
+ setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
|
|
|
|
|
+ }}
|
|
|
|
|
+ min={definition.min}
|
|
|
|
|
+ max={definition.max}
|
|
|
|
|
+ step={definition.step}
|
|
|
|
|
+ className="w-32"
|
|
|
|
|
+ />
|
|
|
|
|
+ {definition.key === ConfigKey.ANTI_REFUND_DELAY && (
|
|
|
|
|
+ <span className="text-sm text-muted-foreground">
|
|
|
|
|
+ ({Math.floor(parseInt(value) / 60)}分{parseInt(value) % 60}秒)
|
|
|
|
|
+ </span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Input
|
|
|
|
|
+ value={value}
|
|
|
|
|
+ onChange={(e) => {
|
|
|
|
|
+ const newConfig = { ...config, configValue: e.target.value };
|
|
|
|
|
+ setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
|
|
|
|
|
+ }}
|
|
|
|
|
+ placeholder={`请输入${definition.label}`}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {definition.key === ConfigKey.DEFAULT_PRINTER_SN && value && (
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
|
|
+ 当前默认打印机: <span className="font-mono">{value}</span>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
+ {/* 标题和操作栏 */}
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h2 className="text-2xl font-bold tracking-tight">打印配置管理</h2>
|
|
|
|
|
+ <p className="text-muted-foreground">
|
|
|
|
|
+ 管理飞鹅打印的配置项,包括基础配置、打印策略和模板配置
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ onClick={() => refetch()}
|
|
|
|
|
+ disabled={isLoading}
|
|
|
|
|
+ >
|
|
|
|
|
+ <RefreshCw className={`mr-2 h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
|
|
|
|
|
+ 刷新
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleBatchSave}
|
|
|
|
|
+ disabled={batchUpdateConfigMutation.isPending || isLoading}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Save className="mr-2 h-4 w-4" />
|
|
|
|
|
+ {batchUpdateConfigMutation.isPending ? '保存中...' : '保存所有更改'}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 配置分组标签 */}
|
|
|
|
|
+ <Tabs value={activeGroup} onValueChange={(value) => setActiveGroup(value as ConfigGroup)}>
|
|
|
|
|
+ <TabsList className="grid w-full grid-cols-3">
|
|
|
|
|
+ <TabsTrigger value={ConfigGroup.BASIC}>
|
|
|
|
|
+ <Settings className="mr-2 h-4 w-4" />
|
|
|
|
|
+ 基础配置
|
|
|
|
|
+ </TabsTrigger>
|
|
|
|
|
+ <TabsTrigger value={ConfigGroup.PRINT_POLICY}>
|
|
|
|
|
+ <Timer className="mr-2 h-4 w-4" />
|
|
|
|
|
+ 打印策略
|
|
|
|
|
+ </TabsTrigger>
|
|
|
|
|
+ <TabsTrigger value={ConfigGroup.TEMPLATE}>
|
|
|
|
|
+ <FileText className="mr-2 h-4 w-4" />
|
|
|
|
|
+ 模板配置
|
|
|
|
|
+ </TabsTrigger>
|
|
|
|
|
+ </TabsList>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 加载状态 */}
|
|
|
|
|
+ {isLoading ? (
|
|
|
|
|
+ <Card className="mt-4">
|
|
|
|
|
+ <CardContent className="pt-6">
|
|
|
|
|
+ <div className="flex items-center justify-center py-8">
|
|
|
|
|
+ <div className="text-center">
|
|
|
|
|
+ <RefreshCw className="h-8 w-8 animate-spin mx-auto text-muted-foreground" />
|
|
|
|
|
+ <p className="mt-2 text-sm text-muted-foreground">加载配置中...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ ) : isError ? (
|
|
|
|
|
+ <Card className="mt-4">
|
|
|
|
|
+ <CardContent className="pt-6">
|
|
|
|
|
+ <div className="flex items-center justify-center py-8">
|
|
|
|
|
+ <div className="text-center">
|
|
|
|
|
+ <AlertCircle className="h-8 w-8 mx-auto text-red-500" />
|
|
|
|
|
+ <p className="mt-2 text-sm text-red-600">加载配置失败</p>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="mt-4"
|
|
|
|
|
+ onClick={() => refetch()}
|
|
|
|
|
+ >
|
|
|
|
|
+ 重试
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
|
|
+ {/* 基础配置 */}
|
|
|
|
|
+ <TabsContent value={ConfigGroup.BASIC} className="space-y-4">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>基础配置</CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ 配置飞鹅打印的基础功能,包括启用状态和默认打印机
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-6">
|
|
|
|
|
+ {configDefinitions
|
|
|
|
|
+ .filter(def => def.group === ConfigGroup.BASIC)
|
|
|
|
|
+ .map(renderConfigItem)}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </TabsContent>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 打印策略 */}
|
|
|
|
|
+ <TabsContent value={ConfigGroup.PRINT_POLICY} className="space-y-4">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>打印策略</CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ 配置打印任务的触发条件、重试策略和超时设置
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-6">
|
|
|
|
|
+ {configDefinitions
|
|
|
|
|
+ .filter(def => def.group === ConfigGroup.PRINT_POLICY)
|
|
|
|
|
+ .map(renderConfigItem)}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 策略说明 */}
|
|
|
|
|
+ <div className="rounded-lg border bg-muted/50 p-4">
|
|
|
|
|
+ <h4 className="font-medium mb-2">策略说明</h4>
|
|
|
|
|
+ <ul className="text-sm text-muted-foreground space-y-1">
|
|
|
|
|
+ <li className="flex items-start">
|
|
|
|
|
+ <Clock className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
|
|
|
|
|
+ <span>
|
|
|
|
|
+ <strong>防退款延迟</strong>: 支付成功后等待指定时间确认无退款再打印,避免无效打印
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start">
|
|
|
|
|
+ <Repeat className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
|
|
|
|
|
+ <span>
|
|
|
|
|
+ <strong>重试机制</strong>: 打印失败时自动重试,最多重试指定次数,每次间隔指定时间
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start">
|
|
|
|
|
+ <Timer className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
|
|
|
|
|
+ <span>
|
|
|
|
|
+ <strong>超时取消</strong>: 打印任务执行超过指定时间自动取消,避免任务阻塞
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </TabsContent>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 模板配置 */}
|
|
|
|
|
+ <TabsContent value={ConfigGroup.TEMPLATE} className="space-y-4">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>模板配置</CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ 配置小票和发货单的打印模板,支持变量替换
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-6">
|
|
|
|
|
+ {configDefinitions
|
|
|
|
|
+ .filter(def => def.group === ConfigGroup.TEMPLATE)
|
|
|
|
|
+ .map(renderConfigItem)}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 模板变量说明 */}
|
|
|
|
|
+ <div className="rounded-lg border bg-muted/50 p-4">
|
|
|
|
|
+ <h4 className="font-medium mb-2">可用变量</h4>
|
|
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h5 className="text-sm font-medium mb-1">订单信息</h5>
|
|
|
|
|
+ <ul className="text-sm text-muted-foreground space-y-1">
|
|
|
|
|
+ <li><code>{'{orderNo}'}</code> - 订单号</li>
|
|
|
|
|
+ <li><code>{'{orderTime}'}</code> - 订单时间</li>
|
|
|
|
|
+ <li><code>{'{totalAmount}'}</code> - 订单总金额</li>
|
|
|
|
|
+ <li><code>{'{paymentMethod}'}</code> - 支付方式</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h5 className="text-sm font-medium mb-1">收货信息</h5>
|
|
|
|
|
+ <ul className="text-sm text-muted-foreground space-y-1">
|
|
|
|
|
+ <li><code>{'{receiver}'}</code> - 收货人姓名</li>
|
|
|
|
|
+ <li><code>{'{phone}'}</code> - 联系电话</li>
|
|
|
|
|
+ <li><code>{'{address}'}</code> - 收货地址</li>
|
|
|
|
|
+ <li><code>{'{shippingTime}'}</code> - 发货时间</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="mt-4">
|
|
|
|
|
+ <h5 className="text-sm font-medium mb-1">商品信息</h5>
|
|
|
|
|
+ <ul className="text-sm text-muted-foreground space-y-1">
|
|
|
|
|
+ <li><code>{'{goodsList}'}</code> - 商品列表(自动格式化)</li>
|
|
|
|
|
+ <li><code>{'{goodsName}'}</code> - 商品名称</li>
|
|
|
|
|
+ <li><code>{'{goodsPrice}'}</code> - 商品价格</li>
|
|
|
|
|
+ <li><code>{'{goodsQuantity}'}</code> - 商品数量</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </TabsContent>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Tabs>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 配置列表表格视图(备用) */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>所有配置项</CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ 以表格形式查看所有配置项的当前值
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ {configList?.data.length === 0 ? (
|
|
|
|
|
+ <div className="flex flex-col items-center justify-center py-12 text-center">
|
|
|
|
|
+ <Settings className="h-12 w-12 text-muted-foreground mb-4" />
|
|
|
|
|
+ <h3 className="text-lg font-medium">暂无配置</h3>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mt-2">
|
|
|
|
|
+ 还没有配置信息,系统将使用默认配置
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className="rounded-md border">
|
|
|
|
|
+ <Table>
|
|
|
|
|
+ <TableHeader>
|
|
|
|
|
+ <TableRow>
|
|
|
|
|
+ <TableHead>配置键</TableHead>
|
|
|
|
|
+ <TableHead>描述</TableHead>
|
|
|
|
|
+ <TableHead>类型</TableHead>
|
|
|
|
|
+ <TableHead>当前值</TableHead>
|
|
|
|
|
+ <TableHead>默认值</TableHead>
|
|
|
|
|
+ <TableHead className="text-right">操作</TableHead>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ </TableHeader>
|
|
|
|
|
+ <TableBody>
|
|
|
|
|
+ {configDefinitions.map((definition) => {
|
|
|
|
|
+ const config = configMap[definition.key];
|
|
|
|
|
+ const value = getConfigValue(definition.key);
|
|
|
|
|
+ const isDefault = !config || config.configValue === definition.defaultValue;
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <TableRow key={definition.key}>
|
|
|
|
|
+ <TableCell className="font-mono text-sm">{definition.key}</TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium">{definition.label}</div>
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
|
|
+ {definition.description}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Badge variant="outline" className={typeColorMap[definition.type]}>
|
|
|
|
|
+ {typeLabelMap[definition.type]}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <div className="max-w-xs truncate" title={getConfigValueDisplay(config || {
|
|
|
|
|
+ ...definition,
|
|
|
|
|
+ configValue: value
|
|
|
|
|
+ } as any)}>
|
|
|
|
|
+ {definition.type === ConfigType.BOOLEAN ? (
|
|
|
|
|
+ <div className="flex items-center">
|
|
|
|
|
+ {value === 'true' ? (
|
|
|
|
|
+ <CheckCircle className="h-4 w-4 text-green-500 mr-2" />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <XCircle className="h-4 w-4 text-red-500 mr-2" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ <span>{value === 'true' ? '是' : '否'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <span className="font-mono text-sm">{value}</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <span className="font-mono text-sm">{definition.defaultValue}</span>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell className="text-right">
|
|
|
|
|
+ <div className="flex items-center justify-end space-x-2">
|
|
|
|
|
+ {!isDefault && (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => handleResetConfig(definition.key)}
|
|
|
|
|
+ disabled={resetConfigMutation.isPending}
|
|
|
|
|
+ title="重置为默认值"
|
|
|
|
|
+ >
|
|
|
|
|
+ <RefreshCw className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => config && openEditDialog(config)}
|
|
|
|
|
+ title="编辑"
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </TableBody>
|
|
|
|
|
+ </Table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 编辑配置对话框 */}
|
|
|
|
|
+ <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
|
|
|
|
+ <DialogContent className="sm:max-w-[500px]">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle>编辑配置</DialogTitle>
|
|
|
|
|
+ <DialogDescription>
|
|
|
|
|
+ 修改配置项 "{selectedConfig?.configKey}" 的值
|
|
|
|
|
+ </DialogDescription>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+ <Form {...updateForm}>
|
|
|
|
|
+ <form onSubmit={updateForm.handleSubmit(handleUpdateConfig)} className="space-y-4">
|
|
|
|
|
+ {selectedConfig && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div className="rounded-lg border p-4">
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <span className="text-sm font-medium">配置键</span>
|
|
|
|
|
+ <span className="font-mono text-sm">{selectedConfig.configKey}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <span className="text-sm font-medium">类型</span>
|
|
|
|
|
+ <Badge variant="outline" className={typeColorMap[selectedConfig.configType]}>
|
|
|
|
|
+ {typeLabelMap[selectedConfig.configType]}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {selectedConfig.description && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span className="text-sm font-medium">描述</span>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mt-1">
|
|
|
|
|
+ {selectedConfig.description}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <FormField
|
|
|
|
|
+ control={updateForm.control}
|
|
|
|
|
+ name="configValue"
|
|
|
|
|
+ render={({ field }) => (
|
|
|
|
|
+ <FormItem>
|
|
|
|
|
+ <FormLabel>配置值</FormLabel>
|
|
|
|
|
+ {selectedConfig.configType === ConfigType.BOOLEAN ? (
|
|
|
|
|
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <SelectTrigger>
|
|
|
|
|
+ <SelectValue placeholder="选择配置值" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ <SelectItem value="true">是(启用)</SelectItem>
|
|
|
|
|
+ <SelectItem value="false">否(禁用)</SelectItem>
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ ) : selectedConfig.configType === ConfigType.JSON ? (
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <Textarea
|
|
|
|
|
+ placeholder="请输入JSON格式的配置值"
|
|
|
|
|
+ className="font-mono text-sm"
|
|
|
|
|
+ rows={6}
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <Input placeholder="请输入配置值" {...field} />
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <FormMessage />
|
|
|
|
|
+ </FormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <DialogFooter>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ onClick={() => setIsEditDialogOpen(false)}
|
|
|
|
|
+ disabled={updateConfigMutation.isPending}
|
|
|
|
|
+ >
|
|
|
|
|
+ 取消
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button type="submit" disabled={updateConfigMutation.isPending}>
|
|
|
|
|
+ {updateConfigMutation.isPending ? '更新中...' : '更新配置'}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </DialogFooter>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </Form>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|