上线系统配置功能
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Path
|
from typing import List
|
||||||
|
from fastapi import APIRouter, Depends, Request, UploadFile
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
from app.common.response import SuccessResponse
|
from app.common.response import SuccessResponse
|
||||||
@@ -10,7 +11,7 @@ from app.core.dependencies import AuthPermission
|
|||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||||
from app.api.v1.schemas.system.config_schema import ConfigCreateSchema, ConfigUpdateSchema
|
from app.api.v1.schemas.system.config_schema import ConfigUpdateSchema
|
||||||
from app.api.v1.params.system.config_param import ConfigQueryParams
|
from app.api.v1.params.system.config_param import ConfigQueryParams
|
||||||
from app.api.v1.services.system.config_service import ConfigService
|
from app.api.v1.services.system.config_service import ConfigService
|
||||||
|
|
||||||
@@ -29,42 +30,29 @@ async def get_obj_list(
|
|||||||
logger.info(f"{auth.user.name} 查询配置列表成功")
|
logger.info(f"{auth.user.name} 查询配置列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询配置列表成功")
|
return SuccessResponse(data=result_dict, msg="查询配置列表成功")
|
||||||
|
|
||||||
|
@router.put("/batch", summary="批量修改配置", description="批量修改配置")
|
||||||
@router.post("/create", summary="创建配置", description="创建配置")
|
async def batch_objs(
|
||||||
async def create_obj(
|
request: Request,
|
||||||
data: ConfigCreateSchema,
|
data: List[ConfigUpdateSchema],
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:create"])),
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await ConfigService.create_services(auth=auth, data=data)
|
|
||||||
logger.info(f"{auth.user.name} 创建配置成功: {result_dict}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="创建配置成功")
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/update", summary="修改配置", description="修改配置")
|
|
||||||
async def update_obj(
|
|
||||||
data: ConfigUpdateSchema,
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:update"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:update"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict = await ConfigService.update_services(auth=auth, id=data.id, data=data)
|
result_list_dict = await ConfigService.batch_services(auth=auth, request=request, data=data)
|
||||||
logger.info(f"{auth.user.name} 修改配置成功: {result_dict}")
|
logger.info(f"{auth.user.name} 批量更新配置成功 {result_list_dict}")
|
||||||
return SuccessResponse(data=result_dict, msg="修改配置成功")
|
return SuccessResponse(data=result_list_dict, msg="批量更新配置成功")
|
||||||
|
|
||||||
|
@router.post("/upload", summary="上传文件", dependencies=[Depends(AuthPermission(permissions=["system:config:upload"]))])
|
||||||
@router.delete("/delete", summary="删除配置", description="删除配置")
|
async def upload_file(
|
||||||
async def delete_obj(
|
file: UploadFile,
|
||||||
id: int = Query(..., description="配置ID"),
|
request: Request
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:delete"])),
|
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
await ConfigService.delete_services(auth=auth, id=id)
|
result_str = await ConfigService.upload_services(request=request, file=file)
|
||||||
logger.info(f"{auth.user.name} 删除配置成功: {id}")
|
logger.info(f"上传文件: {result_str}")
|
||||||
return SuccessResponse(msg="删除配置成功")
|
return SuccessResponse(data=result_str, msg='上传文件成功')
|
||||||
|
|
||||||
|
@router.get("/init", summary="获取初始化配置", description="获取初始化配置")
|
||||||
@router.get("/detail", summary="获取配置详情", description="获取配置详情")
|
async def get_init_config(
|
||||||
async def get_obj_detail(
|
request: Request,
|
||||||
id: int = Query(..., description="配置ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:query"])),
|
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict = await ConfigService.detail_services(id=id, auth=auth)
|
result_dict = await ConfigService.get_init_config(request=request)
|
||||||
logger.info(f"{auth.user.name} 获取配置详情成功 {id}")
|
logger.info(f"获取初始化配置成功 {result_dict}")
|
||||||
return SuccessResponse(data=result_dict, msg="获取配置详情成功")
|
return SuccessResponse(data=result_dict, msg="获取初始化配置成功")
|
||||||
|
|||||||
@@ -16,23 +16,10 @@ class ConfigCRUD(CRUDBase[ConfigModel, ConfigCreateSchema, ConfigUpdateSchema]):
|
|||||||
self.auth = auth
|
self.auth = auth
|
||||||
super().__init__(model=ConfigModel, auth=auth)
|
super().__init__(model=ConfigModel, auth=auth)
|
||||||
|
|
||||||
async def get_curd(self, id: int) -> Optional[ConfigModel]:
|
|
||||||
"""获取配置详情"""
|
|
||||||
return await self.get(id=id)
|
|
||||||
|
|
||||||
async def list_curd(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ConfigModel]:
|
async def list_curd(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ConfigModel]:
|
||||||
"""获取配置列表"""
|
"""获取配置列表"""
|
||||||
return await self.list(search=search, order_by=order_by)
|
return await self.list(search=search, order_by=order_by)
|
||||||
|
|
||||||
async def create_curd(self, data: ConfigCreateSchema) -> Optional[ConfigModel]:
|
|
||||||
"""创建配置"""
|
|
||||||
return await self.create(data=data)
|
|
||||||
|
|
||||||
async def update_curd(self, id: int, data: ConfigUpdateSchema) -> Optional[ConfigModel]:
|
async def update_curd(self, id: int, data: ConfigUpdateSchema) -> Optional[ConfigModel]:
|
||||||
"""更新配置"""
|
"""更新配置"""
|
||||||
return await self.update(id=id, data=data)
|
return await self.update(id=id, data=data)
|
||||||
|
|
||||||
async def delete_curd(self, ids: List[int]) -> None:
|
|
||||||
"""删除配置"""
|
|
||||||
return await self.delete(ids=ids)
|
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
|
|
||||||
|
|
||||||
@@ -29,6 +29,4 @@ class ConfigUpdateSchema(ConfigCreateSchema):
|
|||||||
class ConfigOutSchema(ConfigCreateSchema):
|
class ConfigOutSchema(ConfigCreateSchema):
|
||||||
"""配置响应模型"""
|
"""配置响应模型"""
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
parent_name: Optional[str] = Field(default=None, max_length=40, description="父配置名称")
|
|
||||||
id: int = Field(description="主键ID")
|
id: int = Field(description="主键ID")
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ class CacheService:
|
|||||||
:param request: Request对象
|
:param request: Request对象
|
||||||
:return: 缓存监控信息
|
:return: 缓存监控信息
|
||||||
"""
|
"""
|
||||||
info = await Cache(request).info()
|
info = await Cache(request.app.state.redis).info()
|
||||||
db_size = await Cache(request).db_size()
|
db_size = await Cache(request.app.state.redis).db_size()
|
||||||
command_stats_dict = await Cache(request).commandstats()
|
command_stats_dict = await Cache(request.app.state.redis).commandstats()
|
||||||
|
|
||||||
command_stats = [
|
command_stats = [
|
||||||
dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items()
|
dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items()
|
||||||
@@ -59,7 +59,7 @@ class CacheService:
|
|||||||
:param cache_name: 缓存名称
|
:param cache_name: 缓存名称
|
||||||
:return: 缓存键名列表信息
|
:return: 缓存键名列表信息
|
||||||
"""
|
"""
|
||||||
cache_keys = await Cache(request).get_keys(f'{cache_name}*')
|
cache_keys = await Cache(request.app.state.redis).get_keys(f'{cache_name}*')
|
||||||
cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')]
|
cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')]
|
||||||
|
|
||||||
return cache_key_list
|
return cache_key_list
|
||||||
@@ -74,7 +74,7 @@ class CacheService:
|
|||||||
:param cache_key: 缓存键名
|
:param cache_key: 缓存键名
|
||||||
:return: 缓存内容信息
|
:return: 缓存内容信息
|
||||||
"""
|
"""
|
||||||
cache_value = await Cache(request).get(f'{cache_name}:{cache_key}')
|
cache_value = await Cache(request.app.state.redis).get(f'{cache_name}:{cache_key}')
|
||||||
|
|
||||||
return CacheInfoSchema(cache_key=cache_key, cache_name=cache_name, cache_value=cache_value, remark='').model_dump()
|
return CacheInfoSchema(cache_key=cache_key, cache_name=cache_name, cache_value=cache_value, remark='').model_dump()
|
||||||
|
|
||||||
@@ -87,9 +87,9 @@ class CacheService:
|
|||||||
:param cache_name: 缓存名称
|
:param cache_name: 缓存名称
|
||||||
:return: 操作缓存响应信息
|
:return: 操作缓存响应信息
|
||||||
"""
|
"""
|
||||||
cache_keys = await Cache(request).get_keys(f'{cache_name}*')
|
cache_keys = await Cache(request.app.state.redis).get_keys(f'{cache_name}*')
|
||||||
if cache_keys:
|
if cache_keys:
|
||||||
await Cache(request).delete(*cache_keys)
|
await Cache(request.app.state.redis).delete(*cache_keys)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -102,9 +102,9 @@ class CacheService:
|
|||||||
:param cache_key: 缓存键名
|
:param cache_key: 缓存键名
|
||||||
:return: 操作缓存响应信息
|
:return: 操作缓存响应信息
|
||||||
"""
|
"""
|
||||||
cache_keys = await Cache(request).get_keys(f'*{cache_key}')
|
cache_keys = await Cache(request.app.state.redis).get_keys(f'*{cache_key}')
|
||||||
if cache_keys:
|
if cache_keys:
|
||||||
await Cache(request).delete(*cache_keys)
|
await Cache(request.app.state.redis).delete(*cache_keys)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -116,8 +116,8 @@ class CacheService:
|
|||||||
:param request: Request对象
|
:param request: Request对象
|
||||||
:return: 操作缓存响应信息
|
:return: 操作缓存响应信息
|
||||||
"""
|
"""
|
||||||
cache_keys = await Cache(request).get_keys
|
cache_keys = await Cache(request.app.state.redis).get_keys
|
||||||
if cache_keys:
|
if cache_keys:
|
||||||
await Cache(request).delete(*cache_keys)
|
await Cache(request.app.state.redis).delete(*cache_keys)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ class OnlineService:
|
|||||||
async def get_online_list(cls, request: Request, search: OnlineQueryParams) -> List[Dict]:
|
async def get_online_list(cls, request: Request, search: OnlineQueryParams) -> List[Dict]:
|
||||||
"""获取在线用户列表信息"""
|
"""获取在线用户列表信息"""
|
||||||
# 获取所有在线用户信息
|
# 获取所有在线用户信息
|
||||||
token_keys = await Cache(request).get_keys(f'{RedisInitKeyConfig.ONLINE_USER.key}*')
|
token_keys = await Cache(request.app.state.redis).get_keys(f'{RedisInitKeyConfig.ONLINE_USER.key}*')
|
||||||
if not token_keys:
|
if not token_keys:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 批量获取在线用户信息
|
# 批量获取在线用户信息
|
||||||
online_values = await Cache(request).mget(*token_keys)
|
online_values = await Cache(request.app.state.redis).mget(*token_keys)
|
||||||
online_list = []
|
online_list = []
|
||||||
|
|
||||||
for online_value in online_values:
|
for online_value in online_values:
|
||||||
@@ -53,8 +53,8 @@ class OnlineService:
|
|||||||
# 批量删除token
|
# 批量删除token
|
||||||
token_ids = ids.split(',')
|
token_ids = ids.split(',')
|
||||||
for token_id in token_ids:
|
for token_id in token_ids:
|
||||||
await Cache(request).delete(f"{RedisInitKeyConfig.ONLINE_USER.key}:{token_id}")
|
await Cache(request.app.state.redis).delete(f"{RedisInitKeyConfig.ONLINE_USER.key}:{token_id}")
|
||||||
await Cache(request).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}")
|
await Cache(request.app.state.redis).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class LoginService:
|
|||||||
user_agent = parse(request.headers.get("user-agent"))
|
user_agent = parse(request.headers.get("user-agent"))
|
||||||
|
|
||||||
# 缓存中构建在线用户信息
|
# 缓存中构建在线用户信息
|
||||||
await Cache(request).set(
|
await Cache(request.app.state.redis).set(
|
||||||
key=f"{RedisInitKeyConfig.ONLINE_USER.key}:{user.username}",
|
key=f"{RedisInitKeyConfig.ONLINE_USER.key}:{user.username}",
|
||||||
value=OnlineOutSchema(
|
value=OnlineOutSchema(
|
||||||
session_id=token.access_token,
|
session_id=token.access_token,
|
||||||
@@ -130,17 +130,17 @@ class LoginService:
|
|||||||
))
|
))
|
||||||
|
|
||||||
# 清除该用户之前的token
|
# 清除该用户之前的token
|
||||||
await Cache(request).delete(key=f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{username}')
|
await Cache(request.app.state.redis).delete(key=f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{username}')
|
||||||
await Cache(request).delete(key=f'{RedisInitKeyConfig.REFRESH_TOKEN.key}:{username}')
|
await Cache(request.app.state.redis).delete(key=f'{RedisInitKeyConfig.REFRESH_TOKEN.key}:{username}')
|
||||||
|
|
||||||
# 设置新的token
|
# 设置新的token
|
||||||
await Cache(request).set(
|
await Cache(request.app.state.redis).set(
|
||||||
key=f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{username}',
|
key=f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{username}',
|
||||||
value=access_token,
|
value=access_token,
|
||||||
expire=int(access_expires.total_seconds())
|
expire=int(access_expires.total_seconds())
|
||||||
)
|
)
|
||||||
|
|
||||||
await Cache(request).set(
|
await Cache(request.app.state.redis).set(
|
||||||
key=f'{RedisInitKeyConfig.REFRESH_TOKEN.key}:{username}',
|
key=f'{RedisInitKeyConfig.REFRESH_TOKEN.key}:{username}',
|
||||||
value=refresh_token,
|
value=refresh_token,
|
||||||
expire=int(refresh_expires.total_seconds())
|
expire=int(refresh_expires.total_seconds())
|
||||||
@@ -189,7 +189,7 @@ class LoginService:
|
|||||||
username: str = payload.sub
|
username: str = payload.sub
|
||||||
|
|
||||||
# 删除Redis中的token
|
# 删除Redis中的token
|
||||||
await Cache(request).clear()
|
# await Cache(request.app.state.redis).clear()
|
||||||
|
|
||||||
logger.info(f"用户退出登录成功,会话编号:{username}")
|
logger.info(f"用户退出登录成功,会话编号:{username}")
|
||||||
return True
|
return True
|
||||||
@@ -221,7 +221,7 @@ class CaptchaService:
|
|||||||
|
|
||||||
# 保存到Redis并设置过期时间
|
# 保存到Redis并设置过期时间
|
||||||
redis_key = f"{RedisInitKeyConfig.CAPTCHA_CODES.key}:{captcha_key}"
|
redis_key = f"{RedisInitKeyConfig.CAPTCHA_CODES.key}:{captcha_key}"
|
||||||
await Cache(request).set(
|
await Cache(request.app.state.redis).set(
|
||||||
key=redis_key,
|
key=redis_key,
|
||||||
value=captcha_value,
|
value=captcha_value,
|
||||||
expire=settings.CAPTCHA_EXPIRE_SECONDS
|
expire=settings.CAPTCHA_EXPIRE_SECONDS
|
||||||
@@ -257,7 +257,7 @@ class CaptchaService:
|
|||||||
# 获取Redis中存储的验证码
|
# 获取Redis中存储的验证码
|
||||||
redis_key = f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{key}'
|
redis_key = f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{key}'
|
||||||
|
|
||||||
captcha_value = await Cache(request).get(redis_key)
|
captcha_value = await Cache(request.app.state.redis).get(redis_key)
|
||||||
if not captcha_value:
|
if not captcha_value:
|
||||||
logger.warning('验证码已过期或不存在')
|
logger.warning('验证码已过期或不存在')
|
||||||
raise CustomException(msg="验证码已过期")
|
raise CustomException(msg="验证码已过期")
|
||||||
@@ -268,6 +268,6 @@ class CaptchaService:
|
|||||||
raise CustomException(msg="验证码错误")
|
raise CustomException(msg="验证码错误")
|
||||||
|
|
||||||
# 验证成功后删除验证码,避免重复使用
|
# 验证成功后删除验证码,避免重复使用
|
||||||
await Cache(request).delete(key=redis_key)
|
await Cache(request.app.state.redis).delete(key=redis_key)
|
||||||
logger.info(f'验证码校验成功,key:{key}')
|
logger.info(f'验证码校验成功,key:{key}')
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from fastapi import Depends, Request, UploadFile
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from aioredis import Redis
|
||||||
|
|
||||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||||
from app.api.v1.schemas.system.config_schema import ConfigCreateSchema, ConfigUpdateSchema, ConfigOutSchema
|
from app.api.v1.schemas.system.config_schema import ConfigOutSchema, ConfigUpdateSchema
|
||||||
from app.api.v1.params.system.config_param import ConfigQueryParams
|
from app.api.v1.params.system.config_param import ConfigQueryParams
|
||||||
from app.api.v1.cruds.system.config_crud import ConfigCRUD
|
from app.api.v1.cruds.system.config_crud import ConfigCRUD
|
||||||
|
from app.common.enums import RedisInitKeyConfig
|
||||||
|
from app.core.cache_crud import Cache
|
||||||
|
from app.core.dependencies import db_getter
|
||||||
|
from app.utils.upload_util import UploadUtil
|
||||||
|
from app.core.base_schema import UploadResponseSchema
|
||||||
|
from app.core.exceptions import CustomException
|
||||||
|
from app.config.setting import settings
|
||||||
|
from app.core.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
@@ -13,27 +26,61 @@ class ConfigService:
|
|||||||
配置管理模块服务层
|
配置管理模块服务层
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def detail_services(cls, auth: AuthSchema, id: int) -> Dict:
|
|
||||||
config_obj = await ConfigCRUD(auth).get_curd(id=id)
|
|
||||||
return ConfigOutSchema.model_validate(config_obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def list_services(cls, auth: AuthSchema, search: ConfigQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
async def list_services(cls, auth: AuthSchema, search: ConfigQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||||
config_obj_list = await ConfigCRUD(auth).list_curd(search=search.__dict__, order_by=order_by)
|
config_obj_list = await ConfigCRUD(auth).list_curd(search=search.__dict__, order_by=order_by)
|
||||||
return [ConfigOutSchema.model_validate(config_obj).model_dump() for config_obj in config_obj_list]
|
return [ConfigOutSchema.model_validate(config_obj).model_dump() for config_obj in config_obj_list]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_services(cls, auth: AuthSchema, data: ConfigCreateSchema) -> Dict:
|
async def batch_services(cls, auth: AuthSchema, request: Request, data: List[ConfigUpdateSchema]) -> List[Dict]:
|
||||||
config_obj = await ConfigCRUD(auth).create_curd(data=data)
|
result = []
|
||||||
return ConfigOutSchema.model_validate(config_obj).model_dump()
|
for obj in data:
|
||||||
|
new_obj = await ConfigCRUD(auth).update_curd(id=obj.id, data=obj)
|
||||||
|
new_obj_dict = ConfigOutSchema.model_validate(new_obj).model_dump()
|
||||||
|
result.append(new_obj_dict)
|
||||||
|
|
||||||
|
cls.init_config(redis=request.app.state.redis, db=auth.db)
|
||||||
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def update_services(cls, auth: AuthSchema, id: int, data: ConfigUpdateSchema) -> Dict:
|
async def upload_services(cls, request: Request, file: UploadFile) -> Dict:
|
||||||
config_obj = await ConfigCRUD(auth).update_curd(id=id, data=data)
|
"""上传文件"""
|
||||||
return ConfigOutSchema.model_validate(config_obj).model_dump()
|
if not file:
|
||||||
|
raise CustomException(msg="请选择要上传的文件")
|
||||||
|
filename, filepath = await UploadUtil.upload_file(file=file)
|
||||||
|
|
||||||
|
return UploadResponseSchema(
|
||||||
|
file_path=f'{filepath}',
|
||||||
|
file_name=filename,
|
||||||
|
origin_name=file.filename,
|
||||||
|
file_url=f'{request.base_url}{filepath}',
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_services(cls, auth: AuthSchema, id: int) -> None:
|
async def init_config(cls, redis: Redis, db: AsyncSession):
|
||||||
await ConfigCRUD(auth).delete_curd(ids=[id])
|
auth = AuthSchema(db=db)
|
||||||
|
config_obj_list = await ConfigCRUD(auth).list_curd()
|
||||||
|
config_obj_list_dict = [ConfigOutSchema.model_validate(config_obj).model_dump() for config_obj in config_obj_list]
|
||||||
|
|
||||||
|
# 保存到Redis并设置过期时间
|
||||||
|
redis_key = f"{RedisInitKeyConfig.System_Config.key}:{'init_system_config'}"
|
||||||
|
try:
|
||||||
|
value = json.dumps(config_obj_list_dict, ensure_ascii=False)
|
||||||
|
await Cache(redis).set(
|
||||||
|
key=redis_key,
|
||||||
|
value=value,
|
||||||
|
expire=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||||
|
)
|
||||||
|
logger.info(f"初始化系统配置成功: {config_obj_list_dict}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"初始化系统配置失败: {e}")
|
||||||
|
raise CustomException(msg="初始化系统配置失败")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_init_config(cls, request: Request) -> Dict:
|
||||||
|
"""获取系统配置"""
|
||||||
|
redis_key = f"{RedisInitKeyConfig.System_Config.key}:{'init_system_config'}"
|
||||||
|
config_obj_list_dict = await Cache(request.app.state.redis).get(redis_key)
|
||||||
|
if not config_obj_list_dict:
|
||||||
|
raise CustomException(msg="系统配置不存在")
|
||||||
|
return config_obj_list_dict
|
||||||
@@ -30,6 +30,7 @@ class RedisInitKeyConfig(Enum):
|
|||||||
REFRESH_TOKEN = {'key': 'refresh_token', 'remark': '刷新令牌信息'}
|
REFRESH_TOKEN = {'key': 'refresh_token', 'remark': '刷新令牌信息'}
|
||||||
CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'}
|
CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'}
|
||||||
ONLINE_USER = {'key': 'online_user', 'remark': '在线用户信息'}
|
ONLINE_USER = {'key': 'online_user', 'remark': '在线用户信息'}
|
||||||
|
System_Config = {'key': 'system_config', 'remark': '系统配置'}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
|
|||||||
@@ -3,18 +3,16 @@
|
|||||||
import pickle
|
import pickle
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from aioredis import Redis
|
from aioredis import Redis
|
||||||
from fastapi import Request
|
|
||||||
|
|
||||||
from app.config.setting import settings
|
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Cache:
|
class Cache:
|
||||||
"""缓存工具类"""
|
"""缓存工具类"""
|
||||||
|
|
||||||
def __init__(self, request: Request):
|
def __init__(self, redis: Redis):
|
||||||
"""初始化"""
|
"""初始化"""
|
||||||
self.redis: Redis = request.app.state.redis
|
self.redis = redis
|
||||||
|
|
||||||
async def mget(self, *keys: list)-> list:
|
async def mget(self, *keys: list)-> list:
|
||||||
"""批量获取缓存
|
"""批量获取缓存
|
||||||
@@ -73,7 +71,7 @@ class Cache:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
await self.redis.set(
|
await self.redis.set(
|
||||||
name = f"{key}",
|
name = key,
|
||||||
value = data,
|
value = data,
|
||||||
ex=expire
|
ex=expire
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class RequestLogMiddleware(BaseHTTPMiddleware):
|
|||||||
response.headers["X-Process-Time"] = str(process_time)
|
response.headers["X-Process-Time"] = str(process_time)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"请求来源: {request.client.host}, "
|
|
||||||
f"响应状态: {response.status_code}, "
|
f"响应状态: {response.status_code}, "
|
||||||
f"响应内容长度: {response.headers.get('content-length', '0')}, "
|
f"响应内容长度: {response.headers.get('content-length', '0')}, "
|
||||||
f"处理时间: {process_time}s"
|
f"处理时间: {process_time}s"
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ from app.core.exceptions import (
|
|||||||
ResponseValidationHandle,
|
ResponseValidationHandle,
|
||||||
ResponseValidationError
|
ResponseValidationError
|
||||||
)
|
)
|
||||||
|
from app.api.v1.services.system.config_service import ConfigService
|
||||||
|
from app.core.database import async_session
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -40,6 +42,10 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
|
|||||||
"""
|
"""
|
||||||
logger.info(settings.BANNER + '\n' + f'{settings.TITLE} 服务开始启动...')
|
logger.info(settings.BANNER + '\n' + f'{settings.TITLE} 服务开始启动...')
|
||||||
await import_modules_async(modules=settings.get_events, desc="全局事件", app=app, status=True)
|
await import_modules_async(modules=settings.get_events, desc="全局事件", app=app, status=True)
|
||||||
|
async with async_session() as session:
|
||||||
|
await ConfigService().init_config(redis=app.state.redis, db=session)
|
||||||
|
logger.info('初始化系统配置完成...')
|
||||||
|
|
||||||
logger.info(f'{settings.TITLE} 服务成功启动...')
|
logger.info(f'{settings.TITLE} 服务成功启动...')
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"parent_id": 1,
|
"parent_id": 1,
|
||||||
"name": "网页标题",
|
"name": "网页标题",
|
||||||
"fied_key": "web_title",
|
"fied_key": "web_title",
|
||||||
"fied_value": "fastapi-vue-admin",
|
"fied_value": "FastAPI Vue Admin",
|
||||||
"order": 1
|
"order": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"parent_id": 1,
|
"parent_id": 1,
|
||||||
"name": "网站小图标",
|
"name": "网站小图标",
|
||||||
"fied_key": "web_favicon",
|
"fied_key": "web_favicon",
|
||||||
"fied_value": null,
|
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090456A114.png",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "登陆标题",
|
"name": "登陆标题",
|
||||||
"fied_key": "login_title",
|
"fied_key": "login_title",
|
||||||
"fied_value": "fastapi-vue-admin",
|
"fied_value": "FastAPI Vue Admin",
|
||||||
"order": 1
|
"order": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "登陆描述",
|
"name": "登陆描述",
|
||||||
"fied_key": "login_description",
|
"fied_key": "login_description",
|
||||||
"fied_value": "企业级后台管理系统",
|
"fied_value": "FastAPI Vue Admin 是完全开源的权限管理系统",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "登录logo",
|
"name": "登录logo",
|
||||||
"fied_key": "login_logo",
|
"fied_key": "login_logo",
|
||||||
"fied_value": null,
|
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090448A263.png",
|
||||||
"order": 3
|
"order": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "登录背景",
|
"name": "登录背景",
|
||||||
"fied_key": "login_background",
|
"fied_key": "login_background",
|
||||||
"fied_value": null,
|
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/background_20250109090435A357.png",
|
||||||
"order": 4
|
"order": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "版权信息",
|
"name": "版权信息",
|
||||||
"fied_key": "copyright",
|
"fied_key": "copyright",
|
||||||
"fied_value": "2021-2025 fastapi-vue-admin.com 版权所有",
|
"fied_value": "Copyright © 2021-2025 fastapi-vue-admin.com 版权所有",
|
||||||
"order": 5
|
"order": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "帮助链接",
|
"name": "帮助",
|
||||||
"fied_key": "help_url",
|
"fied_key": "help_url",
|
||||||
"fied_value": "https://django-vue-admin.com",
|
"fied_value": "https://django-vue-admin.com",
|
||||||
"order": 7
|
"order": 7
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
{
|
{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "隐私链接",
|
"name": "隐私",
|
||||||
"fied_key": "privacy_url",
|
"fied_key": "privacy_url",
|
||||||
"fied_value": "/api/system/clause/privacy.html",
|
"fied_value": "/api/system/clause/privacy.html",
|
||||||
"order": 8
|
"order": 8
|
||||||
@@ -98,9 +98,17 @@
|
|||||||
{
|
{
|
||||||
"id": 13,
|
"id": 13,
|
||||||
"parent_id": 4,
|
"parent_id": 4,
|
||||||
"name": "条款链接",
|
"name": "条款",
|
||||||
"fied_key": "clause_url",
|
"fied_key": "clause_url",
|
||||||
"fied_value": "/api/system/clause/terms_service.html",
|
"fied_value": "/api/system/clause/terms_service.html",
|
||||||
"order": 9
|
"order": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"parent_id": 4,
|
||||||
|
"name": "源码",
|
||||||
|
"fied_key": "code_url",
|
||||||
|
"fied_value": "https://gitee.com/tao__tao/fastapi_vue_admin.git",
|
||||||
|
"order": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -970,5 +970,41 @@
|
|||||||
"redirect": null,
|
"redirect": null,
|
||||||
"description": "初始化数据",
|
"description": "初始化数据",
|
||||||
"creator_id": 1
|
"creator_id": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"name": "批量更新配置",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 4,
|
||||||
|
"permission": "system:config:update",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"parent_id": 51,
|
||||||
|
"available": true,
|
||||||
|
"cache": true,
|
||||||
|
"hidden": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据",
|
||||||
|
"creator_id": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"name": "文件上传",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 5,
|
||||||
|
"permission": "system:config:upload",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"parent_id": 51,
|
||||||
|
"available": true,
|
||||||
|
"cache": true,
|
||||||
|
"hidden": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据",
|
||||||
|
"creator_id": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -223,10 +223,6 @@
|
|||||||
"role_id": 1,
|
"role_id": 1,
|
||||||
"menu_id": 56
|
"menu_id": 56
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 57
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"role_id": 2,
|
"role_id": 2,
|
||||||
"menu_id": 1
|
"menu_id": 1
|
||||||
|
|||||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 256 KiB |
@@ -20,6 +20,7 @@
|
|||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-echarts": "^6.6.9",
|
"vue-echarts": "^6.6.9",
|
||||||
|
"vue-json-viewer": "^3.0.4",
|
||||||
"vue-request": "^2.0.4",
|
"vue-request": "^2.0.4",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vuex": "^4.1.0",
|
"vuex": "^4.1.0",
|
||||||
@@ -1506,6 +1507,17 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clipboard": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"good-listener": "^1.2.2",
|
||||||
|
"select": "^1.1.2",
|
||||||
|
"tiny-emitter": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/codepage": {
|
"node_modules/codepage": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
|
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
|
||||||
@@ -1604,6 +1616,12 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delegate": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
@@ -1767,6 +1785,15 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/good-listener": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delegate": "^3.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -2537,6 +2564,12 @@
|
|||||||
"compute-scroll-into-view": "^1.0.20"
|
"compute-scroll-into-view": "^1.0.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/select": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shallow-equal": {
|
"node_modules/shallow-equal": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||||
@@ -2679,6 +2712,12 @@
|
|||||||
"node": ">=12.22"
|
"node": ">=12.22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@@ -2845,6 +2884,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-json-viewer": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-json-viewer/-/vue-json-viewer-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clipboard": "^2.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-request": {
|
"node_modules/vue-request": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-request/-/vue-request-2.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-request/-/vue-request-2.0.4.tgz",
|
||||||
|
|||||||
@@ -24,19 +24,26 @@ export function createConfig(body) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConfig(body) {
|
export function batchConfig(body) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/v1/system/config/update",
|
url: "/api/v1/system/config/batch",
|
||||||
method: "put",
|
method: "put",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteConfig(params) {
|
export function uploadFile(body) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/v1/system/config/delete",
|
url: "/api/v1/system/config/upload",
|
||||||
method: "delete",
|
method: "post",
|
||||||
params: params,
|
data: body,
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getInitConfig() {
|
||||||
|
return request({
|
||||||
|
url: "/api/v1/system/config/init",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
>
|
>
|
||||||
<!-- Logo 区域 -->
|
<!-- Logo 区域 -->
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<a-image src="/logo.png" :preview="false" :width="28" :height="28" />
|
<a-image :src="initConfigState.web_favicon" :preview="false" :width="28" :height="28" />
|
||||||
<h1 class="logo-title" v-show="!menuState.collapsed">fastapi-vue-admin</h1>
|
<h1 class="logo-title" v-show="!menuState.collapsed">{{ initConfigState.web_title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-menu
|
<a-menu
|
||||||
@@ -147,6 +147,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, computed, watch, onMounted, inject, type Ref } from "vue";
|
import { reactive, computed, watch, onMounted, inject, type Ref } from "vue";
|
||||||
import type { MenuProps, ItemType } from "ant-design-vue";
|
import type { MenuProps, ItemType } from "ant-design-vue";
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import storage from 'store';
|
import storage from 'store';
|
||||||
import store from '@/store';
|
import store from '@/store';
|
||||||
@@ -166,6 +167,7 @@ import {
|
|||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { logout } from '@/api/system/auth';
|
import { logout } from '@/api/system/auth';
|
||||||
import { getNoticeList } from '@/api/system/notice'
|
import { getNoticeList } from '@/api/system/notice'
|
||||||
|
import { getInitConfig } from "@/api/system/config"
|
||||||
|
|
||||||
// 通知公告获取
|
// 通知公告获取
|
||||||
const dataSource = reactive({
|
const dataSource = reactive({
|
||||||
@@ -327,13 +329,38 @@ const noticeState = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const initConfigState = reactive({
|
||||||
|
web_title: '',
|
||||||
|
web_favicon: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const initConfig = () => {
|
||||||
|
getInitConfig()
|
||||||
|
.then(response => {
|
||||||
|
const { status_code, data } = response.data;
|
||||||
|
if (status_code === 200) {
|
||||||
|
const configData = JSON.parse(data);
|
||||||
|
configData.forEach(item => {
|
||||||
|
if (item.fied_key === 'web_title') {
|
||||||
|
initConfigState.web_title = item.fied_value;
|
||||||
|
} else if (item.fied_key === 'web_favicon') {
|
||||||
|
initConfigState.web_favicon = item.fied_value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
message.error('获取系统配置失败');
|
||||||
|
console.error(error); // 打印错误信息以便调试
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 在页面加载时获取通知列表
|
// 在页面加载时获取通知列表
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initMenu();
|
initMenu();
|
||||||
handleNoticeList();
|
handleNoticeList();
|
||||||
|
initConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:dataSource="cacheNames"
|
:dataSource="cacheNames"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
:scroll="{ y: 600 }"
|
||||||
rowKey="cache_name"
|
rowKey="cache_name"
|
||||||
>
|
>
|
||||||
<a-table-column key="cache_name" title="缓存名称" align="center" :ellipsis="true">
|
<a-table-column key="cache_name" title="缓存名称" align="center" :ellipsis="true">
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
:loading="subLoading"
|
:loading="subLoading"
|
||||||
:dataSource="cacheKeys.map(key => ({ cacheKey: key }))"
|
:dataSource="cacheKeys.map(key => ({ cacheKey: key }))"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
:scroll="{ y: 600 }"
|
||||||
rowKey="cacheKey"
|
rowKey="cacheKey"
|
||||||
>
|
>
|
||||||
<a-table-column key="cacheKey" title="缓存键名" align="center" :ellipsis="true">
|
<a-table-column key="cacheKey" title="缓存键名" align="center" :ellipsis="true">
|
||||||
@@ -147,7 +149,7 @@
|
|||||||
<a-form-item label="缓存内容:" name="cache_value">
|
<a-form-item label="缓存内容:" name="cache_value">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="cacheForm.cache_value"
|
v-model:value="cacheForm.cache_value"
|
||||||
:rows="8"
|
:rows="19"
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout>
|
<a-layout>
|
||||||
<!-- 页面主体 -->
|
<!-- 页面主体 -->
|
||||||
<div class="container">
|
<div class="container" :style="{ backgroundImage: `url(${initConfigState.login_background})` }">
|
||||||
<a-layout-content :style="contentStyle">
|
<a-layout-content :style="contentStyle">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<a-image src="/logo.png" :preview="false" />
|
<a-image :src="initConfigState.login_logo" :preview="false" />
|
||||||
</div>
|
</div>
|
||||||
<div class="title">FastAPI Vue Admin</div>
|
<div class="title">{{ initConfigState.login_title }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">FastAPI Vue Admin 是完全开源的权限管理系统</div>
|
<div class="desc">{{ initConfigState.login_description }}</div>
|
||||||
|
|
||||||
<div class="login-main" style="width: 330px; margin: 0 auto;">
|
<div class="login-main" style="width: 330px; margin: 0 auto;">
|
||||||
<a-tabs centered>
|
<a-tabs centered>
|
||||||
@@ -62,13 +62,28 @@
|
|||||||
|
|
||||||
<!-- 页面底部 -->
|
<!-- 页面底部 -->
|
||||||
<a-layout-footer :style="footerStyle">
|
<a-layout-footer :style="footerStyle">
|
||||||
<div class="footer-copyright">
|
<div class="footer-content">
|
||||||
<a-button type="link" href="https://gitee.com/tao__tao/fastapi_vue_admin.git">
|
<div class="footer-links">
|
||||||
<icon-font type="icon-copyright" :style="{ fontSize: '16px' }" />
|
<a-button type="link" :href="initConfigState.code_url">
|
||||||
Copyright © 2024 insistence.tech All Rights Reserved. 皖ICP备2023021369号-1
|
<icon-font type="icon-copyright" :style="{ fontSize: '16px' }" />
|
||||||
</a-button>
|
{{ initConfigState.copyright }} |
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" :href="initConfigState.help_url">
|
||||||
|
{{ initConfigState.help_name }} |
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" :href="initConfigState.privacy_url">
|
||||||
|
{{ initConfigState.privacy_name }} |
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" :href="initConfigState.clause_url">
|
||||||
|
{{ initConfigState.clause_name }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="footer-record">
|
||||||
|
{{ initConfigState.keep_record }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-footer>
|
</a-layout-footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 弹窗区域 -->
|
<!-- 弹窗区域 -->
|
||||||
@@ -146,6 +161,7 @@ import { save_token } from "@/utils/util"
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { login, getCaptcha } from "@/api/system/auth"
|
import { login, getCaptcha } from "@/api/system/auth"
|
||||||
import { registerUser, forgetPassword } from "@/api/system/user"
|
import { registerUser, forgetPassword } from "@/api/system/user"
|
||||||
|
import { getInitConfig } from "@/api/system/config"
|
||||||
import type { LoginForm, CaptchaState, ForgetPasswordForm, RegisterForm } from './types'
|
import type { LoginForm, CaptchaState, ForgetPasswordForm, RegisterForm } from './types'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -271,7 +287,70 @@ const requestCaptcha = () => {
|
|||||||
.catch(() => captchaState.enable = false);
|
.catch(() => captchaState.enable = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(requestCaptcha);
|
const initConfigState = reactive({
|
||||||
|
login_title: '',
|
||||||
|
login_description: '/logo.png',
|
||||||
|
login_logo: '',
|
||||||
|
login_background: '/background.png',
|
||||||
|
copyright: '',
|
||||||
|
copyright_name: '',
|
||||||
|
keep_record: '',
|
||||||
|
keep_record_name: '',
|
||||||
|
help_url: '',
|
||||||
|
help_name: '',
|
||||||
|
privacy_url: '',
|
||||||
|
privacy_name: '',
|
||||||
|
clause_url: '',
|
||||||
|
clause_name: '',
|
||||||
|
code_url: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const initConfig = () => {
|
||||||
|
getInitConfig()
|
||||||
|
.then(response => {
|
||||||
|
const { status_code, data } = response.data;
|
||||||
|
if (status_code === 200) {
|
||||||
|
const configData = JSON.parse(data);
|
||||||
|
configData.forEach(item => {
|
||||||
|
if (item.fied_key === 'login_title') {
|
||||||
|
initConfigState.login_title = item.fied_value;
|
||||||
|
} else if (item.fied_key === 'login_description') {
|
||||||
|
initConfigState.login_description = item.fied_value;
|
||||||
|
} else if (item.fied_key === 'login_logo') {
|
||||||
|
initConfigState.login_logo = item.fied_value;
|
||||||
|
} else if (item.fied_key === 'login_background') {
|
||||||
|
initConfigState.login_background = item.fied_value;
|
||||||
|
} else if (item.fied_key === 'copyright') {
|
||||||
|
initConfigState.copyright = item.fied_value;
|
||||||
|
initConfigState.copyright_name = item.name;
|
||||||
|
} else if (item.fied_key === 'keep_record') {
|
||||||
|
initConfigState.keep_record = item.fied_value;
|
||||||
|
initConfigState.keep_record_name = item.name;
|
||||||
|
} else if (item.fied_key === 'help_url') {
|
||||||
|
initConfigState.help_url = item.fied_value;
|
||||||
|
initConfigState.help_name = item.name;
|
||||||
|
} else if (item.fied_key === 'privacy_url') {
|
||||||
|
initConfigState.privacy_url = item.fied_value;
|
||||||
|
initConfigState.privacy_name = item.name;
|
||||||
|
} else if (item.fied_key === 'clause_url') {
|
||||||
|
initConfigState.clause_url = item.fied_value;
|
||||||
|
initConfigState.clause_name = item.name;
|
||||||
|
} else if (item.fied_key === 'code_url') {
|
||||||
|
initConfigState.code_url = item.fied_value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
message.error('获取系统配置失败');
|
||||||
|
console.error(error); // 打印错误信息以便调试
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
requestCaptcha();
|
||||||
|
initConfig();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -282,7 +361,6 @@ onMounted(requestCaptcha);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-image: url("/background.png");
|
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
|
|||||||
@@ -1,376 +1,179 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 页面头部 -->
|
<!-- 页面头部 -->
|
||||||
<page-header />
|
<page-header />
|
||||||
|
|
||||||
<!-- 搜索表单 -->
|
<!-- 配置项编辑区域 -->
|
||||||
<div class="tree-search-wrapper">
|
<div class="config-edit-wrapper">
|
||||||
<a-card :bordered="false">
|
<a-card title="系统配置" :bordered="false">
|
||||||
<a-form :model="queryState" @finish="onFinish">
|
<!-- 动态生成配置项 -->
|
||||||
<a-row>
|
<template v-for="group in updateState" :key="group.id">
|
||||||
<a-col flex="0 1 450px">
|
<a-card :title="group.name" :bordered="false" style="margin-bottom: 24px;">
|
||||||
<a-form-item name="name" label="名称" style="max-width: 300px;">
|
<a-row :gutter="16">
|
||||||
<a-input v-model:value="queryState.name" placeholder="请输入名称" allowClear />
|
<template v-for="config in group.children" :key="config.id">
|
||||||
</a-form-item>
|
<a-col :span="12">
|
||||||
</a-col>
|
<!-- 图片上传类型 -->
|
||||||
</a-row>
|
<a-form-item v-if="config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background'" :label="config.name">
|
||||||
<a-row>
|
<a-upload
|
||||||
<a-col>
|
v-model:file-list="config.fileList"
|
||||||
<a-space>
|
list-type="picture-card"
|
||||||
<a-button type="primary" html-type="submit" :loading="tableLoading">查询</a-button>
|
:before-upload="beforeUpload"
|
||||||
<a-button @click="resetFields">重置</a-button>
|
:custom-request="(options) => handleUpload(options, config)"
|
||||||
</a-space>
|
>
|
||||||
</a-col>
|
<div v-if="!config.fileList || config.fileList.length === 0">
|
||||||
</a-row>
|
<plus-outlined />
|
||||||
</a-form>
|
<div style="margin-top: 8px">上传图片</div>
|
||||||
</a-card>
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 输入框类型 -->
|
||||||
|
<a-form-item v-else :label="config.name">
|
||||||
|
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</template>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 保存按钮 -->
|
||||||
|
<div style="text-align: right; margin-top: 24px;">
|
||||||
|
<a-button type="primary" @click="handleSave">保存</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
</a-card>
|
||||||
<!-- 表格区域 -->
|
|
||||||
<div class="table-wrapper">
|
|
||||||
<a-card title="配置列表" :bordered="false" :headStyle="{ borderBottom: 'none', padding: '20px 24px' }"
|
|
||||||
:bodyStyle="{ padding: '0 24px', minHeight: 'calc(100vh - 400px)' }">
|
|
||||||
<template #extra>
|
|
||||||
<a-space>
|
|
||||||
<a-button type="primary" :icon="h(PlusOutlined)" @click="modalHandle('create')">新建</a-button>
|
|
||||||
<a-button type="primary" @click="openSystemConfigModal">系统配置</a-button>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 树形表格 -->
|
|
||||||
<a-table :rowKey="record => record.id"
|
|
||||||
:columns="columns"
|
|
||||||
:data-source="dataSource"
|
|
||||||
:loading="tableLoading"
|
|
||||||
:scroll="{ x: 500, y: 'calc(100vh - 500px)' }"
|
|
||||||
:row-selection="rowSelection"
|
|
||||||
:pagination="false"
|
|
||||||
:style="{ minHeight: '500px' }">
|
|
||||||
<template #bodyCell="{ column, record }">
|
|
||||||
|
|
||||||
<template v-if="column.dataIndex === 'action'">
|
|
||||||
<a-space size="middle">
|
|
||||||
<a @click="modalHandle('view', record)">查看</a>
|
|
||||||
<a @click="modalHandle('update', record)">修改</a>
|
|
||||||
<a-popconfirm title="确定要删除吗?" ok-text="确定" cancel-text="取消"
|
|
||||||
@confirm="deleteRow(record)">
|
|
||||||
<a style="color: red;">删除</a>
|
|
||||||
</a-popconfirm>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 系统配置弹窗 -->
|
|
||||||
<a-modal v-model:open="systemConfigModalVisible" title="系统配置" :width="800" :destroyOnClose="true"
|
|
||||||
:confirmLoading="systemConfigModalLoading" @ok="handleSystemConfigSubmit">
|
|
||||||
<!-- 基础配置部分 -->
|
|
||||||
<a-card title="基础配置" :bordered="false" style="margin-bottom: 16px;">
|
|
||||||
<a-form ref="systemConfigForm" :model="systemConfigState" :label-col="{ span: 5 }"
|
|
||||||
:wrapper-col="{ span: 15 }">
|
|
||||||
<!-- 基础配置的顶级配置项 -->
|
|
||||||
<a-form-item v-for="config in systemConfigState.filter(item => item.fied_key === 'base')"
|
|
||||||
:key="config.id" :name="config.id" :label="config.name"
|
|
||||||
:rules="[{ required: true, message: `请输入${config.name}` }]">
|
|
||||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<!-- 基础配置的子配置项 -->
|
|
||||||
<a-form-item v-for="config in systemConfigState.filter(item => item.parent_id === 1)"
|
|
||||||
:key="config.id" :name="config.id" :label="config.name"
|
|
||||||
:rules="[{ required: true, message: `请输入${config.name}` }]">
|
|
||||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-card>
|
|
||||||
|
|
||||||
<!-- 登录页配置部分 -->
|
|
||||||
<a-card title="登录页配置" :bordered="false">
|
|
||||||
<a-form ref="systemConfigForm" :model="systemConfigState" :label-col="{ span: 5 }"
|
|
||||||
:wrapper-col="{ span: 15 }">
|
|
||||||
<!-- 登录页配置的顶级配置项 -->
|
|
||||||
<a-form-item v-for="config in systemConfigState.filter(item => item.fied_key === 'login')"
|
|
||||||
:key="config.id" :name="config.id" :label="config.name"
|
|
||||||
:rules="[{ required: true, message: `请输入${config.name}` }]">
|
|
||||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<!-- 登录页配置的子配置项 -->
|
|
||||||
<a-form-item v-for="config in systemConfigState.filter(item => item.parent_id === 4)"
|
|
||||||
:key="config.id" :name="config.id" :label="config.name"
|
|
||||||
:rules="[{ required: true, message: `请输入${config.name}` }]">
|
|
||||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-card>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, computed, onMounted, h, unref } from 'vue';
|
import { reactive, onMounted } from 'vue';
|
||||||
import { message, Modal, Table } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import type { MenuProps, TableColumnsType } from 'ant-design-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { PlusOutlined, DownOutlined, CheckOutlined, StopOutlined } from '@ant-design/icons-vue';
|
import { getConfigList, batchConfig, uploadFile } from '@/api/system/config';
|
||||||
import { listToTree, cloneDeep, isEmpty } from '@/utils/util';
|
import PageHeader from '@/components/PageHeader.vue';
|
||||||
import { getConfigList, createConfig, updateConfig, deleteConfig } from '@/api/system/config';
|
import type { tableDataType } from './types'
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
|
||||||
import type { searchDataType, tableDataType } from './types';
|
|
||||||
|
|
||||||
// 响应式数据
|
interface ConfigGroup extends tableDataType {
|
||||||
const createForm = ref();
|
children: tableDataType[];
|
||||||
const updateForm = ref();
|
fileList?: any[];
|
||||||
const tableLoading = ref(false);
|
}
|
||||||
const openModal = ref(false);
|
|
||||||
const modalTitle = ref('');
|
|
||||||
const modalSubmitLoading = ref(false);
|
|
||||||
const detailStateLoading = ref(false);
|
|
||||||
const dataSource = ref<tableDataType[]>([]);
|
|
||||||
const selectedRowKeys = ref<tableDataType['id'][]>([]);
|
|
||||||
|
|
||||||
const queryState = reactive<searchDataType>({
|
const updateState = reactive<ConfigGroup[]>([]);
|
||||||
name: null,
|
|
||||||
});
|
|
||||||
const createState = reactive<tableDataType>({
|
|
||||||
name: '',
|
|
||||||
order: 1,
|
|
||||||
parent_id: undefined,
|
|
||||||
fied_key: null,
|
|
||||||
fied_value: '',
|
|
||||||
|
|
||||||
});
|
// 加载配置数据
|
||||||
const updateState = reactive<tableDataType>({
|
const loadConfigData = async () => {
|
||||||
id: undefined,
|
try {
|
||||||
name: '',
|
const response = await getConfigList({});
|
||||||
order: 1,
|
const items = response.data.data.items;
|
||||||
parent_id: undefined,
|
|
||||||
fied_key: null,
|
|
||||||
fied_value: '',
|
|
||||||
});
|
|
||||||
const detailState = ref<tableDataType>({});
|
|
||||||
|
|
||||||
const columns = reactive<TableColumnsType>([
|
// 将配置项按父级分组
|
||||||
{
|
const groups = items
|
||||||
title: '配置名称',
|
.filter(item => item.parent_id === null) // 获取顶级配置项
|
||||||
dataIndex: 'name',
|
.map(group => ({
|
||||||
ellipsis: true,
|
...group,
|
||||||
key: 'name',
|
children: items.filter(item => item.parent_id === group.id) // 获取子配置项
|
||||||
// width: 160
|
}));
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '排序',
|
|
||||||
dataIndex: 'order',
|
|
||||||
key: 'order',
|
|
||||||
// align: 'center',
|
|
||||||
ellipsis: true,
|
|
||||||
// width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '父级',
|
|
||||||
dataIndex: 'parent_id',
|
|
||||||
key: 'parent_id',
|
|
||||||
ellipsis: true,
|
|
||||||
// align: 'center',
|
|
||||||
// width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '键',
|
|
||||||
dataIndex: 'fied_key',
|
|
||||||
key: 'fied_key',
|
|
||||||
ellipsis: true,
|
|
||||||
// align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '值',
|
|
||||||
dataIndex: 'fied_value',
|
|
||||||
key: 'fied_value',
|
|
||||||
ellipsis: true,
|
|
||||||
// align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: 'action',
|
|
||||||
key: 'action',
|
|
||||||
align: 'center',
|
|
||||||
width: 150
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const rowSelection = computed(() => {
|
// 初始化图片上传列表
|
||||||
return {
|
groups.forEach(group => {
|
||||||
selectedRowKeys: unref(selectedRowKeys),
|
group.children.forEach(config => {
|
||||||
onChange: (selectingRowKeys: tableDataType['id'][]) => {
|
if (config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background') {
|
||||||
selectedRowKeys.value = selectingRowKeys;
|
config.fileList = config.fied_value ? [{ url: config.fied_value }] : [];
|
||||||
},
|
}
|
||||||
hideDefaultSelections: true,
|
});
|
||||||
selections: [
|
});
|
||||||
Table.SELECTION_ALL,
|
|
||||||
Table.SELECTION_INVERT,
|
// 避免直接修改 reactive 对象,使用重新赋值的方式
|
||||||
Table.SELECTION_NONE
|
updateState.length = 0; // 清空数组
|
||||||
]
|
updateState.push(...groups); // 添加新数据
|
||||||
}
|
} catch (error) {
|
||||||
});
|
console.error('加载配置数据失败:', error);
|
||||||
|
message.error('加载配置数据失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图片上传前的校验
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
const isImage = file.type.startsWith('image/');
|
||||||
|
if (!isImage) {
|
||||||
|
message.error('只能上传图片文件!');
|
||||||
|
}
|
||||||
|
return isImage;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义上传逻辑
|
||||||
|
const handleUpload = async (options: any, config: any) => {
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const response = await uploadFile(formData);
|
||||||
|
const fileUrl = response.data.data.file_url; // 使用 file_url 更新配置项的值
|
||||||
|
|
||||||
|
// 更新配置项的值
|
||||||
|
config.fied_value = fileUrl;
|
||||||
|
config.fileList = [{ url: fileUrl }]; // 更新文件列表显示
|
||||||
|
|
||||||
|
onSuccess(response, file);
|
||||||
|
message.success('上传成功');
|
||||||
|
} catch (error) {
|
||||||
|
onError(error);
|
||||||
|
console.error('上传失败:', error);
|
||||||
|
message.error('上传失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
// 提取所有配置项(包括父级和子级)
|
||||||
|
const configs = updateState.flatMap(group => [
|
||||||
|
{
|
||||||
|
id: group.id,
|
||||||
|
name: group.name,
|
||||||
|
order: group.order,
|
||||||
|
fied_key: group.fied_key,
|
||||||
|
fied_value: group.fied_value,
|
||||||
|
parent_id: group.parent_id,
|
||||||
|
},
|
||||||
|
...group.children.map(config => ({
|
||||||
|
id: config.id,
|
||||||
|
name: config.name,
|
||||||
|
order: config.order,
|
||||||
|
fied_key: config.fied_key,
|
||||||
|
fied_value: config.fied_value,
|
||||||
|
parent_id: config.parent_id,
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 调用批量保存接口,直接发送数组
|
||||||
|
await batchConfig(configs); // 修改为直接发送数组
|
||||||
|
message.success('配置保存成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存配置失败:', error);
|
||||||
|
message.error('配置保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(() => loadingData());
|
onMounted(() => {
|
||||||
|
loadConfigData();
|
||||||
// 查询
|
});
|
||||||
const onFinish = () => {
|
|
||||||
loadingData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载表格数据
|
|
||||||
const loadingData = () => {
|
|
||||||
tableLoading.value = true;
|
|
||||||
|
|
||||||
let params = {};
|
|
||||||
if (queryState.name) {
|
|
||||||
params['name'] = queryState.name;
|
|
||||||
}
|
|
||||||
getConfigList(params).then(response => {
|
|
||||||
const result = response.data;
|
|
||||||
dataSource.value = listToTree(result.data.items);
|
|
||||||
}).catch(error => {
|
|
||||||
console.log(error);
|
|
||||||
}).finally(() => {
|
|
||||||
tableLoading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 重置查询
|
|
||||||
const resetFields = () => {
|
|
||||||
Object.keys(queryState).forEach((key: string) => {
|
|
||||||
delete queryState[key];
|
|
||||||
});
|
|
||||||
loadingData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const deleteRow = (row: tableDataType) => {
|
|
||||||
deleteConfig({ id: row.id }).then(response => {
|
|
||||||
const result = response.data;
|
|
||||||
message.success(result.msg);
|
|
||||||
loadingData();
|
|
||||||
}).catch(error => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// 弹窗关键字处理
|
|
||||||
const modalHandle = (modalType: string, record?: tableDataType) => {
|
|
||||||
modalTitle.value = modalType;
|
|
||||||
openModal.value = true;
|
|
||||||
|
|
||||||
if (modalType === 'view' && record !== undefined) {
|
|
||||||
detailStateLoading.value = true;
|
|
||||||
detailState.value = record
|
|
||||||
detailStateLoading.value = false;
|
|
||||||
|
|
||||||
} else if (modalType === 'update' && record !== undefined) {
|
|
||||||
Object.keys(updateState).forEach(key => {
|
|
||||||
updateState[key] = record[key];
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 弹窗提交(详情/新建/修改)
|
|
||||||
const handleModalSumbit = () => {
|
|
||||||
modalSubmitLoading.value = true;
|
|
||||||
if (modalTitle.value === 'view') {
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
openModal.value = false;
|
|
||||||
|
|
||||||
} else if (modalTitle.value === 'create') {
|
|
||||||
createForm.value.validate().then(() => {
|
|
||||||
const createBody = cloneDeep(createState);
|
|
||||||
Object.keys(createBody).forEach(key => {
|
|
||||||
if (isEmpty(createBody[key])) {
|
|
||||||
delete createBody[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
createConfig(createBody).then(response => {
|
|
||||||
const result = response.data;
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
openModal.value = false;
|
|
||||||
Object.keys(createState).forEach(key => delete createState[key]);
|
|
||||||
createState.order = 1;
|
|
||||||
message.success(result.msg);
|
|
||||||
loadingData();
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
} else if (modalTitle.value === 'update') {
|
|
||||||
updateForm.value.validate().then(() => {
|
|
||||||
updateConfig(updateState).then(response => {
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
openModal.value = false;
|
|
||||||
const result = response.data;
|
|
||||||
message.success(result.msg);
|
|
||||||
loadingData();
|
|
||||||
|
|
||||||
}).catch(error => {
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
console.error(error)
|
|
||||||
});
|
|
||||||
|
|
||||||
}).catch(error => {
|
|
||||||
modalSubmitLoading.value = false;
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 系统配置弹窗状态
|
|
||||||
const systemConfigModalVisible = ref(false);
|
|
||||||
const systemConfigModalLoading = ref(false);
|
|
||||||
const systemConfigState = ref<tableDataType[]>([]);
|
|
||||||
|
|
||||||
// 打开系统配置弹窗
|
|
||||||
const openSystemConfigModal = async () => {
|
|
||||||
systemConfigModalVisible.value = true;
|
|
||||||
systemConfigModalLoading.value = true;
|
|
||||||
try {
|
|
||||||
const response = await getConfigList({});
|
|
||||||
systemConfigState.value = listToTree(response.data.items);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
systemConfigModalLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 提交系统配置
|
|
||||||
const handleSystemConfigSubmit = async () => {
|
|
||||||
systemConfigModalLoading.value = true;
|
|
||||||
try {
|
|
||||||
const updatePromises = systemConfigState.value.map(config =>
|
|
||||||
updateConfig(config)
|
|
||||||
);
|
|
||||||
await Promise.all(updatePromises);
|
|
||||||
message.success('系统配置更新成功');
|
|
||||||
systemConfigModalVisible.value = false;
|
|
||||||
loadingData(); // 重新加载数据
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
systemConfigModalLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tree-search-wrapper {
|
.config-edit-wrapper {
|
||||||
margin-block-end: 16px;
|
margin-block-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
export interface searchDataType {
|
|
||||||
name?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface tableDataType {
|
export interface tableDataType {
|
||||||
id?: number;
|
id?: number;
|
||||||
index?: number;
|
|
||||||
name?: string;
|
name?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
fied_key?: string;
|
fied_key?: string;
|
||||||
fied_value?: string;
|
fied_value?: string;
|
||||||
parent_id?: number;
|
parent_id?: number;
|
||||||
parent_name?: string;
|
fileList?: any[];
|
||||||
children?: tableDataType[];
|
children?: tableDataType[];
|
||||||
}
|
}
|
||||||