mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-09-21 12:52:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b974e8bd3d | ||
|
|
a389ef66bc | ||
|
|
dcdeaa0394 | ||
|
|
f4d7d7a416 | ||
|
|
f5a91c0ece | ||
|
|
5ba6587b6c | ||
|
|
f2de18aa43 | ||
|
|
24cc7e360a | ||
|
|
3c5f6bf873 | ||
|
|
81c9b82abd | ||
|
|
ac3056b9a7 | ||
|
|
10719a4067 | ||
|
|
5597eb651f | ||
|
|
385013db9d | ||
|
|
2699039c0b | ||
|
|
831768dafd | ||
|
|
6ea37823c3 | ||
|
|
67e9f6c98b | ||
|
|
f19f192170 | ||
|
|
7eb4b75ab1 | ||
|
|
d0138516e7 | ||
|
|
ae4f37f1c5 | ||
|
|
41a9d1edf6 | ||
|
|
61fba98d89 | ||
|
|
cc6a35d7c7 | ||
|
|
ea0303835f | ||
|
|
7b993655ba | ||
|
|
d9d7c94513 | ||
|
|
cc6b38acdd | ||
|
|
27d847fd25 | ||
|
|
9e76400c8e | ||
|
|
c365459d88 | ||
|
|
079b2eefb6 | ||
|
|
c6d4c6c1a9 | ||
|
|
9b6355c68d | ||
|
|
ccacee5f95 | ||
|
|
397a51bd96 | ||
|
|
f144945215 | ||
|
|
a1c78172ca | ||
|
|
f03ab1a845 | ||
|
|
a64eca3aeb | ||
|
|
bc547f90f9 | ||
|
|
7a66854a22 | ||
|
|
795a9204ae | ||
|
|
11debaf213 | ||
|
|
857b7dba80 | ||
|
|
5d6bd822d3 | ||
|
|
0b7df2cfc1 | ||
|
|
20bc7ebaaa | ||
|
|
c1a8060139 | ||
|
|
5558dd181b | ||
|
|
0fd4fb283a | ||
|
|
00a7af2d5b | ||
|
|
e34a493b3e | ||
|
|
a9df9ea56a | ||
|
|
d89c0df425 | ||
|
|
d0730e7993 | ||
|
|
5fe376f978 | ||
|
|
dd5b1c55c9 | ||
|
|
7645c6d9fd | ||
|
|
61073970fa | ||
|
|
9ae2ac02eb | ||
|
|
6622c329fc | ||
|
|
b66d545985 | ||
|
|
1789cb5a9b | ||
|
|
ee376c477d | ||
|
|
2b6f0905a9 | ||
|
|
c953754bed | ||
|
|
cea4a10baa | ||
|
|
b708d86eff | ||
|
|
28aab8c7d4 | ||
|
|
9412e3f344 | ||
|
|
b4d5619b1b | ||
|
|
8ce598ad54 | ||
|
|
ca641055e0 |
@@ -136,3 +136,10 @@ dmypy.json
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.6.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.7.1</h1>
|
||||
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a>
|
||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.6.0-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.7.1-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
|
||||
<img src="https://img.shields.io/badge/node-≥18-blue">
|
||||
<img src="https://img.shields.io/badge/python-≥3.9-blue">
|
||||
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue">
|
||||
</p>
|
||||
|
||||
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.6.0'
|
||||
APP_VERSION= '1.7.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = true
|
||||
# 应用是否开启IP归属区域查询
|
||||
|
||||
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.6.0'
|
||||
APP_VERSION= '1.7.1'
|
||||
# 应用是否开启热重载
|
||||
APP_RELOAD = false
|
||||
# 应用是否开启IP归属区域查询
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts.
|
||||
# this is typically a path given in POSIX (e.g. forward slashes)
|
||||
# format, relative to the token %(here)s which refers to the location of this
|
||||
# ini file
|
||||
script_location = %(here)s/alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||
# for all available tokens
|
||||
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory. for multiple paths, the path separator
|
||||
# is defined by "path_separator" below.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to <script_location>/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "path_separator"
|
||||
# below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
|
||||
|
||||
# path_separator; This indicates what character is used to split lists of file
|
||||
# paths, including version_locations and prepend_sys_path within configparser
|
||||
# files such as alembic.ini.
|
||||
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
|
||||
# to provide os-dependent path splitting.
|
||||
#
|
||||
# Note that in order to support legacy alembic.ini files, this default does NOT
|
||||
# take place if path_separator is not present in alembic.ini. If this
|
||||
# option is omitted entirely, fallback logic is as follows:
|
||||
#
|
||||
# 1. Parsing of the version_locations option falls back to using the legacy
|
||||
# "version_path_separator" key, which if absent then falls back to the legacy
|
||||
# behavior of splitting on spaces and/or commas.
|
||||
# 2. Parsing of the prepend_sys_path option falls back to the legacy
|
||||
# behavior of splitting on spaces, commas, or colons.
|
||||
#
|
||||
# Valid values for path_separator are:
|
||||
#
|
||||
# path_separator = :
|
||||
# path_separator = ;
|
||||
# path_separator = space
|
||||
# path_separator = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
path_separator = os
|
||||
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# database URL. This is consumed by the user-maintained env.py script only.
|
||||
# other means of configuring database URLs may be customized within the env.py
|
||||
# file.
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
|
||||
# hooks = ruff
|
||||
# ruff.type = module
|
||||
# ruff.module = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Alternatively, use the exec runner to execute a binary found on your PATH
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
|
||||
[settings]
|
||||
# This section is used to set environment variables for the alembic.
|
||||
# The env option is used to specify the environment for the alembic.
|
||||
# It can be set to dev or prod.
|
||||
env = dev
|
||||
@@ -0,0 +1 @@
|
||||
Generic single-database configuration with an async dbapi.
|
||||
@@ -0,0 +1,116 @@
|
||||
import asyncio
|
||||
import os
|
||||
from alembic import context
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
from config.database import Base, ASYNC_SQLALCHEMY_DATABASE_URL
|
||||
from utils.import_util import ImportUtil
|
||||
|
||||
|
||||
# 判断vesrions目录是否存在,如果不存在则创建
|
||||
alembic_veresions_path = 'alembic/versions'
|
||||
if not os.path.exists(alembic_veresions_path):
|
||||
os.makedirs(alembic_veresions_path)
|
||||
|
||||
|
||||
# 自动查找所有模型
|
||||
found_models = ImportUtil.find_models(Base)
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
alembic_config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if alembic_config.config_file_name is not None:
|
||||
fileConfig(alembic_config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
target_metadata = Base.metadata
|
||||
# ASYNC_SQLALCHEMY_DATABASE_URL = 'mysql+asyncmy://root:mysqlroot@127.0.0.1:3306/ruoyi-fastapi'
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
alembic_config.set_main_option('sqlalchemy.url', ASYNC_SQLALCHEMY_DATABASE_URL)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = alembic_config.get_main_option('sqlalchemy.url')
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={'paramstyle': 'named'},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
def process_revision_directives(context, revision, directives):
|
||||
script = directives[0]
|
||||
|
||||
# 检查所有操作集是否为空
|
||||
all_empty = all(ops.is_empty() for ops in script.upgrade_ops_list)
|
||||
|
||||
if all_empty:
|
||||
# 如果没有实际变更,不生成迁移文件
|
||||
directives[:] = []
|
||||
print('❎️ 未检测到模型变更,不生成迁移文件')
|
||||
else:
|
||||
print('✅️ 检测到模型变更,生成迁移文件')
|
||||
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True,
|
||||
compare_server_default=True,
|
||||
transaction_per_migration=True,
|
||||
process_revision_directives=process_revision_directives,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
connectable = async_engine_from_config(
|
||||
alembic_config.get_section(alembic_config.config_ini_section, {}),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode."""
|
||||
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,28 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -252,6 +252,8 @@ class GenConstant:
|
||||
'double',
|
||||
'decimal',
|
||||
]
|
||||
COLUMNNAME_NOT_ADD_SHOW = ['create_by', 'create_time']
|
||||
COLUMNNAME_NOT_EDIT_SHOW = ['update_by', 'update_time']
|
||||
COLUMNNAME_NOT_EDIT = ['id', 'create_by', 'create_time', 'del_flag']
|
||||
COLUMNNAME_NOT_LIST = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time']
|
||||
COLUMNNAME_NOT_QUERY = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time', 'remark']
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import argparse
|
||||
import configparser
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
from functools import lru_cache
|
||||
from pydantic import computed_field
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Literal
|
||||
|
||||
@@ -51,6 +53,13 @@ class DataBaseSettings(BaseSettings):
|
||||
db_pool_recycle: int = 3600
|
||||
db_pool_timeout: int = 30
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def sqlglot_parse_dialect(self) -> str:
|
||||
if self.db_type == 'postgresql':
|
||||
return 'postgres'
|
||||
return self.db_type
|
||||
|
||||
|
||||
class RedisSettings(BaseSettings):
|
||||
"""
|
||||
@@ -198,7 +207,15 @@ class GetConfig:
|
||||
"""
|
||||
解析命令行参数
|
||||
"""
|
||||
if 'uvicorn' in sys.argv[0]:
|
||||
# 检查是否在alembic环境中运行,如果是则跳过参数解析
|
||||
if 'alembic' in sys.argv[0] or any('alembic' in arg for arg in sys.argv):
|
||||
ini_config = configparser.ConfigParser()
|
||||
ini_config.read('alembic.ini', encoding='utf-8')
|
||||
if 'settings' in ini_config:
|
||||
# 获取env选项
|
||||
env_value = ini_config['settings'].get('env')
|
||||
os.environ['APP_ENV'] = env_value if env_value else 'dev'
|
||||
elif 'uvicorn' in sys.argv[0]:
|
||||
# 使用uvicorn启动时,命令行参数需要按照uvicorn的文档进行配置,无法自定义参数
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -18,7 +18,7 @@ async def init_create_table():
|
||||
|
||||
:return:
|
||||
"""
|
||||
logger.info('初始化数据库连接...')
|
||||
logger.info('🔎 初始化数据库连接...')
|
||||
async with async_engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
logger.info('数据库连接成功')
|
||||
logger.info('✅️ 数据库连接成功')
|
||||
|
||||
@@ -19,7 +19,7 @@ class RedisUtil:
|
||||
|
||||
:return: Redis连接对象
|
||||
"""
|
||||
logger.info('开始连接redis...')
|
||||
logger.info('🔎 开始连接redis...')
|
||||
redis = await aioredis.from_url(
|
||||
url=f'redis://{RedisConfig.redis_host}',
|
||||
port=RedisConfig.redis_port,
|
||||
@@ -32,15 +32,15 @@ class RedisUtil:
|
||||
try:
|
||||
connection = await redis.ping()
|
||||
if connection:
|
||||
logger.info('redis连接成功')
|
||||
logger.info('✅️ redis连接成功')
|
||||
else:
|
||||
logger.error('redis连接失败')
|
||||
logger.error('❌️ redis连接失败')
|
||||
except AuthenticationError as e:
|
||||
logger.error(f'redis用户名或密码错误,详细错误信息:{e}')
|
||||
logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}')
|
||||
except TimeoutError as e:
|
||||
logger.error(f'redis连接超时,详细错误信息:{e}')
|
||||
logger.error(f'❌️ redis连接超时,详细错误信息:{e}')
|
||||
except RedisError as e:
|
||||
logger.error(f'redis连接错误,详细错误信息:{e}')
|
||||
logger.error(f'❌️ redis连接错误,详细错误信息:{e}')
|
||||
return redis
|
||||
|
||||
@classmethod
|
||||
@@ -52,7 +52,7 @@ class RedisUtil:
|
||||
:return:
|
||||
"""
|
||||
await app.state.redis.close()
|
||||
logger.info('关闭redis连接成功')
|
||||
logger.info('✅️ 关闭redis连接成功')
|
||||
|
||||
@classmethod
|
||||
async def init_sys_dict(cls, redis):
|
||||
|
||||
@@ -131,7 +131,7 @@ class SchedulerUtil:
|
||||
|
||||
:return:
|
||||
"""
|
||||
logger.info('开始启动定时任务...')
|
||||
logger.info('🔎 开始启动定时任务...')
|
||||
scheduler.start()
|
||||
async with AsyncSessionLocal() as session:
|
||||
job_list = await JobDao.get_job_list_for_scheduler(session)
|
||||
@@ -139,7 +139,7 @@ class SchedulerUtil:
|
||||
cls.remove_scheduler_job(job_id=str(item.job_id))
|
||||
cls.add_scheduler_job(item)
|
||||
scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)
|
||||
logger.info('系统初始定时任务加载成功')
|
||||
logger.info('✅️ 系统初始定时任务加载成功')
|
||||
|
||||
@classmethod
|
||||
async def close_system_scheduler(cls):
|
||||
@@ -149,7 +149,7 @@ class SchedulerUtil:
|
||||
:return:
|
||||
"""
|
||||
scheduler.shutdown()
|
||||
logger.info('关闭定时任务成功')
|
||||
logger.info('✅️ 关闭定时任务成功')
|
||||
|
||||
@classmethod
|
||||
def get_scheduler_job(cls, job_id: Union[str, int]):
|
||||
@@ -201,9 +201,12 @@ class SchedulerUtil:
|
||||
job_executor = job_info.job_executor
|
||||
if iscoroutinefunction(job_func):
|
||||
job_executor = 'default'
|
||||
job_trigger = DateTrigger()
|
||||
if job_info.status == '0':
|
||||
job_trigger = OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)])
|
||||
scheduler.add_job(
|
||||
func=eval(job_info.invoke_target),
|
||||
trigger=OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)]),
|
||||
trigger=job_trigger,
|
||||
args=job_info.job_args.split(',') if job_info.job_args else None,
|
||||
kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
|
||||
id=str(job_info.job_id),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import httpx
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
from async_lru import alru_cache
|
||||
from datetime import datetime
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
|
||||
from functools import lru_cache, wraps
|
||||
from functools import wraps
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Any, Callable, Literal, Optional
|
||||
from user_agents import parse
|
||||
@@ -46,7 +47,7 @@ class Log:
|
||||
def __call__(self, func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
start_time = time.perf_counter()
|
||||
# 获取被装饰函数的文件路径
|
||||
file_path = inspect.getfile(func)
|
||||
# 获取项目根路径
|
||||
@@ -74,7 +75,7 @@ class Log:
|
||||
oper_ip = request.headers.get('X-Forwarded-For')
|
||||
oper_location = '内网IP'
|
||||
if AppConfig.app_ip_location_query:
|
||||
oper_location = get_ip_location(oper_ip)
|
||||
oper_location = await get_ip_location(oper_ip)
|
||||
# 根据不同的请求类型使用不同的方法获取请求参数
|
||||
content_type = request.headers.get('Content-Type')
|
||||
if content_type and (
|
||||
@@ -129,7 +130,7 @@ class Log:
|
||||
logger.exception(e)
|
||||
result = ResponseUtil.error(msg=str(e))
|
||||
# 获取请求耗时
|
||||
cost_time = float(time.time() - start_time) * 100
|
||||
cost_time = float(time.perf_counter() - start_time) * 1000
|
||||
# 判断请求是否来自api文档
|
||||
request_from_swagger = (
|
||||
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
|
||||
@@ -203,8 +204,8 @@ class Log:
|
||||
return wrapper
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_ip_location(oper_ip: str):
|
||||
@alru_cache()
|
||||
async def get_ip_location(oper_ip: str):
|
||||
"""
|
||||
查询ip归属区域
|
||||
|
||||
@@ -215,12 +216,13 @@ def get_ip_location(oper_ip: str):
|
||||
try:
|
||||
if oper_ip != '127.0.0.1' and oper_ip != 'localhost':
|
||||
oper_location = '未知'
|
||||
ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
|
||||
if ip_result.status_code == 200:
|
||||
prov = ip_result.json().get('data').get('prov')
|
||||
city = ip_result.json().get('data').get('city')
|
||||
if prov or city:
|
||||
oper_location = f'{prov}-{city}'
|
||||
async with httpx.AsyncClient() as client:
|
||||
ip_result = await client.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
|
||||
if ip_result.status_code == 200:
|
||||
prov = ip_result.json().get('data', {}).get('prov')
|
||||
city = ip_result.json().get('data', {}).get('city')
|
||||
if prov or city:
|
||||
oper_location = f'{prov}-{city}'
|
||||
except Exception as e:
|
||||
oper_location = '未知'
|
||||
print(e)
|
||||
|
||||
@@ -2,10 +2,13 @@ import inspect
|
||||
from fastapi import Form, Query
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import FieldInfo
|
||||
from typing import Type
|
||||
from typing import Type, TypeVar
|
||||
|
||||
|
||||
def as_query(cls: Type[BaseModel]):
|
||||
BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel)
|
||||
|
||||
|
||||
def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
|
||||
"""
|
||||
pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数
|
||||
"""
|
||||
@@ -43,7 +46,7 @@ def as_query(cls: Type[BaseModel]):
|
||||
return cls
|
||||
|
||||
|
||||
def as_form(cls: Type[BaseModel]):
|
||||
def as_form(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
|
||||
"""
|
||||
pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数
|
||||
"""
|
||||
|
||||
@@ -141,8 +141,11 @@ async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)
|
||||
payload = jwt.decode(
|
||||
token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False}
|
||||
)
|
||||
session_id: str = payload.get('session_id')
|
||||
await LoginService.logout_services(request, session_id)
|
||||
if AppConfig.app_same_time_login:
|
||||
token_id: str = payload.get('session_id')
|
||||
else:
|
||||
token_id: str = payload.get('user_id')
|
||||
await LoginService.logout_services(request, token_id)
|
||||
logger.info('退出成功')
|
||||
|
||||
return ResponseUtil.success(msg='退出成功')
|
||||
|
||||
@@ -165,6 +165,7 @@ async def reset_system_user_pwd(
|
||||
edit_user = EditUserModel(
|
||||
userId=reset_user.user_id,
|
||||
password=PwdUtil.get_password_hash(reset_user.password),
|
||||
pwdUpdateDate=datetime.now(),
|
||||
updateBy=current_user.user.user_name,
|
||||
updateTime=datetime.now(),
|
||||
type='pwd',
|
||||
@@ -304,6 +305,7 @@ async def reset_system_user_password(
|
||||
userId=current_user.user.user_id,
|
||||
oldPassword=reset_password.old_password,
|
||||
password=reset_password.new_password,
|
||||
pwdUpdateDate=datetime.now(),
|
||||
updateBy=current_user.user.user_name,
|
||||
updateTime=datetime.now(),
|
||||
)
|
||||
|
||||
@@ -104,15 +104,24 @@ class JobDao:
|
||||
return db_job
|
||||
|
||||
@classmethod
|
||||
async def edit_job_dao(cls, db: AsyncSession, job: dict):
|
||||
async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel):
|
||||
"""
|
||||
编辑定时任务数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param job: 需要更新的定时任务字典
|
||||
:param old_job: 原定时任务对象
|
||||
:return:
|
||||
"""
|
||||
await db.execute(update(SysJob), [job])
|
||||
await db.execute(
|
||||
update(SysJob)
|
||||
.where(
|
||||
SysJob.job_id == old_job.job_id,
|
||||
SysJob.job_name == old_job.job_name,
|
||||
SysJob.job_group == old_job.job_group,
|
||||
)
|
||||
.values(**job)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def delete_job_dao(cls, db: AsyncSession, job: JobModel):
|
||||
|
||||
@@ -5,6 +5,7 @@ from module_admin.entity.do.log_do import SysLogininfor, SysOperLog
|
||||
from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel
|
||||
from utils.common_util import SnakeCaseUtil
|
||||
from utils.page_util import PageUtil
|
||||
from utils.time_format_util import TimeFormatUtil
|
||||
|
||||
|
||||
class OperationLogDao:
|
||||
@@ -38,8 +39,8 @@ class OperationLogDao:
|
||||
SysOperLog.business_type == query_object.business_type if query_object.business_type else True,
|
||||
SysOperLog.status == query_object.status if query_object.status else True,
|
||||
SysOperLog.oper_time.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
@@ -120,8 +121,8 @@ class LoginLogDao:
|
||||
SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
|
||||
SysLogininfor.status == query_object.status if query_object.status else True,
|
||||
SysLogininfor.login_time.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
|
||||
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import CHAR, Column, DateTime, Integer, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysConfig(Base):
|
||||
@@ -9,14 +11,20 @@ class SysConfig(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_config'
|
||||
__table_args__ = {'comment': '参数配置表'}
|
||||
|
||||
config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键')
|
||||
config_name = Column(String(100), nullable=True, default='', comment='参数名称')
|
||||
config_key = Column(String(100), nullable=True, default='', comment='参数键名')
|
||||
config_value = Column(String(500), nullable=True, default='', comment='参数键值')
|
||||
config_type = Column(String(1), nullable=True, default='N', comment='系统内置(Y是 N否)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
config_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='参数主键')
|
||||
config_name = Column(String(100), nullable=True, server_default="''", comment='参数名称')
|
||||
config_key = Column(String(100), nullable=True, server_default="''", comment='参数键名')
|
||||
config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值')
|
||||
config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysDept(Base):
|
||||
@@ -9,18 +11,34 @@ class SysDept(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dept'
|
||||
__table_args__ = {'comment': '部门表'}
|
||||
|
||||
dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id')
|
||||
parent_id = Column(Integer, default=0, comment='父部门id')
|
||||
ancestors = Column(String(50), nullable=True, default='', comment='祖级列表')
|
||||
dept_name = Column(String(30), nullable=True, default='', comment='部门名称')
|
||||
order_num = Column(Integer, default=0, comment='显示顺序')
|
||||
leader = Column(String(20), nullable=True, default=None, comment='负责人')
|
||||
phone = Column(String(11), nullable=True, default=None, comment='联系电话')
|
||||
email = Column(String(50), nullable=True, default=None, comment='邮箱')
|
||||
status = Column(String(1), nullable=True, default='0', comment='部门状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), nullable=True, default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dept_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='部门id')
|
||||
parent_id = Column(BigInteger, server_default='0', comment='父部门id')
|
||||
ancestors = Column(String(50), nullable=True, server_default="''", comment='祖级列表')
|
||||
dept_name = Column(String(30), nullable=True, server_default="''", comment='部门名称')
|
||||
order_num = Column(Integer, server_default='0', comment='显示顺序')
|
||||
leader = Column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='负责人',
|
||||
)
|
||||
phone = Column(
|
||||
String(11),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='联系电话',
|
||||
)
|
||||
email = Column(
|
||||
String(50),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='邮箱',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysDictType(Base):
|
||||
@@ -9,18 +11,22 @@ class SysDictType(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dict_type'
|
||||
__table_args__ = {'comment': '字典类型表'}
|
||||
|
||||
dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键')
|
||||
dict_name = Column(String(100), nullable=True, default='', comment='字典名称')
|
||||
dict_type = Column(String(100), nullable=True, default='', comment='字典类型')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dict_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典主键')
|
||||
dict_name = Column(String(100), nullable=True, server_default="''", comment='字典名称')
|
||||
dict_type = Column(String(100), unique=True, nullable=True, server_default="''", comment='字典类型')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
|
||||
__table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),)
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysDictData(Base):
|
||||
@@ -29,18 +35,34 @@ class SysDictData(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_dict_data'
|
||||
__table_args__ = {'comment': '字典数据表'}
|
||||
|
||||
dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码')
|
||||
dict_sort = Column(Integer, nullable=True, default=0, comment='字典排序')
|
||||
dict_label = Column(String(100), nullable=True, default='', comment='字典标签')
|
||||
dict_value = Column(String(100), nullable=True, default='', comment='字典键值')
|
||||
dict_type = Column(String(100), nullable=True, default='', comment='字典类型')
|
||||
css_class = Column(String(100), nullable=True, default=None, comment='样式属性(其他样式扩展)')
|
||||
list_class = Column(String(100), nullable=True, default=None, comment='表格回显样式')
|
||||
is_default = Column(String(1), nullable=True, default='N', comment='是否默认(Y是 N否)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
dict_code = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='字典编码')
|
||||
dict_sort = Column(Integer, nullable=True, server_default='0', comment='字典排序')
|
||||
dict_label = Column(String(100), nullable=True, server_default="''", comment='字典标签')
|
||||
dict_value = Column(String(100), nullable=True, server_default="''", comment='字典键值')
|
||||
dict_type = Column(String(100), nullable=True, server_default="''", comment='字典类型')
|
||||
css_class = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='样式属性(其他样式扩展)',
|
||||
)
|
||||
list_class = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='表格回显样式',
|
||||
)
|
||||
is_default = Column(CHAR(1), nullable=True, server_default='N', comment='是否默认(Y是 N否)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, DOUBLE, Index, LargeBinary, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysJob(Base):
|
||||
@@ -9,28 +11,29 @@ class SysJob(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_job'
|
||||
__table_args__ = {'comment': '定时任务调度表'}
|
||||
|
||||
job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID')
|
||||
job_name = Column(String(64), nullable=True, default='', comment='任务名称')
|
||||
job_group = Column(String(64), nullable=True, default='default', comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=True, default='default', comment='任务执行器')
|
||||
job_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务ID')
|
||||
job_name = Column(String(64), primary_key=True, nullable=False, server_default="''", comment='任务名称')
|
||||
job_group = Column(String(64), primary_key=True, nullable=False, server_default='default', comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=True, server_default='default', comment='任务执行器')
|
||||
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
|
||||
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
|
||||
cron_expression = Column(String(255), nullable=True, default='', comment='cron执行表达式')
|
||||
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
|
||||
cron_expression = Column(String(255), nullable=True, server_default="''", comment='cron执行表达式')
|
||||
misfire_policy = Column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
default='3',
|
||||
server_default='3',
|
||||
comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
|
||||
)
|
||||
concurrent = Column(String(1), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='状态(0正常 1暂停)')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
concurrent = Column(CHAR(1), nullable=True, server_default='1', comment='是否并发执行(0允许 1禁止)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1暂停)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default='', comment='备注信息')
|
||||
remark = Column(String(500), nullable=True, server_default="''", comment='备注信息')
|
||||
|
||||
|
||||
class SysJobLog(Base):
|
||||
@@ -39,16 +42,33 @@ class SysJobLog(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_job_log'
|
||||
__table_args__ = {'comment': '定时任务调度日志表'}
|
||||
|
||||
job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID')
|
||||
job_log_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='任务日志ID')
|
||||
job_name = Column(String(64), nullable=False, comment='任务名称')
|
||||
job_group = Column(String(64), nullable=False, comment='任务组名')
|
||||
job_executor = Column(String(64), nullable=False, comment='任务执行器')
|
||||
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
|
||||
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
|
||||
job_trigger = Column(String(255), nullable=True, default='', comment='任务触发器')
|
||||
job_message = Column(String(500), nullable=True, default='', comment='日志信息')
|
||||
status = Column(String(1), nullable=True, default='0', comment='执行状态(0正常 1失败)')
|
||||
exception_info = Column(String(2000), nullable=True, default='', comment='异常信息')
|
||||
job_args = Column(String(255), nullable=True, server_default="''", comment='位置参数')
|
||||
job_kwargs = Column(String(255), nullable=True, server_default="''", comment='关键字参数')
|
||||
job_trigger = Column(String(255), nullable=True, server_default="''", comment='任务触发器')
|
||||
job_message = Column(String(500), nullable=True, comment='日志信息')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='执行状态(0正常 1失败)')
|
||||
exception_info = Column(String(2000), nullable=True, server_default="''", comment='异常信息')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
|
||||
|
||||
class ApschedulerJobs(Base):
|
||||
"""
|
||||
定时任务调度任务表
|
||||
"""
|
||||
|
||||
__tablename__ = 'apscheduler_jobs'
|
||||
|
||||
id = Column(String(191), primary_key=True, nullable=False)
|
||||
next_run_time = Column(
|
||||
DOUBLE, nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False)
|
||||
)
|
||||
job_state = Column(LargeBinary, nullable=False)
|
||||
|
||||
idx_sys_logininfor_s = Index('ix_apscheduler_jobs_next_run_time', next_run_time)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Index, Integer, String
|
||||
from config.database import Base
|
||||
|
||||
|
||||
@@ -9,15 +9,16 @@ class SysLogininfor(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_logininfor'
|
||||
__table_args__ = {'comment': '系统访问记录'}
|
||||
|
||||
info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID')
|
||||
user_name = Column(String(50), nullable=True, default='', comment='用户账号')
|
||||
ipaddr = Column(String(128), nullable=True, default='', comment='登录IP地址')
|
||||
login_location = Column(String(255), nullable=True, default='', comment='登录地点')
|
||||
browser = Column(String(50), nullable=True, default='', comment='浏览器类型')
|
||||
os = Column(String(50), nullable=True, default='', comment='操作系统')
|
||||
status = Column(String(1), nullable=True, default='0', comment='登录状态(0成功 1失败)')
|
||||
msg = Column(String(255), nullable=True, default='', comment='提示消息')
|
||||
info_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='访问ID')
|
||||
user_name = Column(String(50), nullable=True, server_default="''", comment='用户账号')
|
||||
ipaddr = Column(String(128), nullable=True, server_default="''", comment='登录IP地址')
|
||||
login_location = Column(String(255), nullable=True, server_default="''", comment='登录地点')
|
||||
browser = Column(String(50), nullable=True, server_default="''", comment='浏览器类型')
|
||||
os = Column(String(50), nullable=True, server_default="''", comment='操作系统')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='登录状态(0成功 1失败)')
|
||||
msg = Column(String(255), nullable=True, server_default="''", comment='提示消息')
|
||||
login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间')
|
||||
|
||||
idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status)
|
||||
@@ -30,24 +31,25 @@ class SysOperLog(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_oper_log'
|
||||
__table_args__ = {'comment': '操作日志记录'}
|
||||
|
||||
oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键')
|
||||
title = Column(String(50), nullable=True, default='', comment='模块标题')
|
||||
business_type = Column(Integer, default=0, comment='业务类型(0其它 1新增 2修改 3删除)')
|
||||
method = Column(String(100), nullable=True, default='', comment='方法名称')
|
||||
request_method = Column(String(10), nullable=True, default='', comment='请求方式')
|
||||
operator_type = Column(Integer, default=0, comment='操作类别(0其它 1后台用户 2手机端用户)')
|
||||
oper_name = Column(String(50), nullable=True, default='', comment='操作人员')
|
||||
dept_name = Column(String(50), nullable=True, default='', comment='部门名称')
|
||||
oper_url = Column(String(255), nullable=True, default='', comment='请求URL')
|
||||
oper_ip = Column(String(128), nullable=True, default='', comment='主机地址')
|
||||
oper_location = Column(String(255), nullable=True, default='', comment='操作地点')
|
||||
oper_param = Column(String(2000), nullable=True, default='', comment='请求参数')
|
||||
json_result = Column(String(2000), nullable=True, default='', comment='返回参数')
|
||||
status = Column(Integer, default=0, comment='操作状态(0正常 1异常)')
|
||||
error_msg = Column(String(2000), nullable=True, default='', comment='错误消息')
|
||||
oper_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='日志主键')
|
||||
title = Column(String(50), nullable=True, server_default="''", comment='模块标题')
|
||||
business_type = Column(Integer, nullable=True, server_default='0', comment='业务类型(0其它 1新增 2修改 3删除)')
|
||||
method = Column(String(100), nullable=True, server_default="''", comment='方法名称')
|
||||
request_method = Column(String(10), nullable=True, server_default="''", comment='请求方式')
|
||||
operator_type = Column(Integer, nullable=True, server_default='0', comment='操作类别(0其它 1后台用户 2手机端用户)')
|
||||
oper_name = Column(String(50), nullable=True, server_default="''", comment='操作人员')
|
||||
dept_name = Column(String(50), nullable=True, server_default="''", comment='部门名称')
|
||||
oper_url = Column(String(255), nullable=True, server_default="''", comment='请求URL')
|
||||
oper_ip = Column(String(128), nullable=True, server_default="''", comment='主机地址')
|
||||
oper_location = Column(String(255), nullable=True, server_default="''", comment='操作地点')
|
||||
oper_param = Column(String(2000), nullable=True, server_default="''", comment='请求参数')
|
||||
json_result = Column(String(2000), nullable=True, server_default="''", comment='返回参数')
|
||||
status = Column(Integer, nullable=True, server_default='0', comment='操作状态(0正常 1异常)')
|
||||
error_msg = Column(String(2000), nullable=True, server_default="''", comment='错误消息')
|
||||
oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间')
|
||||
cost_time = Column(BigInteger, default=0, comment='消耗时间')
|
||||
cost_time = Column(BigInteger, nullable=True, server_default='0', comment='消耗时间')
|
||||
|
||||
idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type)
|
||||
idx_sys_oper_log_s = Index('idx_sys_oper_log_s', status)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysMenu(Base):
|
||||
@@ -9,24 +11,40 @@ class SysMenu(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_menu'
|
||||
__table_args__ = {'comment': '菜单权限表'}
|
||||
|
||||
menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID')
|
||||
menu_name = Column(String(50), nullable=False, default='', comment='菜单名称')
|
||||
parent_id = Column(Integer, default=0, comment='父菜单ID')
|
||||
order_num = Column(Integer, default=0, comment='显示顺序')
|
||||
path = Column(String(200), nullable=True, default='', comment='路由地址')
|
||||
component = Column(String(255), nullable=True, default=None, comment='组件路径')
|
||||
query = Column(String(255), nullable=True, default=None, comment='路由参数')
|
||||
route_name = Column(String(50), nullable=True, default='', comment='路由名称')
|
||||
is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)')
|
||||
is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)')
|
||||
menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)')
|
||||
visible = Column(String(1), nullable=True, default='0', comment='菜单状态(0显示 1隐藏)')
|
||||
status = Column(String(1), nullable=True, default='0', comment='菜单状态(0正常 1停用)')
|
||||
perms = Column(String(100), nullable=True, default=None, comment='权限标识')
|
||||
icon = Column(String(100), nullable=True, default='#', comment='菜单图标')
|
||||
create_by = Column(String(64), nullable=True, default='', comment='创建者')
|
||||
menu_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='菜单ID')
|
||||
menu_name = Column(String(50), nullable=False, comment='菜单名称')
|
||||
parent_id = Column(BigInteger, nullable=True, server_default='0', comment='父菜单ID')
|
||||
order_num = Column(Integer, server_default='0', comment='显示顺序')
|
||||
path = Column(String(200), nullable=True, server_default="''", comment='路由地址')
|
||||
component = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='组件路径',
|
||||
)
|
||||
query = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='路由参数',
|
||||
)
|
||||
route_name = Column(String(50), nullable=True, server_default="''", comment='路由名称')
|
||||
is_frame = Column(Integer, nullable=True, server_default='1', comment='是否为外链(0是 1否)')
|
||||
is_cache = Column(Integer, nullable=True, server_default='0', comment='是否缓存(0缓存 1不缓存)')
|
||||
menu_type = Column(CHAR(1), nullable=True, server_default="''", comment='菜单类型(M目录 C菜单 F按钮)')
|
||||
visible = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0显示 1隐藏)')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='菜单状态(0正常 1停用)')
|
||||
perms = Column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='权限标识',
|
||||
)
|
||||
icon = Column(String(100), nullable=True, server_default='#', comment='菜单图标')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default='', comment='备注')
|
||||
remark = Column(String(500), nullable=True, server_default="''", comment='备注')
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, LargeBinary, String
|
||||
from sqlalchemy import CHAR, Column, DateTime, Integer, LargeBinary, String
|
||||
from sqlalchemy.dialects import mysql
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysNotice(Base):
|
||||
@@ -9,14 +12,25 @@ class SysNotice(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_notice'
|
||||
__table_args__ = {'comment': '通知公告表'}
|
||||
|
||||
notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID')
|
||||
notice_id = Column(Integer, primary_key=True, nullable=False, autoincrement=True, comment='公告ID')
|
||||
notice_title = Column(String(50), nullable=False, comment='公告标题')
|
||||
notice_type = Column(String(1), nullable=False, comment='公告类型(1通知 2公告)')
|
||||
notice_content = Column(LargeBinary, comment='公告内容')
|
||||
status = Column(String(1), default='0', comment='公告状态(0正常 1关闭)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
|
||||
remark = Column(String(255), comment='备注')
|
||||
notice_type = Column(CHAR(1), nullable=False, comment='公告类型(1通知 2公告)')
|
||||
notice_content = Column(
|
||||
mysql.LONGBLOB if DataBaseConfig.db_type == 'mysql' else LargeBinary,
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
|
||||
comment='公告内容',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
|
||||
remark = Column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysPost(Base):
|
||||
@@ -9,14 +11,20 @@ class SysPost(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_post'
|
||||
__table_args__ = {'comment': '岗位信息表'}
|
||||
|
||||
post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID')
|
||||
post_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='岗位ID')
|
||||
post_code = Column(String(64), nullable=False, comment='岗位编码')
|
||||
post_name = Column(String(50), nullable=False, comment='岗位名称')
|
||||
post_sort = Column(Integer, nullable=False, comment='显示顺序')
|
||||
status = Column(String(1), nullable=False, default='0', comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, Integer, SmallInteger, String
|
||||
from sqlalchemy.dialects import mysql
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysRole(Base):
|
||||
@@ -9,25 +12,42 @@ class SysRole(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role'
|
||||
__table_args__ = {'comment': '角色信息表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='角色ID')
|
||||
role_name = Column(String(30), nullable=False, comment='角色名称')
|
||||
role_key = Column(String(100), nullable=False, comment='角色权限字符串')
|
||||
role_sort = Column(Integer, nullable=False, comment='显示顺序')
|
||||
data_scope = Column(
|
||||
String(1),
|
||||
default='1',
|
||||
CHAR(1),
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
|
||||
)
|
||||
menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示')
|
||||
dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示')
|
||||
status = Column(String(1), nullable=False, default='0', comment='角色状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), default=None, comment='备注')
|
||||
menu_check_strictly = Column(
|
||||
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='菜单树选择项是否关联显示',
|
||||
)
|
||||
dept_check_strictly = Column(
|
||||
mysql.TINYINT(display_width=1) if DataBaseConfig.db_type == 'mysql' else SmallInteger,
|
||||
nullable=True,
|
||||
server_default='1',
|
||||
comment='部门树选择项是否关联显示',
|
||||
)
|
||||
status = Column(CHAR(1), nullable=False, comment='角色状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysRoleDept(Base):
|
||||
@@ -36,9 +56,10 @@ class SysRoleDept(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role_dept'
|
||||
__table_args__ = {'comment': '角色和部门关联表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
dept_id = Column(Integer, primary_key=True, nullable=False, comment='部门ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
dept_id = Column(BigInteger, primary_key=True, nullable=False, comment='部门ID')
|
||||
|
||||
|
||||
class SysRoleMenu(Base):
|
||||
@@ -47,6 +68,7 @@ class SysRoleMenu(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_role_menu'
|
||||
__table_args__ = {'comment': '角色和菜单关联表'}
|
||||
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
menu_id = Column(Integer, primary_key=True, nullable=False, comment='菜单ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
menu_id = Column(BigInteger, primary_key=True, nullable=False, comment='菜单ID')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, String
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class SysUser(Base):
|
||||
@@ -9,26 +11,38 @@ class SysUser(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user'
|
||||
__table_args__ = {'comment': '用户信息表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
|
||||
dept_id = Column(Integer, default=None, comment='部门ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='用户ID')
|
||||
dept_id = Column(
|
||||
BigInteger,
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type, False),
|
||||
comment='部门ID',
|
||||
)
|
||||
user_name = Column(String(30), nullable=False, comment='用户账号')
|
||||
nick_name = Column(String(30), nullable=False, comment='用户昵称')
|
||||
user_type = Column(String(2), default='00', comment='用户类型(00系统用户)')
|
||||
email = Column(String(50), default='', comment='用户邮箱')
|
||||
phonenumber = Column(String(11), default='', comment='手机号码')
|
||||
sex = Column(String(1), default='0', comment='用户性别(0男 1女 2未知)')
|
||||
avatar = Column(String(100), default='', comment='头像地址')
|
||||
password = Column(String(100), default='', comment='密码')
|
||||
status = Column(String(1), default='0', comment='帐号状态(0正常 1停用)')
|
||||
del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
login_ip = Column(String(128), default='', comment='最后登录IP')
|
||||
login_date = Column(DateTime, comment='最后登录时间')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
|
||||
remark = Column(String(500), default=None, comment='备注')
|
||||
user_type = Column(String(2), nullable=True, server_default='00', comment='用户类型(00系统用户)')
|
||||
email = Column(String(50), nullable=True, server_default="''", comment='用户邮箱')
|
||||
phonenumber = Column(String(11), nullable=True, server_default="''", comment='手机号码')
|
||||
sex = Column(CHAR(1), nullable=True, server_default='0', comment='用户性别(0男 1女 2未知)')
|
||||
avatar = Column(String(100), nullable=True, server_default="''", comment='头像地址')
|
||||
password = Column(String(100), nullable=True, server_default="''", comment='密码')
|
||||
status = Column(CHAR(1), nullable=True, server_default='0', comment='帐号状态(0正常 1停用)')
|
||||
del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)')
|
||||
login_ip = Column(String(128), nullable=True, server_default="''", comment='最后登录IP')
|
||||
login_date = Column(DateTime, nullable=True, comment='最后登录时间')
|
||||
pwd_update_date = Column(DateTime, nullable=True, comment='密码最后更新时间')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now())
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now())
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
|
||||
class SysUserRole(Base):
|
||||
@@ -37,9 +51,10 @@ class SysUserRole(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user_role'
|
||||
__table_args__ = {'comment': '用户和角色关联表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID')
|
||||
role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
|
||||
role_id = Column(BigInteger, primary_key=True, nullable=False, comment='角色ID')
|
||||
|
||||
|
||||
class SysUserPost(Base):
|
||||
@@ -48,6 +63,7 @@ class SysUserPost(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'sys_user_post'
|
||||
__table_args__ = {'comment': '用户与岗位关联表'}
|
||||
|
||||
user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID')
|
||||
post_id = Column(Integer, primary_key=True, nullable=False, comment='岗位ID')
|
||||
user_id = Column(BigInteger, primary_key=True, nullable=False, comment='用户ID')
|
||||
post_id = Column(BigInteger, primary_key=True, nullable=False, comment='岗位ID')
|
||||
|
||||
@@ -40,6 +40,7 @@ class UserModel(BaseModel):
|
||||
del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)')
|
||||
login_ip: Optional[str] = Field(default=None, description='最后登录IP')
|
||||
login_date: Optional[datetime] = Field(default=None, description='最后登录时间')
|
||||
pwd_update_date: Optional[datetime] = 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='更新者')
|
||||
@@ -125,6 +126,8 @@ class CurrentUserModel(BaseModel):
|
||||
permissions: List = Field(description='权限信息')
|
||||
roles: List = Field(description='角色信息')
|
||||
user: Union[UserInfoModel, None] = Field(description='用户信息')
|
||||
is_default_modify_pwd: bool = Field(default=False, description='是否初始密码修改提醒')
|
||||
is_password_expired: bool = Field(default=False, description='密码是否过期提醒')
|
||||
|
||||
|
||||
class UserDetailModel(BaseModel):
|
||||
|
||||
@@ -103,9 +103,10 @@ class DictTypeService:
|
||||
if dict_type_info.dict_type != page_object.dict_type:
|
||||
for dict_data in dict_data_list:
|
||||
edit_dict_data = DictDataModel(
|
||||
dictCode=dict_data.dict_code,
|
||||
dictCode=dict_data.get('dict_code'),
|
||||
dictType=page_object.dict_type,
|
||||
updateBy=page_object.update_by,
|
||||
updateTime=page_object.update_time,
|
||||
).model_dump(exclude_unset=True)
|
||||
await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data)
|
||||
await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type)
|
||||
|
||||
@@ -129,7 +129,7 @@ class JobService:
|
||||
elif not await cls.check_job_unique_services(query_db, page_object):
|
||||
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在')
|
||||
try:
|
||||
await JobDao.edit_job_dao(query_db, edit_job)
|
||||
await JobDao.edit_job_dao(query_db, edit_job, job_info)
|
||||
SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id'))
|
||||
if edit_job.get('status') == '0':
|
||||
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id'))
|
||||
|
||||
@@ -218,7 +218,7 @@ class LoginService:
|
||||
else:
|
||||
# 此方法可实现同一账号同一时间只能登录一次
|
||||
redis_token = await request.app.state.redis.get(
|
||||
f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}"
|
||||
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}'
|
||||
)
|
||||
if token == redis_token:
|
||||
if AppConfig.app_same_time_login:
|
||||
@@ -229,7 +229,7 @@ class LoginService:
|
||||
)
|
||||
else:
|
||||
await request.app.state.redis.set(
|
||||
f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}",
|
||||
f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get("user_basic_info").user_id}',
|
||||
redis_token,
|
||||
ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes),
|
||||
)
|
||||
@@ -242,6 +242,12 @@ class LoginService:
|
||||
post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')])
|
||||
role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')])
|
||||
roles = [row.role_key for row in query_user.get('user_role_info')]
|
||||
is_default_modify_pwd = await cls.__init_password_is_modify(
|
||||
request, query_user.get('user_basic_info').pwd_update_date
|
||||
)
|
||||
is_password_expired = await cls.__password_is_expired(
|
||||
request, query_user.get('user_basic_info').pwd_update_date
|
||||
)
|
||||
|
||||
current_user = CurrentUserModel(
|
||||
permissions=permissions,
|
||||
@@ -253,12 +259,48 @@ class LoginService:
|
||||
dept=CamelCaseUtil.transform_result(query_user.get('user_dept_info')),
|
||||
role=CamelCaseUtil.transform_result(query_user.get('user_role_info')),
|
||||
),
|
||||
isDefaultModifyPwd=is_default_modify_pwd,
|
||||
isPasswordExpired=is_password_expired,
|
||||
)
|
||||
return current_user
|
||||
else:
|
||||
logger.warning('用户token已失效,请重新登录')
|
||||
raise AuthException(data='', message='用户token已失效,请重新登录')
|
||||
|
||||
@classmethod
|
||||
async def __init_password_is_modify(cls, request: Request, pwd_update_date: datetime):
|
||||
"""
|
||||
判断当前用户是否初始密码登录
|
||||
|
||||
:param request: Request对象
|
||||
:param pwd_update_date: 密码最后更新时间
|
||||
:return: 是否初始密码登录
|
||||
"""
|
||||
init_password_is_modify = await request.app.state.redis.get(
|
||||
f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.initPasswordModify'
|
||||
)
|
||||
return init_password_is_modify == '1' and pwd_update_date is None
|
||||
|
||||
@classmethod
|
||||
async def __password_is_expired(cls, request: Request, pwd_update_date: datetime):
|
||||
"""
|
||||
判断当前用户密码是否过期
|
||||
|
||||
:param request: Request对象
|
||||
:param pwd_update_date: 密码最后更新时间
|
||||
:return: 密码是否过期
|
||||
"""
|
||||
password_validate_days = await request.app.state.redis.get(
|
||||
f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.passwordValidateDays'
|
||||
)
|
||||
if password_validate_days and int(password_validate_days) > 0:
|
||||
if pwd_update_date is None:
|
||||
return True
|
||||
expire_date = pwd_update_date + timedelta(days=int(password_validate_days))
|
||||
if datetime.now() > expire_date:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession):
|
||||
"""
|
||||
@@ -404,6 +446,7 @@ class LoginService:
|
||||
userName=user_register.username,
|
||||
nickName=user_register.username,
|
||||
password=PwdUtil.get_password_hash(user_register.password),
|
||||
pwdUpdateDate=datetime.now(),
|
||||
)
|
||||
result = await UserService.add_user_services(query_db, add_user)
|
||||
return result
|
||||
@@ -466,15 +509,15 @@ class LoginService:
|
||||
return CrudResponseModel(**result)
|
||||
|
||||
@classmethod
|
||||
async def logout_services(cls, request: Request, session_id: str):
|
||||
async def logout_services(cls, request: Request, token_id: str):
|
||||
"""
|
||||
退出登录services
|
||||
|
||||
:param request: Request对象
|
||||
:param session_id: 会话编号
|
||||
:param token_id: 令牌编号
|
||||
:return: 退出登录结果
|
||||
"""
|
||||
await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}')
|
||||
await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}')
|
||||
# await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token')
|
||||
# await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id')
|
||||
|
||||
|
||||
@@ -494,6 +494,7 @@ class UserService:
|
||||
}
|
||||
|
||||
for item in user_list:
|
||||
item['deptName'] = item.get('dept').get('deptName')
|
||||
if item.get('status') == '0':
|
||||
item['status'] = '正常'
|
||||
else:
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime, time
|
||||
from sqlalchemy import delete, func, select, text, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlglot.expressions import Expression
|
||||
from typing import List
|
||||
from config.env import DataBaseConfig
|
||||
from module_generator.entity.do.gen_do import GenTable, GenTableColumn
|
||||
@@ -75,15 +76,17 @@ class GenTableDao:
|
||||
return gen_table_all
|
||||
|
||||
@classmethod
|
||||
async def create_table_by_sql_dao(cls, db: AsyncSession, sql: str):
|
||||
async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]):
|
||||
"""
|
||||
根据sql语句创建表结构
|
||||
|
||||
:param db: orm对象
|
||||
:param sql: sql语句
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return:
|
||||
"""
|
||||
await db.execute(text(sql))
|
||||
for sql_statement in sql_statements:
|
||||
sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect)
|
||||
await db.execute(text(sql))
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy import BigInteger, CHAR, Column, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from config.database import Base
|
||||
from config.env import DataBaseConfig
|
||||
from utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class GenTable(Base):
|
||||
@@ -10,30 +12,48 @@ class GenTable(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'gen_table'
|
||||
__table_args__ = {'comment': '代码生成业务表'}
|
||||
|
||||
table_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
|
||||
table_name = Column(String(200), nullable=True, default='', comment='表名称')
|
||||
table_comment = Column(String(500), nullable=True, default='', comment='表描述')
|
||||
sub_table_name = Column(String(64), nullable=True, comment='关联子表的表名')
|
||||
sub_table_fk_name = Column(String(64), nullable=True, comment='子表关联的外键名')
|
||||
class_name = Column(String(100), nullable=True, default='', comment='实体类名称')
|
||||
tpl_category = Column(String(200), nullable=True, default='crud', comment='使用的模板(crud单表操作 tree树表操作)')
|
||||
table_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='编号')
|
||||
table_name = Column(String(200), nullable=True, server_default="''", comment='表名称')
|
||||
table_comment = Column(String(500), nullable=True, server_default="''", comment='表描述')
|
||||
sub_table_name = Column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='关联子表的表名',
|
||||
)
|
||||
sub_table_fk_name = Column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='子表关联的外键名',
|
||||
)
|
||||
class_name = Column(String(100), nullable=True, server_default="''", comment='实体类名称')
|
||||
tpl_category = Column(
|
||||
String(200), nullable=True, server_default='crud', comment='使用的模板(crud单表操作 tree树表操作)'
|
||||
)
|
||||
tpl_web_type = Column(
|
||||
String(30), nullable=True, default='', comment='前端模板类型(element-ui模版 element-plus模版)'
|
||||
String(30), nullable=True, server_default="''", comment='前端模板类型(element-ui模版 element-plus模版)'
|
||||
)
|
||||
package_name = Column(String(100), nullable=True, comment='生成包路径')
|
||||
module_name = Column(String(30), nullable=True, comment='生成模块名')
|
||||
business_name = Column(String(30), nullable=True, comment='生成业务名')
|
||||
function_name = Column(String(100), nullable=True, comment='生成功能名')
|
||||
function_author = Column(String(100), nullable=True, comment='生成功能作者')
|
||||
gen_type = Column(String(1), nullable=True, default='0', comment='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
gen_path = Column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)')
|
||||
function_name = Column(String(50), nullable=True, comment='生成功能名')
|
||||
function_author = Column(String(50), nullable=True, comment='生成功能作者')
|
||||
gen_type = Column(CHAR(1), nullable=True, server_default='0', comment='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
gen_path = Column(String(200), nullable=True, server_default='/', comment='生成路径(不填默认项目路径)')
|
||||
options = Column(String(1000), nullable=True, comment='其它生成选项')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
remark = Column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type),
|
||||
comment='备注',
|
||||
)
|
||||
|
||||
columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables')
|
||||
|
||||
@@ -44,31 +64,34 @@ class GenTableColumn(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = 'gen_table_column'
|
||||
__table_args__ = {'comment': '代码生成业务表字段'}
|
||||
|
||||
column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
|
||||
table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号')
|
||||
column_id = Column(BigInteger, primary_key=True, autoincrement=True, nullable=False, comment='编号')
|
||||
table_id = Column(BigInteger, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号')
|
||||
column_name = Column(String(200), nullable=True, comment='列名称')
|
||||
column_comment = Column(String(500), nullable=True, comment='列描述')
|
||||
column_type = Column(String(100), nullable=True, comment='列类型')
|
||||
python_type = Column(String(500), nullable=True, comment='PYTHON类型')
|
||||
python_field = Column(String(200), nullable=True, comment='PYTHON字段名')
|
||||
is_pk = Column(String(1), nullable=True, comment='是否主键(1是)')
|
||||
is_increment = Column(String(1), nullable=True, comment='是否自增(1是)')
|
||||
is_required = Column(String(1), nullable=True, comment='是否必填(1是)')
|
||||
is_unique = Column(String(1), nullable=True, comment='是否唯一(1是)')
|
||||
is_insert = Column(String(1), nullable=True, comment='是否为插入字段(1是)')
|
||||
is_edit = Column(String(1), nullable=True, comment='是否编辑字段(1是)')
|
||||
is_list = Column(String(1), nullable=True, comment='是否列表字段(1是)')
|
||||
is_query = Column(String(1), nullable=True, comment='是否查询字段(1是)')
|
||||
query_type = Column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)')
|
||||
is_pk = Column(CHAR(1), nullable=True, comment='是否主键(1是)')
|
||||
is_increment = Column(CHAR(1), nullable=True, comment='是否自增(1是)')
|
||||
is_required = Column(CHAR(1), nullable=True, comment='是否必填(1是)')
|
||||
is_unique = Column(CHAR(1), nullable=True, comment='是否唯一(1是)')
|
||||
is_insert = Column(CHAR(1), nullable=True, comment='是否为插入字段(1是)')
|
||||
is_edit = Column(CHAR(1), nullable=True, comment='是否编辑字段(1是)')
|
||||
is_list = Column(CHAR(1), nullable=True, comment='是否列表字段(1是)')
|
||||
is_query = Column(CHAR(1), nullable=True, comment='是否查询字段(1是)')
|
||||
query_type = Column(
|
||||
String(200), nullable=True, server_default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)'
|
||||
)
|
||||
html_type = Column(
|
||||
String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'
|
||||
)
|
||||
dict_type = Column(String(200), nullable=True, default='', comment='字典类型')
|
||||
dict_type = Column(String(200), nullable=True, server_default="''", comment='字典类型')
|
||||
sort = Column(Integer, nullable=True, comment='排序')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_by = Column(String(64), server_default="''", comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_by = Column(String(64), server_default="''", comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
|
||||
tables = relationship('GenTable', back_populates='columns')
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlglot import parse as sqlglot_parse
|
||||
from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update
|
||||
from typing import List
|
||||
from config.constant import GenConstant
|
||||
from config.env import GenConfig
|
||||
from config.env import DataBaseConfig, GenConfig
|
||||
from exceptions.exception import ServiceException
|
||||
from module_admin.entity.vo.common_vo import CrudResponseModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
@@ -197,10 +198,11 @@ class GenTableService:
|
||||
:param current_user: 当前用户信息对象
|
||||
:return: 创建表结构结果
|
||||
"""
|
||||
if cls.__is_valid_create_table(sql):
|
||||
sql_statements = sqlglot_parse(sql, dialect=DataBaseConfig.sqlglot_parse_dialect)
|
||||
if cls.__is_valid_create_table(sql_statements):
|
||||
try:
|
||||
table_names = re.findall(r'create\s+table\s+(\w+)', sql, re.IGNORECASE)
|
||||
await GenTableDao.create_table_by_sql_dao(query_db, sql)
|
||||
table_names = cls.__get_table_names(sql_statements)
|
||||
await GenTableDao.create_table_by_sql_dao(query_db, sql_statements)
|
||||
gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names)
|
||||
await cls.import_gen_table_services(query_db, gen_table_list, current_user)
|
||||
|
||||
@@ -211,22 +213,39 @@ class GenTableService:
|
||||
raise ServiceException(message='建表语句不合法')
|
||||
|
||||
@classmethod
|
||||
def __is_valid_create_table(cls, sql: str):
|
||||
def __is_valid_create_table(cls, sql_statements: List[Expression]):
|
||||
"""
|
||||
校验sql语句是否为合法的建表语句
|
||||
|
||||
:param sql: sql语句
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return: 校验结果
|
||||
"""
|
||||
create_table_pattern = r'^\s*CREATE\s+TABLE\s+'
|
||||
if not re.search(create_table_pattern, sql, re.IGNORECASE):
|
||||
validate_create = [isinstance(sql_statement, Create) for sql_statement in sql_statements]
|
||||
validate_forbidden_keywords = [
|
||||
isinstance(
|
||||
sql_statement,
|
||||
(Add, Alter, Delete, Drop, Insert, TruncateTable, Update),
|
||||
)
|
||||
for sql_statement in sql_statements
|
||||
]
|
||||
if not any(validate_create) or any(validate_forbidden_keywords):
|
||||
return False
|
||||
forbidden_keywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE']
|
||||
for keyword in forbidden_keywords:
|
||||
if re.search(rf'\b{keyword}\b', sql, re.IGNORECASE):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def __get_table_names(cls, sql_statements: List[Expression]):
|
||||
"""
|
||||
获取sql语句中所有的建表表名
|
||||
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return: 建表表名列表
|
||||
"""
|
||||
table_names = []
|
||||
for sql_statement in sql_statements:
|
||||
if isinstance(sql_statement, Create):
|
||||
table_names.append(sql_statement.find(Table).name)
|
||||
return table_names
|
||||
|
||||
@classmethod
|
||||
async def preview_code_services(cls, query_db: AsyncSession, table_id: int):
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{% set pkField = pkColumn.python_field %}
|
||||
{% set pk_field = pkColumn.python_field | camel_to_snake %}
|
||||
{% set need_import_datetime = namespace(has_datetime=False) %}
|
||||
{% for column in columns %}
|
||||
{% if column.python_field == "createTime" %}
|
||||
from datetime import datetime
|
||||
{% if column.python_field in ["createTime", "updatetime"] %}
|
||||
{% set need_import_datetime.has_datetime = True %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if need_import_datetime.has_datetime %}
|
||||
from datetime import datetime
|
||||
{% endif %}
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -82,8 +86,13 @@ async def edit_{{ moduleName }}_{{ businessName }}(
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
{% for column in columns %}
|
||||
{% if column.python_field == "updateBy" %}
|
||||
edit_{{ businessName }}.update_by = current_user.user.user_name
|
||||
{% elif column.python_field == "updateTime" %}
|
||||
edit_{{ businessName }}.update_time = datetime.now()
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
edit_{{ businessName }}_result = await {{ BusinessName }}Service.edit_{{ businessName }}_services(query_db, edit_{{ businessName }})
|
||||
logger.info(edit_{{ businessName }}_result.message)
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class {{ BusinessName }}Dao:
|
||||
await db.execute(
|
||||
select({{ ClassName }}).where(
|
||||
{% for column in columns %}
|
||||
{% if column.required %}
|
||||
{% if column.unique %}
|
||||
{{ ClassName }}.{{ column.python_field | camel_to_snake }} == {{ businessName }}.{{ column.python_field | camel_to_snake }} if {{ businessName }}.{{ column.python_field | camel_to_snake }} else True,
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -118,12 +118,12 @@ class {{ BusinessName }}Dao:
|
||||
{{ ClassName }}.{{ field }} <= query_object.{{ field }} if query_object.{{ field }} else True,
|
||||
{% elif column.query_type == "LIKE" %}
|
||||
{{ ClassName }}.{{ field }}.like(f'%{% raw %}{{% endraw %}query_object.{{ field }}{% raw %}}{% endraw %}%') if query_object.{{ field }} else True,
|
||||
{% elif column.query_type == "BETWEEN" %}
|
||||
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{{ ClassName }}.{{ field }}.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
datetime.combine(datetime.strptime(query_object.begin_{{ column.column_name }}, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_{{ column.column_name }}, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
if query_object.begin_{{ column.column_name }} and query_object.end_{{ column.column_name }}
|
||||
else True,
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -145,7 +145,7 @@ class {{ BusinessName }}Dao:
|
||||
:param {{ businessName }}: {{ functionName }}对象
|
||||
:return:
|
||||
"""
|
||||
db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.insert %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %}))
|
||||
db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.insert and column.column_name not in column_not_add_show + column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %}))
|
||||
db.add(db_{{ businessName }})
|
||||
await db.flush()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class {{ ClassName }}(Base):
|
||||
"""
|
||||
|
||||
__tablename__ = '{{ tableName }}'
|
||||
__table_args__ = {'comment': '{{ tableComment }}'}
|
||||
|
||||
{% for column in columns %}
|
||||
{{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}')
|
||||
|
||||
@@ -103,7 +103,7 @@ class {{ BusinessName }}Service:
|
||||
:param page_object: 编辑{{ functionName }}对象
|
||||
:return: 编辑{{ functionName }}校验结果
|
||||
"""
|
||||
edit_{{ businessName }} = page_object.model_dump(exclude_unset=True, exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.edit and not column.pk %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %})
|
||||
edit_{{ businessName }} = page_object.model_dump(exclude_unset=True, exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.edit and not column.pk and column.column_name not in column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %})
|
||||
{{ businessName }}_info = await cls.{{ businessName }}_detail_services(query_db, page_object.{{ pk_field }})
|
||||
if {{ businessName }}_info.{{ pk_field }}:
|
||||
{% for column in columns %}
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
{% set pkParentheseIndex = pkColumn.column_comment.find("(") %}
|
||||
{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %}
|
||||
{% set vo_field_required = namespace(has_required=False) %}
|
||||
{% set vo_field_daterange = namespace(has_daterange=False) %}
|
||||
{% for column in columns %}
|
||||
{% if column.required %}
|
||||
{% set vo_field_required.has_required = True %}
|
||||
{% endif %}
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{% set vo_field_daterange.has_daterange = True %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if table.sub %}
|
||||
{% set sub_vo_field_required = namespace(has_required=False) %}
|
||||
{% if table.sub %}
|
||||
{% for sub_column in subTable.columns %}
|
||||
{% if sub_column.required %}
|
||||
{% set sub_vo_field_required.has_required = True %}
|
||||
@@ -21,7 +25,7 @@
|
||||
{% endfor %}
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
{% if vo_field_required.has_required %}
|
||||
{% if vo_field_required.has_required or sub_vo_field_required.has_required %}
|
||||
from pydantic_validation_decorator import NotBlank
|
||||
{% endif %}
|
||||
{% if table.sub %}
|
||||
@@ -142,9 +146,16 @@ class {{ BusinessName }}QueryModel({% if table.sub %}{{ BusinessName }}BaseModel
|
||||
"""
|
||||
{{ functionName }}不分页查询模型
|
||||
"""
|
||||
|
||||
begin_time: Optional[str] = Field(default=None, description='开始时间')
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
{% if vo_field_daterange.has_daterange %}
|
||||
{% for column in columns %}
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
begin_{{ column.column_name }}: Optional[str] = Field(default=None, description='开始{{ column.column_comment }}')
|
||||
end_{{ column.column_name }}: Optional[str] = Field(default=None, description='结束{{ column.column_comment }}')
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
pass
|
||||
{% endif %}
|
||||
|
||||
|
||||
@as_query
|
||||
|
||||
@@ -35,12 +35,13 @@
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-date-picker clearable
|
||||
<el-date-picker
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择{{ comment }}">
|
||||
</el-date-picker>
|
||||
placeholder="请选择{{ comment }}"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}">
|
||||
@@ -52,7 +53,7 @@
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
/>
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -161,33 +162,33 @@
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
{% for column in columns %}
|
||||
{% set field = column.python_field %}
|
||||
{% if column.insert and not column.pk %}
|
||||
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
|
||||
{% if column.usable_column or not column.super_column %}
|
||||
{% set parentheseIndex = column.column_comment.find("(") %}
|
||||
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
|
||||
{% set dictType = column.dict_type %}
|
||||
{% if treeParentCode and column.python_field == treeParentCode %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ treeParentCode }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ treeParentCode }}">
|
||||
<treeselect v-model="form.{{ treeParentCode }}" :options="{{ businessName }}Options" :normalizer="normalizer" placeholder="请选择{{ comment }}" />
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "input" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "imageUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<image-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "fileUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<file-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "editor" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<editor v-model="form.{{ field }}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -202,13 +203,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -219,13 +220,13 @@
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -240,13 +241,13 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio label="请选择字典生成" value="" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.{{ field }}"
|
||||
type="date"
|
||||
@@ -255,7 +256,7 @@
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "textarea" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
@@ -351,8 +352,8 @@ export default {
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
|
||||
if (null != this.daterange{{ AttrName }} && '' != this.daterange{{ AttrName }}) {
|
||||
this.queryParams.params["begin{{ AttrName }}"] = this.daterange{{ AttrName }}[0];
|
||||
this.queryParams.params["end{{ AttrName }}"] = this.daterange{{ AttrName }}[1];
|
||||
this.queryParams.begin{{ AttrName }} = this.daterange{{ AttrName }}[0];
|
||||
this.queryParams.end{{ AttrName }} = this.daterange{{ AttrName }}[1];
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -485,6 +486,10 @@ export default {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
/** 是否渲染字段 */
|
||||
renderField(insert, edit) {
|
||||
return this.form.{{ pkColumn.python_field }} == null ? insert : edit;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,12 +35,13 @@
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-date-picker clearable
|
||||
<el-date-picker
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择{{ comment }}">
|
||||
</el-date-picker>
|
||||
placeholder="请选择{{ comment }}"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}">
|
||||
@@ -52,7 +53,7 @@
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
/>
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -173,29 +174,29 @@
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
{% for column in columns %}
|
||||
{% set field = column.python_field %}
|
||||
{% if column.insert and not column.pk %}
|
||||
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
|
||||
{% if column.usable_column or not column.super_column %}
|
||||
{% set parentheseIndex = column.column_comment.find("(") %}
|
||||
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
|
||||
{% set dictType = column.dict_type %}
|
||||
{% if column.html_type == "input" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "imageUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<image-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "fileUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<file-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "editor" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<editor v-model="form.{{ field }}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -210,13 +211,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -227,13 +228,13 @@
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.{{ dictType }}"
|
||||
@@ -248,13 +249,13 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio label="请选择字典生成" value="" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.{{ field }}"
|
||||
type="date"
|
||||
@@ -263,7 +264,7 @@
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "textarea" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
@@ -426,8 +427,8 @@ export default {
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
|
||||
if (null != this.daterange{{ AttrName }} && '' != this.daterange{{ AttrName }}) {
|
||||
this.queryParams.params["begin{{ AttrName }}"] = this.daterange{{ AttrName }}[0];
|
||||
this.queryParams.params["end{{ AttrName }}"] = this.daterange{{ AttrName }}[1];
|
||||
this.queryParams.begin{{ AttrName }} = this.daterange{{ AttrName }}[0];
|
||||
this.queryParams.end{{ AttrName }} = this.daterange{{ AttrName }}[1];
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -580,6 +581,10 @@ export default {
|
||||
this.download('{{ moduleName }}/{{ businessName }}/export', {
|
||||
...this.queryParams
|
||||
}, `{{ businessName }}_${new Date().getTime()}.xlsx`);
|
||||
},
|
||||
/** 是否渲染字段 */
|
||||
renderField(insert, edit) {
|
||||
return this.form.{{ pkColumn.python_field }} == null ? insert : edit;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
placeholder="请输入{{ comment }}"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
|
||||
<el-option
|
||||
v-for="dict in {{ dictType }}"
|
||||
:key="dict.value"
|
||||
@@ -29,18 +30,20 @@
|
||||
</el-form-item>
|
||||
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-date-picker clearable
|
||||
<el-date-picker
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择{{ comment }}">
|
||||
</el-date-picker>
|
||||
placeholder="请选择{{ comment }}"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" style="width: 308px">
|
||||
@@ -51,7 +54,8 @@
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -136,13 +140,13 @@
|
||||
<el-form ref="{{ businessName }}Ref" :model="form" :rules="rules" label-width="80px">
|
||||
{% for column in columns %}
|
||||
{% set field = column.python_field %}
|
||||
{% if column.insert and not column.pk %}
|
||||
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
|
||||
{% if column.usable_column or not column.super_column %}
|
||||
{% set parentheseIndex = column.column_comment.find("(") %}
|
||||
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
|
||||
{% set dictType = column.dict_type %}
|
||||
{% if treeParentCode and column.python_field == treeParentCode %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ treeParentCode }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ treeParentCode }}">
|
||||
<el-tree-select
|
||||
v-model="form.{{ treeParentCode }}"
|
||||
:data="{{ businessName }}Options"
|
||||
@@ -153,23 +157,23 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "input" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "imageUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<image-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "fileUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<file-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "editor" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<editor v-model="form.{{ field }}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -184,13 +188,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -201,13 +205,13 @@
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -222,13 +226,13 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio label="请选择字典生成" value="" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.{{ field }}"
|
||||
type="date"
|
||||
@@ -237,7 +241,7 @@
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "textarea" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
@@ -315,8 +319,8 @@ function getList() {
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
|
||||
if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) {
|
||||
queryParams.value.params["begin{{ AttrName }}"] = daterange{{ AttrName }}.value[0];
|
||||
queryParams.value.params["end{{ AttrName }}"] = daterange{{ AttrName }}.value[1];
|
||||
queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0];
|
||||
queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1];
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -450,5 +454,10 @@ function handleDelete(row) {
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 是否渲染字段 */
|
||||
function renderField(insert, edit) {
|
||||
return form.value.{{ pkColumn.python_field }} == null ? insert : edit;
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@@ -13,12 +13,13 @@
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
placeholder="请输入{{ comment }}"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
|
||||
<el-option
|
||||
v-for="dict in {{ dictType }}"
|
||||
:key="dict.value"
|
||||
@@ -29,18 +30,20 @@
|
||||
</el-form-item>
|
||||
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
|
||||
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
|
||||
<el-date-picker clearable
|
||||
<el-date-picker
|
||||
v-model="queryParams.{{ column.python_field }}"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择{{ comment }}">
|
||||
</el-date-picker>
|
||||
placeholder="请选择{{ comment }}"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
<el-form-item label="{{ comment }}" style="width: 308px">
|
||||
@@ -51,7 +54,8 @@
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -159,29 +163,29 @@
|
||||
<el-form ref="{{ businessName }}Ref" :model="form" :rules="rules" label-width="80px">
|
||||
{% for column in columns %}
|
||||
{% set field = column.python_field %}
|
||||
{% if column.insert and not column.pk %}
|
||||
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
|
||||
{% if column.usable_column or not column.super_column %}
|
||||
{% set parentheseIndex = column.column_comment.find("(") %}
|
||||
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
|
||||
{% set dictType = column.dict_type %}
|
||||
{% if column.html_type == "input" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "imageUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<image-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "fileUpload" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<file-upload v-model="form.{{ field }}"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "editor" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<editor v-model="form.{{ field }}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -196,13 +200,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "select" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -213,13 +217,13 @@
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "checkbox" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-checkbox-group v-model="form.{{ field }}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio
|
||||
v-for="dict in {{ dictType }}"
|
||||
@@ -234,13 +238,13 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "radio" and not dictType %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-radio-group v-model="form.{{ field }}">
|
||||
<el-radio label="请选择字典生成" value="" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "datetime" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.{{ field }}"
|
||||
type="date"
|
||||
@@ -249,7 +253,7 @@
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
{% elif column.html_type == "textarea" %}
|
||||
<el-form-item label="{{ comment }}" prop="{{ field }}">
|
||||
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
|
||||
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
{% endif %}
|
||||
@@ -397,8 +401,8 @@ function getList() {
|
||||
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
|
||||
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
|
||||
if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) {
|
||||
queryParams.value.params["begin{{ AttrName }}"] = daterange{{ AttrName }}.value[0];
|
||||
queryParams.value.params["end{{ AttrName }}"] = daterange{{ AttrName }}.value[1];
|
||||
queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0];
|
||||
queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1];
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -567,5 +571,10 @@ function handleExport() {
|
||||
}, `{{ businessName }}_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
/** 是否渲染字段 */
|
||||
function renderField(insert, edit) {
|
||||
return form.value.{{ pkColumn.python_field }} == null ? insert : edit;
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@@ -1,17 +1,19 @@
|
||||
alembic==1.16.4
|
||||
APScheduler==3.11.0
|
||||
async-lru==2.0.5
|
||||
asyncpg==0.30.0
|
||||
bcrypt==5.0.0
|
||||
DateTime==5.5
|
||||
fastapi[all]==0.115.8
|
||||
fastapi[all]==0.116.1
|
||||
loguru==0.7.3
|
||||
openpyxl==3.1.5
|
||||
pandas==2.2.3
|
||||
passlib[bcrypt]==1.7.4
|
||||
Pillow==11.1.0
|
||||
pandas==2.3.2
|
||||
Pillow==11.3.0
|
||||
psutil==7.0.0
|
||||
pydantic-validation-decorator==0.1.4
|
||||
PyJWT[crypto]==2.10.1
|
||||
psycopg2==2.9.10
|
||||
redis==5.2.1
|
||||
requests==2.32.3
|
||||
SQLAlchemy[asyncio]==2.0.38
|
||||
redis==6.4.0
|
||||
SQLAlchemy[asyncio]==2.0.43
|
||||
sqlglot[rs]==27.8.0
|
||||
user-agents==2.2.0
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
alembic==1.16.4
|
||||
APScheduler==3.11.0
|
||||
async-lru==2.0.5
|
||||
asyncmy==0.2.10
|
||||
bcrypt==5.0.0
|
||||
DateTime==5.5
|
||||
fastapi[all]==0.115.8
|
||||
fastapi[all]==0.116.1
|
||||
loguru==0.7.3
|
||||
openpyxl==3.1.5
|
||||
pandas==2.2.3
|
||||
passlib[bcrypt]==1.7.4
|
||||
Pillow==11.1.0
|
||||
pandas==2.3.2
|
||||
Pillow==11.3.0
|
||||
psutil==7.0.0
|
||||
pydantic-validation-decorator==0.1.4
|
||||
PyJWT[crypto]==2.10.1
|
||||
PyMySQL==1.1.1
|
||||
redis==5.2.1
|
||||
requests==2.32.3
|
||||
SQLAlchemy[asyncio]==2.0.38
|
||||
redis==6.4.0
|
||||
SQLAlchemy[asyncio]==2.0.43
|
||||
sqlglot[rs]==27.8.0
|
||||
user-agents==2.2.0
|
||||
|
||||
@@ -31,14 +31,14 @@ from utils.log_util import logger
|
||||
# 生命周期事件
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
logger.info(f'{AppConfig.app_name}开始启动')
|
||||
logger.info(f'⏰️ {AppConfig.app_name}开始启动')
|
||||
worship()
|
||||
await init_create_table()
|
||||
app.state.redis = await RedisUtil.create_redis_pool()
|
||||
await RedisUtil.init_sys_dict(app.state.redis)
|
||||
await RedisUtil.init_sys_config(app.state.redis)
|
||||
await SchedulerUtil.init_system_scheduler()
|
||||
logger.info(f'{AppConfig.app_name}启动成功')
|
||||
logger.info(f'🚀 {AppConfig.app_name}启动成功')
|
||||
yield
|
||||
await RedisUtil.close_redis_pool(app)
|
||||
await SchedulerUtil.close_system_scheduler()
|
||||
|
||||
@@ -69,6 +69,7 @@ create table sys_user (
|
||||
del_flag char(1) default '0',
|
||||
login_ip varchar(128) default '',
|
||||
login_date timestamp(0),
|
||||
pwd_update_date timestamp(0),
|
||||
create_by varchar(64) default '',
|
||||
create_time timestamp(0),
|
||||
update_by varchar(64) default '',
|
||||
@@ -91,6 +92,7 @@ comment on column sys_user.status is '帐号状态(0正常 1停用)';
|
||||
comment on column sys_user.del_flag is '删除标志(0代表存在 2代表删除)';
|
||||
comment on column sys_user.login_ip is '最后登录IP';
|
||||
comment on column sys_user.login_date is '最后登录时间';
|
||||
comment on column sys_user.pwd_update_date is '密码最后更新时间';
|
||||
comment on column sys_user.create_by is '创建者';
|
||||
comment on column sys_user.create_time is '创建时间';
|
||||
comment on column sys_user.update_by is '更新者';
|
||||
@@ -98,6 +100,12 @@ comment on column sys_user.update_time is '更新时间';
|
||||
comment on column sys_user.remark is '备注';
|
||||
comment on table sys_user is '用户信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-用户信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, current_timestamp, 'admin', current_timestamp, '', null, '管理员');
|
||||
insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, current_timestamp, 'admin', current_timestamp, '', null, '测试员');
|
||||
|
||||
-- ----------------------------
|
||||
-- 3、岗位信息表
|
||||
-- ----------------------------
|
||||
@@ -136,12 +144,6 @@ insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', current
|
||||
insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', current_timestamp, '', null, '');
|
||||
insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', current_timestamp, '', null, '');
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-用户信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null, '管理员');
|
||||
insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', current_timestamp, 'admin', current_timestamp, '', null, '测试员');
|
||||
|
||||
-- ----------------------------
|
||||
-- 4、角色信息表
|
||||
-- ----------------------------
|
||||
@@ -522,7 +524,7 @@ create table sys_oper_log (
|
||||
status int4 default 0,
|
||||
error_msg varchar(2000) default '',
|
||||
oper_time timestamp(0),
|
||||
cost_time int8 default 0,
|
||||
cost_time bigint default 0,
|
||||
primary key (oper_id)
|
||||
);
|
||||
alter sequence sys_oper_log_oper_id_seq restart 100;
|
||||
@@ -545,6 +547,7 @@ comment on column sys_oper_log.json_result is '返回参数';
|
||||
comment on column sys_oper_log.status is '操作状态(0正常 1异常)';
|
||||
comment on column sys_oper_log.error_msg is '错误消息';
|
||||
comment on column sys_oper_log.oper_time is '操作时间';
|
||||
comment on column sys_oper_log.cost_time is '消耗时间';
|
||||
comment on table sys_oper_log is '操作日志记录';
|
||||
|
||||
-- ----------------------------
|
||||
@@ -703,6 +706,8 @@ insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.
|
||||
insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', current_timestamp, '', null, '是否开启验证码功能(true开启,false关闭)');
|
||||
insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', current_timestamp, '', null, '是否开启注册用户功能(true开启,false关闭)');
|
||||
insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', current_timestamp, '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
|
||||
insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', current_timestamp, '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框');
|
||||
insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', current_timestamp, '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框');
|
||||
|
||||
-- ----------------------------
|
||||
-- 14、系统访问记录
|
||||
@@ -931,7 +936,8 @@ create table gen_table_column (
|
||||
create_time timestamp(0),
|
||||
update_by varchar(64) default '',
|
||||
update_time timestamp(0),
|
||||
primary key (column_id)
|
||||
primary key (column_id),
|
||||
constraint fk_gen_table_column_table_id foreign key (table_id) references gen_table(table_id)
|
||||
);
|
||||
comment on column gen_table_column.column_id is '编号';
|
||||
comment on column gen_table_column.table_id is '归属表编号';
|
||||
|
||||
@@ -54,6 +54,7 @@ create table sys_user (
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
|
||||
login_ip varchar(128) default '' comment '最后登录IP',
|
||||
login_date datetime comment '最后登录时间',
|
||||
pwd_update_date datetime comment '密码最后更新时间',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
@@ -65,8 +66,8 @@ create table sys_user (
|
||||
-- ----------------------------
|
||||
-- 初始化-用户信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员');
|
||||
insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员');
|
||||
insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员');
|
||||
insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
@@ -554,6 +555,8 @@ insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.
|
||||
insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)');
|
||||
insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');
|
||||
insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
|
||||
insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框');
|
||||
insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
@@ -709,5 +712,6 @@ create table gen_table_column (
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
primary key (column_id)
|
||||
primary key (column_id),
|
||||
foreign key (table_id) references gen_table(table_id)
|
||||
) engine=innodb auto_increment=1 comment = '代码生成业务表字段';
|
||||
@@ -8,6 +8,7 @@ from openpyxl.utils import get_column_letter
|
||||
from openpyxl.worksheet.datavalidation import DataValidation
|
||||
from sqlalchemy.engine.row import Row
|
||||
from sqlalchemy.orm.collections import InstrumentedList
|
||||
from sqlalchemy.sql.expression import TextClause, null
|
||||
from typing import Any, Dict, List, Literal, Union
|
||||
from config.database import Base
|
||||
from config.env import CachePathConfig
|
||||
@@ -100,6 +101,19 @@ class SqlalchemyUtil:
|
||||
return result_dict
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_server_default_null(cls, dialect_name: str, need_explicit_null: bool = True) -> Union[TextClause, None]:
|
||||
"""
|
||||
根据数据库方言动态返回值为null的server_default
|
||||
|
||||
:param dialect_name: 数据库方言名称
|
||||
:param need_explicit_null: 是否需要显式DEFAULT NULL
|
||||
:return: 不同数据库方言对应的null_server_default
|
||||
"""
|
||||
if need_explicit_null and dialect_name == 'postgresql':
|
||||
return null()
|
||||
return None
|
||||
|
||||
|
||||
class CamelCaseUtil:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from sqlalchemy import inspect as sa_inspect
|
||||
from typing import Any, List
|
||||
from config.database import Base
|
||||
|
||||
|
||||
class ImportUtil:
|
||||
@classmethod
|
||||
def find_project_root(cls) -> Path:
|
||||
"""
|
||||
查找项目根目录
|
||||
|
||||
:return: 项目根目录路径
|
||||
"""
|
||||
current_dir = Path(__file__).resolve().parent
|
||||
while current_dir != current_dir.parent:
|
||||
if any(current_dir.joinpath(file).exists() for file in ['setup.py', 'pyproject.toml', 'requirements.txt']):
|
||||
return current_dir
|
||||
current_dir = current_dir.parent
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
@classmethod
|
||||
def is_valid_model(cls, obj: Any, base_class: Base) -> bool:
|
||||
"""
|
||||
验证是否为有效的SQLAlchemy模型类
|
||||
|
||||
:param obj: 待验证的对象
|
||||
:param base_class: SQLAlchemy的Base类
|
||||
:return: 验证结果
|
||||
"""
|
||||
# 必须继承自Base且不是Base本身
|
||||
if not (inspect.isclass(obj) and issubclass(obj, base_class) and obj is not base_class):
|
||||
return False
|
||||
|
||||
# 必须有表名定义(排除抽象基类)
|
||||
if not hasattr(obj, '__tablename__') or obj.__tablename__ is None:
|
||||
return False
|
||||
|
||||
# 必须有至少一个列定义
|
||||
try:
|
||||
return len(sa_inspect(obj).columns) > 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=256)
|
||||
def find_models(cls, base_class: Base) -> List[Base]:
|
||||
"""
|
||||
查找并过滤有效的模型类,避免重复和无效定义
|
||||
|
||||
:param base_class: SQLAlchemy的Base类,用于验证模型类
|
||||
:return: 有效模型类列表
|
||||
"""
|
||||
models = []
|
||||
# 按类对象去重
|
||||
seen_models = set()
|
||||
# 按表名去重(防止同表名冲突)
|
||||
seen_tables = set()
|
||||
project_root = cls.find_project_root()
|
||||
|
||||
sys.path.append(str(project_root))
|
||||
print(f"⏰️ 开始在项目根目录 {project_root} 中查找模型...")
|
||||
|
||||
# 排除目录扩展
|
||||
exclude_dirs = {
|
||||
'venv',
|
||||
'.env',
|
||||
'.git',
|
||||
'__pycache__',
|
||||
'migrations',
|
||||
'alembic',
|
||||
'tests',
|
||||
'test',
|
||||
'docs',
|
||||
'examples',
|
||||
'scripts',
|
||||
}
|
||||
|
||||
for root, dirs, files in os.walk(project_root):
|
||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||
|
||||
for file in files:
|
||||
if file.endswith('.py') and not file.startswith('__'):
|
||||
relative_path = Path(root).relative_to(project_root)
|
||||
module_parts = list(relative_path.parts) + [file[:-3]]
|
||||
module_name = '.'.join(module_parts)
|
||||
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
# 验证模型有效性
|
||||
if not cls.is_valid_model(obj, base_class):
|
||||
continue
|
||||
|
||||
# 检查类对象重复
|
||||
if obj in seen_models:
|
||||
continue
|
||||
|
||||
# 检查表名重复
|
||||
table_name = obj.__tablename__
|
||||
if table_name in seen_tables:
|
||||
continue
|
||||
|
||||
seen_models.add(obj)
|
||||
seen_tables.add(table_name)
|
||||
models.append(obj)
|
||||
print(f'✅️ 找到有效模型: {obj.__module__}.{obj.__name__} (表: {table_name})')
|
||||
|
||||
except ImportError as e:
|
||||
if 'cannot import name' not in str(e):
|
||||
print(f'❗️ 警告: 无法导入模块 {module_name}: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌️ 处理模块 {module_name} 时出错: {e}')
|
||||
|
||||
return models
|
||||
@@ -1,6 +1,4 @@
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
import bcrypt
|
||||
|
||||
|
||||
class PwdUtil:
|
||||
@@ -9,7 +7,7 @@ class PwdUtil:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def verify_password(cls, plain_password, hashed_password):
|
||||
def verify_password(cls, plain_password: str, hashed_password: str) -> bool:
|
||||
"""
|
||||
工具方法:校验当前输入的密码与数据库存储的密码是否一致
|
||||
|
||||
@@ -17,7 +15,9 @@ class PwdUtil:
|
||||
:param hashed_password: 数据库存储的密码
|
||||
:return: 校验结果
|
||||
"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
return (
|
||||
bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8')) if hashed_password else None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_password_hash(cls, input_password):
|
||||
@@ -27,4 +27,4 @@ class PwdUtil:
|
||||
:param input_password: 输入的密码
|
||||
:return: 加密成功的密码
|
||||
"""
|
||||
return pwd_context.hash(input_password)
|
||||
return bcrypt.hashpw(input_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
@@ -73,6 +73,7 @@ class TemplateUtils:
|
||||
context = {
|
||||
'tplCategory': tpl_category,
|
||||
'tableName': gen_table.table_name,
|
||||
'tableComment': gen_table.table_comment,
|
||||
'functionName': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】',
|
||||
'ClassName': class_name,
|
||||
'className': class_name.lower(),
|
||||
@@ -91,6 +92,8 @@ class TemplateUtils:
|
||||
'table': gen_table,
|
||||
'dicts': cls.get_dicts(gen_table),
|
||||
'dbType': DataBaseConfig.db_type,
|
||||
'column_not_add_show': GenConstant.COLUMNNAME_NOT_ADD_SHOW,
|
||||
'column_not_edit_show': GenConstant.COLUMNNAME_NOT_EDIT_SHOW,
|
||||
}
|
||||
|
||||
# 设置菜单、树形结构、子表的上下文
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import datetime
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from dateutil.parser import parse
|
||||
from typing import Dict, List, Union
|
||||
|
||||
|
||||
def object_format_datetime(obj):
|
||||
@@ -8,7 +11,7 @@ def object_format_datetime(obj):
|
||||
"""
|
||||
for attr in dir(obj):
|
||||
value = getattr(obj, attr)
|
||||
if isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime):
|
||||
setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S'))
|
||||
return obj
|
||||
|
||||
@@ -21,7 +24,7 @@ def list_format_datetime(lst):
|
||||
for obj in lst:
|
||||
for attr in dir(obj):
|
||||
value = getattr(obj, attr)
|
||||
if isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime):
|
||||
setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S'))
|
||||
return lst
|
||||
|
||||
@@ -41,7 +44,7 @@ def format_datetime_dict_list(dicts):
|
||||
if isinstance(v, dict):
|
||||
# 递归遍历子字典
|
||||
new_item[k] = format_datetime_dict_list([v])[0]
|
||||
elif isinstance(v, datetime.datetime):
|
||||
elif isinstance(v, datetime):
|
||||
# 如果值是 datetime 类型,则格式化为字符串
|
||||
new_item[k] = v.strftime('%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
@@ -50,3 +53,89 @@ def format_datetime_dict_list(dicts):
|
||||
result.append(new_item)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TimeFormatUtil:
|
||||
"""
|
||||
时间格式化工具类
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def format_time(cls, time_info: Union[str, datetime], format: str = '%Y-%m-%d %H:%M:%S'):
|
||||
"""
|
||||
格式化时间字符串或datetime对象为指定格式
|
||||
|
||||
:param time_info: 时间字符串或datetime对象
|
||||
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
|
||||
:return: 格式化后的时间字符串
|
||||
"""
|
||||
if isinstance(time_info, datetime):
|
||||
format_date = time_info.strftime(format)
|
||||
else:
|
||||
try:
|
||||
date = parse(time_info)
|
||||
format_date = date.strftime(format)
|
||||
except Exception:
|
||||
format_date = time_info
|
||||
|
||||
return format_date
|
||||
|
||||
@classmethod
|
||||
def parse_date(cls, time_str: str):
|
||||
"""
|
||||
解析时间字符串提取日期部分
|
||||
|
||||
:param time_str: 时间字符串
|
||||
:return: 日期部分
|
||||
"""
|
||||
try:
|
||||
dt = parse(time_str)
|
||||
return dt.date()
|
||||
except Exception:
|
||||
return time_str
|
||||
|
||||
@classmethod
|
||||
def format_time_dict(cls, time_dict: Dict, format: str = '%Y-%m-%d %H:%M:%S'):
|
||||
"""
|
||||
格式化时间字典
|
||||
|
||||
:param time_dict: 时间字典
|
||||
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
|
||||
:return: 格式化后的时间字典
|
||||
"""
|
||||
copy_time_dict = deepcopy(time_dict)
|
||||
for k, v in copy_time_dict.items():
|
||||
if isinstance(v, (str, datetime)):
|
||||
copy_time_dict[k] = cls.format_time(v, format)
|
||||
elif isinstance(v, dict):
|
||||
copy_time_dict[k] = cls.format_time_dict(v, format)
|
||||
elif isinstance(v, list):
|
||||
copy_time_dict[k] = cls.format_time_list(v, format)
|
||||
else:
|
||||
copy_time_dict[k] = v
|
||||
|
||||
return copy_time_dict
|
||||
|
||||
@classmethod
|
||||
def format_time_list(cls, time_list: List, format: str = '%Y-%m-%d %H:%M:%S'):
|
||||
"""
|
||||
格式化时间列表
|
||||
|
||||
:param time_list: 时间列表
|
||||
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
|
||||
:return: 格式化后的时间列表
|
||||
"""
|
||||
format_time_list = []
|
||||
for item in time_list:
|
||||
if isinstance(item, (str, datetime)):
|
||||
format_item = cls.format_time(item, format)
|
||||
elif isinstance(item, dict):
|
||||
format_item = cls.format_time_dict(item, format)
|
||||
elif isinstance(item, list):
|
||||
format_item = cls.format_time_list(item, format)
|
||||
else:
|
||||
format_item = item
|
||||
|
||||
format_time_list.append(format_item)
|
||||
|
||||
return format_time_list
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vfadmin",
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.1",
|
||||
"description": "vfadmin管理系统",
|
||||
"author": "insistence",
|
||||
"license": "MIT",
|
||||
@@ -20,34 +20,36 @@
|
||||
"@antv/g2plot": "^2.4.31",
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "10.11.0",
|
||||
"@vueuse/core": "13.3.0",
|
||||
"ant-design-vue": "^4.1.1",
|
||||
"axios": "0.28.1",
|
||||
"axios": "1.9.0",
|
||||
"clipboard": "2.0.11",
|
||||
"echarts": "5.5.1",
|
||||
"element-plus": "2.7.6",
|
||||
"echarts": "5.6.0",
|
||||
"element-plus": "2.10.7",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "6.6.2",
|
||||
"js-beautify": "1.15.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"splitpanes": "3.1.5",
|
||||
"vue": "3.4.15",
|
||||
"pinia": "3.0.2",
|
||||
"splitpanes": "4.0.4",
|
||||
"vue": "3.5.16",
|
||||
"vue-cropper": "1.1.1",
|
||||
"vue-router": "4.4.0",
|
||||
"vue-router": "4.5.1",
|
||||
"vuedraggable": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@vue/compiler-sfc": "3.3.9",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"less": "^4.2.0",
|
||||
"sass": "1.77.5",
|
||||
"unplugin-auto-import": "0.17.6",
|
||||
"sass-embedded": "1.89.1",
|
||||
"unplugin-auto-import": "0.18.6",
|
||||
"unplugin-vue-setup-extend-plus": "1.0.1",
|
||||
"vite": "5.3.2",
|
||||
"vite": "6.3.5",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite-plugin-svg-icons": "2.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"quill": "2.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746590936918" class="icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" xmlns:xlink="http://www.w3.org/1999/xlink" width="233.203125" height="200"><path d="M1151.9144 325.11999969V89.12a57.04000031 57.04000031 0 0 0-28.8-49.44 58.15999969 58.15999969 0 0 0-57.76000031 0 57.04000031 57.04000031 0 0 0-28.8 49.44v235.99999969c0.24 84.31999969-33.6 152.56000031-94.08 212.00000062-60.07999969 59.83999969-141.84 80.64-227.04 80.4H225.91440031L388.07439969 457.11999969a56.80000031 56.80000031 0 0 0 12.40000031-62.16 57.76000031 57.76000031 0 0 0-94.00000031-18.63999938L48.8744 631.20000031a56.88 56.88 0 0 0 0 80.79999938l264.96 262.56a58.08 58.08 0 0 0 96.55999969-25.59999938 56.80000031 56.80000031 0 0 0-14.95999969-55.2L232.07439969 731.67999969h483.44000062c116.56000031 0 226.15999969-32.08000031 308.64-113.76 82.15999969-80.80000031 128.23999969-178.15999969 127.83999938-292.87999969" p-id="5379"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746760911144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12537" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M395.21211 182.914448c0 62.669318 49.541323 113.472378 110.642936 113.472378 61.093427 0 110.652146-50.80306 110.65214601-113.472378 0-62.685691-49.559742-113.487727-110.65214601-113.487727C444.75241 69.426721 395.21211 120.22978 395.21211 182.914448zM395.21211 523.34693101c0 62.668295 49.541323 113.487727 110.642936 113.48772699 61.093427 0 110.652146-50.820456 110.652146-113.487727 0-62.669318-49.559742-113.472378-110.652146-113.472378C444.75241 409.874553 395.21211 460.67761301 395.21211 523.34693101zM395.21211 841.084529c0 62.686714 49.541323 113.488751 110.642936 113.488751 61.093427 0 110.652146-50.80203599 110.65214601-113.488751 0-62.669318-49.559742-113.471354-110.65214601-113.471354C444.75241 727.614198 395.21211 778.416234 395.21211 841.084529z" p-id="12538"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,4 @@
|
||||
@import './variables.module.scss';
|
||||
@use './variables.module.scss' as *;
|
||||
|
||||
@mixin colorBtn($color) {
|
||||
background: $color;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
@import './variables.module.scss';
|
||||
@import './mixin.scss';
|
||||
@import './transition.scss';
|
||||
@import './element-ui.scss';
|
||||
@import './sidebar.scss';
|
||||
@import './btn.scss';
|
||||
@import './ruoyi.scss';
|
||||
@use './mixin.scss';
|
||||
@use './transition.scss';
|
||||
@use './element-ui.scss';
|
||||
@use './sidebar.scss';
|
||||
@use './btn.scss';
|
||||
@use './ruoyi.scss';
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
@@ -131,10 +130,6 @@ aside {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
@@ -102,21 +102,12 @@
|
||||
|
||||
/** 表格布局 **/
|
||||
.pagination-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 15px;
|
||||
padding: 10px 20px !important;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 分页器定位 */
|
||||
.pagination-container .el-pagination {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* 弹窗中的分页器 */
|
||||
.el-dialog .pagination-container {
|
||||
position: static !important;
|
||||
@@ -207,8 +198,6 @@
|
||||
}
|
||||
|
||||
.card-box {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
@use './variables.module.scss' as vars;
|
||||
|
||||
#app {
|
||||
|
||||
.main-container {
|
||||
min-height: 100%;
|
||||
transition: margin-left .28s;
|
||||
margin-left: $base-sidebar-width;
|
||||
margin-left: vars.$base-sidebar-width;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -13,7 +15,7 @@
|
||||
|
||||
.sidebar-container {
|
||||
transition: width 0.28s;
|
||||
width: $base-sidebar-width !important;
|
||||
width: vars.$base-sidebar-width !important;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
font-size: 0px;
|
||||
@@ -23,7 +25,7 @@
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
|
||||
box-shadow: 2px 0 6px rgba(0,21,41,.35);
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
// reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
@@ -87,12 +89,12 @@
|
||||
}
|
||||
|
||||
& .theme-dark .is-active > .el-sub-menu__title {
|
||||
color: $base-menu-color-active !important;
|
||||
color: vars.$base-menu-color-active !important;
|
||||
}
|
||||
|
||||
& .nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
& .el-sub-menu .el-menu-item {
|
||||
min-width: $base-sidebar-width !important;
|
||||
min-width: vars.$base-sidebar-width !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
@@ -101,10 +103,10 @@
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
& .theme-dark .el-sub-menu .el-menu-item {
|
||||
background-color: $base-sub-menu-background;
|
||||
background-color: vars.$base-sub-menu-background;
|
||||
|
||||
&:hover {
|
||||
background-color: $base-sub-menu-hover !important;
|
||||
background-color: vars.$base-sub-menu-hover !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +169,7 @@
|
||||
}
|
||||
|
||||
.el-menu--collapse .el-menu .el-sub-menu {
|
||||
min-width: $base-sidebar-width !important;
|
||||
min-width: vars.$base-sidebar-width !important;
|
||||
}
|
||||
|
||||
// mobile responsive
|
||||
@@ -178,14 +180,14 @@
|
||||
|
||||
.sidebar-container {
|
||||
transition: transform .28s;
|
||||
width: $base-sidebar-width !important;
|
||||
width: vars.$base-sidebar-width !important;
|
||||
}
|
||||
|
||||
&.hideSidebar {
|
||||
.sidebar-container {
|
||||
pointer-events: none;
|
||||
transition-duration: 0.3s;
|
||||
transform: translate3d(-$base-sidebar-width, 0, 0);
|
||||
transform: translate3d(-(vars.$base-sidebar-width), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
transition: opacity 0.28s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-enter-from,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
|
||||
@@ -70,42 +70,46 @@
|
||||
<p class="title">时间表达式</p>
|
||||
<table>
|
||||
<thead>
|
||||
<th v-for="item of tabTitles" :key="item">{{item}}</th>
|
||||
<th>Cron 表达式</th>
|
||||
<tr>
|
||||
<th v-for="item of tabTitles" :key="item">{{item}}</th>
|
||||
<th>Cron 表达式</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
|
||||
</td>
|
||||
<td class="result">
|
||||
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
|
||||
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
|
||||
</td>
|
||||
<tr>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
|
||||
<el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
|
||||
</td>
|
||||
<td class="result">
|
||||
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
|
||||
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -61,22 +61,24 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
})
|
||||
const fullYear = ref(0)
|
||||
const maxFullYear = ref(0)
|
||||
|
||||
const fullYear = Number(new Date().getFullYear())
|
||||
const maxFullYear = fullYear + 10
|
||||
const radioValue = ref(1)
|
||||
const cycle01 = ref(0)
|
||||
const cycle02 = ref(0)
|
||||
const average01 = ref(0)
|
||||
const cycle01 = ref(fullYear)
|
||||
const cycle02 = ref(fullYear + 1)
|
||||
const average01 = ref(fullYear)
|
||||
const average02 = ref(1)
|
||||
const checkboxList = ref([])
|
||||
const checkCopy = ref([])
|
||||
const checkCopy = ref([fullYear])
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1)
|
||||
cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value)
|
||||
cycle01.value = props.check(cycle01.value, fullYear, maxFullYear - 1)
|
||||
cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear)
|
||||
return cycle01.value + '-' + cycle02.value
|
||||
})
|
||||
const averageTotal = computed(() => {
|
||||
average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1)
|
||||
average01.value = props.check(average01.value, fullYear, maxFullYear - 1)
|
||||
average02.value = props.check(average02.value, 1, 10)
|
||||
return average01.value + '/' + average02.value
|
||||
})
|
||||
@@ -97,8 +99,8 @@ function changeRadioValue(value) {
|
||||
radioValue.value = 3
|
||||
} else if (value.indexOf("/") > -1) {
|
||||
const indexArr = value.split('/')
|
||||
average01.value = Number(indexArr[1])
|
||||
average02.value = Number(indexArr[0])
|
||||
average01.value = Number(indexArr[0])
|
||||
average02.value = Number(indexArr[1])
|
||||
radioValue.value = 4
|
||||
} else {
|
||||
checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
|
||||
@@ -129,14 +131,6 @@ function onRadioChange() {
|
||||
break
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
fullYear.value = Number(new Date().getFullYear())
|
||||
maxFullYear.value = fullYear.value + 10
|
||||
cycle01.value = fullYear.value
|
||||
cycle02.value = cycle01.value + 1
|
||||
average01.value = fullYear.value
|
||||
checkCopy.value = [fullYear.value]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import { getToken } from "@/utils/auth";
|
||||
@@ -124,6 +125,7 @@ onMounted(() => {
|
||||
quill.format("image", false);
|
||||
}
|
||||
});
|
||||
quill.root.addEventListener('paste', handlePasteCapture, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,6 +170,29 @@ function handleUploadSuccess(res, file) {
|
||||
function handleUploadError() {
|
||||
proxy.$modal.msgError("图片插入失败");
|
||||
}
|
||||
|
||||
// 复制粘贴图片处理
|
||||
function handlePasteCapture(e) {
|
||||
const clipboard = e.clipboardData || window.clipboardData;
|
||||
if (clipboard && clipboard.items) {
|
||||
for (let i = 0; i < clipboard.items.length; i++) {
|
||||
const item = clipboard.items[i];
|
||||
if (item.type.indexOf('image') !== -1) {
|
||||
e.preventDefault();
|
||||
const file = item.getAsFile();
|
||||
insertImage(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertImage(file) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
|
||||
handleUploadSuccess(res.data);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:action="uploadFileUrl"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:file-list="fileList"
|
||||
:data="data"
|
||||
:limit="limit"
|
||||
:on-error="handleUploadError"
|
||||
:on-exceed="handleExceed"
|
||||
@@ -13,25 +14,26 @@
|
||||
:headers="headers"
|
||||
class="upload-file-uploader"
|
||||
ref="fileUpload"
|
||||
v-if="!disabled"
|
||||
>
|
||||
<!-- 上传按钮 -->
|
||||
<el-button type="primary">选取文件</el-button>
|
||||
</el-upload>
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" v-if="showTip">
|
||||
<div class="el-upload__tip" v-if="showTip && !disabled">
|
||||
请上传
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
|
||||
的文件
|
||||
</div>
|
||||
<!-- 文件列表 -->
|
||||
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
|
||||
<transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
|
||||
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
|
||||
<el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
|
||||
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
|
||||
</el-link>
|
||||
<div class="ele-upload-list__item-content-action">
|
||||
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
|
||||
<el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled">删除</el-link>
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
@@ -40,28 +42,48 @@
|
||||
|
||||
<script setup>
|
||||
import { getToken } from "@/utils/auth";
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [String, Object, Array],
|
||||
// 上传接口地址
|
||||
action: {
|
||||
type: String,
|
||||
default: "/common/upload"
|
||||
},
|
||||
// 上传携带的参数
|
||||
data: {
|
||||
type: Object
|
||||
},
|
||||
// 数量限制
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 大小限制(MB)
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["doc", "xls", "ppt", "txt", "pdf"],
|
||||
default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"]
|
||||
},
|
||||
// 是否显示提示
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 禁用组件(仅查看文件)
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 拖动排序
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -70,7 +92,7 @@ const emit = defineEmits();
|
||||
const number = ref(0);
|
||||
const uploadList = ref([]);
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传文件服务器地址
|
||||
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action); // 上传文件服务器地址
|
||||
const headers = ref({ Authorization: "Bearer " + getToken() });
|
||||
const fileList = ref([]);
|
||||
const showTip = computed(
|
||||
@@ -134,6 +156,7 @@ function handleExceed() {
|
||||
// 上传失败
|
||||
function handleUploadError(err) {
|
||||
proxy.$modal.msgError("上传文件失败");
|
||||
proxy.$modal.closeLoading();
|
||||
}
|
||||
|
||||
// 上传成功回调
|
||||
@@ -188,9 +211,30 @@ function listToString(list, separator) {
|
||||
}
|
||||
return strs != '' ? strs.substr(0, strs.length - 1) : '';
|
||||
}
|
||||
|
||||
// 初始化拖拽排序
|
||||
onMounted(() => {
|
||||
if (props.drag && !props.disabled) {
|
||||
nextTick(() => {
|
||||
const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList
|
||||
Sortable.create(element, {
|
||||
ghostClass: 'file-upload-darg',
|
||||
onEnd: (evt) => {
|
||||
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
|
||||
fileList.value.splice(evt.newIndex, 0, movedItem)
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-upload-darg {
|
||||
opacity: 0.5;
|
||||
background: #c8ebfb;
|
||||
}
|
||||
.upload-file-uploader {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@@ -199,6 +243,7 @@ function listToString(list, separator) {
|
||||
line-height: 2;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
transition: none !important;
|
||||
}
|
||||
.upload-file-list .ele-upload-list__item-content {
|
||||
display: flex;
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
<template>
|
||||
<div :class="{ 'show': show }" class="header-search">
|
||||
<div class="header-search">
|
||||
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
|
||||
<el-select
|
||||
ref="headerSearchSelectRef"
|
||||
v-model="search"
|
||||
:remote-method="querySearch"
|
||||
filterable
|
||||
default-first-option
|
||||
remote
|
||||
placeholder="Search"
|
||||
class="header-search-select"
|
||||
@change="change"
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
width="600"
|
||||
@close="close"
|
||||
:show-close="false"
|
||||
append-to-body
|
||||
>
|
||||
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="search"
|
||||
ref="headerSearchSelectRef"
|
||||
size="large"
|
||||
@input="querySearch"
|
||||
prefix-icon="Search"
|
||||
placeholder="菜单搜索,支持标题、URL模糊查询"
|
||||
clearable
|
||||
@keyup.enter="selectActiveResult"
|
||||
@keydown.up.prevent="navigateResult('up')"
|
||||
@keydown.down.prevent="navigateResult('down')"
|
||||
>
|
||||
</el-input>
|
||||
|
||||
<div class="result-wrap">
|
||||
<el-scrollbar>
|
||||
<div class="search-item" tabindex="1" v-for="(item, index) in options" :key="item.path" :style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1">
|
||||
<div class="left">
|
||||
<svg-icon class="menu-icon" :icon-class="item.icon" />
|
||||
</div>
|
||||
<div class="search-info" @click="change(item)">
|
||||
<div class="menu-title">
|
||||
{{ item.title.join(" / ") }}
|
||||
</div>
|
||||
<div class="menu-path">
|
||||
{{ item.path }}
|
||||
</div>
|
||||
</div>
|
||||
<svg-icon icon-class="enter" v-show="index === activeIndex"/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -21,38 +48,46 @@
|
||||
import Fuse from 'fuse.js'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
import { isHttp } from '@/utils/validate'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
const search = ref('');
|
||||
const options = ref([]);
|
||||
const searchPool = ref([]);
|
||||
const show = ref(false);
|
||||
const fuse = ref(undefined);
|
||||
const headerSearchSelectRef = ref(null);
|
||||
const router = useRouter();
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const search = ref('')
|
||||
const options = ref([])
|
||||
const searchPool = ref([])
|
||||
const activeIndex = ref(-1)
|
||||
const show = ref(false)
|
||||
const fuse = ref(undefined)
|
||||
const headerSearchSelectRef = ref(null)
|
||||
const router = useRouter()
|
||||
const theme = computed(() => useSettingsStore().theme)
|
||||
const routes = computed(() => usePermissionStore().defaultRoutes)
|
||||
|
||||
function click() {
|
||||
show.value = !show.value
|
||||
if (show.value) {
|
||||
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
|
||||
options.value = searchPool.value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function close() {
|
||||
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
|
||||
search.value = ''
|
||||
options.value = []
|
||||
show.value = false
|
||||
activeIndex.value = -1
|
||||
}
|
||||
|
||||
function change(val) {
|
||||
const path = val.path;
|
||||
const query = val.query;
|
||||
const path = val.path
|
||||
const query = val.query
|
||||
if (isHttp(path)) {
|
||||
// http(s):// 路径新窗口打开
|
||||
const pindex = path.indexOf("http");
|
||||
window.open(path.substr(pindex, path.length), "_blank");
|
||||
const pindex = path.indexOf("http")
|
||||
window.open(path.substr(pindex, path.length), "_blank")
|
||||
} else {
|
||||
if (query) {
|
||||
router.push({ path: path, query: JSON.parse(query) });
|
||||
router.push({ path: path, query: JSON.parse(query) })
|
||||
} else {
|
||||
router.push(path)
|
||||
}
|
||||
@@ -64,6 +99,7 @@ function change(val) {
|
||||
show.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function initFuse(list) {
|
||||
fuse.value = new Fuse(list, {
|
||||
shouldSort: true,
|
||||
@@ -80,6 +116,7 @@ function initFuse(list) {
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
function generateRoutes(routes, basePath = '', prefixTitle = []) {
|
||||
@@ -88,16 +125,17 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
|
||||
for (const r of routes) {
|
||||
// skip hidden router
|
||||
if (r.hidden) { continue }
|
||||
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
|
||||
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path
|
||||
const data = {
|
||||
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
|
||||
title: [...prefixTitle]
|
||||
title: [...prefixTitle],
|
||||
icon: ''
|
||||
}
|
||||
|
||||
if (r.meta && r.meta.title) {
|
||||
data.title = [...data.title, r.meta.title]
|
||||
|
||||
if (r.redirect !== 'noRedirect') {
|
||||
data.icon = r.meta.icon
|
||||
if (r.redirect !== "noRedirect") {
|
||||
// only push the routes with title
|
||||
// special case: need to exclude parent router without redirect
|
||||
res.push(data)
|
||||
@@ -117,30 +155,42 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function querySearch(query) {
|
||||
activeIndex.value = -1
|
||||
if (query !== '') {
|
||||
options.value = fuse.value.search(query)
|
||||
options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value
|
||||
} else {
|
||||
options.value = []
|
||||
options.value = searchPool.value
|
||||
}
|
||||
}
|
||||
|
||||
function activeStyle(index) {
|
||||
if (index !== activeIndex.value) return {}
|
||||
return {
|
||||
"background-color": theme.value,
|
||||
"color": "#fff"
|
||||
}
|
||||
}
|
||||
|
||||
function navigateResult(direction) {
|
||||
if (direction === "up") {
|
||||
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
|
||||
} else if (direction === "down") {
|
||||
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
|
||||
}
|
||||
}
|
||||
|
||||
function selectActiveResult() {
|
||||
if (options.value.length > 0 && activeIndex.value >= 0) {
|
||||
change(options.value[activeIndex.value])
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchPool.value = generateRoutes(routes.value);
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
searchPool.value = generateRoutes(routes.value)
|
||||
})
|
||||
|
||||
watch(show, (value) => {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', close)
|
||||
} else {
|
||||
document.body.removeEventListener('click', close)
|
||||
}
|
||||
})
|
||||
|
||||
watch(searchPool, (list) => {
|
||||
initFuse(list)
|
||||
})
|
||||
@@ -148,40 +198,55 @@ watch(searchPool, (list) => {
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.header-search {
|
||||
font-size: 0 !important;
|
||||
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.header-search-select {
|
||||
font-size: 18px;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
.result-wrap {
|
||||
height: 280px;
|
||||
margin: 6px 0;
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
vertical-align: middle;
|
||||
.search-item {
|
||||
display: flex;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
padding-right: 10px;
|
||||
|
||||
.left {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
|
||||
.menu-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-info {
|
||||
padding-left: 5px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
|
||||
.menu-title,
|
||||
.menu-path {
|
||||
height: 20px;
|
||||
}
|
||||
.menu-path {
|
||||
color: #ccc;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
.header-search-select {
|
||||
width: 210px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.search-item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
multiple
|
||||
:disabled="disabled"
|
||||
:action="uploadImgUrl"
|
||||
list-type="picture-card"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:data="data"
|
||||
:limit="limit"
|
||||
:on-error="handleUploadError"
|
||||
:on-exceed="handleExceed"
|
||||
@@ -20,7 +22,7 @@
|
||||
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
|
||||
</el-upload>
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" v-if="showTip">
|
||||
<div class="el-upload__tip" v-if="showTip && !disabled">
|
||||
请上传
|
||||
<template v-if="fileSize">
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||
@@ -48,29 +50,49 @@
|
||||
<script setup>
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { isExternal } from "@/utils/validate";
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [String, Object, Array],
|
||||
// 上传接口地址
|
||||
action: {
|
||||
type: String,
|
||||
default: "/common/upload"
|
||||
},
|
||||
// 上传携带的参数
|
||||
data: {
|
||||
type: Object
|
||||
},
|
||||
// 图片数量限制
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 大小限制(MB)
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["png", "jpg", "jpeg"],
|
||||
default: () => ["png", "jpg", "jpeg"]
|
||||
},
|
||||
// 是否显示提示
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 禁用组件(仅查看图片)
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 拖动排序
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
@@ -80,7 +102,7 @@ const uploadList = ref([]);
|
||||
const dialogImageUrl = ref("");
|
||||
const dialogVisible = ref(false);
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
|
||||
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action); // 上传的图片服务器地址
|
||||
const headers = ref({ Authorization: "Bearer " + getToken() });
|
||||
const fileList = ref([]);
|
||||
const showTip = computed(
|
||||
@@ -206,6 +228,22 @@ function listToString(list, separator) {
|
||||
}
|
||||
return strs != "" ? strs.substr(0, strs.length - 1) : "";
|
||||
}
|
||||
|
||||
// 初始化拖拽排序
|
||||
onMounted(() => {
|
||||
if (props.drag && !props.disabled) {
|
||||
nextTick(() => {
|
||||
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
|
||||
Sortable.create(element, {
|
||||
onEnd: (evt) => {
|
||||
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
|
||||
fileList.value.splice(evt.newIndex, 0, movedItem)
|
||||
emit('update:modelValue', listToString(fileList.value))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -213,4 +251,8 @@ function listToString(list, separator) {
|
||||
:deep(.hide .el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.el-upload.el-upload--picture-card.is-disabled) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -97,7 +97,6 @@ function handleCurrentChange(val) {
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
|
||||
@@ -7,15 +7,20 @@
|
||||
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
|
||||
<el-button circle icon="Refresh" @click="refresh()" />
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
|
||||
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0">
|
||||
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
|
||||
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
|
||||
<el-button circle icon="Menu" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<template v-for="item in columns" :key="item.key">
|
||||
<!-- 全选/反选 按钮 -->
|
||||
<el-dropdown-item>
|
||||
<el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox>
|
||||
</el-dropdown-item>
|
||||
<div class="check-line"></div>
|
||||
<template v-for="(item, key) in columns" :key="item.key">
|
||||
<el-dropdown-item>
|
||||
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
|
||||
<el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" />
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
@@ -27,7 +32,7 @@
|
||||
<el-transfer
|
||||
:titles="['显示', '隐藏']"
|
||||
v-model="value"
|
||||
:data="columns"
|
||||
:data="transferData"
|
||||
@change="dataChange"
|
||||
></el-transfer>
|
||||
</el-dialog>
|
||||
@@ -39,83 +44,119 @@ const props = defineProps({
|
||||
/* 是否显示检索条件 */
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
/* 显隐列信息 */
|
||||
/* 显隐列信息(数组格式、对象格式) */
|
||||
columns: {
|
||||
type: Array,
|
||||
type: [Array, Object],
|
||||
default: () => ({})
|
||||
},
|
||||
/* 是否显示检索图标 */
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
/* 显隐列类型(transfer穿梭框、checkbox复选框) */
|
||||
showColumnsType: {
|
||||
type: String,
|
||||
default: "checkbox",
|
||||
default: "checkbox"
|
||||
},
|
||||
/* 右外边距 */
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
default: 10
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:showSearch', 'queryTable']);
|
||||
const emits = defineEmits(['update:showSearch', 'queryTable'])
|
||||
|
||||
// 显隐数据
|
||||
const value = ref([]);
|
||||
const value = ref([])
|
||||
// 弹出层标题
|
||||
const title = ref("显示/隐藏");
|
||||
const title = ref("显示/隐藏")
|
||||
// 是否显示弹出层
|
||||
const open = ref(false);
|
||||
const open = ref(false)
|
||||
|
||||
const style = computed(() => {
|
||||
const ret = {};
|
||||
const ret = {}
|
||||
if (props.gutter) {
|
||||
ret.marginRight = `${props.gutter / 2}px`;
|
||||
ret.marginRight = `${props.gutter / 2}px`
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
return ret
|
||||
})
|
||||
|
||||
// 是否全选/半选 状态
|
||||
const isChecked = computed({
|
||||
get: () => Array.isArray(props.columns) ? props.columns.every(col => col.visible) : Object.values(props.columns).every((col) => col.visible),
|
||||
set: () => {}
|
||||
})
|
||||
const isIndeterminate = computed(() => Array.isArray(props.columns) ? props.columns.some((col) => col.visible) && !isChecked.value : Object.values(props.columns).some((col) => col.visible) && !isChecked.value)
|
||||
const transferData = computed(() => Array.isArray(props.columns) ? props.columns.map((item, index) => ({ key: index, label: item.label })) : Object.keys(props.columns).map((key, index) => ({ key: index, label: props.columns[key].label })))
|
||||
|
||||
// 搜索
|
||||
function toggleSearch() {
|
||||
emits("update:showSearch", !props.showSearch);
|
||||
emits("update:showSearch", !props.showSearch)
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function refresh() {
|
||||
emits("queryTable");
|
||||
emits("queryTable")
|
||||
}
|
||||
|
||||
// 右侧列表元素变化
|
||||
function dataChange(data) {
|
||||
for (let item in props.columns) {
|
||||
const key = props.columns[item].key;
|
||||
props.columns[item].visible = !data.includes(key);
|
||||
if (Array.isArray(props.columns)) {
|
||||
for (let item in props.columns) {
|
||||
const key = props.columns[item].key
|
||||
props.columns[item].visible = !data.includes(key)
|
||||
}
|
||||
} else {
|
||||
Object.keys(props.columns).forEach((key, index) => {
|
||||
props.columns[key].visible = !data.includes(index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 打开显隐列dialog
|
||||
function showColumn() {
|
||||
open.value = true;
|
||||
open.value = true
|
||||
}
|
||||
|
||||
if (props.showColumnsType == 'transfer') {
|
||||
// 显隐列初始默认隐藏列
|
||||
for (let item in props.columns) {
|
||||
if (props.columns[item].visible === false) {
|
||||
value.value.push(parseInt(item));
|
||||
if (props.showColumnsType == "transfer") {
|
||||
// transfer穿梭显隐列初始默认隐藏列
|
||||
if (Array.isArray(props.columns)) {
|
||||
for (let item in props.columns) {
|
||||
if (props.columns[item].visible === false) {
|
||||
value.value.push(parseInt(item))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Object.keys(props.columns).forEach((key, index) => {
|
||||
if (props.columns[key].visible === false) {
|
||||
value.value.push(index)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 勾选
|
||||
function checkboxChange(event, label) {
|
||||
props.columns.filter(item => item.label == label)[0].visible = event;
|
||||
// 单勾选
|
||||
function checkboxChange(event, key) {
|
||||
if (Array.isArray(props.columns)) {
|
||||
props.columns.filter(item => item.key == key)[0].visible = event
|
||||
} else {
|
||||
props.columns[key].visible = event
|
||||
}
|
||||
}
|
||||
|
||||
// 切换全选/反选
|
||||
function toggleCheckAll() {
|
||||
const newValue = !isChecked.value
|
||||
if (Array.isArray(props.columns)) {
|
||||
props.columns.forEach((col) => (col.visible = newValue))
|
||||
} else {
|
||||
Object.values(props.columns).forEach((col) => (col.visible = newValue))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@@ -131,4 +172,10 @@ function checkboxChange(event, label) {
|
||||
line-height: 30px;
|
||||
padding: 0 17px;
|
||||
}
|
||||
.check-line {
|
||||
width: 90%;
|
||||
height: 1px;
|
||||
background-color: #ccc;
|
||||
margin: 3px auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -63,7 +63,7 @@ const topMenus = computed(() => {
|
||||
routers.value.map((menu) => {
|
||||
if (menu.hidden !== true) {
|
||||
// 兼容顶部栏一级菜单内部跳转
|
||||
if (menu.path === "/") {
|
||||
if (menu.path === '/' && menu.children) {
|
||||
topMenus.push(menu.children[0]);
|
||||
} else {
|
||||
topMenus.push(menu);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* v-copyText 复制文本内容
|
||||
* Copyright (c) 2022 ruoyi
|
||||
*/
|
||||
|
||||
export default {
|
||||
beforeMount(el, { value, arg }) {
|
||||
if (arg === "callback") {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* v-hasPermi 操作权限处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
/**
|
||||
* v-hasPermi 操作权限处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* v-hasRole 角色权限处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
/**
|
||||
* v-hasRole 角色权限处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
</transition>
|
||||
</router-view>
|
||||
<iframe-toggle />
|
||||
<copyright />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import copyright from "./Copyright/index"
|
||||
import iframeToggle from "./IframeToggle/index"
|
||||
import useTagsViewStore from '@/store/modules/tagsView'
|
||||
|
||||
@@ -22,7 +24,7 @@ onMounted(() => {
|
||||
addIframe()
|
||||
})
|
||||
|
||||
watch((route) => {
|
||||
watchEffect(() => {
|
||||
addIframe()
|
||||
})
|
||||
|
||||
@@ -42,6 +44,10 @@ function addIframe() {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-main:has(.copyright) {
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<footer v-if="visible" class="copyright">
|
||||
<span>{{ content }}</span>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const visible = computed(() => settingsStore.footerVisible)
|
||||
const content = computed(() => settingsStore.footerContent)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.copyright {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 36px;
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
background-color: #f8f8f8;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #e7e7e7;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div :style="'height:' + height">
|
||||
<div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!">
|
||||
<iframe
|
||||
:id="iframeId"
|
||||
style="width: 100%; height: 100%"
|
||||
:src="src"
|
||||
ref="iframeRef"
|
||||
frameborder="no"
|
||||
></iframe>
|
||||
</div>
|
||||
@@ -20,5 +21,15 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
|
||||
const loading = ref(true)
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + 'px')
|
||||
const iframeRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
if (iframeRef.value) {
|
||||
iframeRef.value.onload = () => {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
|
||||
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
|
||||
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
|
||||
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
|
||||
|
||||
<div class="right-menu">
|
||||
<template v-if="appStore.device !== 'mobile'">
|
||||
@@ -29,27 +29,26 @@
|
||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<div class="avatar-container">
|
||||
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="userStore.avatar" class="user-avatar" />
|
||||
<el-icon><caret-bottom /></el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<router-link to="/user/profile">
|
||||
<el-dropdown-item>个人中心</el-dropdown-item>
|
||||
</router-link>
|
||||
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
|
||||
|
||||
<el-dropdown @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="userStore.avatar" class="user-avatar" />
|
||||
<span class="user-nickname"> {{ userStore.nickName }} </span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<router-link to="/user/profile">
|
||||
<el-dropdown-item>个人中心</el-dropdown-item>
|
||||
</router-link>
|
||||
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
|
||||
<span>布局设置</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-dropdown-item divided command="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -79,13 +78,13 @@ function toggleSideBar() {
|
||||
function handleCommand(command) {
|
||||
switch (command) {
|
||||
case "setLayout":
|
||||
setLayout();
|
||||
break;
|
||||
setLayout()
|
||||
break
|
||||
case "logout":
|
||||
logout();
|
||||
break;
|
||||
logout()
|
||||
break
|
||||
default:
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,14 +95,14 @@ function logout() {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
userStore.logOut().then(() => {
|
||||
location.href = '/index';
|
||||
location.href = '/index'
|
||||
})
|
||||
}).catch(() => { });
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
const emits = defineEmits(['setLayout'])
|
||||
function setLayout() {
|
||||
emits('setLayout');
|
||||
emits('setLayout')
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
@@ -161,7 +160,7 @@ function toggleTheme() {
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: var(--navbar-text);
|
||||
color: #5a5e66;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.hover-effect {
|
||||
@@ -188,17 +187,30 @@ function toggleTheme() {
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
margin-right: 40px;
|
||||
margin-right: 0px;
|
||||
padding-right: 0px;
|
||||
|
||||
.avatar-wrapper {
|
||||
margin-top: 5px;
|
||||
margin-top: 10px;
|
||||
right: 8px;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 8px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.user-nickname{
|
||||
position: relative;
|
||||
left: 0px;
|
||||
// bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
i {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px">
|
||||
<el-drawer v-model="showSettings" :withHeader="false" :lock-scroll="false" direction="rtl" size="300px">
|
||||
<div class="setting-drawer-title">
|
||||
<h3 class="drawer-title">主题风格设置</h3>
|
||||
</div>
|
||||
@@ -49,6 +49,13 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>显示页签图标</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.tagsIcon" :disabled="!settingsStore.tagsView" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>固定 Header</span>
|
||||
<span class="comp-style">
|
||||
@@ -66,7 +73,14 @@
|
||||
<div class="drawer-item">
|
||||
<span>动态标题</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.dynamicTitle" class="drawer-switch" />
|
||||
<el-switch v-model="settingsStore.dynamicTitle" @change="dynamicTitleChange" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>底部版权</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.footerVisible" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -79,10 +93,6 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import variables from '@/assets/styles/variables.module.scss'
|
||||
import axios from 'axios'
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
@@ -106,6 +116,11 @@ function topNavChange(val) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否需要dynamicTitle */
|
||||
function dynamicTitleChange() {
|
||||
useSettingsStore().setTitle(useSettingsStore().title)
|
||||
}
|
||||
|
||||
function themeChange(val) {
|
||||
settingsStore.theme = val;
|
||||
handleThemeStyle(val);
|
||||
@@ -119,9 +134,11 @@ function saveSetting() {
|
||||
let layoutSetting = {
|
||||
"topNav": storeSettings.value.topNav,
|
||||
"tagsView": storeSettings.value.tagsView,
|
||||
"tagsIcon": storeSettings.value.tagsIcon,
|
||||
"fixedHeader": storeSettings.value.fixedHeader,
|
||||
"sidebarLogo": storeSettings.value.sidebarLogo,
|
||||
"dynamicTitle": storeSettings.value.dynamicTitle,
|
||||
"footerVisible": storeSettings.value.footerVisible,
|
||||
"sideTheme": storeSettings.value.sideTheme,
|
||||
"theme": storeSettings.value.theme
|
||||
};
|
||||
|
||||
@@ -47,8 +47,6 @@ const getLogoTextColor = computed(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/styles/variables.module.scss';
|
||||
|
||||
.sidebarLogoFade-enter-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:data-path="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
|
||||
{{ tag.title }}
|
||||
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
|
||||
@@ -62,6 +63,7 @@ const router = useRouter();
|
||||
const visitedViews = computed(() => useTagsViewStore().visitedViews);
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
const tagsIcon = computed(() => useSettingsStore().tagsIcon)
|
||||
|
||||
watch(route, () => {
|
||||
addTags()
|
||||
@@ -285,6 +287,11 @@ function handleScroll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags-view-item.active.has-icon::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: var(--el-bg-color-overlay, #fff);
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
@@ -66,11 +64,11 @@ function setLayout() {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
@use "@/assets/styles/mixin.scss" as mix;
|
||||
@use "@/assets/styles/variables.module.scss" as vars;
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
@include mix.clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -96,7 +94,7 @@ function setLayout() {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
width: calc(100% - #{vars.$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import elementIcons from '@/components/SvgIcon/svgicon'
|
||||
import './permission' // permission control
|
||||
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { getConfigKey } from "@/api/system/config"
|
||||
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
|
||||
|
||||
// 分页组件
|
||||
@@ -52,6 +53,7 @@ app.config.globalProperties.parseTime = parseTime
|
||||
app.config.globalProperties.resetForm = resetForm
|
||||
app.config.globalProperties.handleTree = handleTree
|
||||
app.config.globalProperties.addDateRange = addDateRange
|
||||
app.config.globalProperties.getConfigKey = getConfigKey
|
||||
app.config.globalProperties.selectDictLabel = selectDictLabel
|
||||
app.config.globalProperties.selectDictLabels = selectDictLabels
|
||||
|
||||
|
||||
@@ -59,8 +59,10 @@ export default {
|
||||
return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
|
||||
},
|
||||
// 打开tab页签
|
||||
openPage(url) {
|
||||
return router.push(url);
|
||||
openPage(title, url, params) {
|
||||
const obj = { path: url, meta: { title: title } }
|
||||
useTagsViewStore().addView(obj)
|
||||
return router.push({ path: url, query: params })
|
||||
},
|
||||
// 修改tab页签
|
||||
updatePage(obj) {
|
||||
|
||||
@@ -77,7 +77,7 @@ export const constantRoutes = [
|
||||
redirect: 'noredirect',
|
||||
children: [
|
||||
{
|
||||
path: 'profile',
|
||||
path: 'profile/:activeTab?',
|
||||
component: () => import('@/views/system/user/profile/index'),
|
||||
name: 'Profile',
|
||||
meta: { title: '个人中心', icon: 'user' }
|
||||
|
||||
@@ -3,10 +3,12 @@ export default {
|
||||
* 网页标题
|
||||
*/
|
||||
title: import.meta.env.VITE_APP_TITLE,
|
||||
|
||||
/**
|
||||
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
|
||||
*/
|
||||
sideTheme: 'theme-dark',
|
||||
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
*/
|
||||
@@ -22,6 +24,11 @@ export default {
|
||||
*/
|
||||
tagsView: true,
|
||||
|
||||
/**
|
||||
* 显示页签图标
|
||||
*/
|
||||
tagsIcon: false,
|
||||
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
@@ -38,10 +45,12 @@ export default {
|
||||
dynamicTitle: false,
|
||||
|
||||
/**
|
||||
* @type {string | array} 'production' | ['production', 'development']
|
||||
* @description Need show err logs component.
|
||||
* The default is only used in the production env
|
||||
* If you want to also use it in dev, you can pass ['production', 'development']
|
||||
* 是否显示底部版权
|
||||
*/
|
||||
errorLog: 'production'
|
||||
footerVisible: false,
|
||||
|
||||
/**
|
||||
* 底部版权文本内容
|
||||
*/
|
||||
footerContent: 'Copyright © 2024-2025 insistence.tech All Rights Reserved.'
|
||||
}
|
||||
|
||||
@@ -85,28 +85,13 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
|
||||
function filterChildren(childrenMap, lastRouter = false) {
|
||||
var children = []
|
||||
childrenMap.forEach((el, index) => {
|
||||
if (el.children && el.children.length) {
|
||||
if (el.component === 'ParentView' && !lastRouter) {
|
||||
el.children.forEach(c => {
|
||||
c.path = el.path + '/' + c.path
|
||||
if (c.children && c.children.length) {
|
||||
children = children.concat(filterChildren(c.children, c))
|
||||
return
|
||||
}
|
||||
children.push(c)
|
||||
})
|
||||
return
|
||||
}
|
||||
childrenMap.forEach(el => {
|
||||
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
|
||||
if (el.children && el.children.length && el.component === 'ParentView') {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
} else {
|
||||
children.push(el)
|
||||
}
|
||||
if (lastRouter) {
|
||||
el.path = lastRouter.path + '/' + el.path
|
||||
if (el.children && el.children.length) {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
return
|
||||
}
|
||||
}
|
||||
children = children.concat(el)
|
||||
})
|
||||
return children
|
||||
}
|
||||
@@ -136,7 +121,7 @@ export const loadView = (view) => {
|
||||
res = () => modules[path]();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
return res
|
||||
}
|
||||
|
||||
export default usePermissionStore
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||
const isDark = useDark()
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
|
||||
const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
|
||||
|
||||
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
|
||||
|
||||
@@ -19,9 +19,12 @@ const useSettingsStore = defineStore(
|
||||
showSettings: showSettings,
|
||||
topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
|
||||
tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
|
||||
tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
|
||||
fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
|
||||
sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
|
||||
dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
|
||||
footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible,
|
||||
footerContent: footerContent,
|
||||
isDark: isDark.value
|
||||
}),
|
||||
actions: {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import router from '@/router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
@@ -10,6 +12,7 @@ const useUserStore = defineStore(
|
||||
token: getToken(),
|
||||
id: '',
|
||||
name: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
@@ -48,7 +51,20 @@ const useUserStore = defineStore(
|
||||
}
|
||||
this.id = user.userId
|
||||
this.name = user.userName
|
||||
this.nickName = user.nickName
|
||||
this.avatar = avatar
|
||||
/* 初始密码提示 */
|
||||
if(res.isDefaultModifyPwd) {
|
||||
ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
}
|
||||
/* 过期密码提示 */
|
||||
if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
|
||||
ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
}
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import store from '@/store'
|
||||
import defaultSettings from '@/settings'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ function buildElRadioGroupChild(conf) {
|
||||
if (conf.options && conf.options.length) {
|
||||
const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio'
|
||||
const border = conf.border ? 'border' : ''
|
||||
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
|
||||
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :value="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
@@ -328,7 +328,7 @@ function buildElCheckboxGroupChild(conf) {
|
||||
if (conf.options && conf.options.length) {
|
||||
const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
|
||||
const border = conf.border ? 'border' : ''
|
||||
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
|
||||
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :value="item.label" :disabled="item.disabled" ${border} />`)
|
||||
}
|
||||
return children.join('\n')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">vfadmin后台管理系统</h3>
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
@@ -59,7 +59,7 @@
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2024 insistence.tech All Rights Reserved.</span>
|
||||
<span>Copyright © 2024-2025 insistence.tech All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -70,7 +70,8 @@ import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt";
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const title = import.meta.env.VITE_APP_TITLE;
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
@@ -183,6 +184,7 @@ getCookie();
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
z-index: 1;
|
||||
.el-input {
|
||||
height: 40px;
|
||||
input {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<template #header><Monitor style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">基本信息</span></template>
|
||||
|
||||
@@ -327,8 +327,9 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Job">
|
||||
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
|
||||
import Crontab from '@/components/Crontab'
|
||||
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"
|
||||
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_job_group, sys_job_status, sys_job_executor } = proxy.useDict("sys_job_group", "sys_job_status", "sys_job_executor");
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type","sys_common_status");
|
||||
const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type", "sys_common_status");
|
||||
|
||||
const operlogList = ref([]);
|
||||
const open = ref(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<template #header><Cpu style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">CPU</span></template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="register">
|
||||
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
|
||||
<h3 class="title">vfadmin后台管理系统</h3>
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="registerForm.username"
|
||||
@@ -79,6 +79,7 @@
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { getCodeImg, register } from "@/api/login";
|
||||
|
||||
const title = import.meta.env.VITE_APP_TITLE;
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
|
||||
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
|
||||
<el-table-column label="角色编号" align="center" prop="roleId" />
|
||||
<el-table-column label="角色名称" align="center" prop="roleName" />
|
||||
<el-table-column label="权限字符" align="center" prop="roleKey" />
|
||||
@@ -65,21 +65,32 @@ const form = ref({
|
||||
|
||||
/** 单击选中行数据 */
|
||||
function clickRow(row) {
|
||||
proxy.$refs["roleRef"].toggleRowSelection(row);
|
||||
if (checkSelectable(row)) {
|
||||
proxy.$refs["roleRef"].toggleRowSelection(row);
|
||||
}
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection) {
|
||||
roleIds.value = selection.map(item => item.roleId);
|
||||
};
|
||||
|
||||
/** 保存选中的数据编号 */
|
||||
function getRowKey(row) {
|
||||
return row.roleId;
|
||||
};
|
||||
|
||||
// 检查角色状态
|
||||
function checkSelectable(row) {
|
||||
return row.status === "0" ? true : false;
|
||||
};
|
||||
|
||||
/** 关闭按钮 */
|
||||
function close() {
|
||||
const obj = { path: "/system/user" };
|
||||
proxy.$tab.closeOpenPage(obj);
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
const userId = form.value.userId;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user