feat(system): 添加国际化支持和系统配置管理功能
- 集成 i18next 多语言框架,实现中英双语支持 - 创建完整的多语言资源配置文件 (en-US.json) - 实现平台配置管理页面,支持项目名称、Logo 等可视化设置 - 新增系统参数管理页面,支持参数键值对的增删改查操作 - 添加后端平台配置控制器和相关实体服务层 - 实现系统参数分页查询、创建、更新和删除接口 - 配置多语言检测器和默认语言设置master
parent
9b721929c6
commit
8959680d31
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.imeeting.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value("${app.upload-path}")
|
||||||
|
private String uploadPath;
|
||||||
|
|
||||||
|
@Value("${app.resource-prefix}")
|
||||||
|
private String resourcePrefix;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
// 确保目录存在
|
||||||
|
File directory = new File(uploadPath);
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将资源前缀映射到物理路径
|
||||||
|
// 注意:addResourceLocations 需要以 file: 开头
|
||||||
|
String pathPattern = resourcePrefix.endsWith("/") ? resourcePrefix + "**" : resourcePrefix + "/**";
|
||||||
|
String location = uploadPath.endsWith("/") ? "file:" + uploadPath : "file:" + uploadPath + "/";
|
||||||
|
|
||||||
|
registry.addResourceHandler(pathPattern)
|
||||||
|
.addResourceLocations(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.imeeting.controller;
|
||||||
|
|
||||||
|
import com.imeeting.common.ApiResponse;
|
||||||
|
import com.imeeting.dto.PlatformConfigVO;
|
||||||
|
import com.imeeting.entity.SysPlatformConfig;
|
||||||
|
import com.imeeting.service.SysPlatformConfigService;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api")
|
||||||
|
public class PlatformConfigController {
|
||||||
|
|
||||||
|
private final SysPlatformConfigService platformConfigService;
|
||||||
|
|
||||||
|
public PlatformConfigController(SysPlatformConfigService platformConfigService) {
|
||||||
|
this.platformConfigService = platformConfigService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公开配置接口 (用于登录页、favicon等)
|
||||||
|
*/
|
||||||
|
@GetMapping("/open/platform/config")
|
||||||
|
public ApiResponse<PlatformConfigVO> getOpenConfig() {
|
||||||
|
return ApiResponse.ok(platformConfigService.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取管理配置 (需要登录)
|
||||||
|
*/
|
||||||
|
@GetMapping("/admin/platform/config")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public ApiResponse<PlatformConfigVO> getAdminConfig() {
|
||||||
|
return ApiResponse.ok(platformConfigService.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置 (仅限平台管理员)
|
||||||
|
*/
|
||||||
|
@PutMapping("/admin/platform/config")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @ss.hasPermi('sys_platform:config:update')")
|
||||||
|
public ApiResponse<Boolean> updateConfig(@RequestBody SysPlatformConfig config) {
|
||||||
|
return ApiResponse.ok(platformConfigService.updateConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传资源 (仅限平台管理员)
|
||||||
|
*/
|
||||||
|
@PostMapping("/admin/platform/config/upload")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @ss.hasPermi('sys_platform:config:update')")
|
||||||
|
public ApiResponse<String> upload(@RequestParam("file") MultipartFile file) {
|
||||||
|
return ApiResponse.ok(platformConfigService.uploadAsset(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.imeeting.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PlatformConfigVO {
|
||||||
|
private String projectName;
|
||||||
|
private String logoUrl;
|
||||||
|
private String iconUrl;
|
||||||
|
private String loginBgUrl;
|
||||||
|
private String icpInfo;
|
||||||
|
private String copyrightInfo;
|
||||||
|
private String systemDescription;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.imeeting.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SysParamQueryDTO {
|
||||||
|
private String paramKey;
|
||||||
|
private String paramType;
|
||||||
|
private String description;
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.imeeting.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SysParamVO {
|
||||||
|
private Long paramId;
|
||||||
|
private String paramKey;
|
||||||
|
private String paramValue;
|
||||||
|
private String paramType;
|
||||||
|
private Integer isSystem;
|
||||||
|
private String description;
|
||||||
|
private Integer status;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.imeeting.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_platform_config")
|
||||||
|
public class SysPlatformConfig extends BaseEntity {
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
private String projectName;
|
||||||
|
private String logoUrl;
|
||||||
|
private String iconUrl;
|
||||||
|
private String loginBgUrl;
|
||||||
|
private String icpInfo;
|
||||||
|
private String copyrightInfo;
|
||||||
|
private String systemDescription;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Long tenantId;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.imeeting.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_tenant_user")
|
||||||
|
public class SysTenantUser extends BaseEntity {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private Long userId;
|
||||||
|
private Long tenantId;
|
||||||
|
private Long orgId;
|
||||||
|
|
||||||
|
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
|
||||||
|
private String orgName;
|
||||||
|
|
||||||
|
@com.baomidou.mybatisplus.annotation.TableLogic(value = "0", delval = "0")
|
||||||
|
@com.baomidou.mybatisplus.annotation.TableField(exist = false)
|
||||||
|
private Integer isDeleted;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.imeeting.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.imeeting.entity.SysPlatformConfig;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SysPlatformConfigMapper extends BaseMapper<SysPlatformConfig> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.imeeting.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.imeeting.entity.SysTenantUser;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SysTenantUserMapper extends BaseMapper<SysTenantUser> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.imeeting.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.imeeting.entity.SysPlatformConfig;
|
||||||
|
import com.imeeting.dto.PlatformConfigVO;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface SysPlatformConfigService extends IService<SysPlatformConfig> {
|
||||||
|
PlatformConfigVO getConfig();
|
||||||
|
boolean updateConfig(SysPlatformConfig config);
|
||||||
|
String uploadAsset(MultipartFile file);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.imeeting.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.imeeting.entity.SysTenantUser;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface SysTenantUserService extends IService<SysTenantUser> {
|
||||||
|
List<SysTenantUser> listByUserId(Long userId);
|
||||||
|
void saveTenantUser(Long userId, Long tenantId, Long orgId);
|
||||||
|
void syncMemberships(Long userId, List<SysTenantUser> memberships);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.imeeting.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.imeeting.common.RedisKeys;
|
||||||
|
import com.imeeting.dto.PlatformConfigVO;
|
||||||
|
import com.imeeting.entity.SysPlatformConfig;
|
||||||
|
import com.imeeting.mapper.SysPlatformConfigMapper;
|
||||||
|
import com.imeeting.service.SysPlatformConfigService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class SysPlatformConfigServiceImpl extends ServiceImpl<SysPlatformConfigMapper, SysPlatformConfig> implements SysPlatformConfigService {
|
||||||
|
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Value("${app.upload-path}")
|
||||||
|
private String uploadPath;
|
||||||
|
|
||||||
|
@Value("${app.resource-prefix}")
|
||||||
|
private String resourcePrefix;
|
||||||
|
|
||||||
|
public SysPlatformConfigServiceImpl(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlatformConfigVO getConfig() {
|
||||||
|
String key = RedisKeys.platformConfigKey();
|
||||||
|
try {
|
||||||
|
String cached = redisTemplate.opsForValue().get(key);
|
||||||
|
if (cached != null) {
|
||||||
|
return objectMapper.readValue(cached, PlatformConfigVO.class);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Read platform config from redis error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
SysPlatformConfig config = getById(1L);
|
||||||
|
if (config == null) {
|
||||||
|
return new PlatformConfigVO();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformConfigVO vo = toVO(config);
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(vo), Duration.ofDays(1));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Write platform config to redis error", e);
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean updateConfig(SysPlatformConfig config) {
|
||||||
|
config.setId(1L);
|
||||||
|
SysPlatformConfig old = getById(1L);
|
||||||
|
|
||||||
|
boolean success = updateById(config);
|
||||||
|
if (success) {
|
||||||
|
redisTemplate.delete(RedisKeys.platformConfigKey());
|
||||||
|
// 物理文件清理逻辑可以在这里根据需要扩展(例如对比 old 和 config 的 URL)
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uploadAsset(MultipartFile file) {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
throw new RuntimeException("File is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String extension = "";
|
||||||
|
if (originalFilename != null && originalFilename.contains(".")) {
|
||||||
|
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = UUID.randomUUID().toString() + extension;
|
||||||
|
Path path = Paths.get(uploadPath, fileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.copy(file.getInputStream(), path);
|
||||||
|
String prefix = resourcePrefix.endsWith("/") ? resourcePrefix : resourcePrefix + "/";
|
||||||
|
return prefix + fileName;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Upload asset error", e);
|
||||||
|
throw new RuntimeException("Failed to store file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlatformConfigVO toVO(SysPlatformConfig entity) {
|
||||||
|
PlatformConfigVO vo = new PlatformConfigVO();
|
||||||
|
vo.setProjectName(entity.getProjectName());
|
||||||
|
vo.setLogoUrl(entity.getLogoUrl());
|
||||||
|
vo.setIconUrl(entity.getIconUrl());
|
||||||
|
vo.setLoginBgUrl(entity.getLoginBgUrl());
|
||||||
|
vo.setIcpInfo(entity.getIcpInfo());
|
||||||
|
vo.setCopyrightInfo(entity.getCopyrightInfo());
|
||||||
|
vo.setSystemDescription(entity.getSystemDescription());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.imeeting.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.imeeting.entity.SysTenantUser;
|
||||||
|
import com.imeeting.mapper.SysTenantUserMapper;
|
||||||
|
import com.imeeting.service.SysTenantUserService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysTenantUserServiceImpl extends ServiceImpl<SysTenantUserMapper, SysTenantUser> implements SysTenantUserService {
|
||||||
|
|
||||||
|
private final com.imeeting.service.SysOrgService sysOrgService;
|
||||||
|
|
||||||
|
public SysTenantUserServiceImpl(com.imeeting.service.SysOrgService sysOrgService) {
|
||||||
|
this.sysOrgService = sysOrgService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysTenantUser> listByUserId(Long userId) {
|
||||||
|
List<SysTenantUser> list = list(new LambdaQueryWrapper<SysTenantUser>().eq(SysTenantUser::getUserId, userId));
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
for (SysTenantUser tu : list) {
|
||||||
|
if (tu.getOrgId() != null) {
|
||||||
|
com.imeeting.entity.SysOrg org = sysOrgService.getById(tu.getOrgId());
|
||||||
|
if (org != null) {
|
||||||
|
tu.setOrgName(org.getOrgName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveTenantUser(Long userId, Long tenantId, Long orgId) {
|
||||||
|
LambdaQueryWrapper<SysTenantUser> query = new LambdaQueryWrapper<SysTenantUser>()
|
||||||
|
.eq(SysTenantUser::getUserId, userId)
|
||||||
|
.eq(SysTenantUser::getTenantId, tenantId);
|
||||||
|
SysTenantUser existing = getOne(query);
|
||||||
|
if (existing != null) {
|
||||||
|
existing.setOrgId(orgId);
|
||||||
|
updateById(existing);
|
||||||
|
} else {
|
||||||
|
SysTenantUser tu = new SysTenantUser();
|
||||||
|
tu.setUserId(userId);
|
||||||
|
tu.setTenantId(tenantId);
|
||||||
|
tu.setOrgId(orgId);
|
||||||
|
save(tu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void syncMemberships(Long userId, List<SysTenantUser> memberships) {
|
||||||
|
if (userId == null) return;
|
||||||
|
|
||||||
|
// 1. Physical removal of all existing memberships for this user
|
||||||
|
getBaseMapper().delete(new LambdaQueryWrapper<SysTenantUser>().eq(SysTenantUser::getUserId, userId));
|
||||||
|
|
||||||
|
// 2. Add new ones
|
||||||
|
if (memberships != null && !memberships.isEmpty()) {
|
||||||
|
java.util.Set<Long> processedTenants = new java.util.HashSet<>();
|
||||||
|
for (SysTenantUser m : memberships) {
|
||||||
|
if (m.getTenantId() != null && processedTenants.add(m.getTenantId())) {
|
||||||
|
SysTenantUser tu = new SysTenantUser();
|
||||||
|
tu.setUserId(userId);
|
||||||
|
tu.setTenantId(m.getTenantId());
|
||||||
|
tu.setOrgId(m.getOrgId());
|
||||||
|
save(tu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import http from "./http";
|
||||||
|
import { SysPlatformConfig } from "../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取公开平台配置
|
||||||
|
*/
|
||||||
|
export async function getOpenPlatformConfig() {
|
||||||
|
const resp = await http.get("/api/open/platform/config");
|
||||||
|
return resp.data.data as SysPlatformConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取管理端平台配置
|
||||||
|
*/
|
||||||
|
export async function getAdminPlatformConfig() {
|
||||||
|
const resp = await http.get("/api/admin/platform/config");
|
||||||
|
return resp.data.data as SysPlatformConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新平台配置
|
||||||
|
*/
|
||||||
|
export async function updatePlatformConfig(payload: SysPlatformConfig) {
|
||||||
|
const resp = await http.put("/api/admin/platform/config", payload);
|
||||||
|
return resp.data.data as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传平台资源文件
|
||||||
|
* @param file 文件对象
|
||||||
|
*/
|
||||||
|
export async function uploadPlatformAsset(file: File) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const resp = await http.post("/api/admin/platform/config/upload", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return resp.data.data as string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import zhCN from './locales/zh-CN.json';
|
||||||
|
import enUS from './locales/en-US.json';
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
resources: {
|
||||||
|
'zh-CN': {
|
||||||
|
translation: zhCN,
|
||||||
|
},
|
||||||
|
'en-US': {
|
||||||
|
translation: enUS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fallbackLng: 'zh-CN',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"search": "Search",
|
||||||
|
"reset": "Reset",
|
||||||
|
"create": "Create",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"action": "Action",
|
||||||
|
"status": "Status",
|
||||||
|
"remark": "Remark",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"total": "Total {{total}} items",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"view": "View"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"profile": "Profile",
|
||||||
|
"settings": "Settings",
|
||||||
|
"logout": "Logout",
|
||||||
|
"language": "Language",
|
||||||
|
"notification": "Notification"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard",
|
||||||
|
"subtitle": "Real-time monitoring of meeting transcription and system metrics",
|
||||||
|
"todayMeetings": "Today's Meetings",
|
||||||
|
"activeDevices": "Active Devices",
|
||||||
|
"transcriptionDuration": "Transcription Duration",
|
||||||
|
"totalUsers": "Total Users",
|
||||||
|
"recentMeetings": "Recent Meetings",
|
||||||
|
"deviceLoad": "Device Load",
|
||||||
|
"viewAll": "View All",
|
||||||
|
"meetingName": "Meeting Name",
|
||||||
|
"startTime": "Start Time",
|
||||||
|
"duration": "Duration"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"title": "User Management",
|
||||||
|
"subtitle": "Manage user information, organizations, and roles across tenants",
|
||||||
|
"userInfo": "User Info",
|
||||||
|
"org": "Tenant/Org",
|
||||||
|
"platformAdmin": "Platform Admin",
|
||||||
|
"searchPlaceholder": "Search username, name, or email...",
|
||||||
|
"tenantFilter": "Filter by tenant...",
|
||||||
|
"drawerTitleCreate": "Create User",
|
||||||
|
"drawerTitleEdit": "Edit User",
|
||||||
|
"username": "Username",
|
||||||
|
"displayName": "Display Name",
|
||||||
|
"email": "Email",
|
||||||
|
"phone": "Phone",
|
||||||
|
"password": "Password",
|
||||||
|
"roles": "Roles",
|
||||||
|
"tenant": "Tenant",
|
||||||
|
"orgNode": "Organization"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"title": "Permission Management",
|
||||||
|
"subtitle": "Configure system menu structure and function buttons",
|
||||||
|
"permName": "Name",
|
||||||
|
"permCode": "Code",
|
||||||
|
"permType": "Type",
|
||||||
|
"sort": "Sort",
|
||||||
|
"route": "Route/Component",
|
||||||
|
"visible": "Visible",
|
||||||
|
"isVisible": "Visible in Menu",
|
||||||
|
"drawerTitleCreate": "Create Permission",
|
||||||
|
"drawerTitleEdit": "Edit Permission",
|
||||||
|
"parentId": "Parent",
|
||||||
|
"level": "Level",
|
||||||
|
"path": "Route Path",
|
||||||
|
"component": "Component",
|
||||||
|
"icon": "Icon",
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"title": "System Roles",
|
||||||
|
"subtitle": "Manage system roles and their associated permissions and users",
|
||||||
|
"roleName": "Role Name",
|
||||||
|
"roleCode": "Role Code",
|
||||||
|
"searchPlaceholder": "Search roles...",
|
||||||
|
"drawerTitleCreate": "Create Role",
|
||||||
|
"drawerTitleEdit": "Edit Role Info",
|
||||||
|
"funcPerms": "Functional Permissions",
|
||||||
|
"assignedUsers": "Assigned Users",
|
||||||
|
"savePerms": "Save Permission Changes",
|
||||||
|
"selectRole": "Please select a role from the left list to manage"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"welcome": "Welcome back",
|
||||||
|
"subtitle": "Please log in to your account",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"tenantCode": "Tenant Code",
|
||||||
|
"tenantCodePlaceholder": "Tenant Code (Empty for Platform Admin)",
|
||||||
|
"captcha": "Captcha",
|
||||||
|
"rememberMe": "Remember Me",
|
||||||
|
"forgotPassword": "Forgot Password?",
|
||||||
|
"submit": "Login Now",
|
||||||
|
"loggingIn": "Logging in...",
|
||||||
|
"demoAccount": "Demo Account",
|
||||||
|
"heroTitle1": "Intelligent Meeting",
|
||||||
|
"heroTitle2": "Real-time Voice",
|
||||||
|
"heroTitle3": "System",
|
||||||
|
"heroDesc": "Full-process automated meeting records, voiceprint recognition, and smart summaries. Next-gen solution for team collaboration efficiency.",
|
||||||
|
"enterpriseSecurity": "Enterprise Security",
|
||||||
|
"multiLang": "Multi-language Support",
|
||||||
|
"loginTimeout": "Login timeout, please log in again"
|
||||||
|
},
|
||||||
|
"devices": {
|
||||||
|
"title": "Device Management",
|
||||||
|
"subtitle": "Manage hardware terminals and associated users",
|
||||||
|
"deviceInfo": "Device Info",
|
||||||
|
"owner": "Owner",
|
||||||
|
"updateTime": "Update Time",
|
||||||
|
"searchPlaceholder": "Search device name, code, or owner...",
|
||||||
|
"drawerTitleCreate": "Register Device",
|
||||||
|
"drawerTitleEdit": "Edit Device Info",
|
||||||
|
"deviceCode": "Device Code",
|
||||||
|
"deviceName": "Device Name"
|
||||||
|
},
|
||||||
|
"dicts": {
|
||||||
|
"title": "Data Dictionaries",
|
||||||
|
"subtitle": "Manage system enumerations and constants",
|
||||||
|
"dictType": "Dict Type",
|
||||||
|
"dictItem": "Dict Items",
|
||||||
|
"typeName": "Type Name",
|
||||||
|
"typeCode": "Type Code",
|
||||||
|
"itemLabel": "Item Label",
|
||||||
|
"itemValue": "Item Value",
|
||||||
|
"sort": "Sort",
|
||||||
|
"selectType": "Please select a dictionary type from the left",
|
||||||
|
"drawerTitleTypeCreate": "Create Dict Type",
|
||||||
|
"drawerTitleTypeEdit": "Edit Dict Type",
|
||||||
|
"drawerTitleItemCreate": "Create Dict Item",
|
||||||
|
"drawerTitleItemEdit": "Edit Dict Item"
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"title": "Log Management",
|
||||||
|
"subtitle": "Track important system operations for security and traceability",
|
||||||
|
"opLog": "Operation Log",
|
||||||
|
"loginLog": "Login Log",
|
||||||
|
"opAccount": "Account",
|
||||||
|
"opDetail": "Operation",
|
||||||
|
"ip": "IP Address",
|
||||||
|
"duration": "Duration",
|
||||||
|
"time": "Time",
|
||||||
|
"method": "Method",
|
||||||
|
"params": "Parameters",
|
||||||
|
"searchPlaceholder": "Search operations...",
|
||||||
|
"detailTitle": "Log Details"
|
||||||
|
},
|
||||||
|
"tenants": {
|
||||||
|
"title": "Tenant Management",
|
||||||
|
"subtitle": "Manage system tenants, their status and expiration",
|
||||||
|
"tenantInfo": "Tenant Info",
|
||||||
|
"contact": "Contact",
|
||||||
|
"expireTime": "Expiration",
|
||||||
|
"forever": "Permanent",
|
||||||
|
"drawerTitleCreate": "Create Tenant",
|
||||||
|
"drawerTitleEdit": "Edit Tenant",
|
||||||
|
"tenantName": "Tenant Name",
|
||||||
|
"tenantCode": "Tenant Code",
|
||||||
|
"contactName": "Contact Name",
|
||||||
|
"contactPhone": "Phone"
|
||||||
|
},
|
||||||
|
"orgs": {
|
||||||
|
"title": "Organization Management",
|
||||||
|
"subtitle": "Manage internal department hierarchy and multi-tenant isolation",
|
||||||
|
"orgName": "Organization Name",
|
||||||
|
"orgCode": "Organization Code",
|
||||||
|
"parentOrg": "Parent Department",
|
||||||
|
"rootOrg": "Root Organization",
|
||||||
|
"addSub": "Add Sub-department",
|
||||||
|
"drawerTitleCreate": "Create Department",
|
||||||
|
"drawerTitleEdit": "Edit Organization Node",
|
||||||
|
"selectTenant": "Please select a tenant to view its organization structure",
|
||||||
|
"createRoot": "Create Root Organization",
|
||||||
|
"sort": "sort"
|
||||||
|
},
|
||||||
|
"userRole": {
|
||||||
|
"title": "User-Role Authorization",
|
||||||
|
"subtitle": "Assign system access roles to specific users and control their operational boundaries",
|
||||||
|
"userList": "User List",
|
||||||
|
"grantRoles": "Grant Role Permissions",
|
||||||
|
"searchUser": "Search username or display name...",
|
||||||
|
"selectUser": "Please select a user from the left list first",
|
||||||
|
"editing": "Editing"
|
||||||
|
},
|
||||||
|
"rolePerm": {
|
||||||
|
"title": "Role-Permission Authorization",
|
||||||
|
"subtitle": "Configure menu access and function points for various roles in the system",
|
||||||
|
"roleList": "System Role List",
|
||||||
|
"permConfig": "Permission Configuration",
|
||||||
|
"searchRole": "Search role name or code...",
|
||||||
|
"selectRole": "Please select a role from the left list first",
|
||||||
|
"currentRole": "Current Role",
|
||||||
|
"savePolicy": "Save Permission Policy"
|
||||||
|
},
|
||||||
|
"sysParams": {
|
||||||
|
"title": "System Parameters",
|
||||||
|
"subtitle": "Configure various dynamic parameters, configuration items and switches required for system operation",
|
||||||
|
"paramKey": "Parameter Key",
|
||||||
|
"paramValue": "Parameter Value",
|
||||||
|
"paramType": "Parameter Type",
|
||||||
|
"isSystem": "System Built-in",
|
||||||
|
"description": "Description",
|
||||||
|
"searchPlaceholder": "Search parameter key or description...",
|
||||||
|
"drawerTitleCreate": "Create System Parameter",
|
||||||
|
"drawerTitleEdit": "Edit System Parameter",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
|
},
|
||||||
|
"platformSettings": {
|
||||||
|
"title": "Platform Settings",
|
||||||
|
"subtitle": "Manage system branding, visual style, and legal compliance info",
|
||||||
|
"projectName": "Project Name",
|
||||||
|
"logo": "System Logo",
|
||||||
|
"icon": "Browser Icon",
|
||||||
|
"loginBg": "Login Background",
|
||||||
|
"icp": "ICP Number",
|
||||||
|
"copyright": "Copyright",
|
||||||
|
"desc": "System Description",
|
||||||
|
"uploadHint": "Click or drag to upload",
|
||||||
|
"uploadLimit": "Recommend 1:1 ratio, max 2MB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"search": "查询",
|
||||||
|
"reset": "重置",
|
||||||
|
"create": "新增",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"confirm": "确定",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"action": "操作",
|
||||||
|
"status": "状态",
|
||||||
|
"remark": "备注",
|
||||||
|
"refresh": "刷新",
|
||||||
|
"success": "操作成功",
|
||||||
|
"error": "操作失败",
|
||||||
|
"total": "共 {{total}} 条数据",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"view": "查看"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"profile": "个人信息",
|
||||||
|
"settings": "系统设置",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"language": "语言",
|
||||||
|
"notification": "通知"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "系统总览",
|
||||||
|
"subtitle": "实时监控会议转录状态与系统关键指标",
|
||||||
|
"todayMeetings": "今日会议",
|
||||||
|
"activeDevices": "活跃设备",
|
||||||
|
"transcriptionDuration": "转录时长",
|
||||||
|
"totalUsers": "总用户数",
|
||||||
|
"recentMeetings": "最近会议",
|
||||||
|
"deviceLoad": "设备负载",
|
||||||
|
"viewAll": "查看全部",
|
||||||
|
"meetingName": "会议名称",
|
||||||
|
"startTime": "开始时间",
|
||||||
|
"duration": "时长"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"title": "系统用户管理",
|
||||||
|
"subtitle": "维护系统多租户下的用户信息、组织归属及权限角色",
|
||||||
|
"userInfo": "用户信息",
|
||||||
|
"org": "所属租户/组织",
|
||||||
|
"platformAdmin": "平台管理",
|
||||||
|
"searchPlaceholder": "搜索用户名、姓名或邮箱...",
|
||||||
|
"tenantFilter": "按租户筛选...",
|
||||||
|
"drawerTitleCreate": "创建系统用户",
|
||||||
|
"drawerTitleEdit": "修改用户信息",
|
||||||
|
"username": "用户名",
|
||||||
|
"displayName": "显示姓名",
|
||||||
|
"email": "邮箱地址",
|
||||||
|
"phone": "手机号码",
|
||||||
|
"password": "登录密码",
|
||||||
|
"roles": "授予角色",
|
||||||
|
"tenant": "所属租户",
|
||||||
|
"orgNode": "所属组织"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"title": "功能权限管理",
|
||||||
|
"subtitle": "配置系统的菜单结构与功能按钮的操作权限点",
|
||||||
|
"permName": "权限名称",
|
||||||
|
"permCode": "权限编码",
|
||||||
|
"permType": "类型",
|
||||||
|
"sort": "排序",
|
||||||
|
"route": "路由/组件",
|
||||||
|
"visible": "可见",
|
||||||
|
"isVisible": "是否在菜单可见",
|
||||||
|
"drawerTitleCreate": "新增功能权限",
|
||||||
|
"drawerTitleEdit": "修改权限点信息",
|
||||||
|
"parentId": "上级权限",
|
||||||
|
"level": "权限层级",
|
||||||
|
"path": "路由路径",
|
||||||
|
"component": "组件路径",
|
||||||
|
"icon": "图标名称",
|
||||||
|
"description": "描述说明"
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"title": "系统角色",
|
||||||
|
"subtitle": "管理系统角色及其关联的权限和用户",
|
||||||
|
"roleName": "角色名称",
|
||||||
|
"roleCode": "角色编码",
|
||||||
|
"searchPlaceholder": "搜索角色...",
|
||||||
|
"drawerTitleCreate": "新增系统角色",
|
||||||
|
"drawerTitleEdit": "修改角色基础信息",
|
||||||
|
"funcPerms": "功能权限",
|
||||||
|
"assignedUsers": "关联用户",
|
||||||
|
"savePerms": "保存权限更改",
|
||||||
|
"selectRole": "请从左侧列表选择一个角色进行管理"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"welcome": "欢迎回来",
|
||||||
|
"subtitle": "请登录您的账号",
|
||||||
|
"username": "用户名",
|
||||||
|
"password": "密码",
|
||||||
|
"tenantCode": "租户编码",
|
||||||
|
"tenantCodePlaceholder": "租户编码 (平台管理可留空)",
|
||||||
|
"captcha": "验证码",
|
||||||
|
"rememberMe": "记住我",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"submit": "立即登录",
|
||||||
|
"loggingIn": "登录中...",
|
||||||
|
"demoAccount": "演示账号",
|
||||||
|
"heroTitle1": "智能会议",
|
||||||
|
"heroTitle2": "实时语音处理",
|
||||||
|
"heroTitle3": "系统",
|
||||||
|
"heroDesc": "全流程自动化会议记录,声纹识别与智能摘要。提升团队协作效率的新一代解决方案。",
|
||||||
|
"enterpriseSecurity": "企业级安全",
|
||||||
|
"multiLang": "多语言支持",
|
||||||
|
"loginTimeout": "登录超时,请重新登录"
|
||||||
|
},
|
||||||
|
"devices": {
|
||||||
|
"title": "设备管理",
|
||||||
|
"subtitle": "管理接入系统的硬件终端及关联用户",
|
||||||
|
"deviceInfo": "设备信息",
|
||||||
|
"owner": "归属用户",
|
||||||
|
"updateTime": "更新时间",
|
||||||
|
"searchPlaceholder": "搜索设备名称、编码或归属用户...",
|
||||||
|
"drawerTitleCreate": "接入新设备",
|
||||||
|
"drawerTitleEdit": "修改设备信息",
|
||||||
|
"deviceCode": "设备识别码",
|
||||||
|
"deviceName": "设备名称"
|
||||||
|
},
|
||||||
|
"dicts": {
|
||||||
|
"title": "数据字典管理",
|
||||||
|
"subtitle": "维护系统各类枚举值和常量的映射关系",
|
||||||
|
"dictType": "字典类型",
|
||||||
|
"dictItem": "字典项内容",
|
||||||
|
"typeName": "类型名称",
|
||||||
|
"typeCode": "类型Code",
|
||||||
|
"itemLabel": "显示标签",
|
||||||
|
"itemValue": "存储数值",
|
||||||
|
"sort": "排序",
|
||||||
|
"selectType": "请从左侧选择一个字典类型",
|
||||||
|
"drawerTitleTypeCreate": "新增字典类型",
|
||||||
|
"drawerTitleTypeEdit": "编辑字典类型",
|
||||||
|
"drawerTitleItemCreate": "新增字典项",
|
||||||
|
"drawerTitleItemEdit": "编辑字典项"
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"title": "系统日志管理",
|
||||||
|
"subtitle": "追踪系统内的每一次重要操作,保障系统安全与可追溯性",
|
||||||
|
"opLog": "操作日志",
|
||||||
|
"loginLog": "登录日志",
|
||||||
|
"opAccount": "操作账号",
|
||||||
|
"opDetail": "操作详情",
|
||||||
|
"ip": "IP 地址",
|
||||||
|
"duration": "耗时",
|
||||||
|
"time": "发生时间",
|
||||||
|
"method": "请求方法",
|
||||||
|
"params": "请求参数",
|
||||||
|
"searchPlaceholder": "搜索操作内容...",
|
||||||
|
"detailTitle": "日志详细信息"
|
||||||
|
},
|
||||||
|
"tenants": {
|
||||||
|
"title": "租户管理",
|
||||||
|
"subtitle": "管理系统多租户基础信息、授权状态及过期时间",
|
||||||
|
"tenantInfo": "租户信息",
|
||||||
|
"contact": "联系人",
|
||||||
|
"expireTime": "过期时间",
|
||||||
|
"forever": "永久有效",
|
||||||
|
"drawerTitleCreate": "创建新租户",
|
||||||
|
"drawerTitleEdit": "编辑租户信息",
|
||||||
|
"tenantName": "租户名称",
|
||||||
|
"tenantCode": "租户编码",
|
||||||
|
"contactName": "联系人姓名",
|
||||||
|
"contactPhone": "联系电话"
|
||||||
|
},
|
||||||
|
"orgs": {
|
||||||
|
"title": "组织架构管理",
|
||||||
|
"subtitle": "维护企业内部部门层级关系,支持多租户架构隔离",
|
||||||
|
"orgName": "组织名称",
|
||||||
|
"orgCode": "组织编码",
|
||||||
|
"parentOrg": "上级部门",
|
||||||
|
"rootOrg": "顶级部门",
|
||||||
|
"addSub": "添加下级",
|
||||||
|
"drawerTitleCreate": "新增组织部门",
|
||||||
|
"drawerTitleEdit": "编辑组织节点",
|
||||||
|
"selectTenant": "请先选择一个租户以查看其组织架构",
|
||||||
|
"createRoot": "新增根组织",
|
||||||
|
"sort": "排序"
|
||||||
|
},
|
||||||
|
"userRole": {
|
||||||
|
"title": "用户角色授权",
|
||||||
|
"subtitle": "为指定用户分配系统访问角色,控制其操作权限边界",
|
||||||
|
"userList": "用户选择列表",
|
||||||
|
"grantRoles": "授予角色权限",
|
||||||
|
"searchUser": "搜索用户名或显示名...",
|
||||||
|
"selectUser": "请先从左侧选择一个用户",
|
||||||
|
"editing": "正在编辑"
|
||||||
|
},
|
||||||
|
"rolePerm": {
|
||||||
|
"title": "角色权限授权",
|
||||||
|
"subtitle": "配置系统中各类角色所拥有的菜单访问权限与功能操作权限点",
|
||||||
|
"roleList": "系统角色列表",
|
||||||
|
"permConfig": "功能权限配置",
|
||||||
|
"searchRole": "搜索角色名称或编码...",
|
||||||
|
"selectRole": "请先从左侧列表中选择一个角色",
|
||||||
|
"currentRole": "当前角色",
|
||||||
|
"savePolicy": "保存权限策略"
|
||||||
|
},
|
||||||
|
"sysParams": {
|
||||||
|
"title": "系统参数管理",
|
||||||
|
"subtitle": "配置系统运行所需的各种动态参数、配置项及开关",
|
||||||
|
"paramKey": "参数键名",
|
||||||
|
"paramValue": "参数键值",
|
||||||
|
"paramType": "参数类型",
|
||||||
|
"isSystem": "系统内置",
|
||||||
|
"description": "描述",
|
||||||
|
"searchPlaceholder": "搜索参数键名或描述...",
|
||||||
|
"drawerTitleCreate": "新增系统参数",
|
||||||
|
"drawerTitleEdit": "修改系统参数",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否"
|
||||||
|
},
|
||||||
|
"platformSettings": {
|
||||||
|
"title": "平台信息配置",
|
||||||
|
"subtitle": "管理系统的品牌标识、视觉风格及法律合规信息",
|
||||||
|
"projectName": "项目名称",
|
||||||
|
"logo": "系统 Logo",
|
||||||
|
"icon": "浏览器图标 (Icon)",
|
||||||
|
"loginBg": "登录页背景图",
|
||||||
|
"icp": "ICP 备案号",
|
||||||
|
"copyright": "版权信息",
|
||||||
|
"desc": "系统描述",
|
||||||
|
"uploadHint": "点击或拖拽上传图片",
|
||||||
|
"uploadLimit": "建议比例 1:1,大小不超过 2MB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Upload,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Divider
|
||||||
|
} from "antd";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { getAdminPlatformConfig, updatePlatformConfig, uploadPlatformAsset } from "../api";
|
||||||
|
import {
|
||||||
|
UploadOutlined,
|
||||||
|
SaveOutlined,
|
||||||
|
GlobalOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
FileTextOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import type { SysPlatformConfig } from "../types";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
export default function PlatformSettings() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const loadConfig = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getAdminPlatformConfig();
|
||||||
|
form.setFieldsValue(data);
|
||||||
|
} catch (e) {
|
||||||
|
message.error(t('common.error'));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUpload = async (file: File, fieldName: keyof SysPlatformConfig) => {
|
||||||
|
try {
|
||||||
|
const url = await uploadPlatformAsset(file);
|
||||||
|
form.setFieldValue(fieldName, url);
|
||||||
|
message.success(t('common.success'));
|
||||||
|
} catch (e) {
|
||||||
|
message.error(t('common.error'));
|
||||||
|
}
|
||||||
|
return false; // 阻止自动上传
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFinish = async (values: SysPlatformConfig) => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
await updatePlatformConfig(values);
|
||||||
|
message.success(t('common.success'));
|
||||||
|
} catch (e) {
|
||||||
|
message.error(t('common.error'));
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImagePreview = ({ url, label }: { url?: string; label: string }) => (
|
||||||
|
<div className="flex flex-col items-center justify-center p-4 border border-dashed border-gray-300 rounded-lg bg-gray-50 h-32">
|
||||||
|
{url ? (
|
||||||
|
<img src={url} alt={label} className="max-h-full max-w-full object-contain" />
|
||||||
|
) : (
|
||||||
|
<div className="text-gray-400">
|
||||||
|
<PictureOutlined style={{ fontSize: 24 }} />
|
||||||
|
<div className="text-xs mt-1">{t('platformSettings.uploadHint')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 max-w-4xl mx-auto">
|
||||||
|
<div className="flex justify-between items-end mb-6">
|
||||||
|
<div>
|
||||||
|
<Title level={4} className="mb-1">{t('platformSettings.title')}</Title>
|
||||||
|
<Text type="secondary">{t('platformSettings.subtitle')}</Text>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SaveOutlined />}
|
||||||
|
loading={saving}
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
initialValues={{ projectName: 'iMeeting' }}
|
||||||
|
>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card
|
||||||
|
title={<><GlobalOutlined className="mr-2" /> 基础信息</>}
|
||||||
|
className="shadow-sm mb-6"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label={t('platformSettings.projectName')}
|
||||||
|
name="projectName"
|
||||||
|
rules={[{ required: true, message: t('platformSettings.projectName') }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="例如:iMeeting 智能会议系统" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('platformSettings.desc')} name="systemDescription">
|
||||||
|
<Input.TextArea rows={3} placeholder="系统的简要介绍..." />
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={24}>
|
||||||
|
<Card
|
||||||
|
title={<><PictureOutlined className="mr-2" /> 视觉资源</>}
|
||||||
|
className="shadow-sm mb-6"
|
||||||
|
>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item label={t('platformSettings.logo')} name="logoUrl">
|
||||||
|
<Input placeholder="Logo URL" className="mb-2" />
|
||||||
|
</Form.Item>
|
||||||
|
<ImagePreview url={Form.useWatch('logoUrl', form)} label="Logo" />
|
||||||
|
<Upload
|
||||||
|
accept="image/*"
|
||||||
|
showUploadList={false}
|
||||||
|
beforeUpload={(file) => handleUpload(file, 'logoUrl')}
|
||||||
|
>
|
||||||
|
<Button icon={<UploadOutlined />} block className="mt-2">上传 Logo</Button>
|
||||||
|
</Upload>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item label={t('platformSettings.icon')} name="iconUrl">
|
||||||
|
<Input placeholder="Icon URL" className="mb-2" />
|
||||||
|
</Form.Item>
|
||||||
|
<ImagePreview url={Form.useWatch('iconUrl', form)} label="Icon" />
|
||||||
|
<Upload
|
||||||
|
accept="image/*"
|
||||||
|
showUploadList={false}
|
||||||
|
beforeUpload={(file) => handleUpload(file, 'iconUrl')}
|
||||||
|
>
|
||||||
|
<Button icon={<UploadOutlined />} block className="mt-2">上传 Icon</Button>
|
||||||
|
</Upload>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item label={t('platformSettings.loginBg')} name="loginBgUrl">
|
||||||
|
<Input placeholder="Background URL" className="mb-2" />
|
||||||
|
</Form.Item>
|
||||||
|
<ImagePreview url={Form.useWatch('loginBgUrl', form)} label="Background" />
|
||||||
|
<Upload
|
||||||
|
accept="image/*"
|
||||||
|
showUploadList={false}
|
||||||
|
beforeUpload={(file) => handleUpload(file, 'loginBgUrl')}
|
||||||
|
>
|
||||||
|
<Button icon={<UploadOutlined />} block className="mt-2">上传背景图</Button>
|
||||||
|
</Upload>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={24}>
|
||||||
|
<Card
|
||||||
|
title={<><FileTextOutlined className="mr-2" /> 合规与版权</>}
|
||||||
|
className="shadow-sm"
|
||||||
|
>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label={t('platformSettings.icp')} name="icpInfo">
|
||||||
|
<Input placeholder="例如:京ICP备12345678号" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label={t('platformSettings.copyright')} name="copyrightInfo">
|
||||||
|
<Input placeholder="例如:© 2026 iMeeting Team." />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
.sys-params-page {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-params-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-params-table-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-params-table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-params-search-input {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-key-text {
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-type-tag {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,375 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Drawer,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Popconfirm,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
Switch,
|
||||||
|
Tooltip,
|
||||||
|
Row,
|
||||||
|
Col
|
||||||
|
} from "antd";
|
||||||
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
pageParams,
|
||||||
|
createParam,
|
||||||
|
updateParam,
|
||||||
|
deleteParam
|
||||||
|
} from "../api";
|
||||||
|
import { usePermission } from "../hooks/usePermission";
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
InfoCircleOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import type { SysParamVO, SysParamQuery } from "../types";
|
||||||
|
import "./SysParams.css";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
export default function SysParams() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { can } = usePermission();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [data, setData] = useState<SysParamVO[]>([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
|
const [queryParams, setQueryParams] = useState<SysParamQuery>({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drawer state
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [editing, setEditing] = useState<SysParamVO | null>(null);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const loadData = useCallback(async (query = queryParams) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await pageParams(query);
|
||||||
|
setData(res.records || []);
|
||||||
|
setTotal(res.total || 0);
|
||||||
|
} catch (e) {
|
||||||
|
message.error(t('common.error'));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [queryParams, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [loadData]);
|
||||||
|
|
||||||
|
const handleSearch = (values: any) => {
|
||||||
|
const newQuery = { ...queryParams, ...values, pageNum: 1 };
|
||||||
|
setQueryParams(newQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
form.resetFields();
|
||||||
|
const newQuery = { pageNum: 1, pageSize: 10 };
|
||||||
|
setQueryParams(newQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setQueryParams(prev => ({ ...prev, pageNum: page, pageSize }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreate = () => {
|
||||||
|
setEditing(null);
|
||||||
|
form.resetFields();
|
||||||
|
form.setFieldsValue({ isSystem: false, status: 1 });
|
||||||
|
setDrawerOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEdit = (record: SysParamVO) => {
|
||||||
|
setEditing(record);
|
||||||
|
form.setFieldsValue(record);
|
||||||
|
setDrawerOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await deleteParam(id);
|
||||||
|
message.success(t('common.success'));
|
||||||
|
loadData();
|
||||||
|
} catch (e) {
|
||||||
|
message.error(t('common.error'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setSaving(true);
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
await updateParam(editing.paramId, values);
|
||||||
|
} else {
|
||||||
|
await createParam(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success(t('common.success'));
|
||||||
|
setDrawerOpen(false);
|
||||||
|
loadData();
|
||||||
|
} catch (e) {
|
||||||
|
// Form validation errors or API errors
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t('sysParams.paramKey'),
|
||||||
|
dataIndex: "paramKey",
|
||||||
|
key: "paramKey",
|
||||||
|
render: (text: string, record: SysParamVO) => (
|
||||||
|
<Space direction="vertical" size={0}>
|
||||||
|
<Text className="param-key-text">{text}</Text>
|
||||||
|
{record.isSystem === 1 && (
|
||||||
|
<Tag color="orange" size={0} style={{ fontSize: 10, marginTop: 2, padding: '0 4px' }}>
|
||||||
|
{t('sysParams.isSystem')}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('sysParams.paramValue'),
|
||||||
|
dataIndex: "paramValue",
|
||||||
|
key: "paramValue",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) => (
|
||||||
|
<Tooltip title={text}>
|
||||||
|
<Text code>{text}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('sysParams.paramType'),
|
||||||
|
dataIndex: "paramType",
|
||||||
|
key: "paramType",
|
||||||
|
width: 120,
|
||||||
|
render: (type: string) => <Tag className="param-type-tag">{type || 'DEFAULT'}</Tag>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('sysParams.description'),
|
||||||
|
dataIndex: "description",
|
||||||
|
key: "description",
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.status'),
|
||||||
|
dataIndex: "status",
|
||||||
|
width: 80,
|
||||||
|
render: (status: number) => (
|
||||||
|
<Tag color={status === 1 ? "green" : "red"}>
|
||||||
|
{status === 1 ? "正常" : "禁用"}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.action'),
|
||||||
|
key: "action",
|
||||||
|
width: 110,
|
||||||
|
fixed: "right" as const,
|
||||||
|
render: (_: any, record: SysParamVO) => (
|
||||||
|
<Space>
|
||||||
|
{can("sys_param:update") && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => openEdit(record)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{can("sys_param:delete") && record.isSystem !== 1 && (
|
||||||
|
<Popconfirm title="确定删除该参数吗?" onConfirm={() => handleDelete(record.paramId)}>
|
||||||
|
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sys-params-page p-6">
|
||||||
|
<div className="sys-params-header mb-6">
|
||||||
|
<div>
|
||||||
|
<Title level={4} className="mb-1">{t('sysParams.title')}</Title>
|
||||||
|
<Text type="secondary">{t('sysParams.subtitle')}</Text>
|
||||||
|
</div>
|
||||||
|
{can("sys_param:create") && (
|
||||||
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||||
|
{t('common.create')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="sys-params-table-card shadow-sm mb-4">
|
||||||
|
<Form
|
||||||
|
layout="inline"
|
||||||
|
onFinish={handleSearch}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
<Form.Item name="paramKey">
|
||||||
|
<Input
|
||||||
|
placeholder={t('sysParams.paramKey')}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 200 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="paramType">
|
||||||
|
<Select
|
||||||
|
placeholder={t('sysParams.paramType')}
|
||||||
|
allowClear
|
||||||
|
style={{ width: 150 }}
|
||||||
|
options={[
|
||||||
|
{ label: 'String', value: 'String' },
|
||||||
|
{ label: 'Number', value: 'Number' },
|
||||||
|
{ label: 'Boolean', value: 'Boolean' },
|
||||||
|
{ label: 'JSON', value: 'JSON' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
{t('common.search')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleReset}>
|
||||||
|
{t('common.reset')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
rowKey="paramId"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
loading={loading}
|
||||||
|
size="middle"
|
||||||
|
pagination={{
|
||||||
|
current: queryParams.pageNum,
|
||||||
|
pageSize: queryParams.pageSize,
|
||||||
|
total: total,
|
||||||
|
showTotal: (tTotal) => t('common.total', { total: tTotal }),
|
||||||
|
onChange: handlePageChange,
|
||||||
|
showSizeChanger: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
<SettingOutlined className="mr-2" />
|
||||||
|
{editing ? t('sysParams.drawerTitleEdit') : t('sysParams.drawerTitleCreate')}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
open={drawerOpen}
|
||||||
|
onClose={() => setDrawerOpen(false)}
|
||||||
|
width={500}
|
||||||
|
destroyOnClose
|
||||||
|
footer={
|
||||||
|
<div className="flex justify-end gap-2 p-2">
|
||||||
|
<Button onClick={() => setDrawerOpen(false)}>{t('common.cancel')}</Button>
|
||||||
|
<Button type="primary" loading={saving} onClick={submit}>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
label={t('sysParams.paramKey')}
|
||||||
|
name="paramKey"
|
||||||
|
rules={[{ required: true, message: t('sysParams.paramKey') }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="sys.config.example" disabled={!!editing} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t('sysParams.paramValue')}
|
||||||
|
name="paramValue"
|
||||||
|
rules={[{ required: true, message: t('sysParams.paramValue') }]}
|
||||||
|
>
|
||||||
|
<Input.TextArea rows={4} placeholder="Enter parameter value" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('sysParams.paramType')}
|
||||||
|
name="paramType"
|
||||||
|
rules={[{ required: true, message: t('sysParams.paramType') }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ label: 'String', value: 'String' },
|
||||||
|
{ label: 'Number', value: 'Number' },
|
||||||
|
{ label: 'Boolean', value: 'Boolean' },
|
||||||
|
{ label: 'JSON', value: 'JSON' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('common.status')}
|
||||||
|
name="status"
|
||||||
|
initialValue={1}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ label: '启用', value: 1 },
|
||||||
|
{ label: '禁用', value: 0 }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<Space>
|
||||||
|
{t('sysParams.isSystem')}
|
||||||
|
<Tooltip title="系统参数通常禁止删除,由开发者在代码中使用">
|
||||||
|
<InfoCircleOutlined style={{ color: '#8c8c8c' }} />
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
name="isSystem"
|
||||||
|
valuePropName="checked"
|
||||||
|
getValueProps={(value) => ({ checked: value === 1 })}
|
||||||
|
getValueFromEvent={(checked) => (checked ? 1 : 0)}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t('sysParams.description')} name="description">
|
||||||
|
<Input.TextArea rows={3} placeholder="Describe the purpose of this parameter" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue