mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-25 13:51:04 +00:00
1、系统配置修改上线 2、 pinia取代vuex
This commit is contained in:
@@ -19,26 +19,23 @@ from app.api.v1.services.system.config_service import ConfigService
|
|||||||
router = APIRouter(route_class=OperationLogRoute)
|
router = APIRouter(route_class=OperationLogRoute)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/list", summary="查询配置", description="查询配置")
|
@router.get("/info", summary="获取配置", description="获取配置")
|
||||||
async def get_obj_list(
|
async def get_obj_list(
|
||||||
page: PaginationQueryParams = Depends(),
|
|
||||||
search: ConfigQueryParams = Depends(),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:query"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:query"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await ConfigService.list_services(auth=auth, search=search, order_by=page.order_by)
|
result_dict = await ConfigService.get_services(auth=auth, id=1)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
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.put("/update", summary="修改配置", description="修改配置")
|
||||||
async def batch_objs(
|
async def update_objs(
|
||||||
request: Request,
|
request: Request,
|
||||||
data: List[ConfigUpdateSchema],
|
data: ConfigUpdateSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:update"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:config:update"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_list_dict = await ConfigService.batch_services(auth=auth, request=request, data=data)
|
result_dict = await ConfigService.update_services(auth=auth, request=request, data=data)
|
||||||
logger.info(f"{auth.user.name} 批量更新配置成功 {result_list_dict}")
|
logger.info(f"{auth.user.name} 更新配置成功 {result_dict}")
|
||||||
return SuccessResponse(data=result_list_dict, msg="批量更新配置成功")
|
return SuccessResponse(data=result_dict, msg="更新配置成功")
|
||||||
|
|
||||||
@router.post("/upload", summary="上传文件", dependencies=[Depends(AuthPermission(permissions=["system:config:upload"]))])
|
@router.post("/upload", summary="上传文件", dependencies=[Depends(AuthPermission(permissions=["system:config:upload"]))])
|
||||||
async def upload_file(
|
async def upload_file(
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ 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 list_curd(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ConfigModel]:
|
async def get_curd(self, id: int) -> Optional[ConfigModel]:
|
||||||
"""获取配置列表"""
|
"""获取配置"""
|
||||||
return await self.list(search=search, order_by=order_by)
|
return await self.get(id=id)
|
||||||
|
|
||||||
async def update_curd(self, id: int, data: ConfigUpdateSchema) -> Optional[ConfigModel]:
|
async def update_curd(self, id: int, data: ConfigUpdateSchema) -> Optional[ConfigModel]:
|
||||||
"""更新配置"""
|
"""更新配置"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime, JSON
|
from sqlalchemy import Column, String, Integer
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from app.core.base_model import ModelBase
|
from app.core.base_model import ModelBase
|
||||||
|
|
||||||
@@ -15,21 +14,15 @@ class ConfigModel(ModelBase):
|
|||||||
|
|
||||||
# 基础字段
|
# 基础字段
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True, unique=True, comment='主键ID')
|
id = Column(Integer, primary_key=True, autoincrement=True, unique=True, comment='主键ID')
|
||||||
name = Column(String(40), nullable=False, comment="配置名称", unique=True)
|
|
||||||
order = Column(Integer, nullable=False, default=1, comment="显示排序")
|
|
||||||
fied_key = Column(String(100), nullable=False, comment="键", unique=True)
|
|
||||||
fied_value = Column(Text, nullable=True, comment="值")
|
|
||||||
|
|
||||||
# 层级关系
|
title = Column(String(40), nullable=False, default="FastAPI Vue Admin", comment="网站标题")
|
||||||
parent_id = Column(
|
favicon = Column(String(100), nullable=False, default="http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090456A114.png", comment="网站favicon")
|
||||||
Integer,
|
logo = Column(String(100), nullable=False, default="http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090448A263.png", comment="网站logo")
|
||||||
ForeignKey("system_config.id", ondelete="CASCADE", onupdate="CASCADE"),
|
background = Column(String(100), nullable=False, default="http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/background_20250109090435A357.png", comment="网站背景")
|
||||||
nullable=True,
|
description = Column(String(100), nullable=False, default="FastAPI Vue Admin 是完全开源的权限管理系统", comment="网站描述")
|
||||||
index=True,
|
copyright = Column(String(100), nullable=False, default="Copyright © 2021-2025 fastapi-vue-admin.com 版权所有", comment="版权信息")
|
||||||
comment="父级配置ID"
|
keep_record = Column(String(100), nullable=False, default="晋ICP备18005113号-3", comment="备案信息")
|
||||||
)
|
help_url = Column(String(100), nullable=False, default="https://django-vue-admin.com", comment="帮助链接")
|
||||||
parent = relationship(
|
privacy_url = Column(String(100), nullable=False, default="https://gitee.com/tao__tao/fastapi_vue_admin/blob/main/docs/clause/privacy.md", comment="隐私政策链接")
|
||||||
"ConfigModel",
|
clause_url = Column(String(100), nullable=False, default="https://gitee.com/tao__tao/fastapi_vue_admin/blob/main/docs/clause/terms_service.md", comment="服务条款链接")
|
||||||
cascade='all, delete-orphan',
|
code_url = Column(String(100), nullable=False, default="https://gitee.com/tao__tao/fastapi_vue_admin.git", comment="源码地址")
|
||||||
uselist=False
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ class ConfigQueryParams:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: Optional[str] = Query(None, description="配置名称", min_length=2, max_length=50),
|
title: Optional[str] = Query(None, description="网站标题", min_length=2, max_length=50),
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# 模糊查询字段
|
# 模糊查询字段
|
||||||
self.name = ("like", name)
|
self.title = ("like", title)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,17 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|||||||
class ConfigCreateSchema(BaseModel):
|
class ConfigCreateSchema(BaseModel):
|
||||||
"""配置创建模型"""
|
"""配置创建模型"""
|
||||||
|
|
||||||
name: str = Field(..., max_length=40, description="配置名称")
|
title: str = Field(..., max_length=40, description="网站标题")
|
||||||
order: int = Field(default=1, ge=0, description="显示顺序")
|
favicon: str = Field(..., max_length=100, description="网站favicon")
|
||||||
fied_key: str = Field(..., description="键")
|
logo: str = Field(..., max_length=100, description="网站logo")
|
||||||
fied_value: Optional[str] = Field(default=None, description="值")
|
background: str = Field(..., max_length=100, description="网站背景")
|
||||||
parent_id: Optional[int] = Field(default=None, ge=0, description="父配置ID")
|
description: str = Field(..., max_length=100, description="网站描述")
|
||||||
|
copyright: str = Field(..., max_length=100, description="版权信息")
|
||||||
@classmethod
|
keep_record: str = Field(..., max_length=100, description="备案信息")
|
||||||
@model_validator(mode='after')
|
help_url: str = Field(..., max_length=100, description="帮助链接")
|
||||||
def validate_fields(cls, data):
|
privacy_url: str = Field(..., max_length=100, description="隐私政策链接")
|
||||||
if not data.name or len(data.name.strip()) == 0:
|
clause_url: str = Field(..., max_length=100, description="服务条款链接")
|
||||||
raise ValueError("配置名称不能为空")
|
code_url: str = Field(..., max_length=100, description="源码地址")
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigUpdateSchema(ConfigCreateSchema):
|
class ConfigUpdateSchema(ConfigCreateSchema):
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from typing import List, Dict
|
from typing import Dict
|
||||||
|
|
||||||
from fastapi import Depends, Request, UploadFile
|
from fastapi import Request, UploadFile
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from aioredis import Redis
|
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 ConfigOutSchema, ConfigUpdateSchema
|
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.cruds.system.config_crud import ConfigCRUD
|
from app.api.v1.cruds.system.config_crud import ConfigCRUD
|
||||||
from app.common.enums import RedisInitKeyConfig
|
from app.common.enums import RedisInitKeyConfig
|
||||||
from app.core.cache_crud import Cache
|
from app.core.cache_crud import Cache
|
||||||
from app.core.dependencies import db_getter
|
|
||||||
from app.utils.upload_util import UploadUtil
|
from app.utils.upload_util import UploadUtil
|
||||||
from app.core.base_schema import UploadResponseSchema
|
from app.core.base_schema import UploadResponseSchema
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
@@ -27,20 +25,17 @@ class ConfigService:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def list_services(cls, auth: AuthSchema, search: ConfigQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
async def get_services(cls, auth: AuthSchema, id: int) -> Dict:
|
||||||
config_obj_list = await ConfigCRUD(auth).list_curd(search=search.__dict__, order_by=order_by)
|
config_obj = await ConfigCRUD(auth).get_curd(id=id)
|
||||||
return [ConfigOutSchema.model_validate(config_obj).model_dump() for config_obj in config_obj_list]
|
return ConfigOutSchema.model_validate(config_obj).model_dump()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def batch_services(cls, auth: AuthSchema, request: Request, data: List[ConfigUpdateSchema]) -> List[Dict]:
|
async def update_services(cls, auth: AuthSchema, request: Request, data: ConfigUpdateSchema) -> Dict:
|
||||||
result = []
|
new_obj = await ConfigCRUD(auth).update_curd(id=data.id, data=data)
|
||||||
for obj in data:
|
new_obj_dict = ConfigOutSchema.model_validate(new_obj).model_dump()
|
||||||
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)
|
cls.init_config(redis=request.app.state.redis, db=auth.db)
|
||||||
return result
|
return new_obj_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def upload_services(cls, request: Request, file: UploadFile) -> Dict:
|
async def upload_services(cls, request: Request, file: UploadFile) -> Dict:
|
||||||
@@ -59,19 +54,19 @@ class ConfigService:
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def init_config(cls, redis: Redis, db: AsyncSession):
|
async def init_config(cls, redis: Redis, db: AsyncSession):
|
||||||
auth = AuthSchema(db=db)
|
auth = AuthSchema(db=db)
|
||||||
config_obj_list = await ConfigCRUD(auth).list_curd()
|
config_obj = await ConfigCRUD(auth).get_curd(id=1)
|
||||||
config_obj_list_dict = [ConfigOutSchema.model_validate(config_obj).model_dump() for config_obj in config_obj_list]
|
config_obj_dict = ConfigOutSchema.model_validate(config_obj).model_dump()
|
||||||
|
|
||||||
# 保存到Redis并设置过期时间
|
# 保存到Redis并设置过期时间
|
||||||
redis_key = f"{RedisInitKeyConfig.System_Config.key}:{'init_system_config'}"
|
redis_key = f"{RedisInitKeyConfig.System_Config.key}:{'init_system_config'}"
|
||||||
try:
|
try:
|
||||||
value = json.dumps(config_obj_list_dict, ensure_ascii=False)
|
value = json.dumps(config_obj_dict, ensure_ascii=False)
|
||||||
await Cache(redis).set(
|
await Cache(redis).set(
|
||||||
key=redis_key,
|
key=redis_key,
|
||||||
value=value,
|
value=value,
|
||||||
expire=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
expire=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||||
)
|
)
|
||||||
logger.info(f"初始化系统配置成功: {config_obj_list_dict}")
|
logger.info(f"初始化系统配置成功: {config_obj_dict}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"初始化系统配置失败: {e}")
|
logger.error(f"初始化系统配置失败: {e}")
|
||||||
raise CustomException(msg="初始化系统配置失败")
|
raise CustomException(msg="初始化系统配置失败")
|
||||||
|
|||||||
@@ -1,114 +1,16 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"parent_id": null,
|
"title": "FastAPI Vue Admin",
|
||||||
"name": "基础配置",
|
"favicon": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090456A114.png",
|
||||||
"fied_key": "base",
|
"logo": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090448A263.png",
|
||||||
"fied_value": null,
|
"background": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/background_20250109090435A357.png",
|
||||||
"order": 1
|
"description": "FastAPI Vue Admin 是完全开源的权限管理系统",
|
||||||
},
|
"copyright": "Copyright © 2021-2025 fastapi-vue-admin.com 版权所有",
|
||||||
{
|
"keep_record": "晋ICP备18005113号-3",
|
||||||
"id": 2,
|
"help_url": "https://django-vue-admin.com",
|
||||||
"parent_id": 1,
|
"privacy_url": "/api/system/clause/privacy.html",
|
||||||
"name": "网页标题",
|
"clause_url": "/api/system/clause/terms_service.html",
|
||||||
"fied_key": "web_title",
|
"code_url": "https://gitee.com/tao__tao/fastapi_vue_admin.git"
|
||||||
"fied_value": "FastAPI Vue Admin",
|
|
||||||
"order": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"parent_id": 1,
|
|
||||||
"name": "网站小图标",
|
|
||||||
"fied_key": "web_favicon",
|
|
||||||
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090456A114.png",
|
|
||||||
"order": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"parent_id": null,
|
|
||||||
"name": "登录页配置",
|
|
||||||
"fied_key": "login",
|
|
||||||
"fied_value": null,
|
|
||||||
"order": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "登陆标题",
|
|
||||||
"fied_key": "login_title",
|
|
||||||
"fied_value": "FastAPI Vue Admin",
|
|
||||||
"order": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "登陆描述",
|
|
||||||
"fied_key": "login_description",
|
|
||||||
"fied_value": "FastAPI Vue Admin 是完全开源的权限管理系统",
|
|
||||||
"order": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "登录logo",
|
|
||||||
"fied_key": "login_logo",
|
|
||||||
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/logo_20250109090448A263.png",
|
|
||||||
"order": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "登录背景",
|
|
||||||
"fied_key": "login_background",
|
|
||||||
"fied_value": "http://localhost:8000/api/v1/static/upload/image/png/2025/01/09/background_20250109090435A357.png",
|
|
||||||
"order": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "版权信息",
|
|
||||||
"fied_key": "copyright",
|
|
||||||
"fied_value": "Copyright © 2021-2025 fastapi-vue-admin.com 版权所有",
|
|
||||||
"order": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "备案信息",
|
|
||||||
"fied_key": "keep_record",
|
|
||||||
"fied_value": "晋ICP备18005113号-3",
|
|
||||||
"order": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "帮助",
|
|
||||||
"fied_key": "help_url",
|
|
||||||
"fied_value": "https://django-vue-admin.com",
|
|
||||||
"order": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "隐私",
|
|
||||||
"fied_key": "privacy_url",
|
|
||||||
"fied_value": "/api/system/clause/privacy.html",
|
|
||||||
"order": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 13,
|
|
||||||
"parent_id": 4,
|
|
||||||
"name": "条款",
|
|
||||||
"fied_key": "clause_url",
|
|
||||||
"fied_value": "/api/system/clause/terms_service.html",
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
@@ -1,32 +1,16 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
export function getConfigList(params) {
|
export function getConfigInfo() {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/v1/system/config/list",
|
url: "/api/v1/system/config/info",
|
||||||
method: "get",
|
method: "get",
|
||||||
params: params,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfigDetail(params) {
|
|
||||||
return request({
|
|
||||||
url: "/api/v1/system/config/detail",
|
|
||||||
method: "get",
|
|
||||||
params: params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createConfig(body) {
|
export function updateConfig(body) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/v1/system/config/create",
|
url: "/api/v1/system/config/update",
|
||||||
method: "post",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function batchConfig(body) {
|
|
||||||
return request({
|
|
||||||
url: "/api/v1/system/config/batch",
|
|
||||||
method: "put",
|
method: "put",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ import type { MenuProps, ItemType } from "ant-design-vue";
|
|||||||
import { message } 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 { useUserStore } from "@/store/index";
|
||||||
import * as icons from '@ant-design/icons-vue';
|
import * as icons from '@ant-design/icons-vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { listToTree } from '@/utils/util';
|
import { listToTree } from '@/utils/util';
|
||||||
@@ -168,6 +168,9 @@ import {
|
|||||||
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"
|
import { getInitConfig } from "@/api/system/config"
|
||||||
|
import { useConfigStore } from "@/store/index";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 通知公告获取
|
// 通知公告获取
|
||||||
const dataSource = reactive({
|
const dataSource = reactive({
|
||||||
@@ -194,7 +197,7 @@ const router = useRouter();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const userInfo = computed(() => store.state.user.basicInfo);
|
const userInfo = computed(() => userStore.basicInfo);
|
||||||
const unreadCount = computed(() => dataSource.total); // 通知条数
|
const unreadCount = computed(() => dataSource.total); // 通知条数
|
||||||
|
|
||||||
// 菜单状态
|
// 菜单状态
|
||||||
@@ -229,7 +232,7 @@ const handleLogout = async () => {
|
|||||||
await logout({ token: storage.get('Access-Token') });
|
await logout({ token: storage.get('Access-Token') });
|
||||||
storage.remove('Access-Token');
|
storage.remove('Access-Token');
|
||||||
storage.remove('Refresh-Token');
|
storage.remove('Refresh-Token');
|
||||||
await store.dispatch('clearUserInfo');
|
await userStore.clearUserInfo;
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('退出登录失败:', error);
|
console.error('退出登录失败:', error);
|
||||||
@@ -244,7 +247,7 @@ const handleMenuClick: MenuProps['onClick'] = (menuInfo) => {
|
|||||||
|
|
||||||
// 监听路由变化
|
// 监听路由变化
|
||||||
watch(() => route.path, (newPath) => {
|
watch(() => route.path, (newPath) => {
|
||||||
const routePath = store.state.user.routeList.map(item => item.route_path);
|
const routePath = userStore.routeList.map(item => item.route_path);
|
||||||
if (!routePath.includes(newPath)) {
|
if (!routePath.includes(newPath)) {
|
||||||
menuState.openKeys = [];
|
menuState.openKeys = [];
|
||||||
menuState.selectedKeys = [];
|
menuState.selectedKeys = [];
|
||||||
@@ -274,11 +277,11 @@ const generateMenuItem = (item: any): ItemType => {
|
|||||||
// 初始化菜单
|
// 初始化菜单
|
||||||
const initMenu = () => {
|
const initMenu = () => {
|
||||||
// 确保路由列表存在
|
// 确保路由列表存在
|
||||||
if (!store.state.user.routeList?.length) {
|
if (!userStore.routeList?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuTree = listToTree(store.state.user.routeList);
|
const menuTree = listToTree(userStore.routeList);
|
||||||
menuState.menus = menuTree
|
menuState.menus = menuTree
|
||||||
.filter(item => !item.hidden)
|
.filter(item => !item.hidden)
|
||||||
.map(generateMenuItem)
|
.map(generateMenuItem)
|
||||||
@@ -328,38 +331,18 @@ const noticeState = reactive({
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
const initConfigState = reactive({
|
const initConfigState = reactive({
|
||||||
web_title: '',
|
web_title: configStore.getConfigValue("title"),
|
||||||
web_favicon: '',
|
web_favicon: configStore.getConfigValue("logo"),
|
||||||
});
|
});
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
|
|||||||
+26
-28
@@ -6,39 +6,37 @@ import "ant-design-vue/dist/reset.css";
|
|||||||
import VChart from "vue-echarts";
|
import VChart from "vue-echarts";
|
||||||
import "echarts";
|
import "echarts";
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
import { getInitConfig } from "@/api/system/config"
|
import { useConfigStore } from "@/store/index";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
const initConfig = () => {
|
app.use(pinia);
|
||||||
return getInitConfig()
|
|
||||||
.then((response) => {
|
const initConfig = async () => {
|
||||||
const { status_code, data } = response.data;
|
const configStore = useConfigStore();
|
||||||
if (status_code === 200) {
|
await configStore.fetchConfig();
|
||||||
const configData = JSON.parse(data);
|
|
||||||
configData.forEach((item) => {
|
const loginTitle = configStore.getConfigValue("title");
|
||||||
if (item.fied_key === "login_title") {
|
const loginLogo = configStore.getConfigValue("favicon");
|
||||||
document.title = item.fied_value || "fastapi vue admin";
|
|
||||||
} else if (item.fied_key === "login_logo") {
|
if (loginTitle) {
|
||||||
const favicon = document.querySelector('link[rel="icon"]');
|
document.title = loginTitle;
|
||||||
if (favicon) {
|
}
|
||||||
favicon.href = item.fied_value || "/logo.png";
|
|
||||||
}
|
if (loginLogo) {
|
||||||
}
|
const favicon = document.querySelector('link[rel="icon"]');
|
||||||
});
|
if (favicon) {
|
||||||
}
|
favicon.href = loginLogo;
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
}
|
||||||
message.error("获取系统配置失败");
|
|
||||||
console.error(error); // 打印错误信息以便调试
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化配置并挂载应用
|
// 初始化配置并挂载应用
|
||||||
initConfig().then(() => {
|
initConfig().then(() => {
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(Antd);
|
app.use(Antd);
|
||||||
app.component("VChart", VChart);
|
app.component("VChart", VChart);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
});
|
});
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
const modules = import.meta.glob("../views/**/**.vue");
|
|
||||||
|
|
||||||
export const generator = (routers) => {
|
|
||||||
return routers.map((item) => {
|
|
||||||
const currentRouter = {
|
|
||||||
path: item.route_path,
|
|
||||||
name: item.route_name,
|
|
||||||
component: item.component_path
|
|
||||||
? modules[`../views/${item.component_path}.vue`]
|
|
||||||
: null,
|
|
||||||
redirect: item.redirect,
|
|
||||||
meta: {
|
|
||||||
title: item.name,
|
|
||||||
icon: item.icon || undefined,
|
|
||||||
keepAlive: item.cache,
|
|
||||||
hidden: item.hidden,
|
|
||||||
order: item.order,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (item.children && item.children.length > 0) {
|
|
||||||
currentRouter.children = generator(item.children);
|
|
||||||
}
|
|
||||||
return currentRouter;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,37 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
import storage from "store";
|
||||||
|
|
||||||
import BasicLayout from "@/layouts/basicLayout.vue";
|
import BasicLayout from "@/layouts/basicLayout.vue";
|
||||||
import Login from "@/views/system/auth/login.vue";
|
import Login from "@/views/system/auth/login.vue";
|
||||||
import storage from "store";
|
|
||||||
import store from "@/store";
|
|
||||||
import { generator } from "./generateRouter";
|
|
||||||
import { listToTree } from "@/utils/util";
|
import { listToTree } from "@/utils/util";
|
||||||
|
import { useUserStore } from "@/store/index";
|
||||||
|
|
||||||
|
const modules = import.meta.glob("../views/**/**.vue");
|
||||||
|
|
||||||
|
export const generator = (routers) => {
|
||||||
|
return routers.map((item) => {
|
||||||
|
const currentRouter = {
|
||||||
|
path: item.route_path,
|
||||||
|
name: item.route_name,
|
||||||
|
component: item.component_path
|
||||||
|
? modules[`../views/${item.component_path}.vue`]
|
||||||
|
: null,
|
||||||
|
redirect: item.redirect,
|
||||||
|
meta: {
|
||||||
|
title: item.name,
|
||||||
|
icon: item.icon || undefined,
|
||||||
|
keepAlive: item.cache,
|
||||||
|
hidden: item.hidden,
|
||||||
|
order: item.order,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
currentRouter.children = generator(item.children);
|
||||||
|
}
|
||||||
|
return currentRouter;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: "/login", name: "Login", component: Login },
|
{ path: "/login", name: "Login", component: Login },
|
||||||
@@ -39,21 +66,21 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach((to) => {
|
router.beforeEach(async (to) => {
|
||||||
const token = storage.get("Access-Token");
|
const token = storage.get("Access-Token");
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
if (!token && to.name !== "Login") {
|
if (!token && to.name !== "Login") {
|
||||||
return { name: "Login" };
|
return { name: "Login" };
|
||||||
} else if (token && to.name === "Login") {
|
} else if (token && to.name === "Login") {
|
||||||
return { name: "Index" };
|
return { name: "Index" };
|
||||||
} else if (token && !store.state.user.hasGetRoute) {
|
} else if (token && !userStore.hasGetRoute) {
|
||||||
return store.dispatch("getUserInfo").then(() => {
|
await userStore.getUserInfo();
|
||||||
const routersTree = listToTree(store.state.user.routeList);
|
const routersTree = listToTree(userStore.routeList);
|
||||||
const routerMap = generator(routersTree);
|
const routerMap = generator(routersTree);
|
||||||
rootRouter.children = [...rootRouter.children, ...routerMap];
|
rootRouter.children = [...rootRouter.children, ...routerMap];
|
||||||
router.addRoute(rootRouter);
|
router.addRoute(rootRouter);
|
||||||
return to.fullPath;
|
return to.fullPath;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { getInitConfig } from "@/api/system/config";
|
|
||||||
|
|
||||||
export const useConfigStore = defineStore('config', {
|
|
||||||
state: () => ({
|
|
||||||
systemConfig: {},
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
async getConfigInfo() {
|
|
||||||
try {
|
|
||||||
const response = await getInitConfig();
|
|
||||||
const result = response.data;
|
|
||||||
const configData = JSON.parse(result.data);
|
|
||||||
this.systemConfig = configData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setSystemConfig(config) {
|
|
||||||
this.systemConfig = config;
|
|
||||||
},
|
|
||||||
clearConfigInfo() {
|
|
||||||
this.systemConfig = {};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
+59
-10
@@ -1,14 +1,63 @@
|
|||||||
import { createStore } from "vuex";
|
import { defineStore } from "pinia";
|
||||||
import user from "./modules/user";
|
import { getCurrentUserInfo } from "@/api/system/user";
|
||||||
|
import { getInitConfig } from "@/api/system/config";
|
||||||
|
|
||||||
const store = createStore({
|
|
||||||
modules: {
|
export const useUserStore = defineStore("user", {
|
||||||
user,
|
state: () => ({
|
||||||
|
basicInfo: {},
|
||||||
|
routeList: [],
|
||||||
|
hasGetRoute: false,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async getUserInfo() {
|
||||||
|
try {
|
||||||
|
const response = await getCurrentUserInfo();
|
||||||
|
const result = response.data;
|
||||||
|
const routers = result.data.menus;
|
||||||
|
delete result.data.menus;
|
||||||
|
this.setRoute(routers);
|
||||||
|
this.basicInfo = { ...this.basicInfo, ...result.data };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setRoute(routers) {
|
||||||
|
this.routeList = routers;
|
||||||
|
this.hasGetRoute = true;
|
||||||
|
},
|
||||||
|
setAvatar(avatar) {
|
||||||
|
this.basicInfo = { ...this.basicInfo, avatar };
|
||||||
|
},
|
||||||
|
clearUserInfo() {
|
||||||
|
this.basicInfo = {};
|
||||||
|
this.routeList = [];
|
||||||
|
this.hasGetRoute = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
state: {},
|
|
||||||
mutations: {},
|
|
||||||
actions: {},
|
|
||||||
getters: {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default store;
|
export const useConfigStore = defineStore("config", {
|
||||||
|
state: () => ({
|
||||||
|
configData: {}, // 存储系统配置
|
||||||
|
isConfigLoaded: false, // 标记配置是否已加载
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async fetchConfig() {
|
||||||
|
try {
|
||||||
|
const response = await getInitConfig();
|
||||||
|
const { status_code, data } = response.data;
|
||||||
|
if (status_code === 200) {
|
||||||
|
this.configData = JSON.parse(data);
|
||||||
|
console.log(this.configData);
|
||||||
|
this.isConfigLoaded = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取系统配置失败", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getConfigValue(key) {
|
||||||
|
return this.configData[key] || null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { getCurrentUserInfo } from "@/api/system/user";
|
|
||||||
|
|
||||||
const user = {
|
|
||||||
state: {
|
|
||||||
basicInfo: {},
|
|
||||||
routeList: [],
|
|
||||||
hasGetRoute: false,
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
setRoute(state, routers) {
|
|
||||||
state.routeList = routers;
|
|
||||||
state.hasGetRoute = true;
|
|
||||||
},
|
|
||||||
setAvatar(state, avatar) {
|
|
||||||
state.basicInfo.avatar = avatar;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
getUserInfo({ commit, state }) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
getCurrentUserInfo()
|
|
||||||
.then((response) => {
|
|
||||||
const result = response.data;
|
|
||||||
const routers = result.data.menus;
|
|
||||||
delete result.data.menus;
|
|
||||||
commit("setRoute", routers);
|
|
||||||
state.basicInfo = Object.assign(state.basicInfo || {}, result.data);
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
clearUserInfo({ state }) {
|
|
||||||
state.basicInfo = {};
|
|
||||||
state.routeList = [];
|
|
||||||
state.hasGetRoute = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default user;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { getCurrentUserInfo } from "@/api/system/user";
|
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', {
|
|
||||||
state: () => ({
|
|
||||||
basicInfo: {},
|
|
||||||
routeList: [],
|
|
||||||
hasGetRoute: false,
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
async getUserInfo() {
|
|
||||||
try {
|
|
||||||
const response = await getCurrentUserInfo();
|
|
||||||
const result = response.data;
|
|
||||||
const routers = result.data.menus;
|
|
||||||
delete result.data.menus;
|
|
||||||
this.setRoute(routers);
|
|
||||||
this.basicInfo = Object.assign(this.basicInfo || {}, result.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setRoute(routers) {
|
|
||||||
this.routeList = routers;
|
|
||||||
this.hasGetRoute = true;
|
|
||||||
},
|
|
||||||
setAvatar(avatar) {
|
|
||||||
this.basicInfo.avatar = avatar;
|
|
||||||
},
|
|
||||||
clearUserInfo() {
|
|
||||||
this.basicInfo = {};
|
|
||||||
this.routeList = [];
|
|
||||||
this.hasGetRoute = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -122,15 +122,17 @@ import { ref, reactive } from 'vue';
|
|||||||
import PageHeader from '@/components/PageHeader.vue'
|
import PageHeader from '@/components/PageHeader.vue'
|
||||||
import { timeFix } from '@/utils/util';
|
import { timeFix } from '@/utils/util';
|
||||||
import { PlusOutlined, UserOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined, UserOutlined } from '@ant-design/icons-vue';
|
||||||
import store from '@/store';
|
import { useUserStore } from "@/store/index";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
let timefix = timeFix();
|
let timefix = timeFix();
|
||||||
|
|
||||||
const userInfo = store.state.user.basicInfo;
|
const userInfo = userStore.basicInfo;
|
||||||
|
|
||||||
console.log("store.state.user.basicInfo",store.state.user.basicInfo)
|
console.log("store.state.user.basicInfo",userStore.basicInfo)
|
||||||
const welcome = '祝你开心每一天!';
|
const welcome = '祝你开心每一天!';
|
||||||
|
|
||||||
const projectItems = [
|
const projectItems = [
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout>
|
<a-layout>
|
||||||
<!-- 页面主体 -->
|
<!-- 页面主体 -->
|
||||||
<div class="container" :style="{ backgroundImage: `url(${initConfigState.login_background})` }">
|
<div class="container" :style="{ backgroundImage: `url(${initConfigState.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="initConfigState.login_logo" :preview="false" />
|
<a-image :src="initConfigState.logo" :preview="false" />
|
||||||
</div>
|
</div>
|
||||||
<div class="title">{{ initConfigState.login_title }}</div>
|
<div class="title">{{ initConfigState.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="desc">{{ initConfigState.login_description }}</div>
|
<div class="desc">{{ initConfigState.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>
|
||||||
@@ -69,13 +69,13 @@
|
|||||||
{{ initConfigState.copyright }} |
|
{{ initConfigState.copyright }} |
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="link" :href="initConfigState.help_url">
|
<a-button type="link" :href="initConfigState.help_url">
|
||||||
{{ initConfigState.help_name }} |
|
帮助 |
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="link" :href="initConfigState.privacy_url">
|
<a-button type="link" :href="initConfigState.privacy_url">
|
||||||
{{ initConfigState.privacy_name }} |
|
隐私 |
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="link" :href="initConfigState.clause_url">
|
<a-button type="link" :href="initConfigState.clause_url">
|
||||||
{{ initConfigState.clause_name }}
|
条款
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-record">
|
<div class="footer-record">
|
||||||
@@ -161,8 +161,8 @@ 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'
|
||||||
|
import { useConfigStore } from "@/store/index";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loginFlag = ref(false);
|
const loginFlag = ref(false);
|
||||||
@@ -287,69 +287,24 @@ const requestCaptcha = () => {
|
|||||||
.catch(() => captchaState.enable = false);
|
.catch(() => captchaState.enable = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
const initConfigState = reactive({
|
const initConfigState = reactive({
|
||||||
login_title: '',
|
title: configStore.getConfigValue("title"),
|
||||||
login_description: '/logo.png',
|
description: configStore.getConfigValue("description"),
|
||||||
login_logo: '',
|
logo: configStore.getConfigValue("logo"),
|
||||||
login_background: '/background.png',
|
background: configStore.getConfigValue("background"),
|
||||||
copyright: '',
|
copyright: configStore.getConfigValue("copyright"),
|
||||||
copyright_name: '',
|
keep_record: configStore.getConfigValue("keep_record"),
|
||||||
keep_record: '',
|
help_url: configStore.getConfigValue("help_url"),
|
||||||
keep_record_name: '',
|
privacy_url: configStore.getConfigValue("privacy_url"),
|
||||||
help_url: '',
|
clause_url: configStore.getConfigValue("clause_url"),
|
||||||
help_name: '',
|
code_url: configStore.getConfigValue("code_url"),
|
||||||
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(() => {
|
onMounted(() => {
|
||||||
requestCaptcha();
|
requestCaptcha();
|
||||||
initConfig();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -7,35 +7,94 @@
|
|||||||
<div class="config-edit-wrapper">
|
<div class="config-edit-wrapper">
|
||||||
<a-card title="系统配置" :bordered="false">
|
<a-card title="系统配置" :bordered="false">
|
||||||
<!-- 动态生成配置项 -->
|
<!-- 动态生成配置项 -->
|
||||||
<template v-for="group in updateState" :key="group.id">
|
<a-row :gutter="16">
|
||||||
<a-card :title="group.name" :bordered="false" style="margin-bottom: 24px;">
|
<a-col :span="12">
|
||||||
<a-row :gutter="16">
|
<!-- 网站标题 -->
|
||||||
<template v-for="config in group.children" :key="config.id">
|
<a-form-item label="网站标题">
|
||||||
<a-col :span="12">
|
<a-input v-model:value="configData.title" placeholder="请输入网站标题" allowClear />
|
||||||
<!-- 图片上传类型 -->
|
</a-form-item>
|
||||||
<a-form-item v-if="config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background'" :label="config.name">
|
|
||||||
<a-upload
|
|
||||||
v-model:file-list="config.fileList"
|
|
||||||
list-type="picture-card"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
:custom-request="(options) => handleUpload(options, config)"
|
|
||||||
>
|
|
||||||
<div v-if="!config.fileList || config.fileList.length === 0">
|
|
||||||
<plus-outlined />
|
|
||||||
<div style="margin-top: 8px">上传图片</div>
|
|
||||||
</div>
|
|
||||||
</a-upload>
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<!-- 输入框类型 -->
|
<!-- 网站图标 -->
|
||||||
<a-form-item v-else :label="config.name">
|
<a-form-item label="网站图标">
|
||||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
<a-upload
|
||||||
</a-form-item>
|
v-model:file-list="faviconFileList"
|
||||||
</a-col>
|
list-type="picture-card"
|
||||||
</template>
|
:before-upload="beforeUpload"
|
||||||
</a-row>
|
:custom-request="(options) => handleUpload(options, 'favicon')"
|
||||||
</a-card>
|
>
|
||||||
</template>
|
<div v-if="!faviconFileList || faviconFileList.length === 0">
|
||||||
|
<plus-outlined />
|
||||||
|
<div style="margin-top: 8px">上传图标</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 登录页Logo -->
|
||||||
|
<a-form-item label="登录页Logo">
|
||||||
|
<a-upload
|
||||||
|
v-model:file-list="logoFileList"
|
||||||
|
list-type="picture-card"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="(options) => handleUpload(options, 'logo')"
|
||||||
|
>
|
||||||
|
<div v-if="!logoFileList || logoFileList.length === 0">
|
||||||
|
<plus-outlined />
|
||||||
|
<div style="margin-top: 8px">上传Logo</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 登录页背景图 -->
|
||||||
|
<a-form-item label="登录页背景图">
|
||||||
|
<a-upload
|
||||||
|
v-model:file-list="backgroundFileList"
|
||||||
|
list-type="picture-card"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="(options) => handleUpload(options, 'background')"
|
||||||
|
>
|
||||||
|
<div v-if="!backgroundFileList || backgroundFileList.length === 0">
|
||||||
|
<plus-outlined />
|
||||||
|
<div style="margin-top: 8px">上传背景图</div>
|
||||||
|
</div>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 版权信息 -->
|
||||||
|
<a-form-item label="网站描述">
|
||||||
|
<a-input v-model:value="configData.description" placeholder="请输入版权信息" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 版权信息 -->
|
||||||
|
<a-form-item label="版权信息">
|
||||||
|
<a-input v-model:value="configData.copyright" placeholder="请输入版权信息" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 备案号 -->
|
||||||
|
<a-form-item label="备案号">
|
||||||
|
<a-input v-model:value="configData.keep_record" placeholder="请输入备案号" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 帮助链接 -->
|
||||||
|
<a-form-item label="帮助链接">
|
||||||
|
<a-input v-model:value="configData.help_url" placeholder="请输入帮助链接" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 隐私条款链接 -->
|
||||||
|
<a-form-item label="隐私条款链接">
|
||||||
|
<a-input v-model:value="configData.privacy_url" placeholder="请输入隐私条款链接" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 服务条款链接 -->
|
||||||
|
<a-form-item label="服务条款链接">
|
||||||
|
<a-input v-model:value="configData.clause_url" placeholder="请输入服务条款链接" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 代码仓库链接 -->
|
||||||
|
<a-form-item label="代码仓库链接">
|
||||||
|
<a-input v-model:value="configData.code_url" placeholder="请输入代码仓库链接" allowClear />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
<!-- 保存按钮 -->
|
<!-- 保存按钮 -->
|
||||||
<div style="text-align: right; margin-top: 24px;">
|
<div style="text-align: right; margin-top: 24px;">
|
||||||
@@ -47,46 +106,53 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, onMounted } from 'vue';
|
import { reactive, ref, onMounted } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { getConfigList, batchConfig, uploadFile } from '@/api/system/config';
|
import { getConfigInfo, updateConfig, uploadFile } from '@/api/system/config';
|
||||||
import PageHeader from '@/components/PageHeader.vue';
|
import PageHeader from '@/components/PageHeader.vue';
|
||||||
import type { tableDataType } from './types'
|
import type { tableDataType } from './types';
|
||||||
|
|
||||||
interface ConfigGroup extends tableDataType {
|
// 配置数据
|
||||||
children: tableDataType[];
|
const configData = reactive<tableDataType>({
|
||||||
fileList?: any[];
|
id: 1,
|
||||||
}
|
title: 'FastAPI Vue Admin',
|
||||||
|
favicon: 'http://example.com/favicon.png',
|
||||||
|
logo: 'http://example.com/logo.png',
|
||||||
|
background: 'http://example.com/background.png',
|
||||||
|
description: 'FastAPI Vue Admin 是完全开源的权限管理系统',
|
||||||
|
copyright: 'Copyright © 2021-2025 fastapi-vue-admin.com',
|
||||||
|
keep_record: '晋ICP备18005113号-3',
|
||||||
|
help_url: 'https://django-vue-admin.com',
|
||||||
|
privacy_url: '/api/system/clause/privacy.html',
|
||||||
|
clause_url: '/api/system/clause/terms_service.html',
|
||||||
|
code_url: 'https://gitee.com/tao__tao/fastapi_vue_admin.git',
|
||||||
|
});
|
||||||
|
|
||||||
const updateState = reactive<ConfigGroup[]>([]);
|
// 文件上传列表
|
||||||
|
const faviconFileList = ref<any[]>([]);
|
||||||
|
const logoFileList = ref<any[]>([]);
|
||||||
|
const backgroundFileList = ref<any[]>([]);
|
||||||
|
|
||||||
// 加载配置数据
|
// 加载配置数据
|
||||||
const loadConfigData = async () => {
|
const loadConfigData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getConfigList({});
|
const response = await getConfigInfo({});
|
||||||
const items = response.data.data.items;
|
const data = response.data.data;
|
||||||
|
|
||||||
// 将配置项按父级分组
|
// 填充配置数据
|
||||||
const groups = items
|
Object.assign(configData, data);
|
||||||
.filter(item => item.parent_id === null) // 获取顶级配置项
|
|
||||||
.map(group => ({
|
|
||||||
...group,
|
|
||||||
children: items.filter(item => item.parent_id === group.id) // 获取子配置项
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 初始化图片上传列表
|
// 初始化文件上传列表
|
||||||
groups.forEach(group => {
|
if (data.favicon) {
|
||||||
group.children.forEach(config => {
|
faviconFileList.value = [{ url: data.favicon }];
|
||||||
if (config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background') {
|
}
|
||||||
config.fileList = config.fied_value ? [{ url: config.fied_value }] : [];
|
if (data.logo) {
|
||||||
}
|
logoFileList.value = [{ url: data.logo }];
|
||||||
});
|
}
|
||||||
});
|
if (data.background) {
|
||||||
|
backgroundFileList.value = [{ url: data.background }];
|
||||||
// 避免直接修改 reactive 对象,使用重新赋值的方式
|
}
|
||||||
updateState.length = 0; // 清空数组
|
|
||||||
updateState.push(...groups); // 添加新数据
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载配置数据失败:', error);
|
console.error('加载配置数据失败:', error);
|
||||||
message.error('加载配置数据失败');
|
message.error('加载配置数据失败');
|
||||||
@@ -103,7 +169,7 @@ const beforeUpload = (file: File) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 自定义上传逻辑
|
// 自定义上传逻辑
|
||||||
const handleUpload = async (options: any, config: any) => {
|
const handleUpload = async (options: any, type: string) => {
|
||||||
const { file, onSuccess, onError } = options;
|
const { file, onSuccess, onError } = options;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -111,11 +177,19 @@ const handleUpload = async (options: any, config: any) => {
|
|||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
const response = await uploadFile(formData);
|
const response = await uploadFile(formData);
|
||||||
const fileUrl = response.data.data.file_url; // 使用 file_url 更新配置项的值
|
const fileUrl = response.data.data.file_url;
|
||||||
|
|
||||||
// 更新配置项的值
|
// 更新配置数据
|
||||||
config.fied_value = fileUrl;
|
if (type === 'favicon') {
|
||||||
config.fileList = [{ url: fileUrl }]; // 更新文件列表显示
|
configData.favicon = fileUrl;
|
||||||
|
faviconFileList.value = [{ url: fileUrl }];
|
||||||
|
} else if (type === 'logo') {
|
||||||
|
configData.logo = fileUrl;
|
||||||
|
logoFileList.value = [{ url: fileUrl }];
|
||||||
|
} else if (type === 'background') {
|
||||||
|
configData.background = fileUrl;
|
||||||
|
backgroundFileList.value = [{ url: fileUrl }];
|
||||||
|
}
|
||||||
|
|
||||||
onSuccess(response, file);
|
onSuccess(response, file);
|
||||||
message.success('上传成功');
|
message.success('上传成功');
|
||||||
@@ -129,28 +203,8 @@ const handleUpload = async (options: any, config: any) => {
|
|||||||
// 保存配置
|
// 保存配置
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
// 提取所有配置项(包括父级和子级)
|
// 调用更新配置接口
|
||||||
const configs = updateState.flatMap(group => [
|
await updateConfig(configData);
|
||||||
{
|
|
||||||
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('配置保存成功');
|
message.success('配置保存成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存配置失败:', error);
|
console.error('保存配置失败:', error);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
export interface tableDataType {
|
export interface tableDataType {
|
||||||
id?: number;
|
id?: number; // id 是可选的,类型为 number
|
||||||
name?: string;
|
title: string; // 网站标题
|
||||||
order?: number;
|
favicon: string; // 网站图标
|
||||||
fied_key?: string;
|
logo: string; // 登录页Logo
|
||||||
fied_value?: string;
|
background: string; // 登录页背景图
|
||||||
parent_id?: number;
|
description: string; // 网站描述
|
||||||
fileList?: any[];
|
copyright: string; // 版权信息
|
||||||
children?: tableDataType[];
|
keep_record: string; // 备案号
|
||||||
|
help_url: string; // 帮助链接
|
||||||
|
privacy_url: string; // 隐私条款链接
|
||||||
|
clause_url: string; // 服务条款链接
|
||||||
|
code_url: string; // 代码仓库链接
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user