mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-04 01:10:36 +00:00
refactor(module): 重构模块结构和代码生成模板
- 删除废弃的ticket和version模块相关代码 - 将resource模块从system迁移到monitor - 移除前端资源管理相关配置 - 重构代码生成模板路径和配置 - 修复CRUD基类初始化参数问题 - 优化模板工具类路径处理 - 更新基础模型字段和配置
This commit is contained in:
@@ -11,9 +11,6 @@ from .module_monitor import MonitorRouter
|
|||||||
# 通用模块
|
# 通用模块
|
||||||
from .module_common import CommonRouter
|
from .module_common import CommonRouter
|
||||||
|
|
||||||
# 资源管理模块
|
|
||||||
from .module_resource import ResourceRouter
|
|
||||||
|
|
||||||
# 示例模块
|
# 示例模块
|
||||||
from .module_example import ExampleRouter
|
from .module_example import ExampleRouter
|
||||||
|
|
||||||
@@ -34,7 +31,6 @@ router = APIRouter()
|
|||||||
router.include_router(SystemRouter)
|
router.include_router(SystemRouter)
|
||||||
router.include_router(MonitorRouter)
|
router.include_router(MonitorRouter)
|
||||||
router.include_router(CommonRouter)
|
router.include_router(CommonRouter)
|
||||||
router.include_router(ResourceRouter)
|
|
||||||
router.include_router(ExampleRouter)
|
router.include_router(ExampleRouter)
|
||||||
router.include_router(ApplicationRouter)
|
router.include_router(ApplicationRouter)
|
||||||
router.include_router(AIRouter)
|
router.include_router(AIRouter)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
|
|||||||
def __init__(self, auth: AuthSchema) -> None:
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
"""初始化CRUD"""
|
"""初始化CRUD"""
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
super().__init__(model=McpModel(), auth=auth)
|
super().__init__(model=McpModel, auth=auth)
|
||||||
|
|
||||||
async def get_by_id_crud(self, id: int) -> Optional[McpModel]:
|
async def get_by_id_crud(self, id: int) -> Optional[McpModel]:
|
||||||
"""详情"""
|
"""详情"""
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]):
|
|||||||
def __init__(self, auth: AuthSchema) -> None:
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
"""初始化CRUD"""
|
"""初始化CRUD"""
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
super().__init__(model=DemoModel, auth=auth)
|
super().__init__(model=DemoModel(), auth=auth)
|
||||||
|
|
||||||
async def get_by_id_crud(self, id: int) -> Optional[DemoModel]:
|
async def get_by_id_crud(self, id: int) -> Optional[DemoModel]:
|
||||||
"""详情"""
|
"""详情"""
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from .gencode.controller import genController
|
from .gencode.controller import GenRouter
|
||||||
|
|
||||||
# 创建代码生成模块路由
|
# 创建代码生成模块路由
|
||||||
GeneratorRouter = APIRouter(prefix="/generator")
|
GeneratorRouter = APIRouter(prefix="/generator")
|
||||||
|
|
||||||
# 包含代码生成路由
|
# 包含代码生成路由
|
||||||
GeneratorRouter.include_router(genController)
|
GeneratorRouter.include_router(GenRouter)
|
||||||
@@ -1,107 +1,107 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from fastapi import APIRouter, Depends, Query, Request, Body
|
from typing import List
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi import APIRouter, Depends, Query, Body
|
||||||
|
from fastapi.responses import StreamingResponse, JSONResponse
|
||||||
from pydantic_validation_decorator import ValidateFields
|
from pydantic_validation_decorator import ValidateFields
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
from app.common.enums import BusinessType
|
|
||||||
from app.common.response import SuccessResponse, ErrorResponse, StreamResponse
|
from app.common.response import SuccessResponse, ErrorResponse, StreamResponse
|
||||||
from app.core.dependencies import AuthPermission
|
from app.core.dependencies import AuthPermission
|
||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.core.base_params import PaginationQueryParam
|
from app.core.base_params import PaginationQueryParam
|
||||||
|
from app.common.request import PaginationService
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||||
from app.api.v1.module_system.user.schema import UserOutSchema
|
from app.api.v1.module_system.user.schema import UserOutSchema
|
||||||
from .param import GenTableQueryParam
|
from .param import GenTableQueryParam
|
||||||
from .schema import DeleteGenTableSchema, EditGenTableSchema, GenTableSchema
|
from .schema import GenTableDeleteSchema, GenTableUpdateSchema, GenTableOutSchema
|
||||||
from .service import GenTableColumnService, GenTableService
|
from .service import GenTableColumnService, GenTableService
|
||||||
from app.utils.common_util import bytes2file_response
|
from app.utils.common_util import bytes2file_response
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
|
|
||||||
|
|
||||||
genController = APIRouter(route_class=OperationLogRoute, prefix='/tool/gen', tags=["代码生成模块"])
|
GenRouter = APIRouter(route_class=OperationLogRoute, prefix='/tool/gen', tags=["代码生成模块"])
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/list', summary="查询代码生成业务表列表", description="查询代码生成业务表列表")
|
@GenRouter.get('/list', summary="查询代码生成业务表列表", description="查询代码生成业务表列表")
|
||||||
async def get_gen_table_list(
|
async def get_gen_table_list_controller(
|
||||||
page: PaginationQueryParam = Depends(),
|
page: PaginationQueryParam = Depends(),
|
||||||
search: GenTableQueryParam = Depends(),
|
search: GenTableQueryParam = Depends(),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
# 获取分页数据
|
result_dict_list = await GenTableService.get_gen_table_list_services(auth=auth, query_object=search, is_page=False)
|
||||||
gen_page_query_result = await GenTableService.get_gen_table_list_services(auth, search, is_page=True)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list["items"], page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info('获取代码生成业务表列表成功')
|
logger.info('获取代码生成业务表列表成功')
|
||||||
return SuccessResponse(data=gen_page_query_result, msg="获取代码生成业务表列表成功")
|
return SuccessResponse(data=result_dict, msg="获取代码生成业务表列表成功")
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/db/list', summary="查询数据库表列表", description="查询数据库表列表")
|
@GenRouter.get('/db/list', summary="查询数据库表列表", description="查询数据库表列表")
|
||||||
async def get_gen_db_table_list(
|
async def get_gen_db_table_list_controller(
|
||||||
page: PaginationQueryParam = Depends(),
|
page: PaginationQueryParam = Depends(),
|
||||||
search: GenTableQueryParam = Depends(),
|
search: GenTableQueryParam = Depends(),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
# 获取分页数据
|
result_dict_list = await GenTableService.get_gen_db_table_list_services(auth=auth, query_object=search, is_page=False)
|
||||||
gen_page_query_result = await GenTableService.get_gen_db_table_list_services(auth, search, is_page=True)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list["items"], page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info('获取数据库表列表成功')
|
logger.info('获取数据库表列表成功')
|
||||||
return SuccessResponse(data=gen_page_query_result, msg="获取数据库表列表成功")
|
return SuccessResponse(data=result_dict, msg="获取数据库表列表成功")
|
||||||
|
|
||||||
|
|
||||||
@genController.post('/importTable', summary="导入表结构", description="导入表结构")
|
@GenRouter.post('/importTable', summary="导入表结构", description="导入表结构")
|
||||||
@ValidateFields(validate_model='edit_gen_table')
|
@ValidateFields(validate_model='edit_gen_table')
|
||||||
async def import_gen_table(
|
async def import_gen_table_controller(
|
||||||
tables: str = Query(..., description="表名列表"),
|
tables: List[str] = Body(..., description="表名列表", embed=True),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:import"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:import"])),
|
||||||
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
||||||
):
|
) -> JSONResponse:
|
||||||
table_names = tables.split(',') if tables else []
|
table_names = tables if tables else []
|
||||||
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(auth, table_names)
|
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(auth, table_names)
|
||||||
result = await GenTableService.import_gen_table_services(auth, add_gen_table_list, current_user)
|
result = await GenTableService.import_gen_table_services(auth, add_gen_table_list, current_user)
|
||||||
logger.info('导入表结构成功')
|
logger.info('导入表结构成功')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@genController.put('', summary="编辑业务表信息", description="编辑业务表信息")
|
@GenRouter.put('', summary="编辑业务表信息", description="编辑业务表信息")
|
||||||
@ValidateFields(validate_model='edit_gen_table')
|
@ValidateFields(validate_model='edit_gen_table')
|
||||||
async def edit_gen_table(
|
async def edit_gen_table_controller(
|
||||||
data: EditGenTableSchema,
|
data: GenTableUpdateSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"])),
|
||||||
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
||||||
):
|
) -> JSONResponse:
|
||||||
data.update_by = current_user.username
|
data.update_by = current_user.username
|
||||||
data.update_time = datetime.now()
|
data.updated_at = datetime.now()
|
||||||
await GenTableService.validate_edit(data)
|
await GenTableService.validate_edit(data)
|
||||||
edit_gen_result = await GenTableService.edit_gen_table_services(auth, data)
|
edit_gen_result = await GenTableService.edit_gen_table_services(auth, data)
|
||||||
logger.info('编辑业务表信息成功')
|
logger.info('编辑业务表信息成功')
|
||||||
return SuccessResponse(data=edit_gen_result, msg="编辑业务表信息成功")
|
return SuccessResponse(data=edit_gen_result, msg="编辑业务表信息成功")
|
||||||
|
|
||||||
|
|
||||||
@genController.delete('/{table_ids}', summary="删除业务表信息", description="删除业务表信息")
|
@GenRouter.delete('/{table_ids}', summary="删除业务表信息", description="删除业务表信息")
|
||||||
async def delete_gen_table(
|
async def delete_gen_table_controller(
|
||||||
table_ids: str,
|
table_ids: str,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:remove"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:remove"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
delete_gen_table = DeleteGenTableSchema(table_ids=table_ids)
|
delete_gen_table = GenTableDeleteSchema(table_ids=table_ids)
|
||||||
result = await GenTableService.delete_gen_table_services(auth, delete_gen_table)
|
result = await GenTableService.delete_gen_table_services(auth, delete_gen_table)
|
||||||
logger.info('删除业务表信息成功')
|
logger.info('删除业务表信息成功')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@genController.post('/createTable', summary="创建表结构", description="创建表结构")
|
@GenRouter.post('/createTable', summary="创建表结构", description="创建表结构")
|
||||||
async def create_table(
|
async def create_table_controller(
|
||||||
sql: str = Query(..., description="SQL语句"),
|
sql: str = Query(..., description="SQL语句"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:create"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:create"])),
|
||||||
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
current_user: UserOutSchema = Depends(lambda auth: auth.user)
|
||||||
):
|
) -> JSONResponse:
|
||||||
result = await GenTableService.create_table_services(auth, sql, current_user)
|
result = await GenTableService.create_table_services(auth, sql, current_user)
|
||||||
logger.info('创建表结构成功')
|
logger.info('创建表结构成功')
|
||||||
return result
|
return SuccessResponse(msg="创建表结构成功", data=result)
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/batchGenCode', summary="批量生成代码", description="批量生成代码")
|
@GenRouter.get('/batchGenCode', summary="批量生成代码", description="批量生成代码")
|
||||||
async def batch_gen_code(
|
async def batch_gen_code_controller(
|
||||||
tables: str = Query(..., description="表名列表"),
|
tables: str = Query(..., description="表名列表"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
|
||||||
):
|
) -> StreamResponse:
|
||||||
table_names = tables.split(',') if tables else []
|
table_names = tables.split(',') if tables else []
|
||||||
batch_gen_code_result = await GenTableService.batch_gen_code_services(auth, table_names)
|
batch_gen_code_result = await GenTableService.batch_gen_code_services(auth, table_names)
|
||||||
logger.info('批量生成代码成功')
|
logger.info('批量生成代码成功')
|
||||||
@@ -112,11 +112,11 @@ async def batch_gen_code(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/genCode/{table_name}', summary="生成代码到指定路径", description="生成代码到指定路径")
|
@GenRouter.get('/genCode/{table_name}', summary="生成代码到指定路径", description="生成代码到指定路径")
|
||||||
async def gen_code_local(
|
async def gen_code_local_controller(
|
||||||
table_name: str,
|
table_name: str,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
if not settings.allow_overwrite:
|
if not settings.allow_overwrite:
|
||||||
logger.error('【系统预设】不允许生成文件覆盖到本地')
|
logger.error('【系统预设】不允许生成文件覆盖到本地')
|
||||||
@@ -126,11 +126,11 @@ async def gen_code_local(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/{table_id}', summary="获取业务表详细信息", description="获取业务表详细信息")
|
@GenRouter.get('/{table_id}', summary="获取业务表详细信息", description="获取业务表详细信息")
|
||||||
async def query_detail_gen_table(
|
async def query_detail_gen_table_controller(
|
||||||
table_id: int,
|
table_id: int,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:query"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
gen_table = await GenTableService.get_gen_table_by_id_services(auth, table_id)
|
gen_table = await GenTableService.get_gen_table_by_id_services(auth, table_id)
|
||||||
gen_tables = await GenTableService.get_gen_table_all_services(auth)
|
gen_tables = await GenTableService.get_gen_table_all_services(auth)
|
||||||
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(auth, table_id)
|
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(auth, table_id)
|
||||||
@@ -139,21 +139,21 @@ async def query_detail_gen_table(
|
|||||||
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
|
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/preview/{table_id}', summary="预览代码", description="预览代码")
|
@GenRouter.get('/preview/{table_id}', summary="预览代码", description="预览代码")
|
||||||
async def preview_code(
|
async def preview_code_controller(
|
||||||
table_id: int,
|
table_id: int,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:preview"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:preview"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
preview_code_result = await GenTableService.preview_code_services(auth, table_id)
|
preview_code_result = await GenTableService.preview_code_services(auth, table_id)
|
||||||
logger.info('预览代码成功')
|
logger.info('预览代码成功')
|
||||||
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
|
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
|
||||||
|
|
||||||
|
|
||||||
@genController.get('/synchDb/{table_name}', summary="同步数据库", description="同步数据库")
|
@GenRouter.get('/synchDb/{table_name}', summary="同步数据库", description="同步数据库")
|
||||||
async def sync_db(
|
async def sync_db_controller(
|
||||||
table_name: str,
|
table_name: str,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"]))
|
||||||
):
|
) -> JSONResponse:
|
||||||
result = await GenTableService.sync_db_services(auth, table_name)
|
result = await GenTableService.sync_db_services(auth, table_name)
|
||||||
logger.info('同步数据库成功')
|
logger.info('同步数据库成功')
|
||||||
return result
|
return result
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
from datetime import datetime, time
|
from datetime import datetime, time
|
||||||
|
from sqlalchemy.engine.row import Row
|
||||||
from sqlalchemy import delete, func, select, text, update
|
from sqlalchemy import delete, func, select, text, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
@@ -9,25 +10,20 @@ from typing import List, Optional, Sequence, Any, Dict
|
|||||||
from .model import GenTableModel, GenTableColumnModel
|
from .model import GenTableModel, GenTableColumnModel
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
from app.common.request import PaginationService
|
from app.common.request import PaginationService
|
||||||
from .schema import (
|
from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
|
||||||
GenTableBaseSchema,
|
|
||||||
GenTableColumnBaseSchema,
|
|
||||||
GenTableColumnSchema,
|
|
||||||
GenTableSchema,
|
|
||||||
)
|
|
||||||
from .param import GenTableQueryParam
|
from .param import GenTableQueryParam
|
||||||
from app.core.base_crud import CRUDBase
|
from app.core.base_crud import CRUDBase
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||||
|
|
||||||
|
|
||||||
class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema]):
|
class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateSchema]):
|
||||||
"""
|
"""
|
||||||
代码生成业务表模块数据库操作层
|
代码生成业务表模块数据库操作层
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema) -> None:
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
"""初始化CRUD"""
|
"""初始化CRUD"""
|
||||||
super().__init__(model=GenTableModel(), auth=auth)
|
super().__init__(model=GenTableModel, auth=auth)
|
||||||
|
|
||||||
async def get_gen_table_by_id(self, db: AsyncSession, table_id: int) -> Optional[GenTableModel]:
|
async def get_gen_table_by_id(self, db: AsyncSession, table_id: int) -> Optional[GenTableModel]:
|
||||||
"""
|
"""
|
||||||
@@ -104,23 +100,24 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
|
|||||||
# 构建查询条件
|
# 构建查询条件
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
# 访问name属性而不是table_name
|
# 访问table_name属性
|
||||||
if query_object.name:
|
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
|
||||||
conditions.append(func.lower(GenTableModel.table_name).like(f'%{str(query_object.name).lower()}%'))
|
conditions.append(func.lower(GenTableModel.table_name).like(f'%{str(query_object.table_name[1]).lower()}%'))
|
||||||
|
|
||||||
# 访问table_comment属性
|
# 访问table_comment属性
|
||||||
if query_object.table_comment:
|
if getattr(query_object, 'table_comment', None):
|
||||||
conditions.append(func.lower(GenTableModel.table_comment).like(f'%{str(query_object.table_comment).lower()}%'))
|
conditions.append(func.lower(GenTableModel.table_comment).like(f'%{str(query_object.table_comment).lower()}%'))
|
||||||
|
|
||||||
# 访问created_at属性而不是start_time和end_time
|
# 访问created_at属性而不是start_time和end_time
|
||||||
if hasattr(query_object, 'created_at') and query_object.created_at:
|
if hasattr(query_object, 'created_at') and query_object.created_at:
|
||||||
if isinstance(query_object.created_at, tuple) and query_object.created_at[0] == "between":
|
if isinstance(query_object.created_at, tuple) and query_object.created_at[0] == "between":
|
||||||
conditions.append(GenTableModel.create_time.between(*query_object.created_at[1]))
|
conditions.append(GenTableModel.created_at.between(*query_object.created_at[1]))
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
select(GenTableModel)
|
select(GenTableModel)
|
||||||
.options(selectinload(GenTableModel.columns))
|
.options(selectinload(GenTableModel.columns))
|
||||||
.where(*conditions)
|
.where(*conditions)
|
||||||
|
.order_by(GenTableModel.created_at.desc())
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,47 +151,43 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
|
|||||||
:param is_page: 是否开启分页
|
:param is_page: 是否开启分页
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
if settings.DATABASE_TYPE == 'postgresql':
|
query_sql = """
|
||||||
query_sql = """
|
SELECT table_name as table_name,
|
||||||
SELECT table_name as table_name,
|
table_comment as table_comment,
|
||||||
table_comment as table_comment,
|
create_time as create_time,
|
||||||
create_time as create_time,
|
update_time as update_time
|
||||||
update_time as update_time
|
from
|
||||||
from
|
information_schema.tables
|
||||||
list_table
|
where
|
||||||
where
|
table_schema = (select database())
|
||||||
table_name not like 'apscheduler_%'
|
and table_name not like 'apscheduler\_%'
|
||||||
and table_name not like 'gen_%'
|
and table_name not like 'gen\_%'
|
||||||
and table_name not in (select table_name from gen_table)
|
and table_name not in (select table_name from gen_table)
|
||||||
"""
|
"""
|
||||||
else:
|
# 根据param.py中的定义,table_name是元组形式("like", value)
|
||||||
query_sql = """
|
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
|
||||||
SELECT table_name as table_name,
|
|
||||||
table_comment as table_comment,
|
|
||||||
create_time as create_time,
|
|
||||||
update_time as update_time
|
|
||||||
from
|
|
||||||
information_schema.tables
|
|
||||||
where
|
|
||||||
table_schema = (select database())
|
|
||||||
and table_name not like 'apscheduler\_%'
|
|
||||||
and table_name not like 'gen\_%'
|
|
||||||
and table_name not in (select table_name from gen_table)
|
|
||||||
"""
|
|
||||||
if query_object.name:
|
|
||||||
query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))"""
|
query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))"""
|
||||||
if query_object.table_comment:
|
|
||||||
|
# 处理table_comment字段(如果有)
|
||||||
|
if getattr(query_object, 'table_comment', None):
|
||||||
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
|
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
|
||||||
# 修复查询参数处理
|
|
||||||
query_params = {
|
# 构建查询参数
|
||||||
k: v for k, v in query_object.model_dump(exclude_none=True, exclude={'page_no', 'page_size'}).items()
|
query_params = {}
|
||||||
}
|
|
||||||
|
# 添加table_name查询参数
|
||||||
|
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
|
||||||
|
query_params['table_name'] = query_object.table_name[1]
|
||||||
|
|
||||||
|
# 添加table_comment查询参数
|
||||||
|
if getattr(query_object, 'table_comment', None):
|
||||||
|
query_params['table_comment'] = query_object.table_comment
|
||||||
|
|
||||||
query_sql += """order by create_time desc"""
|
query_sql += """order by create_time desc"""
|
||||||
query = text(query_sql).bindparams(**query_params)
|
query = text(query_sql).bindparams(**query_params)
|
||||||
|
|
||||||
# 执行查询
|
# 执行查询
|
||||||
result = await db.execute(select(query))
|
result = await db.execute(query)
|
||||||
all_data = list(result.fetchall())
|
all_data = list(result.fetchall())
|
||||||
|
|
||||||
# 使用PaginationService.paginate进行分页
|
# 使用PaginationService.paginate进行分页
|
||||||
@@ -222,49 +215,32 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
|
|||||||
:param table_names: 业务表名称组
|
:param table_names: 业务表名称组
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
if settings.DATABASE_TYPE == 'postgresql':
|
query_sql = """
|
||||||
query_sql = """
|
select
|
||||||
select
|
table_name as table_name,
|
||||||
table_name as table_name,
|
table_comment as table_comment,
|
||||||
table_comment as table_comment,
|
create_time as create_time,
|
||||||
create_time as create_time,
|
update_time as update_time
|
||||||
update_time as update_time
|
from
|
||||||
from
|
information_schema.tables
|
||||||
list_table
|
where
|
||||||
where
|
table_schema = (select database())
|
||||||
table_name not like 'qrtz_%'
|
and table_name in :table_names
|
||||||
and table_name not like 'gen_%'
|
"""
|
||||||
and table_name = any(:table_names)
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
query_sql = """
|
|
||||||
select
|
|
||||||
table_name as table_name,
|
|
||||||
table_comment as table_comment,
|
|
||||||
create_time as create_time,
|
|
||||||
update_time as update_time
|
|
||||||
from
|
|
||||||
information_schema.tables
|
|
||||||
where
|
|
||||||
table_name not like 'qrtz\_%'
|
|
||||||
and table_name not like 'gen\_%'
|
|
||||||
and table_schema = (select database())
|
|
||||||
and table_name in :table_names
|
|
||||||
"""
|
|
||||||
query = text(query_sql).bindparams(table_names=tuple(table_names))
|
query = text(query_sql).bindparams(table_names=tuple(table_names))
|
||||||
gen_db_table_list = (await db.execute(query)).fetchall()
|
gen_db_table_list = (await db.execute(query)).fetchall()
|
||||||
|
|
||||||
return gen_db_table_list
|
return gen_db_table_list
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema, GenTableColumnBaseSchema]):
|
class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnCreateSchema, GenTableColumnUpdateSchema]):
|
||||||
"""
|
"""
|
||||||
代码生成业务表字段模块数据库操作层
|
代码生成业务表字段模块数据库操作层
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema) -> None:
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
"""初始化CRUD"""
|
"""初始化CRUD"""
|
||||||
super().__init__(model=GenTableColumnModel(), auth=auth)
|
super().__init__(model=GenTableColumnModel, auth=auth)
|
||||||
|
|
||||||
async def get_gen_table_column_list_by_table_id(self, db: AsyncSession, table_id: int) -> Sequence[GenTableColumnModel]:
|
async def get_gen_table_column_list_by_table_id(self, db: AsyncSession, table_id: int) -> Sequence[GenTableColumnModel]:
|
||||||
"""
|
"""
|
||||||
@@ -286,7 +262,7 @@ class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema,
|
|||||||
|
|
||||||
return gen_table_column_list
|
return gen_table_column_list
|
||||||
|
|
||||||
async def get_gen_db_table_columns_by_name(self, db: AsyncSession, table_name: str):
|
async def get_gen_db_table_columns_by_name(self, db: AsyncSession, table_name: str) -> Sequence[Row[Any]]:
|
||||||
"""
|
"""
|
||||||
根据业务表名称获取业务表字段列表信息
|
根据业务表名称获取业务表字段列表信息
|
||||||
|
|
||||||
@@ -294,42 +270,32 @@ class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema,
|
|||||||
:param table_name: 业务表名称
|
:param table_name: 业务表名称
|
||||||
:return: 业务表字段列表信息对象
|
:return: 业务表字段列表信息对象
|
||||||
"""
|
"""
|
||||||
if settings.DATABASE_TYPE == 'postgresql':
|
query_sql = """
|
||||||
query_sql = """
|
select
|
||||||
select
|
column_name as column_name,
|
||||||
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
|
case
|
||||||
from
|
when is_nullable = 'no' and column_key != 'PRI' then '1'
|
||||||
list_column
|
else '0'
|
||||||
where
|
end as is_required,
|
||||||
table_name = :table_name
|
case
|
||||||
"""
|
when column_key = 'PRI' then '1'
|
||||||
else:
|
else '0'
|
||||||
query_sql = """
|
end as is_pk,
|
||||||
select
|
ordinal_position as sort,
|
||||||
column_name as column_name,
|
column_comment as column_comment,
|
||||||
case
|
case
|
||||||
when is_nullable = 'no' and column_key != 'PRI' then '1'
|
when extra = 'auto_increment' then '1'
|
||||||
else '0'
|
else '0'
|
||||||
end as is_required,
|
end as is_increment,
|
||||||
case
|
column_type as column_type
|
||||||
when column_key = 'PRI' then '1'
|
from
|
||||||
else '0'
|
information_schema.columns
|
||||||
end as is_pk,
|
where
|
||||||
ordinal_position as sort,
|
table_schema = (select database())
|
||||||
column_comment as column_comment,
|
and table_name = :table_name
|
||||||
case
|
order by
|
||||||
when extra = 'auto_increment' then '1'
|
ordinal_position
|
||||||
else '0'
|
"""
|
||||||
end as is_increment,
|
|
||||||
column_type as column_type
|
|
||||||
from
|
|
||||||
information_schema.columns
|
|
||||||
where
|
|
||||||
table_schema = (select database())
|
|
||||||
and table_name = :table_name
|
|
||||||
order by
|
|
||||||
ordinal_position
|
|
||||||
"""
|
|
||||||
query = text(query_sql).bindparams(table_name=table_name)
|
query = text(query_sql).bindparams(table_name=table_name)
|
||||||
gen_db_table_columns = (await db.execute(query)).fetchall()
|
gen_db_table_columns = (await db.execute(query)).fetchall()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from sqlalchemy import String, Integer, ForeignKey
|
from sqlalchemy import String, Integer, ForeignKey
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|||||||
@@ -5,25 +5,21 @@ from typing import Optional
|
|||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
|
||||||
from app.core.validator import DateTimeStr
|
from app.core.validator import DateTimeStr
|
||||||
from app.core.base_params import PaginationQueryParam
|
|
||||||
from .schema import GenTableBaseSchema, GenTableColumnBaseSchema
|
|
||||||
|
|
||||||
|
|
||||||
class GenTableQueryParam(PaginationQueryParam, GenTableBaseSchema):
|
class GenTableQueryParam:
|
||||||
"""数据库表查询参数"""
|
"""数据库表查询参数"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: Optional[str] = Query(None, description="名称"),
|
table_name: Optional[str] = Query(None, description="表名称"),
|
||||||
status: Optional[bool] = Query(None, description="是否启用"),
|
status: Optional[bool] = Query(None, description="是否启用"),
|
||||||
creator: Optional[int] = Query(None, description="创建人"),
|
creator: Optional[int] = Query(None, description="创建人"),
|
||||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
# 存储查询条件,不直接赋值给父类属性
|
||||||
|
self.table_name = ("like", table_name)
|
||||||
# 模糊查询字段
|
|
||||||
self.name = ("like", name)
|
|
||||||
|
|
||||||
# 精确查询字段
|
# 精确查询字段
|
||||||
self.creator_id = creator
|
self.creator_id = creator
|
||||||
@@ -36,21 +32,19 @@ class GenTableQueryParam(PaginationQueryParam, GenTableBaseSchema):
|
|||||||
self.created_at = ("between", (start_datetime, end_datetime))
|
self.created_at = ("between", (start_datetime, end_datetime))
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnQueryParam(PaginationQueryParam, GenTableColumnBaseSchema):
|
class GenTableColumnQueryParam:
|
||||||
"""数据库表字段查询参数"""
|
"""数据库表字段查询参数"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: Optional[str] = Query(None, description="名称"),
|
column_name: Optional[str] = Query(None, description="列名称"),
|
||||||
status: Optional[bool] = Query(None, description="是否启用"),
|
status: Optional[bool] = Query(None, description="是否启用"),
|
||||||
creator: Optional[int] = Query(None, description="创建人"),
|
creator: Optional[int] = Query(None, description="创建人"),
|
||||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
# 存储查询条件,不直接赋值给父类属性
|
||||||
|
self.column_name = ("like", column_name)
|
||||||
# 模糊查询字段
|
|
||||||
self.name = ("like", name)
|
|
||||||
|
|
||||||
# 精确查询字段
|
# 精确查询字段
|
||||||
self.creator_id = creator
|
self.creator_id = creator
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
from datetime import datetime
|
from typing import List, Literal, Optional
|
||||||
from typing import List, Literal, Optional, Union
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
from pydantic_validation_decorator import NotBlank
|
from pydantic_validation_decorator import NotBlank
|
||||||
|
|
||||||
from app.utils.string_util import StringUtil
|
from app.utils.string_util import StringUtil
|
||||||
from app.common.constant import GenConstant
|
from app.common.constant import GenConstant
|
||||||
|
from app.core.base_schema import BaseSchema
|
||||||
|
|
||||||
|
|
||||||
class GenTableBaseSchema(BaseModel):
|
class GenTableCreateSchema(BaseModel):
|
||||||
"""
|
"""
|
||||||
代码生成业务表对应pydantic模型
|
代码生成业务表对应pydantic模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
table_id: Optional[int] = Field(default=None, description='编号')
|
|
||||||
table_name: str = Field(default=..., description='表名称')
|
table_name: str = Field(default=..., description='表名称')
|
||||||
table_comment: str = Field(default=..., description='表描述')
|
table_comment: str = Field(default=..., description='表描述')
|
||||||
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
|
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
|
||||||
@@ -34,12 +33,6 @@ class GenTableBaseSchema(BaseModel):
|
|||||||
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
|
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
|
||||||
options: Optional[str] = Field(default=None, description='其它生成选项')
|
options: Optional[str] = Field(default=None, description='其它生成选项')
|
||||||
|
|
||||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
|
||||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
|
||||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
|
||||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
|
||||||
remark: Optional[str] = Field(default=None, description='备注')
|
|
||||||
|
|
||||||
@NotBlank(field_name='table_name', message='表名称不能为空')
|
@NotBlank(field_name='table_name', message='表名称不能为空')
|
||||||
def get_table_name(self):
|
def get_table_name(self):
|
||||||
return self.table_name
|
return self.table_name
|
||||||
@@ -83,14 +76,14 @@ class GenTableBaseSchema(BaseModel):
|
|||||||
self.get_function_author()
|
self.get_function_author()
|
||||||
|
|
||||||
|
|
||||||
class GenTableSchema(GenTableBaseSchema):
|
class GenTableUpdateSchema(GenTableCreateSchema):
|
||||||
"""
|
"""
|
||||||
代码生成业务表模型
|
代码生成业务表模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pk_column: Optional['GenTableColumnSchema'] = Field(default=None, description='主键信息')
|
pk_column: Optional['GenTableColumnUpdateSchema'] = Field(default=None, description='主键信息')
|
||||||
sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息')
|
sub_table: Optional['GenTableUpdateSchema'] = Field(default=None, description='子表信息')
|
||||||
columns: List['GenTableColumnSchema'] = Field(default=..., description='表列信息')
|
columns: List['GenTableColumnUpdateSchema'] = Field(default=..., description='表列信息')
|
||||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
||||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
||||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
||||||
@@ -101,35 +94,19 @@ class GenTableSchema(GenTableBaseSchema):
|
|||||||
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
def check_some_is(self) -> 'GenTableSchema':
|
def check_some_is(self) -> 'GenTableUpdateSchema':
|
||||||
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
|
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
|
||||||
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
|
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
|
||||||
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
|
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class EditGenTableSchema(GenTableSchema):
|
class GenTableOutSchema(GenTableUpdateSchema, BaseSchema):
|
||||||
"""
|
"""响应模型"""
|
||||||
修改代码生成业务表模型
|
model_config = ConfigDict(from_attributes=True)
|
||||||
"""
|
|
||||||
|
|
||||||
params: Optional['GenTableParamsSchema'] = Field(default=None, description='业务表参数')
|
|
||||||
|
|
||||||
|
|
||||||
class GenTableParamsSchema(BaseModel):
|
class GenTableDeleteSchema(BaseModel):
|
||||||
"""
|
|
||||||
代码生成业务表参数模型
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel)
|
|
||||||
|
|
||||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
|
||||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
|
||||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
|
||||||
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteGenTableSchema(BaseModel):
|
|
||||||
"""
|
"""
|
||||||
删除代码生成业务表模型
|
删除代码生成业务表模型
|
||||||
"""
|
"""
|
||||||
@@ -139,15 +116,15 @@ class DeleteGenTableSchema(BaseModel):
|
|||||||
table_ids: str = Field(description='需要删除的代码生成业务表ID')
|
table_ids: str = Field(description='需要删除的代码生成业务表ID')
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnBaseSchema(BaseModel):
|
class GenTableColumnCreateSchema(BaseModel):
|
||||||
"""
|
"""
|
||||||
代码生成业务表字段对应pydantic模型
|
代码生成业务表字段对应pydantic模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||||
|
|
||||||
column_id: Optional[int] = Field(default=None, description='编号')
|
|
||||||
table_id: Optional[int] = Field(default=None, description='归属表编号')
|
table_id: Optional[int] = Field(default=None, description='归属表编号')
|
||||||
|
|
||||||
column_name: str = Field(default=..., description='列名称')
|
column_name: str = Field(default=..., description='列名称')
|
||||||
column_comment: Optional[str] = Field(default=None, description='列描述')
|
column_comment: Optional[str] = Field(default=None, description='列描述')
|
||||||
column_type: str = Field(default=..., description='列类型')
|
column_type: str = Field(default=..., description='列类型')
|
||||||
@@ -166,11 +143,6 @@ class GenTableColumnBaseSchema(BaseModel):
|
|||||||
dict_type: str = Field(default=..., description='字典类型')
|
dict_type: str = Field(default=..., description='字典类型')
|
||||||
sort: Optional[int] = Field(default=None, description='排序')
|
sort: Optional[int] = Field(default=None, description='排序')
|
||||||
|
|
||||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
|
||||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
|
||||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
|
||||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
|
||||||
|
|
||||||
@NotBlank(field_name='python_field', message='Python属性不能为空')
|
@NotBlank(field_name='python_field', message='Python属性不能为空')
|
||||||
def get_python_field(self):
|
def get_python_field(self):
|
||||||
return self.python_field
|
return self.python_field
|
||||||
@@ -179,7 +151,7 @@ class GenTableColumnBaseSchema(BaseModel):
|
|||||||
self.get_python_field()
|
self.get_python_field()
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnSchema(GenTableColumnBaseSchema):
|
class GenTableColumnUpdateSchema(GenTableColumnCreateSchema):
|
||||||
"""
|
"""
|
||||||
代码生成业务表字段模型
|
代码生成业务表字段模型
|
||||||
"""
|
"""
|
||||||
@@ -197,7 +169,7 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
|
|||||||
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
|
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
def check_some_is(self) -> 'GenTableColumnSchema':
|
def check_some_is(self) -> 'GenTableColumnUpdateSchema':
|
||||||
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
|
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
|
||||||
self.pk = True if self.is_pk and self.is_pk == '1' else False
|
self.pk = True if self.is_pk and self.is_pk == '1' else False
|
||||||
self.increment = True if self.is_increment and self.is_increment == '1' else False
|
self.increment = True if self.is_increment and self.is_increment == '1' else False
|
||||||
@@ -218,11 +190,16 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class DeleteGenTableColumnSchema(BaseModel):
|
class GenTableColumnOutSchema(GenTableColumnUpdateSchema, BaseSchema):
|
||||||
|
"""响应模型"""
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
|
class GenTableColumnDeleteSchema(BaseModel):
|
||||||
"""
|
"""
|
||||||
删除代码生成业务表字段模型
|
删除代码生成业务表字段模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
column_ids: str = Field(description='需要删除的代码生成业务表字段ID')
|
column_ids: str = Field(description='需要删除的代码生成业务表字段ID')
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ from app.common.constant import GenConstant
|
|||||||
from app.common.response import SuccessResponse
|
from app.common.response import SuccessResponse
|
||||||
from app.api.v1.module_system.user.schema import UserOutSchema
|
from app.api.v1.module_system.user.schema import UserOutSchema
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||||
from .schema import DeleteGenTableSchema, EditGenTableSchema, GenTableColumnSchema, GenTableSchema
|
from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
|
||||||
from .param import GenTableQueryParam
|
from .param import GenTableQueryParam
|
||||||
from .crud import GenTableColumnDao, GenTableDao
|
from .crud import GenTableColumnCRUD, GenTableCRUD
|
||||||
from .model import GenTableModel, GenTableColumnModel
|
from .model import GenTableModel, GenTableColumnModel
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class GenTableService:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_table_list_services(
|
async def get_gen_table_list_service(
|
||||||
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
|
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -44,17 +44,13 @@ class GenTableService:
|
|||||||
:param is_page: 是否开启分页
|
:param is_page: 是否开启分页
|
||||||
:return: 代码生成业务列表信息对象
|
:return: 代码生成业务列表信息对象
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table_list_result = await gen_table_dao.get_gen_table_list(auth.db, query_object, is_page)
|
gen_table_list_result = await gen_table_dao.get_gen_table_list(auth.db, query_object, is_page)
|
||||||
|
|
||||||
return gen_table_list_result
|
return gen_table_list_result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_db_table_list_services(
|
async def get_gen_db_table_list_service(
|
||||||
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
|
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -65,17 +61,13 @@ class GenTableService:
|
|||||||
:param is_page: 是否开启分页
|
:param is_page: 是否开启分页
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list(auth.db, query_object, is_page)
|
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list(auth.db, query_object, is_page)
|
||||||
|
|
||||||
return gen_db_table_list_result
|
return gen_db_table_list_result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_db_table_list_by_name_services(cls, auth: AuthSchema, table_names: List[str]) -> list[GenTableSchema]:
|
async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> list[GenTableOutSchema]:
|
||||||
"""
|
"""
|
||||||
根据表名称组获取数据库列表信息service
|
根据表名称组获取数据库列表信息service
|
||||||
|
|
||||||
@@ -83,18 +75,14 @@ class GenTableService:
|
|||||||
:param table_names: 表名称组
|
:param table_names: 表名称组
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list_by_names(auth.db, table_names)
|
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list_by_names(auth.db, table_names)
|
||||||
|
|
||||||
return [GenTableSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
|
return [GenTableOutSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def import_gen_table_services(
|
async def import_gen_table_service(
|
||||||
cls, auth: AuthSchema, gen_table_list: List[GenTableSchema], current_user: UserOutSchema
|
cls, auth: AuthSchema, gen_table_list: List[GenTableOutSchema], current_user: UserOutSchema
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
导入表结构service
|
导入表结构service
|
||||||
@@ -105,12 +93,8 @@ class GenTableService:
|
|||||||
:return: 导入结果
|
:return: 导入结果
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table_column_dao = GenTableColumnDao(auth=auth)
|
|
||||||
|
|
||||||
for table in gen_table_list:
|
for table in gen_table_list:
|
||||||
table_name = table.table_name
|
table_name = table.table_name
|
||||||
@@ -120,24 +104,22 @@ class GenTableService:
|
|||||||
table.table_id = add_gen_table.id
|
table.table_id = add_gen_table.id
|
||||||
gen_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name or "")
|
gen_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name or "")
|
||||||
for column in [
|
for column in [
|
||||||
GenTableColumnSchema(**gen_table_column)
|
GenTableColumnOutSchema(**gen_table_column)
|
||||||
for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns)
|
for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns)
|
||||||
]:
|
]:
|
||||||
GenUtils.init_column_field(column, table)
|
GenUtils.init_column_field(column, table)
|
||||||
await gen_table_column_dao.create(data=column.model_dump())
|
await gen_table_column_dao.create(data=column.model_dump())
|
||||||
if isinstance(auth.db, AsyncSession):
|
await auth.db.commit()
|
||||||
await auth.db.commit()
|
|
||||||
return SuccessResponse(msg='导入成功')
|
return SuccessResponse(msg='导入成功')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(auth.db, AsyncSession):
|
try:
|
||||||
try:
|
await auth.db.rollback()
|
||||||
await auth.db.rollback()
|
except:
|
||||||
except:
|
pass # 忽略回滚错误
|
||||||
pass # 忽略回滚错误
|
|
||||||
raise CustomException(msg=f'导入失败, {str(e)}')
|
raise CustomException(msg=f'导入失败, {str(e)}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def edit_gen_table_services(cls, auth: AuthSchema, page_object: EditGenTableSchema) -> Dict[str, Any]:
|
async def edit_gen_table_service(cls, auth: AuthSchema, page_object: GenTableUpdateSchema) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
编辑业务表信息service
|
编辑业务表信息service
|
||||||
|
|
||||||
@@ -145,30 +127,26 @@ class GenTableService:
|
|||||||
:param page_object: 编辑业务表对象
|
:param page_object: 编辑业务表对象
|
||||||
:return: 编辑业务表校验结果
|
:return: 编辑业务表校验结果
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table_column_dao = GenTableColumnDao(auth=auth)
|
|
||||||
|
|
||||||
# 检查必要字段是否存在
|
# 检查必要字段是否存在
|
||||||
if page_object.table_id is None:
|
if getattr(page_object, 'table_id', None) is None:
|
||||||
raise CustomException(msg='业务表ID不能为空')
|
raise CustomException(msg='业务表ID不能为空')
|
||||||
|
|
||||||
edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True)
|
edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True)
|
||||||
gen_table_info = await cls.get_gen_table_by_id_services(auth, page_object.table_id)
|
gen_table_info = await cls.get_gen_table_by_id_service(auth, page_object.table_id)
|
||||||
if gen_table_info.table_id:
|
if gen_table_info.table_id:
|
||||||
try:
|
try:
|
||||||
# 处理params字段,确保不为None
|
# 确保options字段存在且为有效JSON
|
||||||
params = edit_gen_table.get('params')
|
if 'options' not in edit_gen_table or edit_gen_table['options'] is None:
|
||||||
if params is not None:
|
|
||||||
edit_gen_table['options'] = json.dumps(params)
|
|
||||||
else:
|
|
||||||
edit_gen_table['options'] = '{}' # 默认空对象
|
edit_gen_table['options'] = '{}' # 默认空对象
|
||||||
|
else:
|
||||||
# 移除params字段,因为options字段已经包含了序列化的params
|
# 验证options是否为有效的JSON
|
||||||
edit_gen_table.pop('params', None)
|
try:
|
||||||
|
json.loads(edit_gen_table['options'])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
edit_gen_table['options'] = '{}'
|
||||||
|
|
||||||
await gen_table_dao.update(id=page_object.table_id, data=edit_gen_table)
|
await gen_table_dao.update(id=page_object.table_id, data=edit_gen_table)
|
||||||
if page_object.columns:
|
if page_object.columns:
|
||||||
@@ -180,21 +158,19 @@ class GenTableService:
|
|||||||
id=gen_table_column.column_id,
|
id=gen_table_column.column_id,
|
||||||
data=gen_table_column.model_dump(by_alias=True)
|
data=gen_table_column.model_dump(by_alias=True)
|
||||||
)
|
)
|
||||||
if isinstance(auth.db, AsyncSession):
|
await auth.db.commit()
|
||||||
await auth.db.commit()
|
|
||||||
return {"is_success": True, "message": "更新成功"}
|
return {"is_success": True, "message": "更新成功"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(auth.db, AsyncSession):
|
try:
|
||||||
try:
|
await auth.db.rollback()
|
||||||
await auth.db.rollback()
|
except:
|
||||||
except:
|
pass # 忽略回滚错误
|
||||||
pass # 忽略回滚错误
|
|
||||||
raise CustomException(msg=f'更新失败: {str(e)}')
|
raise CustomException(msg=f'更新失败: {str(e)}')
|
||||||
else:
|
else:
|
||||||
raise CustomException(msg='业务表不存在')
|
raise CustomException(msg='业务表不存在')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_gen_table_services(cls, auth: AuthSchema, page_object: DeleteGenTableSchema) -> SuccessResponse:
|
async def delete_gen_table_service(cls, auth: AuthSchema, page_object: GenTableDeleteSchema) -> SuccessResponse:
|
||||||
"""
|
"""
|
||||||
删除业务表信息service
|
删除业务表信息service
|
||||||
|
|
||||||
@@ -202,12 +178,8 @@ class GenTableService:
|
|||||||
:param page_object: 删除业务表对象
|
:param page_object: 删除业务表对象
|
||||||
:return: 删除业务表校验结果
|
:return: 删除业务表校验结果
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table_column_dao = GenTableColumnDao(auth=auth)
|
|
||||||
|
|
||||||
if page_object.table_ids:
|
if page_object.table_ids:
|
||||||
table_id_list = page_object.table_ids.split(',')
|
table_id_list = page_object.table_ids.split(',')
|
||||||
@@ -220,21 +192,19 @@ class GenTableService:
|
|||||||
if columns:
|
if columns:
|
||||||
column_ids = [column.id for column in columns]
|
column_ids = [column.id for column in columns]
|
||||||
await gen_table_column_dao.delete(ids=column_ids)
|
await gen_table_column_dao.delete(ids=column_ids)
|
||||||
if isinstance(auth.db, AsyncSession):
|
await auth.db.commit()
|
||||||
await auth.db.commit()
|
|
||||||
return SuccessResponse(msg='删除成功')
|
return SuccessResponse(msg='删除成功')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(auth.db, AsyncSession):
|
try:
|
||||||
try:
|
await auth.db.rollback()
|
||||||
await auth.db.rollback()
|
except:
|
||||||
except:
|
pass # 忽略回滚错误
|
||||||
pass # 忽略回滚错误
|
|
||||||
raise CustomException(msg=f'删除失败: {str(e)}')
|
raise CustomException(msg=f'删除失败: {str(e)}')
|
||||||
else:
|
else:
|
||||||
raise CustomException(msg='传入业务表id为空')
|
raise CustomException(msg='传入业务表id为空')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_table_by_id_services(cls, auth: AuthSchema, table_id: int) -> GenTableSchema:
|
async def get_gen_table_by_id_service(cls, auth: AuthSchema, table_id: int) -> GenTableOutSchema:
|
||||||
"""
|
"""
|
||||||
获取需要生成的业务表详细信息service
|
获取需要生成的业务表详细信息service
|
||||||
|
|
||||||
@@ -242,38 +212,32 @@ class GenTableService:
|
|||||||
:param table_id: 需要生成的业务表id
|
:param table_id: 需要生成的业务表id
|
||||||
:return: 需要生成的业务表id对应的信息
|
:return: 需要生成的业务表id对应的信息
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table = await gen_table_dao.get_gen_table_by_id(auth.db, table_id)
|
gen_table = await gen_table_dao.get_gen_table_by_id(auth.db, table_id)
|
||||||
if gen_table:
|
if gen_table:
|
||||||
result = await cls.set_table_from_options(GenTableSchema(**CamelCaseUtil.transform_result(gen_table)))
|
result = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table)))
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
raise CustomException(msg='业务表不存在')
|
raise CustomException(msg='业务表不存在')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_table_all_services(cls, auth: AuthSchema) -> list[GenTableSchema]:
|
async def get_gen_table_all_service(cls, auth: AuthSchema) -> list[GenTableOutSchema]:
|
||||||
"""
|
"""
|
||||||
获取所有业务表信息service
|
获取所有业务表信息service
|
||||||
|
|
||||||
:param auth: 认证信息
|
:param auth: 认证信息
|
||||||
:return: 所有业务表信息
|
:return: 所有业务表信息列表
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
gen_tables = await gen_table_dao.get_gen_table_all(auth.db)
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
result = []
|
||||||
|
for table in gen_tables:
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
table_info = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(table)))
|
||||||
gen_table_all = await gen_table_dao.get_gen_table_all(auth.db)
|
result.append(table_info)
|
||||||
result = [GenTableSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)]
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_table_services(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse:
|
async def create_table_service(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse:
|
||||||
"""
|
"""
|
||||||
创建表结构service
|
创建表结构service
|
||||||
|
|
||||||
@@ -282,28 +246,22 @@ class GenTableService:
|
|||||||
:param current_user: 当前用户信息对象
|
:param current_user: 当前用户信息对象
|
||||||
:return: 创建表结构结果
|
:return: 创建表结构结果
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 执行SQL语句创建表
|
# 执行SQL语句创建表
|
||||||
await gen_table_dao.create_table_by_sql_dao(auth.db, [sql])
|
await gen_table_dao.create_table_by_sql_dao(auth.db, [sql])
|
||||||
if isinstance(auth.db, AsyncSession):
|
await auth.db.commit()
|
||||||
await auth.db.commit()
|
|
||||||
return SuccessResponse(msg='创建表结构成功')
|
return SuccessResponse(msg='创建表结构成功')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(auth.db, AsyncSession):
|
try:
|
||||||
try:
|
await auth.db.rollback()
|
||||||
await auth.db.rollback()
|
except:
|
||||||
except:
|
pass # 忽略回滚错误
|
||||||
pass # 忽略回滚错误
|
|
||||||
raise CustomException(msg=f'创建表结构失败: {str(e)}')
|
raise CustomException(msg=f'创建表结构失败: {str(e)}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def preview_code_services(cls, auth: AuthSchema, table_id: int) -> dict[Any, Any]:
|
async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> dict[Any, Any]:
|
||||||
"""
|
"""
|
||||||
预览代码service
|
预览代码service
|
||||||
|
|
||||||
@@ -311,7 +269,7 @@ class GenTableService:
|
|||||||
:param table_id: 业务表id
|
:param table_id: 业务表id
|
||||||
:return: 预览数据列表
|
:return: 预览数据列表
|
||||||
"""
|
"""
|
||||||
gen_table = await cls.get_gen_table_by_id_services(auth, table_id)
|
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
|
||||||
await cls.set_sub_table(auth, gen_table)
|
await cls.set_sub_table(auth, gen_table)
|
||||||
await cls._set_pk_column(gen_table)
|
await cls._set_pk_column(gen_table)
|
||||||
env = TemplateInitializer.init_jinja2()
|
env = TemplateInitializer.init_jinja2()
|
||||||
@@ -327,7 +285,7 @@ class GenTableService:
|
|||||||
return preview_code_result
|
return preview_code_result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def generate_code_services(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
|
async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
|
||||||
"""
|
"""
|
||||||
生成代码至指定路径service
|
生成代码至指定路径service
|
||||||
|
|
||||||
@@ -351,7 +309,7 @@ class GenTableService:
|
|||||||
return SuccessResponse(msg='生成代码成功')
|
return SuccessResponse(msg='生成代码成功')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def batch_gen_code_services(cls, auth: AuthSchema, table_names: List[str]) -> bytes:
|
async def batch_gen_code_service(cls, auth: AuthSchema, table_names: List[str]) -> bytes:
|
||||||
"""
|
"""
|
||||||
批量生成代码service
|
批量生成代码service
|
||||||
|
|
||||||
@@ -381,14 +339,11 @@ class GenTableService:
|
|||||||
:param table_name: 业务表名称
|
:param table_name: 业务表名称
|
||||||
:return: 生成代码渲染模板相关信息
|
:return: 生成代码渲染模板相关信息
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
|
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
|
||||||
if gen_table:
|
if gen_table:
|
||||||
gen_table_schema = GenTableSchema(**CamelCaseUtil.transform_result(gen_table))
|
gen_table_schema = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table))
|
||||||
await cls.set_sub_table(auth, gen_table_schema)
|
await cls.set_sub_table(auth, gen_table_schema)
|
||||||
await cls._set_pk_column(gen_table_schema)
|
await cls._set_pk_column(gen_table_schema)
|
||||||
context = TemplateUtils.prepare_context(gen_table_schema)
|
context = TemplateUtils.prepare_context(gen_table_schema)
|
||||||
@@ -403,7 +358,7 @@ class GenTableService:
|
|||||||
raise CustomException(msg=f'业务表 {table_name} 不存在')
|
raise CustomException(msg=f'业务表 {table_name} 不存在')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __get_gen_path(cls, gen_table: GenTableSchema, template: str) -> Optional[str]:
|
def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
根据GenTableModel对象和模板名称生成路径
|
根据GenTableModel对象和模板名称生成路径
|
||||||
|
|
||||||
@@ -423,7 +378,7 @@ class GenTableService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def sync_db_services(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
|
async def sync_db_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
|
||||||
"""
|
"""
|
||||||
同步数据库service
|
同步数据库service
|
||||||
|
|
||||||
@@ -431,21 +386,17 @@ class GenTableService:
|
|||||||
:param table_name: 业务表名称
|
:param table_name: 业务表名称
|
||||||
:return: 同步数据库结果
|
:return: 同步数据库结果
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
|
||||||
gen_table_column_dao = GenTableColumnDao(auth=auth)
|
|
||||||
|
|
||||||
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
|
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
|
||||||
if gen_table:
|
if gen_table:
|
||||||
table = GenTableSchema(**CamelCaseUtil.transform_result(gen_table))
|
table = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table))
|
||||||
table_columns = table.columns or [] # 确保不为None
|
table_columns = table.columns or [] # 确保不为None
|
||||||
table_column_map = {column.column_name: column for column in table_columns}
|
table_column_map = {column.column_name: column for column in table_columns}
|
||||||
query_db_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name)
|
query_db_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name)
|
||||||
db_table_columns = [
|
db_table_columns = [
|
||||||
GenTableColumnSchema(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
|
GenTableColumnOutSchema(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
|
||||||
]
|
]
|
||||||
if not db_table_columns:
|
if not db_table_columns:
|
||||||
raise CustomException('同步数据失败,原表结构不存在')
|
raise CustomException('同步数据失败,原表结构不存在')
|
||||||
@@ -476,21 +427,19 @@ class GenTableService:
|
|||||||
for column in del_columns:
|
for column in del_columns:
|
||||||
if column.column_id is not None:
|
if column.column_id is not None:
|
||||||
await gen_table_column_dao.delete(ids=[column.column_id])
|
await gen_table_column_dao.delete(ids=[column.column_id])
|
||||||
if isinstance(auth.db, AsyncSession):
|
await auth.db.commit()
|
||||||
await auth.db.commit()
|
|
||||||
return SuccessResponse(msg='同步成功')
|
return SuccessResponse(msg='同步成功')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(auth.db, AsyncSession):
|
try:
|
||||||
try:
|
await auth.db.rollback()
|
||||||
await auth.db.rollback()
|
except:
|
||||||
except:
|
pass # 忽略回滚错误
|
||||||
pass # 忽略回滚错误
|
|
||||||
raise CustomException(msg=f'同步失败: {str(e)}')
|
raise CustomException(msg=f'同步失败: {str(e)}')
|
||||||
else:
|
else:
|
||||||
raise CustomException('业务表不存在')
|
raise CustomException('业务表不存在')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableSchema) -> None:
|
async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableOutSchema) -> None:
|
||||||
"""
|
"""
|
||||||
设置主子表信息
|
设置主子表信息
|
||||||
|
|
||||||
@@ -498,18 +447,15 @@ class GenTableService:
|
|||||||
:param gen_table: 业务表信息
|
:param gen_table: 业务表信息
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
if gen_table.sub_table_name:
|
if gen_table.sub_table_name:
|
||||||
gen_table_dao = GenTableDao(auth=auth)
|
gen_table_dao = GenTableCRUD(auth=auth)
|
||||||
sub_table = await gen_table_dao.get_gen_table_by_name(auth.db, gen_table.sub_table_name)
|
sub_table = await gen_table_dao.get_gen_table_by_name(auth.db, gen_table.sub_table_name)
|
||||||
if sub_table:
|
if sub_table:
|
||||||
gen_table.sub_table = GenTableSchema(**CamelCaseUtil.transform_result(sub_table))
|
gen_table.sub_table = GenTableOutSchema(**CamelCaseUtil.transform_result(sub_table))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _set_pk_column(cls, gen_table: GenTableSchema) -> None:
|
async def _set_pk_column(cls, gen_table: GenTableOutSchema) -> None:
|
||||||
"""
|
"""
|
||||||
设置主键列信息
|
设置主键列信息
|
||||||
|
|
||||||
@@ -533,7 +479,7 @@ class GenTableService:
|
|||||||
gen_table.sub_table.pk_column = gen_table.sub_table.columns[0]
|
gen_table.sub_table.pk_column = gen_table.sub_table.columns[0]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def set_table_from_options(cls, gen_table: GenTableSchema) -> GenTableSchema:
|
async def set_table_from_options(cls, gen_table: GenTableOutSchema) -> GenTableOutSchema:
|
||||||
"""
|
"""
|
||||||
设置代码生成其他选项值
|
设置代码生成其他选项值
|
||||||
|
|
||||||
@@ -551,18 +497,18 @@ class GenTableService:
|
|||||||
return gen_table
|
return gen_table
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def validate_edit(cls, edit_gen_table: EditGenTableSchema):
|
async def validate_edit(cls, edit_gen_table: GenTableUpdateSchema):
|
||||||
"""
|
"""
|
||||||
编辑保存参数校验
|
编辑保存参数校验
|
||||||
|
|
||||||
:param edit_gen_table: 编辑业务表对象
|
:param edit_gen_table: 编辑业务表对象
|
||||||
"""
|
"""
|
||||||
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
|
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
|
||||||
# 检查params是否为None
|
# 从options字段获取参数,而不是params
|
||||||
if edit_gen_table.params is None:
|
if not edit_gen_table.options:
|
||||||
raise CustomException(msg='树表参数不能为空')
|
raise CustomException(msg='树表参数不能为空')
|
||||||
|
|
||||||
params_obj = edit_gen_table.params.model_dump(by_alias=True)
|
params_obj = json.loads(edit_gen_table.options)
|
||||||
|
|
||||||
if GenConstant.TREE_CODE not in params_obj:
|
if GenConstant.TREE_CODE not in params_obj:
|
||||||
raise CustomException(msg='树编码字段不能为空')
|
raise CustomException(msg='树编码字段不能为空')
|
||||||
@@ -583,7 +529,7 @@ class GenTableColumnService:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_table_column_list_by_table_id_services(cls, auth: AuthSchema, table_id: int):
|
async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[GenTableColumnOutSchema]:
|
||||||
"""
|
"""
|
||||||
获取业务表字段列表信息service
|
获取业务表字段列表信息service
|
||||||
|
|
||||||
@@ -591,14 +537,10 @@ class GenTableColumnService:
|
|||||||
:param table_id: 业务表格id
|
:param table_id: 业务表格id
|
||||||
:return: 业务表字段列表信息对象
|
:return: 业务表字段列表信息对象
|
||||||
"""
|
"""
|
||||||
# 确保db是AsyncSession类型
|
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
|
||||||
if not isinstance(auth.db, AsyncSession):
|
|
||||||
raise CustomException(msg='数据库会话类型不正确')
|
|
||||||
|
|
||||||
gen_table_column_dao = GenTableColumnDao(auth=auth)
|
|
||||||
gen_table_column_list_result = await gen_table_column_dao.get_gen_table_column_list_by_table_id(auth.db, table_id)
|
gen_table_column_list_result = await gen_table_column_dao.get_gen_table_column_list_by_table_id(auth.db, table_id)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
GenTableColumnSchema(**gen_table_column)
|
GenTableColumnOutSchema(**gen_table_column)
|
||||||
for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result)
|
for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result)
|
||||||
]
|
]
|
||||||
@@ -6,6 +6,7 @@ from .cache.controller import CacheRouter
|
|||||||
from .job.controller import JobRouter
|
from .job.controller import JobRouter
|
||||||
from .online.controller import OnlineRouter
|
from .online.controller import OnlineRouter
|
||||||
from .server.controller import ServerRouter
|
from .server.controller import ServerRouter
|
||||||
|
from .resource.controller import ResourceRouter
|
||||||
|
|
||||||
|
|
||||||
MonitorRouter = APIRouter(prefix="/monitor")
|
MonitorRouter = APIRouter(prefix="/monitor")
|
||||||
@@ -15,3 +16,4 @@ MonitorRouter.include_router(CacheRouter)
|
|||||||
MonitorRouter.include_router(JobRouter)
|
MonitorRouter.include_router(JobRouter)
|
||||||
MonitorRouter.include_router(OnlineRouter)
|
MonitorRouter.include_router(OnlineRouter)
|
||||||
MonitorRouter.include_router(ServerRouter)
|
MonitorRouter.include_router(ServerRouter)
|
||||||
|
MonitorRouter.include_router(ResourceRouter)
|
||||||
+23
-25
@@ -4,10 +4,8 @@ from fastapi import APIRouter, Body, Depends, Path, Query, Request, UploadFile,
|
|||||||
from fastapi.responses import JSONResponse, StreamingResponse, FileResponse
|
from fastapi.responses import JSONResponse, StreamingResponse, FileResponse
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from app.common.request import PaginationService
|
|
||||||
from app.common.response import StreamResponse, SuccessResponse
|
from app.common.response import StreamResponse, SuccessResponse
|
||||||
from app.utils.common_util import bytes2file_response
|
from app.utils.common_util import bytes2file_response
|
||||||
from app.core.base_params import PaginationQueryParam
|
|
||||||
from app.core.dependencies import AuthPermission
|
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
|
||||||
@@ -23,15 +21,15 @@ from .schema import (
|
|||||||
from .service import ResourceService
|
from .service import ResourceService
|
||||||
|
|
||||||
|
|
||||||
ResourceFileRouter = APIRouter(route_class=OperationLogRoute, prefix="/resource", tags=["资源管理"])
|
ResourceRouter = APIRouter(route_class=OperationLogRoute, prefix="/resource", tags=["资源管理"])
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.get("/list", summary="获取目录列表", description="获取指定目录下的文件和子目录列表")
|
@ResourceRouter.get("/list", summary="获取目录列表", description="获取指定目录下的文件和子目录列表")
|
||||||
async def get_directory_list_controller(
|
async def get_directory_list_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
path: Optional[str] = Query(None, description="目录路径"),
|
path: Optional[str] = Query(None, description="目录路径"),
|
||||||
include_hidden: bool = Query(False, description="是否包含隐藏文件"),
|
include_hidden: bool = Query(False, description="是否包含隐藏文件"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""获取目录列表"""
|
"""获取目录列表"""
|
||||||
result_dict = await ResourceService.get_directory_list_service(
|
result_dict = await ResourceService.get_directory_list_service(
|
||||||
@@ -44,11 +42,11 @@ async def get_directory_list_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="获取目录列表成功")
|
return SuccessResponse(data=result_dict, msg="获取目录列表成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/search", summary="搜索资源", description="根据条件搜索资源")
|
@ResourceRouter.post("/search", summary="搜索资源", description="根据条件搜索资源")
|
||||||
async def search_resources_controller(
|
async def search_resources_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
search: ResourceSearchSchema,
|
search: ResourceSearchSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:search"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:search"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""搜索资源"""
|
"""搜索资源"""
|
||||||
result_list = await ResourceService.search_resources_service(
|
result_list = await ResourceService.search_resources_service(
|
||||||
@@ -60,12 +58,12 @@ async def search_resources_controller(
|
|||||||
return SuccessResponse(data=result_list, msg=f"搜索成功,找到 {len(result_list)} 个结果")
|
return SuccessResponse(data=result_list, msg=f"搜索成功,找到 {len(result_list)} 个结果")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/upload", summary="上传文件", description="上传文件到指定目录")
|
@ResourceRouter.post("/upload", summary="上传文件", description="上传文件到指定目录")
|
||||||
async def upload_file_controller(
|
async def upload_file_controller(
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
request: Request,
|
request: Request,
|
||||||
target_path: Optional[str] = Form(None, description="目标目录路径"),
|
target_path: Optional[str] = Form(None, description="目标目录路径"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:upload"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:upload"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""上传文件"""
|
"""上传文件"""
|
||||||
result_dict = await ResourceService.upload_file_service(
|
result_dict = await ResourceService.upload_file_service(
|
||||||
@@ -78,11 +76,11 @@ async def upload_file_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="上传文件成功")
|
return SuccessResponse(data=result_dict, msg="上传文件成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.get("/download", summary="下载文件", description="下载指定文件")
|
@ResourceRouter.get("/download", summary="下载文件", description="下载指定文件")
|
||||||
async def download_file_controller(
|
async def download_file_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
path: str = Query(..., description="文件路径"),
|
path: str = Query(..., description="文件路径"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:download"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:download"]))
|
||||||
) -> FileResponse:
|
) -> FileResponse:
|
||||||
"""下载文件"""
|
"""下载文件"""
|
||||||
file_path = await ResourceService.download_file_service(
|
file_path = await ResourceService.download_file_service(
|
||||||
@@ -103,10 +101,10 @@ async def download_file_controller(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.delete("/delete", summary="删除文件", description="删除指定文件或目录")
|
@ResourceRouter.delete("/delete", summary="删除文件", description="删除指定文件或目录")
|
||||||
async def delete_files_controller(
|
async def delete_files_controller(
|
||||||
paths: List[str] = Body(..., description="文件路径列表"),
|
paths: List[str] = Body(..., description="文件路径列表"),
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:delete"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:delete"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""删除文件"""
|
"""删除文件"""
|
||||||
await ResourceService.delete_file_service(auth=auth, paths=paths)
|
await ResourceService.delete_file_service(auth=auth, paths=paths)
|
||||||
@@ -114,10 +112,10 @@ async def delete_files_controller(
|
|||||||
return SuccessResponse(msg="删除文件成功")
|
return SuccessResponse(msg="删除文件成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/move", summary="移动文件", description="移动文件或目录")
|
@ResourceRouter.post("/move", summary="移动文件", description="移动文件或目录")
|
||||||
async def move_file_controller(
|
async def move_file_controller(
|
||||||
data: ResourceMoveSchema,
|
data: ResourceMoveSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:move"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:move"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""移动文件"""
|
"""移动文件"""
|
||||||
await ResourceService.move_file_service(auth=auth, data=data)
|
await ResourceService.move_file_service(auth=auth, data=data)
|
||||||
@@ -125,10 +123,10 @@ async def move_file_controller(
|
|||||||
return SuccessResponse(msg="移动文件成功")
|
return SuccessResponse(msg="移动文件成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/copy", summary="复制文件", description="复制文件或目录")
|
@ResourceRouter.post("/copy", summary="复制文件", description="复制文件或目录")
|
||||||
async def copy_file_controller(
|
async def copy_file_controller(
|
||||||
data: ResourceCopySchema,
|
data: ResourceCopySchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:copy"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:copy"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""复制文件"""
|
"""复制文件"""
|
||||||
await ResourceService.copy_file_service(auth=auth, data=data)
|
await ResourceService.copy_file_service(auth=auth, data=data)
|
||||||
@@ -136,10 +134,10 @@ async def copy_file_controller(
|
|||||||
return SuccessResponse(msg="复制文件成功")
|
return SuccessResponse(msg="复制文件成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/rename", summary="重命名文件", description="重命名文件或目录")
|
@ResourceRouter.post("/rename", summary="重命名文件", description="重命名文件或目录")
|
||||||
async def rename_file_controller(
|
async def rename_file_controller(
|
||||||
data: ResourceRenameSchema,
|
data: ResourceRenameSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:rename"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["rmonitor:resource:rename"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""重命名文件"""
|
"""重命名文件"""
|
||||||
await ResourceService.rename_file_service(auth=auth, data=data)
|
await ResourceService.rename_file_service(auth=auth, data=data)
|
||||||
@@ -147,10 +145,10 @@ async def rename_file_controller(
|
|||||||
return SuccessResponse(msg="重命名文件成功")
|
return SuccessResponse(msg="重命名文件成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/create-dir", summary="创建目录", description="在指定路径创建新目录")
|
@ResourceRouter.post("/create-dir", summary="创建目录", description="在指定路径创建新目录")
|
||||||
async def create_directory_controller(
|
async def create_directory_controller(
|
||||||
data: ResourceCreateDirSchema,
|
data: ResourceCreateDirSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:create_dir"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:create_dir"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""创建目录"""
|
"""创建目录"""
|
||||||
await ResourceService.create_directory_service(auth=auth, data=data)
|
await ResourceService.create_directory_service(auth=auth, data=data)
|
||||||
@@ -158,10 +156,10 @@ async def create_directory_controller(
|
|||||||
return SuccessResponse(msg="创建目录成功")
|
return SuccessResponse(msg="创建目录成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.get("/stats", summary="获取资源统计", description="获取资源统计信息")
|
@ResourceRouter.get("/stats", summary="获取资源统计", description="获取资源统计信息")
|
||||||
async def get_resource_stats_controller(
|
async def get_resource_stats_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:stats:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""获取资源统计"""
|
"""获取资源统计"""
|
||||||
result_dict = await ResourceService.get_stats_service(
|
result_dict = await ResourceService.get_stats_service(
|
||||||
@@ -172,11 +170,11 @@ async def get_resource_stats_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="获取资源统计成功")
|
return SuccessResponse(data=result_dict, msg="获取资源统计成功")
|
||||||
|
|
||||||
|
|
||||||
@ResourceFileRouter.post("/export", summary="导出资源列表", description="导出资源列表")
|
@ResourceRouter.post("/export", summary="导出资源列表", description="导出资源列表")
|
||||||
async def export_resource_list_controller(
|
async def export_resource_list_controller(
|
||||||
request: Request,
|
request: Request,
|
||||||
search: ResourceSearchSchema,
|
search: ResourceSearchSchema,
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:export"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:export"]))
|
||||||
) -> StreamingResponse:
|
) -> StreamingResponse:
|
||||||
"""导出资源列表"""
|
"""导出资源列表"""
|
||||||
# 获取搜索结果
|
# 获取搜索结果
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
from .resource.controller import ResourceFileRouter
|
|
||||||
|
|
||||||
|
|
||||||
ResourceRouter = APIRouter(prefix="/resource")
|
|
||||||
|
|
||||||
# 包含所有子路由
|
|
||||||
ResourceRouter.include_router(ResourceFileRouter)
|
|
||||||
@@ -11,13 +11,9 @@ from .dict.controller import DictRouter
|
|||||||
from .params.controller import ParamsRouter
|
from .params.controller import ParamsRouter
|
||||||
from .notice.controller import NoticeRouter
|
from .notice.controller import NoticeRouter
|
||||||
from .log.controller import LogRouter
|
from .log.controller import LogRouter
|
||||||
from .version.controller import VersionRouter
|
|
||||||
from .ticket.controller import TicketRouter
|
|
||||||
|
|
||||||
|
|
||||||
SystemRouter = APIRouter(prefix="/system")
|
SystemRouter = APIRouter(prefix="/system")
|
||||||
|
|
||||||
# 包含所有子路由
|
|
||||||
SystemRouter.include_router(AuthRouter)
|
SystemRouter.include_router(AuthRouter)
|
||||||
SystemRouter.include_router(UserRouter)
|
SystemRouter.include_router(UserRouter)
|
||||||
SystemRouter.include_router(RoleRouter)
|
SystemRouter.include_router(RoleRouter)
|
||||||
@@ -28,5 +24,3 @@ SystemRouter.include_router(DictRouter)
|
|||||||
SystemRouter.include_router(ParamsRouter)
|
SystemRouter.include_router(ParamsRouter)
|
||||||
SystemRouter.include_router(NoticeRouter)
|
SystemRouter.include_router(NoticeRouter)
|
||||||
SystemRouter.include_router(LogRouter)
|
SystemRouter.include_router(LogRouter)
|
||||||
SystemRouter.include_router(VersionRouter)
|
|
||||||
SystemRouter.include_router(TicketRouter)
|
|
||||||
@@ -161,8 +161,6 @@ class ParamsService:
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def upload_service(cls, base_url: str, file: UploadFile) -> Dict:
|
async def upload_service(cls, base_url: str, file: UploadFile) -> Dict:
|
||||||
"""上传文件"""
|
"""上传文件"""
|
||||||
if not file:
|
|
||||||
raise CustomException(msg="请选择要上传的文件")
|
|
||||||
filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
|
filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
|
||||||
|
|
||||||
return UploadResponseSchema(
|
return UploadResponseSchema(
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Path
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from app.common.response import SuccessResponse
|
|
||||||
from app.common.request import PaginationService
|
|
||||||
from app.core.router_class import OperationLogRoute
|
|
||||||
from app.core.base_params import PaginationQueryParam
|
|
||||||
from app.core.dependencies import AuthPermission
|
|
||||||
from app.core.logger import logger
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .param import TicketQueryParam
|
|
||||||
from .service import TicketService
|
|
||||||
from .schema import TicketCreateSchema, TicketUpdateSchema
|
|
||||||
|
|
||||||
|
|
||||||
TicketRouter = APIRouter(route_class=OperationLogRoute, prefix="/ticket", tags=["工单管理"])
|
|
||||||
|
|
||||||
|
|
||||||
@TicketRouter.get("/detail/{id}", summary="获取工单详情", description="获取工单详情")
|
|
||||||
async def get_ticket_detail_controller(
|
|
||||||
id: int = Path(..., description="工单ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await TicketService.get_ticket_detail_service(auth=auth, id=id)
|
|
||||||
logger.info(f"获取工单详情成功 {id}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="获取工单详情成功")
|
|
||||||
|
|
||||||
|
|
||||||
@TicketRouter.get("/list", summary="查询工单列表", description="查询工单列表")
|
|
||||||
async def get_ticket_list_controller(
|
|
||||||
page: PaginationQueryParam = Depends(),
|
|
||||||
search: TicketQueryParam = Depends(),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict_list = await TicketService.get_ticket_list_service(auth=auth, search=search, order_by=page.order_by)
|
|
||||||
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
|
||||||
logger.info("查询工单列表成功")
|
|
||||||
return SuccessResponse(data=result_dict, msg="查询工单列表成功")
|
|
||||||
|
|
||||||
|
|
||||||
@TicketRouter.post("/create", summary="创建工单", description="创建工单")
|
|
||||||
async def create_ticket_controller(
|
|
||||||
data: TicketCreateSchema,
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:create"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await TicketService.create_ticket_service(auth=auth, data=data)
|
|
||||||
logger.info(f"创建工单成功: {result_dict}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="创建工单成功")
|
|
||||||
|
|
||||||
|
|
||||||
@TicketRouter.put("/update/{id}", summary="修改工单", description="修改工单")
|
|
||||||
async def update_ticket_controller(
|
|
||||||
data: TicketUpdateSchema,
|
|
||||||
id: int = Path(..., description="工单ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:update"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await TicketService.update_ticket_service(auth=auth, id=id, data=data)
|
|
||||||
logger.info(f"修改工单成功: {result_dict}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="修改工单成功")
|
|
||||||
|
|
||||||
|
|
||||||
@TicketRouter.delete("/delete", summary="删除工单", description="删除工单")
|
|
||||||
async def delete_ticket_controller(
|
|
||||||
ids: list[int] = Body(..., description="ID列表"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:delete"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
await TicketService.delete_ticket_service(auth=auth, ids=ids)
|
|
||||||
logger.info(f"删除工单成功: {ids}")
|
|
||||||
return SuccessResponse(msg="删除工单成功")
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
from typing import Dict, List, Optional, Sequence
|
|
||||||
|
|
||||||
from app.core.base_crud import CRUDBase
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .model import TicketModel
|
|
||||||
from .schema import TicketCreateSchema, TicketUpdateSchema
|
|
||||||
|
|
||||||
|
|
||||||
class TicketCRUD(CRUDBase[TicketModel, TicketCreateSchema, TicketUpdateSchema]):
|
|
||||||
"""工单 CRUD 操作"""
|
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema) -> None:
|
|
||||||
"""初始化工单CRUD"""
|
|
||||||
self.auth = auth
|
|
||||||
super().__init__(model=TicketModel, auth=auth)
|
|
||||||
|
|
||||||
async def get_by_id_crud(self, id: int) -> Optional[TicketModel]:
|
|
||||||
"""根据id获取工单信息
|
|
||||||
|
|
||||||
:param id: 工单ID
|
|
||||||
:return: 工单信息
|
|
||||||
"""
|
|
||||||
return await self.get(id=id)
|
|
||||||
|
|
||||||
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[TicketModel]:
|
|
||||||
"""获取工单列表
|
|
||||||
|
|
||||||
:param search: 搜索条件
|
|
||||||
:param order_by: 排序字段
|
|
||||||
:return: 工单列表
|
|
||||||
"""
|
|
||||||
return await self.list(search=search or {}, order_by=order_by or [])
|
|
||||||
|
|
||||||
async def create_crud(self, data: TicketCreateSchema) -> Optional[TicketModel]:
|
|
||||||
"""创建工单
|
|
||||||
|
|
||||||
:param data: 工单创建数据
|
|
||||||
:return: 创建的工单
|
|
||||||
"""
|
|
||||||
return await self.create(data=data)
|
|
||||||
|
|
||||||
async def update_crud(self, id: int, data: TicketUpdateSchema) -> Optional[TicketModel]:
|
|
||||||
"""更新工单
|
|
||||||
|
|
||||||
:param id: 工单ID
|
|
||||||
:param data: 工单更新数据
|
|
||||||
:return: 更新的工单
|
|
||||||
"""
|
|
||||||
return await self.update(id=id, data=data)
|
|
||||||
|
|
||||||
async def delete_crud(self, ids: List[int]) -> None:
|
|
||||||
"""批量删除工单
|
|
||||||
|
|
||||||
:param ids: 工单ID列表
|
|
||||||
"""
|
|
||||||
return await self.delete(ids=ids)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from sqlalchemy import String, Text, DateTime, ForeignKey
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from app.core.base_model import CreatorMixin
|
|
||||||
|
|
||||||
|
|
||||||
class TicketModel(CreatorMixin):
|
|
||||||
"""
|
|
||||||
工单模型 - SQLAlchemy 2.0 语法
|
|
||||||
兼容 MySQL 和 PostgreSQL
|
|
||||||
"""
|
|
||||||
__tablename__ = 'system_ticket'
|
|
||||||
__table_args__ = ({'comment': '工单表'})
|
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='工单标题')
|
|
||||||
status: Mapped[str] = mapped_column(String(50), default='pending', comment='工单状态(pending:待处理 progress:处理中 resolved:已解决 closed:已关闭)')
|
|
||||||
priority: Mapped[str] = mapped_column(String(50), default='medium', comment='优先级(low:低, medium:中, high:高, urgent:紧急)')
|
|
||||||
type: Mapped[Optional[str]] = mapped_column(String(50), comment='工单类型(bug:缺陷, feature:功能, task:任务)')
|
|
||||||
assignee_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='指派给用户ID')
|
|
||||||
reporter_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='报告人ID')
|
|
||||||
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目或模块')
|
|
||||||
version: Mapped[Optional[str]] = mapped_column(String(50), comment='版本号')
|
|
||||||
|
|
||||||
# 关系
|
|
||||||
assignee = relationship('UserModel', foreign_keys=[assignee_id])
|
|
||||||
reporter = relationship('UserModel', foreign_keys=[reporter_id])
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
from fastapi import Query
|
|
||||||
|
|
||||||
from app.core.validator import DateTimeStr
|
|
||||||
|
|
||||||
|
|
||||||
class TicketQueryParam:
|
|
||||||
"""工单管理查询参数"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
title: Optional[str] = Query(None, description="工单标题"),
|
|
||||||
status: Optional[bool] = Query(None, description="工单状态"),
|
|
||||||
priority: Optional[str] = Query(None, description="优先级"),
|
|
||||||
creator: Optional[int] = Query(None, description="创建人"),
|
|
||||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
|
||||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# 模糊查询字段
|
|
||||||
self.title = ("like", title)
|
|
||||||
|
|
||||||
# 精确查询字段
|
|
||||||
self.creator_id = creator
|
|
||||||
self.status = status
|
|
||||||
self.priority = priority
|
|
||||||
|
|
||||||
# 时间范围查询
|
|
||||||
if start_time and end_time:
|
|
||||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
|
||||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
|
||||||
self.created_at = ("between", (start_datetime, end_datetime))
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from app.core.base_schema import BaseSchema
|
|
||||||
|
|
||||||
|
|
||||||
class TicketCreateSchema(BaseModel):
|
|
||||||
"""创建工单"""
|
|
||||||
title: str = Field(..., max_length=255, description='工单标题')
|
|
||||||
description: Optional[str] = Field(default=None, description='工单描述')
|
|
||||||
status: Optional[str] = Field(default='pending', description='工单处理状态(pending:待处理 progress:处理中 resolved:已解决 closed:已关闭)')
|
|
||||||
priority: Optional[str] = Field(default='medium', description='优先级(low, medium, high, urgent)')
|
|
||||||
type: Optional[str] = Field(default='bug', description='工单类型(bug, feature, task)')
|
|
||||||
assignee_id: Optional[int] = Field(default=None, description='指派给用户ID')
|
|
||||||
reporter_id: int = Field(..., description='报告人ID')
|
|
||||||
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
|
|
||||||
version: Optional[str] = Field(default=None, max_length=50, description='版本号')
|
|
||||||
|
|
||||||
|
|
||||||
class TicketUpdateSchema(TicketCreateSchema):
|
|
||||||
"""更新工单"""
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class TicketOutSchema(TicketCreateSchema, BaseSchema):
|
|
||||||
"""工单输出"""
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
...
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Dict, List, Optional, Union
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from app.core.exceptions import CustomException
|
|
||||||
from .crud import TicketCRUD
|
|
||||||
from .schema import TicketCreateSchema, TicketUpdateSchema, TicketOutSchema
|
|
||||||
from .param import TicketQueryParam
|
|
||||||
|
|
||||||
|
|
||||||
class TicketService:
|
|
||||||
"""工单模块服务层"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_ticket_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
|
||||||
"""获取工单详情"""
|
|
||||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
return TicketOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_ticket_list_service(cls, auth: AuthSchema, search: Optional[TicketQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
|
||||||
"""获取工单列表"""
|
|
||||||
# 处理排序参数
|
|
||||||
processed_order_by = None
|
|
||||||
if order_by:
|
|
||||||
if isinstance(order_by, str):
|
|
||||||
processed_order_by = eval(order_by)
|
|
||||||
else:
|
|
||||||
processed_order_by = order_by
|
|
||||||
|
|
||||||
obj_list = await TicketCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
|
|
||||||
return [TicketOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def create_ticket_service(cls, auth: AuthSchema, data: TicketCreateSchema) -> Dict:
|
|
||||||
"""创建工单"""
|
|
||||||
obj = await TicketCRUD(auth).create_crud(data=data)
|
|
||||||
return TicketOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def update_ticket_service(cls, auth: AuthSchema, id: int, data: TicketUpdateSchema) -> Dict:
|
|
||||||
"""更新工单"""
|
|
||||||
# 检查工单是否存在
|
|
||||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
if not obj:
|
|
||||||
raise CustomException(msg='更新失败,该工单不存在')
|
|
||||||
|
|
||||||
obj = await TicketCRUD(auth).update_crud(id=id, data=data)
|
|
||||||
return TicketOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def delete_ticket_service(cls, auth: AuthSchema, ids: List[int]) -> None:
|
|
||||||
"""删除工单"""
|
|
||||||
if len(ids) < 1:
|
|
||||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
|
||||||
for id in ids:
|
|
||||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
if not obj:
|
|
||||||
raise CustomException(msg='删除失败,该工单不存在')
|
|
||||||
await TicketCRUD(auth).delete_crud(ids=ids)
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, Path
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from app.common.response import SuccessResponse
|
|
||||||
from app.common.request import PaginationService
|
|
||||||
from app.core.router_class import OperationLogRoute
|
|
||||||
from app.core.base_params import PaginationQueryParam
|
|
||||||
from app.core.dependencies import AuthPermission
|
|
||||||
from app.core.logger import logger
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .param import VersionQueryParam
|
|
||||||
from .service import VersionService
|
|
||||||
from .schema import VersionCreateSchema, VersionUpdateSchema
|
|
||||||
|
|
||||||
|
|
||||||
VersionRouter = APIRouter(route_class=OperationLogRoute, prefix="/version", tags=["版本管理"])
|
|
||||||
|
|
||||||
|
|
||||||
@VersionRouter.get("/detail/{id}", summary="获取版本详情", description="获取版本详情")
|
|
||||||
async def get_version_detail_controller(
|
|
||||||
id: int = Path(..., description="版本ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await VersionService.get_version_detail_service(auth=auth, id=id)
|
|
||||||
logger.info(f"获取版本详情成功 {id}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="获取版本详情成功")
|
|
||||||
|
|
||||||
|
|
||||||
@VersionRouter.get("/list", summary="查询版本列表", description="查询版本列表")
|
|
||||||
async def get_version_list_controller(
|
|
||||||
page: PaginationQueryParam = Depends(),
|
|
||||||
search: VersionQueryParam = Depends(),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict_list = await VersionService.get_version_list_service(auth=auth, search=search, order_by=page.order_by)
|
|
||||||
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
|
||||||
logger.info("查询版本列表成功")
|
|
||||||
return SuccessResponse(data=result_dict, msg="查询版本列表成功")
|
|
||||||
|
|
||||||
|
|
||||||
@VersionRouter.post("/create", summary="创建版本", description="创建版本")
|
|
||||||
async def create_version_controller(
|
|
||||||
data: VersionCreateSchema,
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:create"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await VersionService.create_version_service(auth=auth, data=data)
|
|
||||||
logger.info(f"创建版本成功: {result_dict}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="创建版本成功")
|
|
||||||
|
|
||||||
|
|
||||||
@VersionRouter.put("/update/{id}", summary="修改版本", description="修改版本")
|
|
||||||
async def update_version_controller(
|
|
||||||
data: VersionUpdateSchema,
|
|
||||||
id: int = Path(..., description="版本ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:update"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result_dict = await VersionService.update_version_service(auth=auth, id=id, data=data)
|
|
||||||
logger.info(f"修改版本成功: {result_dict}")
|
|
||||||
return SuccessResponse(data=result_dict, msg="修改版本成功")
|
|
||||||
|
|
||||||
|
|
||||||
@VersionRouter.delete("/delete", summary="删除版本", description="删除版本")
|
|
||||||
async def delete_version_controller(
|
|
||||||
ids: list[int] = Body(..., description="ID列表"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:delete"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
await VersionService.delete_version_service(auth=auth, ids=ids)
|
|
||||||
logger.info(f"删除版本成功: {ids}")
|
|
||||||
return SuccessResponse(msg="删除版本成功")
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Dict, List, Optional, Sequence
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from app.core.base_crud import CRUDBase
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .model import VersionModel
|
|
||||||
from .schema import VersionCreateSchema, VersionUpdateSchema
|
|
||||||
|
|
||||||
|
|
||||||
class VersionCRUD(CRUDBase[VersionModel, VersionCreateSchema, VersionUpdateSchema]):
|
|
||||||
"""版本模块数据层"""
|
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema) -> None:
|
|
||||||
"""初始化版本CRUD"""
|
|
||||||
self.auth = auth
|
|
||||||
super().__init__(model=VersionModel, auth=auth)
|
|
||||||
|
|
||||||
async def get_by_id_crud(self, id: int) -> Optional[VersionModel]:
|
|
||||||
"""根据id获取版本信息
|
|
||||||
|
|
||||||
:param id: 版本ID
|
|
||||||
:return: 版本信息
|
|
||||||
"""
|
|
||||||
return await self.get(id=id)
|
|
||||||
|
|
||||||
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
|
|
||||||
"""获取版本列表
|
|
||||||
|
|
||||||
:param search: 搜索条件
|
|
||||||
:param order_by: 排序字段
|
|
||||||
:return: 版本列表
|
|
||||||
"""
|
|
||||||
return await self.list(search=search or {}, order_by=order_by or [])
|
|
||||||
|
|
||||||
async def create_crud(self, data: VersionCreateSchema) -> Optional[VersionModel]:
|
|
||||||
"""创建版本
|
|
||||||
|
|
||||||
:param data: 版本创建数据
|
|
||||||
:return: 创建的版本
|
|
||||||
"""
|
|
||||||
# 处理 released_at 字段,确保字符串转换为 datetime 对象
|
|
||||||
data_dict = data.model_dump()
|
|
||||||
if data_dict.get('released_at') and isinstance(data_dict['released_at'], str):
|
|
||||||
try:
|
|
||||||
data_dict['released_at'] = datetime.strptime(data_dict['released_at'], '%Y-%m-%d %H:%M:%S')
|
|
||||||
except ValueError:
|
|
||||||
# 如果格式不正确,让数据库层处理错误
|
|
||||||
pass
|
|
||||||
|
|
||||||
return await self.create(data=data_dict)
|
|
||||||
|
|
||||||
async def update_crud(self, id: int, data: VersionUpdateSchema) -> Optional[VersionModel]:
|
|
||||||
"""更新版本
|
|
||||||
|
|
||||||
:param id: 版本ID
|
|
||||||
:param data: 版本更新数据
|
|
||||||
:return: 更新的版本
|
|
||||||
"""
|
|
||||||
# 处理 released_at 字段,确保字符串转换为 datetime 对象
|
|
||||||
data_dict = data.model_dump(exclude_unset=True, exclude={"id"})
|
|
||||||
if data_dict.get('released_at') and isinstance(data_dict['released_at'], str):
|
|
||||||
try:
|
|
||||||
data_dict['released_at'] = datetime.strptime(data_dict['released_at'], '%Y-%m-%d %H:%M:%S')
|
|
||||||
except ValueError:
|
|
||||||
# 如果格式不正确,让数据库层处理错误
|
|
||||||
pass
|
|
||||||
|
|
||||||
return await self.update(id=id, data=data_dict)
|
|
||||||
|
|
||||||
async def delete_crud(self, ids: List[int]) -> None:
|
|
||||||
"""批量删除版本
|
|
||||||
|
|
||||||
:param ids: 版本ID列表
|
|
||||||
"""
|
|
||||||
return await self.delete(ids=ids)
|
|
||||||
|
|
||||||
async def get_by_number_crud(self, version_number: str) -> Optional[VersionModel]:
|
|
||||||
"""根据版本号获取版本
|
|
||||||
|
|
||||||
:param version_number: 版本号
|
|
||||||
:return: 版本信息
|
|
||||||
"""
|
|
||||||
return await self.get(version_number=version_number)
|
|
||||||
|
|
||||||
async def get_by_status_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
|
|
||||||
"""根据状态获取版本列表
|
|
||||||
|
|
||||||
:param search: 搜索条件
|
|
||||||
:param order_by: 排序字段
|
|
||||||
:return: 版本列表
|
|
||||||
"""
|
|
||||||
if search is None:
|
|
||||||
search = {}
|
|
||||||
return await self.list(search=search, order_by=order_by or [])
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from sqlalchemy import String, Text, DateTime
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from app.core.base_model import CreatorMixin
|
|
||||||
|
|
||||||
|
|
||||||
class VersionModel(CreatorMixin):
|
|
||||||
"""
|
|
||||||
版本模型 - SQLAlchemy 2.0 语法
|
|
||||||
兼容 MySQL 和 PostgreSQL
|
|
||||||
"""
|
|
||||||
__tablename__ = 'system_version'
|
|
||||||
__table_args__ = ({'comment': '版本表'})
|
|
||||||
|
|
||||||
version_number: Mapped[str] = mapped_column(String(50), nullable=False, comment='版本号')
|
|
||||||
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='版本标题')
|
|
||||||
release_notes: Mapped[Optional[str]] = mapped_column(Text, comment='发布说明')
|
|
||||||
status: Mapped[str] = mapped_column(String(50), default='draft', comment='版本状态(draft: 草稿, released: 已发布, archived: 已归档)')
|
|
||||||
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目')
|
|
||||||
released_at: Mapped[Optional[datetime]] = mapped_column(DateTime, comment='发布时间')
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
from fastapi import Query
|
|
||||||
|
|
||||||
from app.core.validator import DateTimeStr
|
|
||||||
|
|
||||||
|
|
||||||
class VersionQueryParam:
|
|
||||||
"""版本管理查询参数"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
title: Optional[str] = Query(None, description="版本标题"),
|
|
||||||
status: Optional[str] = Query(None, description="版本状态"),
|
|
||||||
creator: Optional[int] = Query(None, description="创建人"),
|
|
||||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
|
||||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# 模糊查询字段
|
|
||||||
self.title = ("like", title)
|
|
||||||
|
|
||||||
# 精确查询字段
|
|
||||||
self.creator_id = creator
|
|
||||||
self.status = status
|
|
||||||
|
|
||||||
# 时间范围查询
|
|
||||||
if start_time and end_time:
|
|
||||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
|
||||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
|
||||||
self.created_at = ("between", (start_datetime, end_datetime))
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from app.core.base_schema import BaseSchema
|
|
||||||
from app.core.validator import DateTimeStr
|
|
||||||
|
|
||||||
|
|
||||||
class VersionCreateSchema(BaseModel):
|
|
||||||
"""创建版本"""
|
|
||||||
version_number: str = Field(..., max_length=50, description='版本号')
|
|
||||||
title: str = Field(..., max_length=255, description='版本标题')
|
|
||||||
release_notes: Optional[str] = Field(default=None, description='发布说明')
|
|
||||||
description: Optional[str] = Field(default=None, description='工单描述')
|
|
||||||
status: Optional[str] = Field(default='draft', description='版本状态(draft: 草稿, released: 已发布, archived: 已归档)')
|
|
||||||
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
|
|
||||||
released_at: Optional[DateTimeStr] = Field(default=None, description='发布时间')
|
|
||||||
|
|
||||||
|
|
||||||
class VersionUpdateSchema(VersionCreateSchema):
|
|
||||||
"""更新版本"""
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class VersionOutSchema(VersionCreateSchema, BaseSchema):
|
|
||||||
"""版本输出"""
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
...
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from typing import Dict, List, Optional, Union
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from app.core.exceptions import CustomException
|
|
||||||
from .crud import VersionCRUD
|
|
||||||
from .schema import VersionCreateSchema, VersionUpdateSchema, VersionOutSchema
|
|
||||||
from .param import VersionQueryParam
|
|
||||||
|
|
||||||
|
|
||||||
class VersionService:
|
|
||||||
"""版本模块服务层"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_version_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
|
||||||
"""获取版本详情"""
|
|
||||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
return VersionOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_version_list_service(cls, auth: AuthSchema, search: Optional[VersionQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
|
||||||
"""获取版本列表"""
|
|
||||||
# 处理排序参数
|
|
||||||
processed_order_by = None
|
|
||||||
if order_by:
|
|
||||||
if isinstance(order_by, str):
|
|
||||||
processed_order_by = eval(order_by)
|
|
||||||
else:
|
|
||||||
processed_order_by = order_by
|
|
||||||
|
|
||||||
obj_list = await VersionCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
|
|
||||||
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def create_version_service(cls, auth: AuthSchema, data: VersionCreateSchema) -> Dict:
|
|
||||||
"""创建版本"""
|
|
||||||
# 检查版本号是否已存在
|
|
||||||
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
|
|
||||||
if exist_obj:
|
|
||||||
raise CustomException(msg='创建失败,版本号已存在')
|
|
||||||
obj = await VersionCRUD(auth).create_crud(data=data)
|
|
||||||
return VersionOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def update_version_service(cls, auth: AuthSchema, id: int, data: VersionUpdateSchema) -> Dict:
|
|
||||||
"""更新版本"""
|
|
||||||
# 检查版本是否存在
|
|
||||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
if not obj:
|
|
||||||
raise CustomException(msg='更新失败,该版本不存在')
|
|
||||||
|
|
||||||
# 检查版本号是否重复(如果提供了版本号)
|
|
||||||
if data.version_number:
|
|
||||||
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
|
|
||||||
if exist_obj and exist_obj.id != id:
|
|
||||||
raise CustomException(msg='更新失败,版本号已存在')
|
|
||||||
|
|
||||||
obj = await VersionCRUD(auth).update_crud(id=id, data=data)
|
|
||||||
return VersionOutSchema.model_validate(obj).model_dump()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def delete_version_service(cls, auth: AuthSchema, ids: List[int]) -> None:
|
|
||||||
"""删除版本"""
|
|
||||||
if len(ids) < 1:
|
|
||||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
|
||||||
for id in ids:
|
|
||||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
|
||||||
if not obj:
|
|
||||||
raise CustomException(msg='删除失败,该版本不存在')
|
|
||||||
await VersionCRUD(auth).delete_crud(ids=ids)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_version_by_status_service(cls, auth: AuthSchema, search: Optional[VersionQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
|
||||||
"""根据状态获取版本列表"""
|
|
||||||
# 处理排序参数
|
|
||||||
processed_order_by = None
|
|
||||||
if order_by:
|
|
||||||
if isinstance(order_by, str):
|
|
||||||
processed_order_by = eval(order_by)
|
|
||||||
else:
|
|
||||||
processed_order_by = order_by
|
|
||||||
|
|
||||||
obj_list = await VersionCRUD(auth).get_by_status_crud(
|
|
||||||
search=search.__dict__ if search else None,
|
|
||||||
order_by=processed_order_by
|
|
||||||
)
|
|
||||||
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, ClassVar, Dict, List, Optional, Union, Literal
|
from typing import Any, ClassVar, Dict, List, Optional, Literal
|
||||||
from pydantic import MongoDsn, PostgresDsn, RedisDsn, MySQLDsn
|
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from pydantic_validation_decorator import Pattern
|
from pydantic_validation_decorator import Pattern
|
||||||
from uvicorn.config import LifespanType
|
from uvicorn.config import LifespanType
|
||||||
@@ -161,11 +160,6 @@ class Settings(BaseSettings):
|
|||||||
IGNORE_OPERATION_FUNCTION: List[str] = ["get_captcha_for_login"] # 忽略记录的函数
|
IGNORE_OPERATION_FUNCTION: List[str] = ["get_captcha_for_login"] # 忽略记录的函数
|
||||||
LOG_RETENTION_DAYS: int = 30 # 日志保留天数,超过此天数的日志文件将被自动清理
|
LOG_RETENTION_DAYS: int = 30 # 日志保留天数,超过此天数的日志文件将被自动清理
|
||||||
|
|
||||||
# 日志文件说明:
|
|
||||||
# all.log - 包含所有级别日志(应用日志 + uvicorn日志 + uvicorn访问日志)
|
|
||||||
# error.log - 包含ERROR及以上级别日志(应用错误 + uvicorn错误)
|
|
||||||
# 轮转后文件名: info_日期.log, error_日期.log
|
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ******************* Gzip压缩配置 ******************* #
|
# ******************* Gzip压缩配置 ******************* #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
@@ -204,10 +198,16 @@ class Settings(BaseSettings):
|
|||||||
STATIC_DIR: str = "static" # 目录名
|
STATIC_DIR: str = "static" # 目录名
|
||||||
STATIC_ROOT: Path = BASE_DIR.joinpath(STATIC_DIR) # 绝对路径
|
STATIC_ROOT: Path = BASE_DIR.joinpath(STATIC_DIR) # 绝对路径
|
||||||
|
|
||||||
|
# ================================================= #
|
||||||
|
# ***************** 模版文件配置 ***************** #
|
||||||
|
# ================================================= #
|
||||||
|
TEMPLATE: str = "templates"
|
||||||
|
TEMPLATE_DIR: Path = BASE_DIR.joinpath(TEMPLATE) # 模版目录
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ***************** 动态文件配置 ***************** #
|
# ***************** 动态文件配置 ***************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
UPLOAD_FILE_PATH: Path = 'static/upload' # 上传目录
|
UPLOAD_FILE_PATH: Path = BASE_DIR.joinpath('static/upload') # 上传目录
|
||||||
UPLOAD_MACHINE: str = 'A' # 上传机器标识
|
UPLOAD_MACHINE: str = 'A' # 上传机器标识
|
||||||
ALLOWED_EXTENSIONS: list[str] = [ # 允许的文件类型
|
ALLOWED_EXTENSIONS: list[str] = [ # 允许的文件类型
|
||||||
# 图片
|
# 图片
|
||||||
@@ -280,8 +280,7 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def EVENT_LIST(self) -> List[Optional[str]]:
|
def EVENT_LIST(self) -> List[Optional[str]]:
|
||||||
|
"""获取事件列表"""
|
||||||
# 事件列表
|
|
||||||
EVENTS: List[Optional[str]] = [
|
EVENTS: List[Optional[str]] = [
|
||||||
"app.core.database.mongodb_connect" if self.MONGO_DB_ENABLE else None,
|
"app.core.database.mongodb_connect" if self.MONGO_DB_ENABLE else None,
|
||||||
"app.core.database.redis_connect" if self.REDIS_ENABLE else None,
|
"app.core.database.redis_connect" if self.REDIS_ENABLE else None,
|
||||||
|
|||||||
@@ -27,8 +27,12 @@ class BaseSchema(BaseModel):
|
|||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
id: int = Field(description="主键ID")
|
id: int = Field(description="主键ID")
|
||||||
|
description: Optional[str] = Field(default=None, description="描述")
|
||||||
|
del_flag: str = Field(description="删除标志")
|
||||||
created_at: DateTimeStr = Field(description="创建时间")
|
created_at: DateTimeStr = Field(description="创建时间")
|
||||||
|
create_by: Optional[str] = Field(default=None, description="创建者")
|
||||||
updated_at: DateTimeStr = Field(description="更新时间")
|
updated_at: DateTimeStr = Field(description="更新时间")
|
||||||
|
update_by: Optional[str] = Field(default=None, description="更新者")
|
||||||
creator_id: Optional[int] = Field(default=None, description="创建人ID")
|
creator_id: Optional[int] = Field(default=None, description="创建人ID")
|
||||||
creator: Optional[UserInfoSchema] = Field(default=None, description="创建人信息")
|
creator: Optional[UserInfoSchema] = Field(default=None, description="创建人信息")
|
||||||
|
|
||||||
|
|||||||
@@ -1177,162 +1177,6 @@
|
|||||||
"description": "初始化数据"
|
"description": "初始化数据"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "版本管理",
|
|
||||||
"type": 2,
|
|
||||||
"icon": "el-icon-Guide",
|
|
||||||
"order": 10,
|
|
||||||
"permission": "system:version:query",
|
|
||||||
"route_name": "Version",
|
|
||||||
"route_path": "/system/version",
|
|
||||||
"component_path": "system/version/index",
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "版本管理",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "创建版本",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 1,
|
|
||||||
"permission": "system:version:create",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "创建版本",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "修改版本",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 2,
|
|
||||||
"permission": "system:version:update",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "修改版本",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "删除版本",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 3,
|
|
||||||
"permission": "system:version:delete",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "删除版本",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "工单管理",
|
|
||||||
"type": 2,
|
|
||||||
"icon": "el-icon-Help",
|
|
||||||
"order": 11,
|
|
||||||
"permission": "system:ticket:query",
|
|
||||||
"route_name": "Ticket",
|
|
||||||
"route_path": "/system/ticket",
|
|
||||||
"component_path": "system/ticket/index",
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "工单管理",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "创建工单",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 1,
|
|
||||||
"permission": "system:ticket:create",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "创建工单",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "修改工单",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 2,
|
|
||||||
"permission": "system:ticket:update",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "修改工单",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "删除工单",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 3,
|
|
||||||
"permission": "system:ticket:delete",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "删除工单",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1550,6 +1394,217 @@
|
|||||||
"description": "初始化数据"
|
"description": "初始化数据"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件管理",
|
||||||
|
"type": 2,
|
||||||
|
"icon": "el-icon-Files",
|
||||||
|
"order": 1,
|
||||||
|
"permission": "monitor:resource:query",
|
||||||
|
"route_name": "Resource",
|
||||||
|
"route_path": "/monitor/resource",
|
||||||
|
"component_path": "monitor/resource/index",
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件管理",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "文件上传",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 1,
|
||||||
|
"permission": "monitor:resource:upload",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件上传",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件下载",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 2,
|
||||||
|
"permission": "monitor:resource:download",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件下载",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件删除",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 3,
|
||||||
|
"permission": "monitor:resource:delete",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件删除",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件移动",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 4,
|
||||||
|
"permission": "monitor:resource:move",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件移动",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件复制",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 5,
|
||||||
|
"permission": "rmonitor:resource:copy",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件复制",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件重命名",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 6,
|
||||||
|
"permission": "monitor:resource:rename",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件重命名",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "创建目录",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 7,
|
||||||
|
"permission": "monitor:resource:create_dir",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "创建目录",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "文件搜索",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 8,
|
||||||
|
"permission": "monitor:resource:search",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "文件搜索",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "导出文件列表",
|
||||||
|
"type": 3,
|
||||||
|
"icon": null,
|
||||||
|
"order": 9,
|
||||||
|
"permission": "rmonitor:resource:export",
|
||||||
|
"route_name": null,
|
||||||
|
"route_path": null,
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "导出文件列表",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "存储监控",
|
||||||
|
"type": 2,
|
||||||
|
"icon": "el-icon-PieChart",
|
||||||
|
"order": 2,
|
||||||
|
"permission": "monitor:stats:query",
|
||||||
|
"route_name": "Stats",
|
||||||
|
"route_path": "/monitor/stats",
|
||||||
|
"component_path": "monitor/stats/index",
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "存储监控",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "初始化数据"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1905,238 +1960,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "存储管理",
|
|
||||||
"type": 1,
|
|
||||||
"icon": "el-icon-Coin",
|
|
||||||
"order": 7,
|
|
||||||
"permission": null,
|
|
||||||
"route_name": "Resource",
|
|
||||||
"route_path": "/resource",
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": false,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "存储管理",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": "/resource/file",
|
|
||||||
"description": "初始化数据",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "文件管理",
|
|
||||||
"type": 2,
|
|
||||||
"icon": "el-icon-Files",
|
|
||||||
"order": 1,
|
|
||||||
"permission": "resource:file:query",
|
|
||||||
"route_name": "ResourceFile",
|
|
||||||
"route_path": "/resource/file",
|
|
||||||
"component_path": "resource/file/index",
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件管理",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "文件上传",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 1,
|
|
||||||
"permission": "resource:file:upload",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件上传",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件下载",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 2,
|
|
||||||
"permission": "resource:file:download",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件下载",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件删除",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 3,
|
|
||||||
"permission": "resource:file:delete",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件删除",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件移动",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 4,
|
|
||||||
"permission": "resource:file:move",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件移动",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件复制",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 5,
|
|
||||||
"permission": "resource:file:copy",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件复制",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件重命名",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 6,
|
|
||||||
"permission": "resource:file:rename",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件重命名",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "创建目录",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 7,
|
|
||||||
"permission": "resource:file:create_dir",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "创建目录",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "文件搜索",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 8,
|
|
||||||
"permission": "resource:file:search",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "文件搜索",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "导出文件列表",
|
|
||||||
"type": 3,
|
|
||||||
"icon": null,
|
|
||||||
"order": 9,
|
|
||||||
"permission": "resource:file:export",
|
|
||||||
"route_name": null,
|
|
||||||
"route_path": null,
|
|
||||||
"component_path": null,
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "导出文件列表",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "存储监控",
|
|
||||||
"type": 2,
|
|
||||||
"icon": "el-icon-PieChart",
|
|
||||||
"order": 2,
|
|
||||||
"permission": "resource:stats:query",
|
|
||||||
"route_name": "Stats",
|
|
||||||
"route_path": "/resource/stats",
|
|
||||||
"component_path": "resource/stats/index",
|
|
||||||
"status": true,
|
|
||||||
"keep_alive": true,
|
|
||||||
"hidden": false,
|
|
||||||
"always_show": false,
|
|
||||||
"title": "存储监控",
|
|
||||||
"params": null,
|
|
||||||
"affix": false,
|
|
||||||
"redirect": null,
|
|
||||||
"description": "初始化数据"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "AI大模型",
|
"name": "AI大模型",
|
||||||
"type": 1,
|
"type": 1,
|
||||||
|
|||||||
@@ -434,41 +434,5 @@
|
|||||||
{
|
{
|
||||||
"role_id": 1,
|
"role_id": 1,
|
||||||
"menu_id": 109
|
"menu_id": 109
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 111
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 112
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 113
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 114
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 115
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 116
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 117
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role_id": 1,
|
|
||||||
"menu_id": 118
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -4,7 +4,7 @@ from typing import List
|
|||||||
|
|
||||||
from app.common.constant import GenConstant
|
from app.common.constant import GenConstant
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
from app.api.v1.module_generator.gencode.schema import GenTableColumnSchema, GenTableSchema
|
from app.api.v1.module_generator.gencode.schema import GenTableColumnOutSchema as GenTableColumnSchema, GenTableOutSchema as GenTableSchema
|
||||||
from .string_util import StringUtil
|
from .string_util import StringUtil
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from jinja2 import Environment, FileSystemLoader
|
|||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
from app.common.constant import GenConstant
|
from app.common.constant import GenConstant
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
from app.api.v1.module_generator.gencode.schema import GenTableSchema, GenTableColumnSchema
|
from app.api.v1.module_generator.gencode.schema import GenTableOutSchema as GenTableSchema, GenTableColumnOutSchema as GenTableColumnSchema
|
||||||
from app.core.base_model import CamelCaseUtil, SnakeCaseUtil
|
from app.core.base_model import CamelCaseUtil, SnakeCaseUtil
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from .string_util import StringUtil
|
from .string_util import StringUtil
|
||||||
@@ -25,8 +25,8 @@ class TemplateInitializer:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 修复模板路径,使用正确的相对路径
|
# 修复模板路径,使用正确的相对路径
|
||||||
template_dir = os.path.join(os.path.dirname(__file__), '..', 'api', 'v1', 'module_generator', 'gencode', 'templates')
|
|
||||||
template_dir = os.path.abspath(template_dir)
|
template_dir = settings.TEMPLATE_DIR
|
||||||
env = Environment(
|
env = Environment(
|
||||||
loader=FileSystemLoader(template_dir),
|
loader=FileSystemLoader(template_dir),
|
||||||
keep_trailing_newline=True,
|
keep_trailing_newline=True,
|
||||||
|
|||||||
@@ -19,8 +19,5 @@ VITE_APP_PORT=5180
|
|||||||
# 超时时间
|
# 超时时间
|
||||||
VITE_TIMEOUT=10000
|
VITE_TIMEOUT=10000
|
||||||
|
|
||||||
# 资源管理根路径
|
|
||||||
VITE_RESOURCE_ROOT_PATH=/home/static
|
|
||||||
|
|
||||||
# WebSocket 端点(不配置则关闭)
|
# WebSocket 端点(不配置则关闭)
|
||||||
VITE_APP_WS_ENDPOINT=ws://127.0.0.1:8001
|
VITE_APP_WS_ENDPOINT=ws://127.0.0.1:8001
|
||||||
|
|||||||
@@ -16,8 +16,5 @@ VITE_APP_PORT=5180
|
|||||||
# 超时时间
|
# 超时时间
|
||||||
VITE_TIMEOUT=10000
|
VITE_TIMEOUT=10000
|
||||||
|
|
||||||
# 资源管理根路径
|
|
||||||
VITE_RESOURCE_ROOT_PATH=/home/static
|
|
||||||
|
|
||||||
# WebSocket 端点(不配置则关闭)
|
# WebSocket 端点(不配置则关闭)
|
||||||
VITE_APP_WS_ENDPOINT=wss://service.fastapiadmin.com
|
VITE_APP_WS_ENDPOINT=wss://service.fastapiadmin.com
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
getResourceList(query: ResourceListQuery) {
|
getResourceList(query: ResourceListQuery) {
|
||||||
return request<ApiResponse<ResourceListResponse>>({
|
return request<ApiResponse<ResourceListResponse>>({
|
||||||
url: `/resource/resource/list`,
|
url: `/monitor/resource/list`,
|
||||||
method: "get",
|
method: "get",
|
||||||
params: query,
|
params: query,
|
||||||
});
|
});
|
||||||
@@ -19,7 +19,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
searchResource(body: ResourceSearchQuery) {
|
searchResource(body: ResourceSearchQuery) {
|
||||||
return request<ApiResponse<ResourceListResponse>>({
|
return request<ApiResponse<ResourceListResponse>>({
|
||||||
url: `/resource/resource/search`,
|
url: `/monitor/resource/search`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -31,7 +31,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
uploadFile(formData: FormData) {
|
uploadFile(formData: FormData) {
|
||||||
return request<ApiResponse<UploadFilePath>>({
|
return request<ApiResponse<UploadFilePath>>({
|
||||||
url: `/resource/resource/upload`,
|
url: `/monitor/resource/upload`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: formData,
|
data: formData,
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
@@ -44,7 +44,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
downloadFile(path: string) {
|
downloadFile(path: string) {
|
||||||
return request<Blob>({
|
return request<Blob>({
|
||||||
url: `/resource/resource/download`,
|
url: `/monitor/resource/download`,
|
||||||
method: "get",
|
method: "get",
|
||||||
params: { path },
|
params: { path },
|
||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
@@ -57,7 +57,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
deleteResource(body: string[]) {
|
deleteResource(body: string[]) {
|
||||||
return request<ApiResponse>({
|
return request<ApiResponse>({
|
||||||
url: `/resource/resource/delete`,
|
url: `/monitor/resource/delete`,
|
||||||
method: "delete",
|
method: "delete",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -69,7 +69,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
moveResource(body: ResourceMoveQuery) {
|
moveResource(body: ResourceMoveQuery) {
|
||||||
return request<ApiResponse>({
|
return request<ApiResponse>({
|
||||||
url: `/resource/resource/move`,
|
url: `/monitor/resource/move`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -81,7 +81,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
copyResource(body: ResourceCopyQuery) {
|
copyResource(body: ResourceCopyQuery) {
|
||||||
return request<ApiResponse>({
|
return request<ApiResponse>({
|
||||||
url: `/resource/resource/copy`,
|
url: `/monitor/resource/copy`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -93,7 +93,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
renameResource(body: ResourceRenameQuery) {
|
renameResource(body: ResourceRenameQuery) {
|
||||||
return request<ApiResponse>({
|
return request<ApiResponse>({
|
||||||
url: `/resource/resource/rename`,
|
url: `/monitor/resource/rename`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -105,7 +105,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
createDirectory(body: ResourceCreateDirQuery) {
|
createDirectory(body: ResourceCreateDirQuery) {
|
||||||
return request<ApiResponse>({
|
return request<ApiResponse>({
|
||||||
url: `/resource/resource/create-dir`,
|
url: `/monitor/resource/create-dir`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
});
|
});
|
||||||
@@ -116,7 +116,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
getResourceStats() {
|
getResourceStats() {
|
||||||
return request<ApiResponse<ResourceStats>>({
|
return request<ApiResponse<ResourceStats>>({
|
||||||
url: `/resource/resource/stats`,
|
url: `/monitor/resource/stats`,
|
||||||
method: "get",
|
method: "get",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -127,7 +127,7 @@ export const ResourceAPI = {
|
|||||||
*/
|
*/
|
||||||
exportResource(body: ResourceSearchQuery) {
|
exportResource(body: ResourceSearchQuery) {
|
||||||
return request<Blob>({
|
return request<Blob>({
|
||||||
url: `/resource/resource/export`,
|
url: `/monitor/resource/export`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: body,
|
data: body,
|
||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import request from "@/utils/request";
|
|
||||||
|
|
||||||
const TicketAPI = {
|
|
||||||
getTicketList(query: TicketPageQuery) {
|
|
||||||
return request<ApiResponse<PageResult<TicketTable[]>>>({
|
|
||||||
url: `/system/ticket/list`,
|
|
||||||
method: "get",
|
|
||||||
params: query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getTicketDetail(query: number) {
|
|
||||||
return request<ApiResponse<TicketTable>>({
|
|
||||||
url: `/system/ticket/detail/${query}`,
|
|
||||||
method: "get",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
createTicket(body: TicketForm) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/ticket/create`,
|
|
||||||
method: "post",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTicket(id: number, body: TicketForm) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/ticket/update/${id}`,
|
|
||||||
method: "put",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteTicket(body: number[]) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/ticket/delete`,
|
|
||||||
method: "delete",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TicketAPI;
|
|
||||||
|
|
||||||
export interface TicketPageQuery extends PageQuery {
|
|
||||||
title?: string;
|
|
||||||
status?: string;
|
|
||||||
priority?: string;
|
|
||||||
start_time?: string;
|
|
||||||
end_time?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TicketTable {
|
|
||||||
index?: number;
|
|
||||||
id?: number;
|
|
||||||
title?: string;
|
|
||||||
priority?: string;
|
|
||||||
type?: string;
|
|
||||||
status?: string;
|
|
||||||
description?: string;
|
|
||||||
assignee?: creatorType;
|
|
||||||
reporter?: creatorType;
|
|
||||||
project?: string;
|
|
||||||
version?: string;
|
|
||||||
created_at?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
creator?: creatorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TicketForm {
|
|
||||||
id?: number;
|
|
||||||
title: string;
|
|
||||||
priority?: string;
|
|
||||||
type?: string;
|
|
||||||
status?: string;
|
|
||||||
description?: string;
|
|
||||||
assignee_id?: number;
|
|
||||||
reporter_id: number;
|
|
||||||
project?: string;
|
|
||||||
version?: string;
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import request from "@/utils/request";
|
|
||||||
|
|
||||||
const VersionAPI = {
|
|
||||||
getVersionList(query: VersionPageQuery) {
|
|
||||||
return request<ApiResponse<PageResult<VersionTable[]>>>({
|
|
||||||
url: `/system/version/list`,
|
|
||||||
method: "get",
|
|
||||||
params: query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getVersionDetail(query: number) {
|
|
||||||
return request<ApiResponse<VersionTable>>({
|
|
||||||
url: `/system/version/detail/${query}`,
|
|
||||||
method: "get",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
createVersion(body: VersionForm) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/version/create`,
|
|
||||||
method: "post",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateVersion(id: number, body: VersionForm) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/version/update/${id}`,
|
|
||||||
method: "put",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteVersion(body: number[]) {
|
|
||||||
return request<ApiResponse>({
|
|
||||||
url: `/system/version/delete`,
|
|
||||||
method: "delete",
|
|
||||||
data: body,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VersionAPI;
|
|
||||||
|
|
||||||
export interface VersionPageQuery extends PageQuery {
|
|
||||||
title?: string;
|
|
||||||
status?: string;
|
|
||||||
start_time?: string;
|
|
||||||
end_time?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionTable {
|
|
||||||
index?: number;
|
|
||||||
id?: number;
|
|
||||||
version_number?: string;
|
|
||||||
title?: string;
|
|
||||||
release_notes?: string;
|
|
||||||
project?: string;
|
|
||||||
status?: string;
|
|
||||||
released_at?: string;
|
|
||||||
description?: string;
|
|
||||||
created_at?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
creator?: creatorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionForm {
|
|
||||||
id?: number;
|
|
||||||
version_number: string;
|
|
||||||
title: string;
|
|
||||||
release_notes?: string;
|
|
||||||
project?: string;
|
|
||||||
status?: string;
|
|
||||||
released_at?: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
export const ROLE_ROOT = "admin";
|
export const ROLE_ROOT = "admin";
|
||||||
|
|
||||||
// 资源管理根路径
|
|
||||||
export const RESOURCE_ROOT_PATH = import.meta.env.VITE_RESOURCE_ROOT_PATH || "/home/static";
|
|
||||||
|
|
||||||
// 🔗 导出所有存储键常量
|
// 🔗 导出所有存储键常量
|
||||||
export * from "./storage-keys";
|
export * from "./storage-keys";
|
||||||
|
|||||||
Vendored
-3
@@ -20,9 +20,6 @@ interface ImportMetaEnv {
|
|||||||
/** 超时时间 */
|
/** 超时时间 */
|
||||||
VITE_TIMEOUT: number;
|
VITE_TIMEOUT: number;
|
||||||
|
|
||||||
/** 资源管理根路径 */
|
|
||||||
VITE_RESOURCE_ROOT_PATH: string;
|
|
||||||
|
|
||||||
/** ws 端点 */
|
/** ws 端点 */
|
||||||
VITE_APP_WS_ENDPOINT: string;
|
VITE_APP_WS_ENDPOINT: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-84
@@ -96,8 +96,8 @@
|
|||||||
<el-breadcrumb-item
|
<el-breadcrumb-item
|
||||||
v-for="(item, index) in breadcrumbList"
|
v-for="(item, index) in breadcrumbList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleBreadcrumbClick(item, index)"
|
|
||||||
:class="{ 'is-link': index < breadcrumbList.length - 1 }"
|
:class="{ 'is-link': index < breadcrumbList.length - 1 }"
|
||||||
|
@click="handleBreadcrumbClick(item, index)"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
<Document v-else />
|
<Document v-else />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span
|
<span
|
||||||
:class="{ 'file-name-clickable': !row.is_dir }"
|
:class="{ 'file-name-clickable': true }"
|
||||||
@click="handleFileNameClick(row)"
|
@click="handleFileNameClick(row)"
|
||||||
>
|
>
|
||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-name">{{ item.name }}</div>
|
<div class="item-name">{{ item.name }}</div>
|
||||||
<div class="item-size" v-if="!item.is_dir">
|
<div v-if="!item.is_dir" class="item-size">
|
||||||
{{ formatFileSize(item.size) }}
|
{{ formatFileSize(item.size) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
|
|
||||||
<!-- 分页区域 -->
|
<!-- 分页区域 -->
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<pagination
|
<Pagination
|
||||||
v-model:total="total"
|
v-model:total="total"
|
||||||
v-model:page="pagination.page_no"
|
v-model:page="pagination.page_no"
|
||||||
v-model:limit="pagination.page_size"
|
v-model:limit="pagination.page_size"
|
||||||
@@ -218,10 +218,10 @@
|
|||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:file-list="uploadFileList"
|
:file-list="uploadFileList"
|
||||||
@change="handleUploadChange"
|
|
||||||
drag
|
drag
|
||||||
|
@change="handleUploadChange"
|
||||||
>
|
>
|
||||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
||||||
<div class="el-upload__text">
|
<div class="el-upload__text">
|
||||||
将文件拖到此处,或<em>点击上传</em>
|
将文件拖到此处,或<em>点击上传</em>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
</el-upload>
|
</el-upload>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="uploadDialogVisible = false">取消</el-button>
|
<el-button @click="uploadDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleUploadConfirm" :loading="uploading">
|
<el-button type="primary" :loading="uploading" @click="handleUploadConfirm">
|
||||||
确定上传
|
确定上传
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -287,11 +287,6 @@
|
|||||||
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
Upload,
|
|
||||||
FolderAdd,
|
|
||||||
Refresh,
|
|
||||||
Search,
|
|
||||||
RefreshLeft,
|
|
||||||
List,
|
List,
|
||||||
Grid,
|
Grid,
|
||||||
Folder,
|
Folder,
|
||||||
@@ -299,20 +294,20 @@ import {
|
|||||||
UploadFilled,
|
UploadFilled,
|
||||||
QuestionFilled
|
QuestionFilled
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import Pagination from '@/components/Pagination/index.vue'
|
import { ResourceAPI, type ResourceItem, type ResourceSearchQuery } from '@/api/monitor/resource'
|
||||||
import { ResourceAPI, type ResourceItem, type ResourceListQuery, type ResourceSearchQuery, type ResourceListResponse } from '@/api/resource/resource'
|
|
||||||
import { RESOURCE_ROOT_PATH } from '@/constants'
|
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const fileList = ref<ResourceItem[]>([])
|
const fileList = ref<ResourceItem[]>([])
|
||||||
const selectedItems = ref<ResourceItem[]>([])
|
const selectedItems = ref<ResourceItem[]>([])
|
||||||
const currentPath = ref(RESOURCE_ROOT_PATH)
|
const breadcrumbList = ref([{ name: '资源根目录', path: '/' }])
|
||||||
const breadcrumbList = ref([{ name: '资源根目录', path: RESOURCE_ROOT_PATH }])
|
|
||||||
const showHiddenFiles = ref(false)
|
const showHiddenFiles = ref(false)
|
||||||
const viewMode = ref<'list' | 'grid'>('list')
|
const viewMode = ref<'list' | 'grid'>('list')
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 路径相关数据
|
||||||
|
const currentPath = ref('/') // 添加当前路径的响应式变量
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page_no: 1,
|
page_no: 1,
|
||||||
@@ -347,25 +342,21 @@ const renameForm = reactive({
|
|||||||
old_path: ''
|
old_path: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 工具函数:处理路径转换
|
|
||||||
const ensureRootPath = (path: string) => {
|
|
||||||
// 如果是HTTP URL,提取相对路径部分
|
|
||||||
if (path.startsWith('http')) {
|
|
||||||
const url = new URL(path)
|
|
||||||
return url.pathname.replace('/api/v1/static', '/home/static')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.startsWith(RESOURCE_ROOT_PATH)) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return RESOURCE_ROOT_PATH + '/' + path.replace(/^\/+/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const currentQuery = computed(() => ({
|
const currentQuery = computed(() => {
|
||||||
path: currentPath.value,
|
// 构建查询参数
|
||||||
include_hidden: showHiddenFiles.value
|
const query: any = {
|
||||||
}))
|
include_hidden: showHiddenFiles.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前路径不是根路径,则添加路径参数
|
||||||
|
if (currentPath.value && currentPath.value !== '/') {
|
||||||
|
// 对于文件夹导航,直接传递文件夹名称
|
||||||
|
query.path = currentPath.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
})
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const loadFileList = async () => {
|
const loadFileList = async () => {
|
||||||
@@ -397,54 +388,62 @@ const loadFileList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBreadcrumbClick = (item: any, index: number) => {
|
const handleBreadcrumbClick = (item: any) => {
|
||||||
if (index < breadcrumbList.value.length - 1) {
|
// 更新当前路径为点击的面包屑项路径
|
||||||
currentPath.value = ensureRootPath(item.path)
|
currentPath.value = item.path
|
||||||
updateBreadcrumb()
|
updateBreadcrumb()
|
||||||
loadFileList()
|
loadFileList()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateBreadcrumb = () => {
|
const updateBreadcrumb = () => {
|
||||||
const rootPath = RESOURCE_ROOT_PATH
|
// 对于根路径,直接显示根目录
|
||||||
let relativePath = currentPath.value
|
if (currentPath.value === '/') {
|
||||||
|
breadcrumbList.value = [{ name: '资源根目录', path: '/' }]
|
||||||
// 处理HTTP URL格式的路径
|
return
|
||||||
if (currentPath.value.startsWith('http')) {
|
|
||||||
const url = new URL(currentPath.value)
|
|
||||||
relativePath = url.pathname.replace('/api/v1/static', '/home/static')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
relativePath = relativePath.replace(rootPath, '').replace(/^\/+/, '')
|
// 对于嵌套路径,需要分解并构建面包屑
|
||||||
const pathParts = relativePath ? relativePath.split('/').filter(Boolean) : []
|
const parts = currentPath.value.split('/').filter(part => part !== '')
|
||||||
|
|
||||||
breadcrumbList.value = [
|
breadcrumbList.value = [
|
||||||
{ name: '资源根目录', path: rootPath },
|
{ name: '资源根目录', path: '/' },
|
||||||
...pathParts.map((part, index) => ({
|
...parts.map((part, index) => ({
|
||||||
name: part,
|
name: part,
|
||||||
path: rootPath + '/' + pathParts.slice(0, index + 1).join('/')
|
path: parts.slice(0, index + 1).join('/')
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileNameClick = (row: ResourceItem) => {
|
const handleFileNameClick = (row: ResourceItem) => {
|
||||||
if (row.is_dir) {
|
if (row.is_dir) {
|
||||||
currentPath.value = ensureRootPath(row.path)
|
// 如果当前在根路径,则直接使用文件夹名称
|
||||||
|
// 如果当前已在某个文件夹中,则拼接路径
|
||||||
|
if (currentPath.value === '/') {
|
||||||
|
currentPath.value = row.name
|
||||||
|
} else {
|
||||||
|
currentPath.value = currentPath.value + '/' + row.name
|
||||||
|
}
|
||||||
updateBreadcrumb()
|
updateBreadcrumb()
|
||||||
loadFileList()
|
loadFileList()
|
||||||
} else {
|
} else {
|
||||||
// 文件预览
|
// 文件预览,使用后端返回的完整URL
|
||||||
handleFilePreview(row)
|
handleFilePreview(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleItemClick = (item: ResourceItem) => {
|
const handleItemClick = (item: ResourceItem) => {
|
||||||
if (item.is_dir) {
|
if (item.is_dir) {
|
||||||
currentPath.value = ensureRootPath(item.path)
|
// 如果当前在根路径,则直接使用文件夹名称
|
||||||
|
// 如果当前已在某个文件夹中,则拼接路径
|
||||||
|
if (currentPath.value === '/') {
|
||||||
|
currentPath.value = item.name
|
||||||
|
} else {
|
||||||
|
currentPath.value = currentPath.value + '/' + item.name
|
||||||
|
}
|
||||||
updateBreadcrumb()
|
updateBreadcrumb()
|
||||||
loadFileList()
|
loadFileList()
|
||||||
} else {
|
} else {
|
||||||
// 文件预览
|
// 文件预览,使用后端返回的完整URL
|
||||||
handleFilePreview(item)
|
handleFilePreview(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,7 +451,13 @@ const handleItemClick = (item: ResourceItem) => {
|
|||||||
// 文件预览
|
// 文件预览
|
||||||
const handleFilePreview = (file: ResourceItem) => {
|
const handleFilePreview = (file: ResourceItem) => {
|
||||||
// 使用后端返回的完整URL路径进行预览
|
// 使用后端返回的完整URL路径进行预览
|
||||||
const previewUrl = file.path
|
let previewUrl = file.path
|
||||||
|
|
||||||
|
// 如果是完整URL,直接使用
|
||||||
|
if (!previewUrl.startsWith('http')) {
|
||||||
|
// 对于相对路径,需要构建完整URL
|
||||||
|
previewUrl = `${window.location.origin}${previewUrl}`
|
||||||
|
}
|
||||||
|
|
||||||
// 根据文件类型决定预览方式
|
// 根据文件类型决定预览方式
|
||||||
const fileExtension = file.file_extension?.toLowerCase() || ''
|
const fileExtension = file.file_extension?.toLowerCase() || ''
|
||||||
@@ -497,7 +502,19 @@ const handleUploadConfirm = async () => {
|
|||||||
uploadFileList.value.forEach((file: any) => {
|
uploadFileList.value.forEach((file: any) => {
|
||||||
formData.append('file', file.raw)
|
formData.append('file', file.raw)
|
||||||
})
|
})
|
||||||
formData.append('target_path', ensureRootPath(currentPath.value))
|
|
||||||
|
// 处理目标路径
|
||||||
|
let targetPath = currentPath.value
|
||||||
|
if (targetPath.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(targetPath)
|
||||||
|
targetPath = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', targetPath, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append('target_path', targetPath)
|
||||||
|
|
||||||
await ResourceAPI.uploadFile(formData)
|
await ResourceAPI.uploadFile(formData)
|
||||||
ElMessage.success('上传成功')
|
ElMessage.success('上传成功')
|
||||||
@@ -528,8 +545,19 @@ const handleCreateDirConfirm = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 处理父路径
|
||||||
|
let parentPath = currentPath.value
|
||||||
|
if (parentPath.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(parentPath)
|
||||||
|
parentPath = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', parentPath, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await ResourceAPI.createDirectory({
|
await ResourceAPI.createDirectory({
|
||||||
parent_path: ensureRootPath(currentPath.value),
|
parent_path: parentPath,
|
||||||
dir_name: createDirForm.dir_name.trim()
|
dir_name: createDirForm.dir_name.trim()
|
||||||
})
|
})
|
||||||
ElMessage.success('创建成功')
|
ElMessage.success('创建成功')
|
||||||
@@ -590,7 +618,17 @@ const handleShowHiddenChange = () => {
|
|||||||
|
|
||||||
const handleDownload = async (item: ResourceItem) => {
|
const handleDownload = async (item: ResourceItem) => {
|
||||||
try {
|
try {
|
||||||
const filePath = ensureRootPath(item.path)
|
// 处理完整URL路径
|
||||||
|
let filePath = item.path
|
||||||
|
if (filePath.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(filePath)
|
||||||
|
filePath = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', filePath, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await ResourceAPI.downloadFile(filePath)
|
const response = await ResourceAPI.downloadFile(filePath)
|
||||||
const blob = response.data
|
const blob = response.data
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
@@ -608,7 +646,18 @@ const handleDownload = async (item: ResourceItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleRename = (item: ResourceItem) => {
|
const handleRename = (item: ResourceItem) => {
|
||||||
renameForm.old_path = ensureRootPath(item.path)
|
// 处理完整URL路径
|
||||||
|
let oldPath = item.path
|
||||||
|
if (oldPath.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(oldPath)
|
||||||
|
oldPath = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', oldPath, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renameForm.old_path = oldPath
|
||||||
renameForm.new_name = item.name
|
renameForm.new_name = item.name
|
||||||
renameDialogVisible.value = true
|
renameDialogVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -633,12 +682,6 @@ const handleRenameConfirm = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMove = (item: ResourceItem) => {
|
|
||||||
// TODO: 实现移动功能
|
|
||||||
ElMessage.info('移动功能待实现')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async (item: ResourceItem) => {
|
const handleDelete = async (item: ResourceItem) => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
@@ -651,7 +694,17 @@ const handleDelete = async (item: ResourceItem) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const filePath = ensureRootPath(item.path)
|
// 处理完整URL路径
|
||||||
|
let filePath = item.path
|
||||||
|
if (filePath.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(filePath)
|
||||||
|
filePath = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', filePath, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await ResourceAPI.deleteResource([filePath])
|
await ResourceAPI.deleteResource([filePath])
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
loadFileList()
|
loadFileList()
|
||||||
@@ -663,16 +716,6 @@ const handleDelete = async (item: ResourceItem) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSizeChange = (size: number) => {
|
|
||||||
pagination.page_size = size
|
|
||||||
loadFileList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCurrentChange = (page: number) => {
|
|
||||||
pagination.page_no = page
|
|
||||||
loadFileList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePagination = (params: { page: number; limit: number }) => {
|
const handlePagination = (params: { page: number; limit: number }) => {
|
||||||
pagination.page_no = params.page
|
pagination.page_no = params.page
|
||||||
pagination.page_size = params.limit
|
pagination.page_size = params.limit
|
||||||
@@ -696,7 +739,20 @@ const handleBatchDelete = async () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const paths = selectedItems.value.map(item => ensureRootPath(item.path))
|
const paths = selectedItems.value.map(item => {
|
||||||
|
// 处理完整URL路径
|
||||||
|
let path = item.path
|
||||||
|
if (path.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(path)
|
||||||
|
path = url.pathname.replace('/api/v1/static', '') || '/'
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse URL:', path, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
await ResourceAPI.deleteResource(paths)
|
await ResourceAPI.deleteResource(paths)
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
loadFileList()
|
loadFileList()
|
||||||
@@ -723,10 +779,6 @@ const formatFileSize = (size?: number | null) => {
|
|||||||
return `${fileSize.toFixed(1)} ${units[unitIndex]}`
|
return `${fileSize.toFixed(1)} ${units[unitIndex]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
return new Date(dateString).toLocaleString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadFileList()
|
loadFileList()
|
||||||
+1
-1
@@ -120,7 +120,7 @@ import {
|
|||||||
Monitor,
|
Monitor,
|
||||||
QuestionFilled
|
QuestionFilled
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { ResourceAPI, type ResourceStats } from '@/api/resource/resource'
|
import { ResourceAPI, type ResourceStats } from '@/api/monitor/resource'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const stats = ref<ResourceStats | null>(null)
|
const stats = ref<ResourceStats | null>(null)
|
||||||
@@ -1,578 +0,0 @@
|
|||||||
<!-- 工单管理 -->
|
|
||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<!-- 搜索区域 -->
|
|
||||||
<div class="search-container">
|
|
||||||
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":">
|
|
||||||
<el-form-item prop="title" label="工单标题">
|
|
||||||
<el-input v-model="queryFormData.title" placeholder="请输入工单标题" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="priority" label="优先级">
|
|
||||||
<el-select v-model="queryFormData.priority" placeholder="请选择优先级" style="width: 167.5px" clearable>
|
|
||||||
<el-option value="low" label="低" />
|
|
||||||
<el-option value="medium" label="中" />
|
|
||||||
<el-option value="high" label="高" />
|
|
||||||
<el-option value="urgent" label="紧急" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="status" label="状态">
|
|
||||||
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
|
|
||||||
<el-option value="pending" label="待处理" />
|
|
||||||
<el-option value="progress" label="处理中" />
|
|
||||||
<el-option value="resolved" label="已解决" />
|
|
||||||
<el-option value="closed" label="已关闭" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 时间范围,收起状态下隐藏 -->
|
|
||||||
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
|
|
||||||
<DatePicker
|
|
||||||
v-model="dateRange"
|
|
||||||
@update:model-value="handleDateRangeChange"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 查询、重置、展开/收起按钮 -->
|
|
||||||
<el-form-item class="search-buttons">
|
|
||||||
<el-button type="primary" icon="search" @click="handleQuery">
|
|
||||||
查询
|
|
||||||
</el-button>
|
|
||||||
<el-button icon="refresh" @click="handleResetQuery">
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
<!-- 展开/收起 -->
|
|
||||||
<template v-if="isExpandable">
|
|
||||||
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
|
|
||||||
{{ isExpand ? "收起" : "展开" }}
|
|
||||||
<el-icon>
|
|
||||||
<template v-if="isExpand">
|
|
||||||
<ArrowUp />
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<ArrowDown />
|
|
||||||
</template>
|
|
||||||
</el-icon>
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<el-card shadow="hover" class="data-table">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>
|
|
||||||
<el-tooltip content="工单管理维护系统。">
|
|
||||||
<QuestionFilled class="w-4 h-4 mx-1" />
|
|
||||||
</el-tooltip>
|
|
||||||
工单列表
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 功能区域 -->
|
|
||||||
<div class="data-table__toolbar">
|
|
||||||
<div class="data-table__toolbar--actions">
|
|
||||||
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
|
|
||||||
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="data-table__toolbar--tools">
|
|
||||||
<el-tooltip content="刷新">
|
|
||||||
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="列表筛选">
|
|
||||||
<el-dropdown trigger="click">
|
|
||||||
<el-button type="default" icon="operation" circle />
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
|
|
||||||
<el-checkbox v-model="column.show">
|
|
||||||
{{ column.label }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 表格区域:工单列表 -->
|
|
||||||
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
|
|
||||||
<template #empty>
|
|
||||||
<el-empty :image-size="80" description="暂无数据" />
|
|
||||||
</template>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" fixed label="序号" min-width="60" >
|
|
||||||
<template #default="scope">
|
|
||||||
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'title')?.show" label="工单标题" prop="title" min-width="140" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
|
||||||
{{ getStatusLabel(scope.row.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'priority')?.show" label="优先级" prop="priority" min-width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getPriorityTagType(scope.row.priority)">
|
|
||||||
{{ getPriorityLabel(scope.row.priority) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'type')?.show" label="类型" prop="type" min-width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ getTypeLabel(scope.row.type) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'project')?.show" label="项目" prop="project" min-width="100" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'version')?.show" label="版本" prop="version" min-width="80" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'assignee')?.show" label="指派给" prop="assignee" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.assignee?.name }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'reporter')?.show" label="报告人" prop="reporter" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.reporter?.name }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="180" sortable />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="180" sortable />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.creator?.name }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
|
|
||||||
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
|
|
||||||
<el-button type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页区域 -->
|
|
||||||
<template #footer>
|
|
||||||
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
|
|
||||||
</template>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 弹窗区域 -->
|
|
||||||
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
|
|
||||||
<!-- 详情 -->
|
|
||||||
<template v-if="dialogVisible.type === 'detail'">
|
|
||||||
<el-descriptions :column="4" border>
|
|
||||||
<el-descriptions-item label="工单标题" :span="2">
|
|
||||||
{{ detailFormData.title }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态" :span="2">
|
|
||||||
<el-tag :type="getStatusTagType(detailFormData.status)">
|
|
||||||
{{ getStatusLabel(detailFormData.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="优先级" :span="2">
|
|
||||||
<el-tag :type="getPriorityTagType(detailFormData.priority)">
|
|
||||||
{{ getPriorityLabel(detailFormData.priority) }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="类型" :span="2">
|
|
||||||
{{ getTypeLabel(detailFormData.type) }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="项目" :span="2">
|
|
||||||
{{ detailFormData.project }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="版本" :span="2">
|
|
||||||
{{ detailFormData.version }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="指派给" :span="2">
|
|
||||||
{{ detailFormData.assignee?.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="报告人" :span="2">
|
|
||||||
{{ detailFormData.reporter?.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="描述" :span="4">
|
|
||||||
{{ detailFormData.description }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建人" :span="2">
|
|
||||||
{{ detailFormData.creator?.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建时间" :span="2">
|
|
||||||
{{ detailFormData.created_at }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="更新时间" :span="2">
|
|
||||||
{{ detailFormData.updated_at }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</template>
|
|
||||||
<!-- 新增、编辑表单 -->
|
|
||||||
<template v-else>
|
|
||||||
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
|
|
||||||
<el-form-item label="工单标题" prop="title">
|
|
||||||
<el-input v-model="formData.title" placeholder="请输入工单标题" :maxlength="255" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="优先级" prop="priority">
|
|
||||||
<el-select v-model="formData.priority" placeholder="请选择优先级" clearable>
|
|
||||||
<el-option value="low" label="低" />
|
|
||||||
<el-option value="medium" label="中" />
|
|
||||||
<el-option value="high" label="高" />
|
|
||||||
<el-option value="urgent" label="紧急" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="类型" prop="type">
|
|
||||||
<el-select v-model="formData.type" placeholder="请选择类型" clearable>
|
|
||||||
<el-option value="bug" label="缺陷" />
|
|
||||||
<el-option value="feature" label="功能" />
|
|
||||||
<el-option value="task" label="任务" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="状态" prop="status">
|
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
|
||||||
<el-option value="pending" label="待处理" />
|
|
||||||
<el-option value="progress" label="处理中" />
|
|
||||||
<el-option value="resolved" label="已解决" />
|
|
||||||
<el-option value="closed" label="已关闭" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="项目" prop="project">
|
|
||||||
<el-input v-model="formData.project" placeholder="请输入项目名称" :maxlength="100" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="版本" prop="version">
|
|
||||||
<el-input v-model="formData.version" placeholder="请输入版本号" :maxlength="50" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="指派给" prop="assignee_id">
|
|
||||||
<!-- 这里应该是一个用户选择器,暂时用输入框代替 -->
|
|
||||||
<el-input v-model.number="formData.assignee_id" placeholder="请输入指派用户ID" type="number" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="描述" prop="description">
|
|
||||||
<el-input v-model="formData.description" :rows="4" :maxlength="500" show-word-limit type="textarea" placeholder="请输入描述" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
|
|
||||||
<el-button @click="handleCloseDialog">取消</el-button>
|
|
||||||
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
|
|
||||||
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineOptions({
|
|
||||||
name: "Ticket",
|
|
||||||
inheritAttrs: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
import TicketAPI, { TicketTable, TicketForm, TicketPageQuery } from "@/api/system/ticket";
|
|
||||||
import { ElMessageBox } from "element-plus";
|
|
||||||
|
|
||||||
const queryFormRef = ref();
|
|
||||||
const dataFormRef = ref();
|
|
||||||
const total = ref(0);
|
|
||||||
const selectIds = ref<number[]>([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const isExpand = ref(false);
|
|
||||||
const isExpandable = ref(true);
|
|
||||||
|
|
||||||
// 分页表单
|
|
||||||
const pageTableData = ref<TicketTable[]>([]);
|
|
||||||
|
|
||||||
// 表格列配置
|
|
||||||
const tableColumns = ref([
|
|
||||||
{ prop: 'selection', label: '选择框', show: true },
|
|
||||||
{ prop: 'index', label: '序号', show: true },
|
|
||||||
{ prop: 'title', label: '工单标题', show: true },
|
|
||||||
{ prop: 'status', label: '状态', show: true },
|
|
||||||
{ prop: 'priority', label: '优先级', show: true },
|
|
||||||
{ prop: 'type', label: '类型', show: true },
|
|
||||||
{ prop: 'project', label: '项目', show: true },
|
|
||||||
{ prop: 'version', label: '版本', show: true },
|
|
||||||
{ prop: 'assignee', label: '指派给', show: true },
|
|
||||||
{ prop: 'reporter', label: '报告人', show: true },
|
|
||||||
{ prop: 'created_at', label: '创建时间', show: true },
|
|
||||||
{ prop: 'updated_at', label: '更新时间', show: true },
|
|
||||||
{ prop: 'creator', label: '创建人', show: true },
|
|
||||||
{ prop: 'operation', label: '操作', show: true }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 详情表单
|
|
||||||
const detailFormData = ref<TicketTable>({});
|
|
||||||
|
|
||||||
// 分页查询参数
|
|
||||||
const queryFormData = reactive<TicketPageQuery>({
|
|
||||||
page_no: 1,
|
|
||||||
page_size: 10,
|
|
||||||
title: undefined,
|
|
||||||
priority: undefined,
|
|
||||||
status: undefined,
|
|
||||||
start_time: undefined,
|
|
||||||
end_time: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 编辑表单
|
|
||||||
const formData = reactive<TicketForm>({
|
|
||||||
id: undefined,
|
|
||||||
title: '',
|
|
||||||
priority: 'medium',
|
|
||||||
type: 'bug',
|
|
||||||
status: 'pending',
|
|
||||||
description: undefined,
|
|
||||||
assignee_id: undefined,
|
|
||||||
reporter_id: 1, // 默认当前用户ID,实际应该从认证信息中获取
|
|
||||||
project: undefined,
|
|
||||||
version: undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// 弹窗状态
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: 'create' as 'create' | 'update' | 'detail',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表单验证规则
|
|
||||||
const rules = reactive({
|
|
||||||
title: [{ required: true, message: "请输入工单标题", trigger: "blur" }],
|
|
||||||
priority: [{ required: false, message: "请选择优先级", trigger: "blur" }],
|
|
||||||
type: [{ required: false, message: "请选择类型", trigger: "blur" }],
|
|
||||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
|
||||||
project: [{ required: false, message: "请输入项目名称", trigger: "blur" }],
|
|
||||||
version: [{ required: false, message: "请输入版本号", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
// 日期范围临时变量
|
|
||||||
const dateRange = ref<[Date, Date] | []>([]);
|
|
||||||
|
|
||||||
// 处理日期范围变化
|
|
||||||
function handleDateRangeChange(range: [Date, Date]) {
|
|
||||||
dateRange.value = range;
|
|
||||||
if (range && range.length === 2) {
|
|
||||||
queryFormData.start_time = range[0].toISOString();
|
|
||||||
queryFormData.end_time = range[1].toISOString();
|
|
||||||
} else {
|
|
||||||
queryFormData.start_time = undefined;
|
|
||||||
queryFormData.end_time = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态标签类型
|
|
||||||
function getStatusTagType(status: string) {
|
|
||||||
switch (status) {
|
|
||||||
case 'pending': return 'info';
|
|
||||||
case 'progress': return 'warning';
|
|
||||||
case 'resolved': return 'success';
|
|
||||||
case 'closed': return 'danger';
|
|
||||||
default: return 'info';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态标签文本
|
|
||||||
function getStatusLabel(status: string) {
|
|
||||||
switch (status) {
|
|
||||||
case 'pending': return '待处理';
|
|
||||||
case 'progress': return '处理中';
|
|
||||||
case 'resolved': return '已解决';
|
|
||||||
case 'closed': return '已关闭';
|
|
||||||
default: return status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取优先级标签类型
|
|
||||||
function getPriorityTagType(priority: string) {
|
|
||||||
switch (priority) {
|
|
||||||
case 'low': return '';
|
|
||||||
case 'medium': return 'warning';
|
|
||||||
case 'high': return 'danger';
|
|
||||||
case 'urgent': return 'danger';
|
|
||||||
default: return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取优先级标签文本
|
|
||||||
function getPriorityLabel(priority: string) {
|
|
||||||
switch (priority) {
|
|
||||||
case 'low': return '低';
|
|
||||||
case 'medium': return '中';
|
|
||||||
case 'high': return '高';
|
|
||||||
case 'urgent': return '紧急';
|
|
||||||
default: return priority;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取类型标签文本
|
|
||||||
function getTypeLabel(type: string) {
|
|
||||||
switch (type) {
|
|
||||||
case 'bug': return '缺陷';
|
|
||||||
case 'feature': return '功能';
|
|
||||||
case 'task': return '任务';
|
|
||||||
default: return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表刷新
|
|
||||||
async function handleRefresh () {
|
|
||||||
await loadingData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载表格数据
|
|
||||||
async function loadingData() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const response = await TicketAPI.getTicketList(queryFormData);
|
|
||||||
pageTableData.value = response.data.data.items;
|
|
||||||
total.value = response.data.data.total;
|
|
||||||
}
|
|
||||||
catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询(重置页码后获取数据)
|
|
||||||
async function handleQuery() {
|
|
||||||
queryFormData.page_no = 1;
|
|
||||||
loadingData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置查询
|
|
||||||
async function handleResetQuery() {
|
|
||||||
queryFormRef.value.resetFields();
|
|
||||||
queryFormData.page_no = 1;
|
|
||||||
dateRange.value = [];
|
|
||||||
queryFormData.start_time = undefined;
|
|
||||||
queryFormData.end_time = undefined;
|
|
||||||
loadingData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义初始表单数据常量
|
|
||||||
const initialFormData: TicketForm = {
|
|
||||||
id: undefined,
|
|
||||||
title: '',
|
|
||||||
priority: 'medium',
|
|
||||||
type: 'bug',
|
|
||||||
status: 'pending',
|
|
||||||
description: undefined,
|
|
||||||
assignee_id: undefined,
|
|
||||||
reporter_id: 1, // 默认当前用户ID,实际应该从认证信息中获取
|
|
||||||
project: undefined,
|
|
||||||
version: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
async function resetForm() {
|
|
||||||
if (dataFormRef.value) {
|
|
||||||
dataFormRef.value.resetFields();
|
|
||||||
dataFormRef.value.clearValidate();
|
|
||||||
}
|
|
||||||
// 完全重置 formData 为初始状态
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 行复选框选中项变化
|
|
||||||
async function handleSelectionChange(selection: any) {
|
|
||||||
selectIds.value = selection.map((item: any) => item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭弹窗
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开弹窗
|
|
||||||
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await TicketAPI.getTicketDetail(id);
|
|
||||||
if (type === 'detail') {
|
|
||||||
dialogVisible.title = "工单详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data);
|
|
||||||
} else if (type === 'update') {
|
|
||||||
dialogVisible.title = "修改工单";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增工单";
|
|
||||||
formData.id = undefined;
|
|
||||||
}
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单(防抖)
|
|
||||||
async function handleSubmit() {
|
|
||||||
// 表单校验
|
|
||||||
dataFormRef.value.validate(async (valid: any) => {
|
|
||||||
if (valid) {
|
|
||||||
loading.value = true;
|
|
||||||
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
|
|
||||||
const id = formData.id;
|
|
||||||
if (id) {
|
|
||||||
try {
|
|
||||||
await TicketAPI.updateTicket(id, { id, ...formData })
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
handleCloseDialog();
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await TicketAPI.createTicket(formData)
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
handleCloseDialog();
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除、批量删除
|
|
||||||
async function handleDelete(ids: number[]) {
|
|
||||||
ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
await TicketAPI.deleteTicket(ids);
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
ElMessageBox.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
// 加载表格数据
|
|
||||||
loadingData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@@ -1,480 +0,0 @@
|
|||||||
<!-- 版本管理 -->
|
|
||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<!-- 搜索区域 -->
|
|
||||||
<div class="search-container">
|
|
||||||
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":">
|
|
||||||
<el-form-item prop="title" label="版本标题">
|
|
||||||
<el-input v-model="queryFormData.title" placeholder="请输入版本标题" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="status" label="状态">
|
|
||||||
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
|
|
||||||
<el-option value="draft" label="草稿" />
|
|
||||||
<el-option value="released" label="已发布" />
|
|
||||||
<el-option value="archived" label="已归档" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 时间范围,收起状态下隐藏 -->
|
|
||||||
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
|
|
||||||
<DatePicker v-model="dateRange" @update:model-value="handleDateRangeChange" />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 查询、重置、展开/收起按钮 -->
|
|
||||||
<el-form-item class="search-buttons">
|
|
||||||
<el-button type="primary" icon="search" @click="handleQuery">
|
|
||||||
查询
|
|
||||||
</el-button>
|
|
||||||
<el-button icon="refresh" @click="handleResetQuery">
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
<!-- 展开/收起 -->
|
|
||||||
<template v-if="isExpandable">
|
|
||||||
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
|
|
||||||
{{ isExpand ? "收起" : "展开" }}
|
|
||||||
<el-icon>
|
|
||||||
<template v-if="isExpand">
|
|
||||||
<ArrowUp />
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<ArrowDown />
|
|
||||||
</template>
|
|
||||||
</el-icon>
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<el-card shadow="hover" class="data-table">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>
|
|
||||||
<el-tooltip content="版本管理维护系统的版本信息。">
|
|
||||||
<QuestionFilled class="w-4 h-4 mx-1" />
|
|
||||||
</el-tooltip>
|
|
||||||
版本列表
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 功能区域 -->
|
|
||||||
<div class="data-table__toolbar">
|
|
||||||
<div class="data-table__toolbar--actions">
|
|
||||||
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
|
|
||||||
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="data-table__toolbar--tools">
|
|
||||||
<el-tooltip content="刷新">
|
|
||||||
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="列表筛选">
|
|
||||||
<el-dropdown trigger="click">
|
|
||||||
<el-button type="default" icon="operation" circle />
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
|
|
||||||
<el-checkbox v-model="column.show">
|
|
||||||
{{ column.label }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 表格区域:版本列表 -->
|
|
||||||
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
|
|
||||||
<template #empty>
|
|
||||||
<el-empty :image-size="80" description="暂无数据" />
|
|
||||||
</template>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" fixed label="序号" min-width="60">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'version_number')?.show" label="版本号" prop="version_number" min-width="100" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'title')?.show" label="版本标题" prop="title" min-width="140" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
|
||||||
{{ getStatusLabel(scope.row.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'project')?.show" label="所属项目" prop="project" min-width="120" />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'release_notes')?.show" label="发布说明" prop="release_notes" min-width="200" show-overflow-tooltip />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'released_at')?.show" label="发布时间" prop="released_at" min-width="180" sortable />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="180" sortable />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="180" sortable />
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.creator?.name }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
|
|
||||||
<el-button type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
|
|
||||||
<el-button type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页区域 -->
|
|
||||||
<template #footer>
|
|
||||||
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
|
|
||||||
</template>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 弹窗区域 -->
|
|
||||||
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
|
|
||||||
<!-- 详情 -->
|
|
||||||
<template v-if="dialogVisible.type === 'detail'">
|
|
||||||
<el-descriptions :column="4" border>
|
|
||||||
<el-descriptions-item label="版本号" :span="2">
|
|
||||||
{{ detailFormData.version_number }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="版本标题" :span="2">
|
|
||||||
{{ detailFormData.title }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态" :span="2">
|
|
||||||
<el-tag :type="getStatusTagType(detailFormData.status)">
|
|
||||||
{{ getStatusLabel(detailFormData.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="所属项目" :span="2">
|
|
||||||
{{ detailFormData.project }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="发布说明" :span="4">
|
|
||||||
{{ detailFormData.release_notes }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="发布时间" :span="2">
|
|
||||||
{{ detailFormData.released_at }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="描述" :span="2">
|
|
||||||
{{ detailFormData.description }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建人" :span="2">
|
|
||||||
{{ detailFormData.creator?.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建时间" :span="2">
|
|
||||||
{{ detailFormData.created_at }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="更新时间" :span="2">
|
|
||||||
{{ detailFormData.updated_at }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</template>
|
|
||||||
<!-- 新增、编辑表单 -->
|
|
||||||
<template v-else>
|
|
||||||
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
|
|
||||||
<el-form-item label="版本号" prop="version_number">
|
|
||||||
<el-input v-model="formData.version_number" placeholder="请输入版本号" :maxlength="50" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="版本标题" prop="title">
|
|
||||||
<el-input v-model="formData.title" placeholder="请输入版本标题" :maxlength="255" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="状态" prop="status">
|
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
|
||||||
<el-option value="draft" label="草稿" />
|
|
||||||
<el-option value="released" label="已发布" />
|
|
||||||
<el-option value="archived" label="已归档" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="所属项目" prop="project">
|
|
||||||
<el-input v-model="formData.project" placeholder="请输入所属项目" :maxlength="100" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="发布说明" prop="release_notes">
|
|
||||||
<el-input v-model="formData.release_notes" :rows="4" type="textarea" placeholder="请输入发布说明" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="发布时间" prop="released_at">
|
|
||||||
<el-date-picker v-model="formData.released_at" type="datetime" placeholder="请选择发布时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="描述" prop="description">
|
|
||||||
<el-input v-model="formData.description" :rows="4" :maxlength="500" show-word-limit type="textarea" placeholder="请输入描述" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
|
|
||||||
<el-button @click="handleCloseDialog">取消</el-button>
|
|
||||||
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
|
|
||||||
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineOptions({
|
|
||||||
name: "Version",
|
|
||||||
inheritAttrs: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
import VersionAPI, { VersionTable, VersionForm, VersionPageQuery } from "@/api/system/version";
|
|
||||||
import { ElMessageBox } from "element-plus";
|
|
||||||
|
|
||||||
const queryFormRef = ref();
|
|
||||||
const dataFormRef = ref();
|
|
||||||
const total = ref(0);
|
|
||||||
const selectIds = ref<number[]>([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const isExpand = ref(false);
|
|
||||||
const isExpandable = ref(true);
|
|
||||||
|
|
||||||
// 分页表单
|
|
||||||
const pageTableData = ref<VersionTable[]>([]);
|
|
||||||
|
|
||||||
// 表格列配置
|
|
||||||
const tableColumns = ref([
|
|
||||||
{ prop: 'selection', label: '选择框', show: true },
|
|
||||||
{ prop: 'index', label: '序号', show: true },
|
|
||||||
{ prop: 'version_number', label: '版本号', show: true },
|
|
||||||
{ prop: 'title', label: '版本标题', show: true },
|
|
||||||
{ prop: 'status', label: '状态', show: true },
|
|
||||||
{ prop: 'project', label: '所属项目', show: true },
|
|
||||||
{ prop: 'release_notes', label: '发布说明', show: true },
|
|
||||||
{ prop: 'released_at', label: '发布时间', show: true },
|
|
||||||
{ prop: 'created_at', label: '创建时间', show: true },
|
|
||||||
{ prop: 'updated_at', label: '更新时间', show: true },
|
|
||||||
{ prop: 'creator', label: '创建人', show: true },
|
|
||||||
{ prop: 'operation', label: '操作', show: true }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 详情表单
|
|
||||||
const detailFormData = ref<VersionTable>({});
|
|
||||||
|
|
||||||
// 分页查询参数
|
|
||||||
const queryFormData = reactive<VersionPageQuery>({
|
|
||||||
page_no: 1,
|
|
||||||
page_size: 10,
|
|
||||||
title: undefined,
|
|
||||||
status: undefined,
|
|
||||||
start_time: undefined,
|
|
||||||
end_time: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 编辑表单
|
|
||||||
const formData = reactive<VersionForm>({
|
|
||||||
id: undefined,
|
|
||||||
version_number: '',
|
|
||||||
title: '',
|
|
||||||
release_notes: undefined,
|
|
||||||
project: undefined,
|
|
||||||
status: 'draft',
|
|
||||||
released_at: undefined,
|
|
||||||
description: undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 弹窗状态
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: 'create' as 'create' | 'update' | 'detail',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表单验证规则
|
|
||||||
const rules = reactive({
|
|
||||||
version_number: [{ required: true, message: "请输入版本号", trigger: "blur" }],
|
|
||||||
title: [{ required: true, message: "请输入版本标题", trigger: "blur" }],
|
|
||||||
// status字段在后端有默认值'draft',因此不是必填项
|
|
||||||
});
|
|
||||||
|
|
||||||
// 日期范围临时变量
|
|
||||||
const dateRange = ref<[Date, Date] | []>([]);
|
|
||||||
|
|
||||||
// 处理日期范围变化
|
|
||||||
function handleDateRangeChange(range: [Date, Date]) {
|
|
||||||
dateRange.value = range;
|
|
||||||
if (range && range.length === 2) {
|
|
||||||
queryFormData.start_time = range[0]?.toISOString();
|
|
||||||
queryFormData.end_time = range[1]?.toISOString();
|
|
||||||
} else {
|
|
||||||
queryFormData.start_time = undefined;
|
|
||||||
queryFormData.end_time = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态标签类型
|
|
||||||
function getStatusTagType(status: string | undefined) {
|
|
||||||
switch (status) {
|
|
||||||
case 'draft': return 'info';
|
|
||||||
case 'released': return 'success';
|
|
||||||
case 'archived': return 'warning';
|
|
||||||
default: return 'info';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态标签文本
|
|
||||||
function getStatusLabel(status: string | undefined) {
|
|
||||||
switch (status) {
|
|
||||||
case 'draft': return '草稿';
|
|
||||||
case 'released': return '已发布';
|
|
||||||
case 'archived': return '已归档';
|
|
||||||
default: return status || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表刷新
|
|
||||||
async function handleRefresh() {
|
|
||||||
await loadingData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载表格数据
|
|
||||||
async function loadingData() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const response = await VersionAPI.getVersionList(queryFormData);
|
|
||||||
pageTableData.value = response.data.data.items;
|
|
||||||
total.value = response.data.data.total;
|
|
||||||
}
|
|
||||||
catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询(重置页码后获取数据)
|
|
||||||
async function handleQuery() {
|
|
||||||
queryFormData.page_no = 1;
|
|
||||||
loadingData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置查询
|
|
||||||
async function handleResetQuery() {
|
|
||||||
queryFormRef.value.resetFields();
|
|
||||||
queryFormData.page_no = 1;
|
|
||||||
dateRange.value = [];
|
|
||||||
queryFormData.start_time = undefined;
|
|
||||||
queryFormData.end_time = undefined;
|
|
||||||
loadingData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义初始表单数据常量
|
|
||||||
const initialFormData: VersionForm = {
|
|
||||||
id: undefined,
|
|
||||||
version_number: '',
|
|
||||||
title: '',
|
|
||||||
release_notes: undefined,
|
|
||||||
project: undefined,
|
|
||||||
status: 'draft',
|
|
||||||
released_at: undefined,
|
|
||||||
description: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
async function resetForm() {
|
|
||||||
if (dataFormRef.value) {
|
|
||||||
dataFormRef.value.resetFields();
|
|
||||||
dataFormRef.value.clearValidate();
|
|
||||||
}
|
|
||||||
// 完全重置 formData 为初始状态
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 行复选框选中项变化
|
|
||||||
async function handleSelectionChange(selection: any) {
|
|
||||||
selectIds.value = selection.map((item: any) => item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭弹窗
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开弹窗
|
|
||||||
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await VersionAPI.getVersionDetail(id);
|
|
||||||
if (type === 'detail') {
|
|
||||||
dialogVisible.title = "版本详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data);
|
|
||||||
} else if (type === 'update') {
|
|
||||||
dialogVisible.title = "修改版本";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增版本";
|
|
||||||
formData.id = undefined;
|
|
||||||
}
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单(防抖)
|
|
||||||
async function handleSubmit() {
|
|
||||||
// 表单校验
|
|
||||||
dataFormRef.value.validate(async (valid: any) => {
|
|
||||||
if (valid) {
|
|
||||||
loading.value = true;
|
|
||||||
// 根据弹窗传入的参数(detail\create\update)判断走什么逻辑
|
|
||||||
const id = formData.id;
|
|
||||||
if (id) {
|
|
||||||
try {
|
|
||||||
await VersionAPI.updateVersion(id, { id, ...formData })
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
handleCloseDialog();
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await VersionAPI.createVersion(formData)
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
resetForm();
|
|
||||||
handleCloseDialog();
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除、批量删除
|
|
||||||
async function handleDelete(ids: number[]) {
|
|
||||||
ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
await VersionAPI.deleteVersion(ids);
|
|
||||||
handleResetQuery();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
ElMessageBox.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
// 加载表格数据
|
|
||||||
loadingData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
Reference in New Issue
Block a user