diff --git a/README.md b/README.md index 06fc68ceeac177f3354ad8a08270217a084751e3..d6d0faa1ea4d4db90abab89dc570e54431613584 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

logo

-

RuoYi-Vue-FastAPI v1.5.1

+

RuoYi-Vue-FastAPI v1.6.0

基于RuoYi-Vue+FastAPI前后端分离的快速开发框架

- + @@ -46,6 +46,7 @@ RuoYi-Vue-FastAPI是一套全部开源的快速开发平台,毫无保留给个 14. 缓存监控:对系统的缓存信息查询,命令统计等。 15. 在线构建器:拖动表单元素生成相应的HTML代码。 16. 系统接口:根据业务代码自动生成相关的api接口文档。 +17. 代码生成:配置数据库表信息一键生成前后端代码(python、sql、vue、js),支持下载。 ## 演示图 @@ -88,6 +89,9 @@ RuoYi-Vue-FastAPI是一套全部开源的快速开发平台,毫无保留给个 + + + diff --git a/demo-pictures/gen.jpg b/demo-pictures/gen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c85bb2985b4e9975fad4f51c935214e13918b62d Binary files /dev/null and b/demo-pictures/gen.jpg differ diff --git a/ruoyi-fastapi-backend/.env.dev b/ruoyi-fastapi-backend/.env.dev index afc6dd6a4d9d36ca35eb29fe408f0a1c908379e3..7b4cc7e941daf11ed25db3a48dd85761c9ee3a5e 100644 --- a/ruoyi-fastapi-backend/.env.dev +++ b/ruoyi-fastapi-backend/.env.dev @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.5.1' +APP_VERSION= '1.6.0' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/.env.prod b/ruoyi-fastapi-backend/.env.prod index 6dac0e69ebe2b24234b2d4decf5e671914928dc1..dbfec7679b095c9c2389d3929ef7e257b052759a 100644 --- a/ruoyi-fastapi-backend/.env.prod +++ b/ruoyi-fastapi-backend/.env.prod @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.5.1' +APP_VERSION= '1.6.0' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 diff --git a/ruoyi-fastapi-backend/config/constant.py b/ruoyi-fastapi-backend/config/constant.py index 6db32dad38752eb48024d794cecf763008e75453..1b124a061ae141a6238f412184381779d4f76c23 100644 --- a/ruoyi-fastapi-backend/config/constant.py +++ b/ruoyi-fastapi-backend/config/constant.py @@ -1,3 +1,6 @@ +from config.env import DataBaseConfig + + class CommonConstant: """ 常用常量 @@ -150,3 +153,329 @@ class MenuConstant: LAYOUT = 'Layout' PARENT_VIEW = 'ParentView' INNER_LINK = 'InnerLink' + + +class GenConstant: + """ + 代码生成常量 + + TPL_CRUD: 单表(增删改查 + TPL_TREE: 树表(增删改查) + TPL_SUB: 主子表(增删改查) + TREE_CODE: 树编码字段 + TREE_PARENT_CODE: 树父编码字段 + TREE_NAME: 树名称字段 + PARENT_MENU_ID: 上级菜单ID字段 + PARENT_MENU_NAME: 上级菜单名称字段 + COLUMNTYPE_STR: 数据库字符串类型 + COLUMNTYPE_TEXT: 数据库文本类型 + COLUMNTYPE_TIME: 数据库时间类型 + COLUMNTYPE_GEOMETRY: 数据库字空间类型 + COLUMNTYPE_NUMBER: 数据库数字类型 + COLUMNNAME_NOT_EDIT: 页面不需要编辑字段 + COLUMNNAME_NOT_LIST: 页面不需要显示的列表字段 + COLUMNNAME_NOT_QUERY: 页面不需要查询字段 + BASE_ENTITY: Entity基类字段 + TREE_ENTITY: Tree基类字段 + HTML_INPUT: 文本框 + HTML_TEXTAREA: 文本域 + HTML_SELECT: 下拉框 + HTML_RADIO: 单选框 + HTML_CHECKBOX: 复选框 + HTML_DATETIME: 日期控件 + HTML_IMAGE_UPLOAD: 图片上传控件 + HTML_FILE_UPLOAD: 文件上传控件 + HTML_EDITOR: 富文本控件 + TYPE_DECIMAL: 高精度计算类型 + TYPE_DATE: 时间类型 + QUERY_LIKE: 模糊查询 + QUERY_EQ: 相等查询 + REQUIRE: 需要 + DB_TO_SQLALCHEMY_TYPE_MAPPING: 数据库类型与sqlalchemy类型映射 + DB_TO_PYTHON_TYPE_MAPPING: 数据库类型与python类型映射 + """ + + TPL_CRUD = 'crud' + TPL_TREE = 'tree' + TPL_SUB = 'sub' + TREE_CODE = 'treeCode' + TREE_PARENT_CODE = 'treeParentCode' + TREE_NAME = 'treeName' + PARENT_MENU_ID = 'parentMenuId' + PARENT_MENU_NAME = 'parentMenuName' + COLUMNTYPE_STR = ( + ['character varying', 'varchar', 'character', 'char'] + if DataBaseConfig.db_type == 'postgresql' + else ['char', 'varchar', 'nvarchar', 'varchar2'] + ) + COLUMNTYPE_TEXT = ( + ['text', 'citext'] if DataBaseConfig.db_type == 'postgresql' else ['tinytext', 'text', 'mediumtext', 'longtext'] + ) + COLUMNTYPE_TIME = ( + [ + 'date', + 'time', + 'time with time zone', + 'time without time zone', + 'timestamp', + 'timestamp with time zone', + 'timestamp without time zone', + 'interval', + ] + if DataBaseConfig.db_type == 'postgresql' + else ['datetime', 'time', 'date', 'timestamp'] + ) + COLUMNTYPE_GEOMETRY = ( + ['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle'] + if DataBaseConfig.db_type == 'postgresql' + else [ + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geometrycollection', + ] + ) + COLUMNTYPE_NUMBER = [ + 'tinyint', + 'smallint', + 'mediumint', + 'int', + 'number', + 'integer', + 'bit', + 'bigint', + 'float', + 'double', + 'decimal', + ] + 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'] + BASE_ENTITY = ['createBy', 'createTime', 'updateBy', 'updateTime', 'remark'] + TREE_ENTITY = ['parentName', 'parentId', 'orderNum', 'ancestors', 'children'] + HTML_INPUT = 'input' + HTML_TEXTAREA = 'textarea' + HTML_SELECT = 'select' + HTML_RADIO = 'radio' + HTML_CHECKBOX = 'checkbox' + HTML_DATETIME = 'datetime' + HTML_IMAGE_UPLOAD = 'imageUpload' + HTML_FILE_UPLOAD = 'fileUpload' + HTML_EDITOR = 'editor' + TYPE_DECIMAL = 'Decimal' + TYPE_DATE = ['date', 'time', 'datetime'] + QUERY_LIKE = 'LIKE' + QUERY_EQ = 'EQ' + REQUIRE = '1' + DB_TO_SQLALCHEMY_TYPE_MAPPING = ( + { + 'boolean': 'Boolean', + 'smallint': 'SmallInteger', + 'integer': 'Integer', + 'bigint': 'BigInteger', + 'real': 'Float', + 'double precision': 'Float', + 'numeric': 'Numeric', + 'character varying': 'String', + 'character': 'String', + 'text': 'Text', + 'bytea': 'LargeBinary', + 'date': 'Date', + 'time': 'Time', + 'time with time zone': 'Time', + 'time without time zone': 'Time', + 'timestamp': 'DateTime', + 'timestamp with time zone': 'DateTime', + 'timestamp without time zone': 'DateTime', + 'interval': 'Interval', + 'json': 'JSON', + 'jsonb': 'JSONB', + 'uuid': 'Uuid', + 'inet': 'INET', + 'cidr': 'CIDR', + 'macaddr': 'MACADDR', + 'point': 'Geometry', + 'line': 'Geometry', + 'lseg': 'Geometry', + 'box': 'Geometry', + 'path': 'Geometry', + 'polygon': 'Geometry', + 'circle': 'Geometry', + 'bit': 'Bit', + 'bit varying': 'Bit', + 'tsvector': 'TSVECTOR', + 'tsquery': 'TSQUERY', + 'xml': 'String', + 'array': 'ARRAY', + 'composite': 'JSON', + 'enum': 'Enum', + 'range': 'Range', + 'money': 'Numeric', + 'pg_lsn': 'BigInteger', + 'txid_snapshot': 'String', + 'oid': 'BigInteger', + 'regproc': 'String', + 'regclass': 'String', + 'regtype': 'String', + 'regrole': 'String', + 'regnamespace': 'String', + 'int2vector': 'ARRAY', + 'oidvector': 'ARRAY', + 'pg_node_tree': 'Text', + } + if DataBaseConfig.db_type == 'postgresql' + else { + # 数值类型 + 'TINYINT': 'SmallInteger', + 'SMALLINT': 'SmallInteger', + 'MEDIUMINT': 'Integer', + 'INT': 'Integer', + 'INTEGER': 'Integer', + 'BIGINT': 'BigInteger', + 'FLOAT': 'Float', + 'DOUBLE': 'Float', + 'DECIMAL': 'DECIMAL', + 'BIT': 'Integer', + # 日期和时间类型 + 'DATE': 'Date', + 'TIME': 'Time', + 'DATETIME': 'DateTime', + 'TIMESTAMP': 'TIMESTAMP', + 'YEAR': 'Integer', + # 字符串类型 + 'CHAR': 'CHAR', + 'VARCHAR': 'String', + 'TINYTEXT': 'Text', + 'TEXT': 'Text', + 'MEDIUMTEXT': 'Text', + 'LONGTEXT': 'Text', + 'BINARY': 'BINARY', + 'VARBINARY': 'VARBINARY', + 'TINYBLOB': 'LargeBinary', + 'BLOB': 'LargeBinary', + 'MEDIUMBLOB': 'LargeBinary', + 'LONGBLOB': 'LargeBinary', + # 枚举和集合类型 + 'ENUM': 'Enum', + 'SET': 'String', + # JSON 类型 + 'JSON': 'JSON', + # 空间数据类型(需要扩展支持,如 GeoAlchemy2) + 'GEOMETRY': 'Geometry', # 需要安装 geoalchemy2 + 'POINT': 'Geometry', + 'LINESTRING': 'Geometry', + 'POLYGON': 'Geometry', + 'MULTIPOINT': 'Geometry', + 'MULTILINESTRING': 'Geometry', + 'MULTIPOLYGON': 'Geometry', + 'GEOMETRYCOLLECTION': 'Geometry', + } + ) + DB_TO_PYTHON_TYPE_MAPPING = ( + { + 'boolean': 'bool', + 'smallint': 'int', + 'integer': 'int', + 'bigint': 'int', + 'real': 'float', + 'double precision': 'float', + 'numeric': 'Decimal', + 'character varying': 'str', + 'character': 'str', + 'text': 'str', + 'bytea': 'bytes', + 'date': 'date', + 'time': 'time', + 'time with time zone': 'time', + 'time without time zone': 'time', + 'timestamp': 'datetime', + 'timestamp with time zone': 'datetime', + 'timestamp without time zone': 'datetime', + 'interval': 'timedelta', + 'json': 'dict', + 'jsonb': 'dict', + 'uuid': 'str', + 'inet': 'str', + 'cidr': 'str', + 'macaddr': 'str', + 'point': 'list', + 'line': 'list', + 'lseg': 'list', + 'box': 'list', + 'path': 'list', + 'polygon': 'list', + 'circle': 'list', + 'bit': 'int', + 'bit varying': 'int', + 'tsvector': 'str', + 'tsquery': 'str', + 'xml': 'str', + 'array': 'list', + 'composite': 'dict', + 'enum': 'str', + 'range': 'list', + 'money': 'Decimal', + 'pg_lsn': 'int', + 'txid_snapshot': 'str', + 'oid': 'int', + 'regproc': 'str', + 'regclass': 'str', + 'regtype': 'str', + 'regrole': 'str', + 'regnamespace': 'str', + 'int2vector': 'list', + 'oidvector': 'list', + 'pg_node_tree': 'str', + } + if DataBaseConfig.db_type == 'postgresql' + else { + # 数值类型 + 'TINYINT': 'int', + 'SMALLINT': 'int', + 'MEDIUMINT': 'int', + 'INT': 'int', + 'INTEGER': 'int', + 'BIGINT': 'int', + 'FLOAT': 'float', + 'DOUBLE': 'float', + 'DECIMAL': 'Decimal', + 'BIT': 'int', + # 日期和时间类型 + 'DATE': 'date', + 'TIME': 'time', + 'DATETIME': 'datetime', + 'TIMESTAMP': 'datetime', + 'YEAR': 'int', + # 字符串类型 + 'CHAR': 'str', + 'VARCHAR': 'str', + 'TINYTEXT': 'str', + 'TEXT': 'str', + 'MEDIUMTEXT': 'str', + 'LONGTEXT': 'str', + 'BINARY': 'bytes', + 'VARBINARY': 'bytes', + 'TINYBLOB': 'bytes', + 'BLOB': 'bytes', + 'MEDIUMBLOB': 'bytes', + 'LONGBLOB': 'bytes', + # 枚举和集合类型 + 'ENUM': 'str', + 'SET': 'str', + # JSON 类型 + 'JSON': 'dict', + # 空间数据类型(通常需要特殊处理) + 'GEOMETRY': 'bytes', + 'POINT': 'bytes', + 'LINESTRING': 'bytes', + 'POLYGON': 'bytes', + 'MULTIPOINT': 'bytes', + 'MULTILINESTRING': 'bytes', + 'MULTIPOLYGON': 'bytes', + 'GEOMETRYCOLLECTION': 'bytes', + } + ) diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py index 78378a6489f3ceff880bbd4571fc0d8e3dd85af8..52cc8da5d96b9709203b94b929589a054cd6e913 100644 --- a/ruoyi-fastapi-backend/config/env.py +++ b/ruoyi-fastapi-backend/config/env.py @@ -64,6 +64,24 @@ class RedisSettings(BaseSettings): redis_database: int = 2 +class GenSettings: + """ + 代码生成配置 + """ + + author = 'insistence' + package_name = 'module_admin.system' + auto_remove_pre = False + table_prefix = 'sys_' + allow_overwrite = False + + GEN_PATH = 'vf_admin/gen_path' + + def __init__(self): + if not os.path.exists(self.GEN_PATH): + os.makedirs(self.GEN_PATH) + + class UploadSettings: """ 上传配置 @@ -159,6 +177,14 @@ class GetConfig: # 实例化Redis配置模型 return RedisSettings() + @lru_cache() + def get_gen_config(self): + """ + 获取代码生成配置 + """ + # 实例化代码生成配置 + return GenSettings() + @lru_cache() def get_upload_config(self): """ @@ -204,5 +230,7 @@ JwtConfig = get_config.get_jwt_config() DataBaseConfig = get_config.get_database_config() # Redis配置 RedisConfig = get_config.get_redis_config() +# 代码生成配置 +GenConfig = get_config.get_gen_config() # 上传配置 UploadConfig = get_config.get_upload_config() diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py index 2c9457b3c5234636fbaafe6f51ddfd1a52a8a916..447339044e67f966d46884650602827732a0f50f 100644 --- a/ruoyi-fastapi-backend/config/get_scheduler.py +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -6,7 +6,9 @@ from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.redis import RedisJobStore from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.combining import OrTrigger from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.date import DateTrigger from asyncio import iscoroutinefunction from datetime import datetime, timedelta from sqlalchemy.engine import create_engine @@ -201,8 +203,7 @@ class SchedulerUtil: job_executor = 'default' scheduler.add_job( func=eval(job_info.invoke_target), - trigger='date', - run_date=datetime.now() + timedelta(seconds=1), + trigger=OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)]), 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), diff --git a/ruoyi-fastapi-backend/middlewares/handle.py b/ruoyi-fastapi-backend/middlewares/handle.py index ea447d464131150869b6b06f2bcd237333dba53d..abb2d0d1216fb5252c6748a97446203691b36122 100644 --- a/ruoyi-fastapi-backend/middlewares/handle.py +++ b/ruoyi-fastapi-backend/middlewares/handle.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from middlewares.cors_middleware import add_cors_middleware from middlewares.gzip_middleware import add_gzip_middleware +from middlewares.trace_middleware import add_trace_middleware def handle_middleware(app: FastAPI): @@ -11,3 +12,5 @@ def handle_middleware(app: FastAPI): add_cors_middleware(app) # 加载gzip压缩中间件 add_gzip_middleware(app) + # 加载trace中间件 + add_trace_middleware(app) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..76f8d8557c64506f48bbbd5cc3f0666edea94655 --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/__init__.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI +from .ctx import TraceCtx +from .middle import TraceASGIMiddleware + +__all__ = ('TraceASGIMiddleware', 'TraceCtx') + +__version__ = '0.1.0' + + +def add_trace_middleware(app: FastAPI): + """ + 添加trace中间件 + + :param app: FastAPI对象 + :return: + """ + app.add_middleware(TraceASGIMiddleware) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py new file mode 100644 index 0000000000000000000000000000000000000000..558a5c9345e9084c89dd384fad5ace86cc746dba --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: ctx.py +@time: 2025/1/17 16:57 +""" + +import contextvars +from uuid import uuid4 + +CTX_REQUEST_ID: contextvars.ContextVar[str] = contextvars.ContextVar('request-id', default='') + + +class TraceCtx: + @staticmethod + def set_id(): + _id = uuid4().hex + CTX_REQUEST_ID.set(_id) + return _id + + @staticmethod + def get_id(): + return CTX_REQUEST_ID.get() diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py new file mode 100644 index 0000000000000000000000000000000000000000..a071692af7b9837821773f4794a6beae5f52505b --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: middle.py +@time: 2025/1/17 16:57 +""" + +from functools import wraps +from starlette.types import ASGIApp, Message, Receive, Scope, Send +from .span import get_current_span, Span + + +class TraceASGIMiddleware: + """ + fastapi-example: + app = FastAPI() + app.add_middleware(TraceASGIMiddleware) + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + @staticmethod + async def my_receive(receive: Receive, span: Span): + await span.request_before() + + @wraps(receive) + async def my_receive(): + message = await receive() + await span.request_after(message) + return message + + return my_receive + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + async with get_current_span(scope) as span: + handle_outgoing_receive = await self.my_receive(receive, span) + + async def handle_outgoing_request(message: 'Message') -> None: + await span.response(message) + await send(message) + + await self.app(scope, handle_outgoing_receive, handle_outgoing_request) diff --git a/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py new file mode 100644 index 0000000000000000000000000000000000000000..1e38eab1020987b7be46a23bd78a7c110d345c2a --- /dev/null +++ b/ruoyi-fastapi-backend/middlewares/trace_middleware/span.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +@author: peng +@file: span.py +@time: 2025/1/17 16:57 +""" + +from contextlib import asynccontextmanager +from starlette.types import Scope, Message +from .ctx import TraceCtx + + +class Span: + """ + 整个http生命周期: + request(before) --> request(after) --> response(before) --> response(after) + """ + + def __init__(self, scope: Scope): + self.scope = scope + + async def request_before(self): + """ + request_before: 处理header信息等, 如记录请求体信息 + """ + TraceCtx.set_id() + + async def request_after(self, message: Message): + """ + request_after: 处理请求bytes, 如记录请求参数 + + example: + message: {'type': 'http.request', 'body': b'{\r\n "name": "\xe8\x8b\x8f\xe8\x8b\x8f\xe8\x8b\x8f"\r\n}', 'more_body': False} + """ + return message + + async def response(self, message: Message): + """ + if message['type'] == "http.response.start": -----> request-before + pass + if message['type'] == "http.response.body": -----> request-after + message.get('body', b'') + pass + """ + if message['type'] == 'http.response.start': + message['headers'].append((b'request-id', TraceCtx.get_id().encode())) + return message + + +@asynccontextmanager +async def get_current_span(scope: Scope): + yield Span(scope) diff --git a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py index e038e0b15909f884e7439657d2dadc2abf4e5e0c..1d01c1c1ffc422a8a49b3f30fe70ec8ed361104a 100644 --- a/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py +++ b/ruoyi-fastapi-backend/module_admin/annotation/log_annotation.py @@ -7,7 +7,8 @@ from datetime import datetime from fastapi import Request from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse from functools import lru_cache, wraps -from typing import Literal, Optional +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Any, Callable, Literal, Optional from user_agents import parse from config.enums import BusinessType from config.env import AppConfig @@ -51,13 +52,15 @@ class Log: # 获取项目根路径 project_root = os.getcwd() # 处理文件路径,去除项目根路径部分 - relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.') + relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.').replace('/', '.') # 获取当前被装饰函数所在路径 func_path = f'{relative_path}{func.__name__}()' # 获取上下文信息 - request: Request = kwargs.get('request') + request_name_list = get_function_parameters_name_by_type(func, Request) + request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs) token = request.headers.get('Authorization') - query_db = kwargs.get('query_db') + session_name_list = get_function_parameters_name_by_type(func, AsyncSession) + query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs) request_method = request.method operator_type = 0 user_agent = request.headers.get('User-Agent') @@ -222,3 +225,37 @@ def get_ip_location(oper_ip: str): oper_location = '未知' print(e) return oper_location + + +def get_function_parameters_name_by_type(func: Callable, param_type: Any): + """ + 获取函数指定类型的参数名称 + + :param func: 函数 + :param arg_type: 参数类型 + :return: 函数指定类型的参数名称 + """ + # 获取函数的参数信息 + parameters = inspect.signature(func).parameters + # 找到指定类型的参数名称 + parameters_name_list = [] + for name, param in parameters.items(): + if param.annotation == param_type: + parameters_name_list.append(name) + return parameters_name_list + + +def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs): + """ + 获取函数指定参数的值 + + :param func: 函数 + :param name: 参数名 + :return: 参数值 + """ + # 获取参数值 + bound_parameters = inspect.signature(func).bind(*args, **kwargs) + bound_parameters.apply_defaults() + parameters_value = bound_parameters.arguments.get(name) + + return parameters_value diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 4af1850754a1c7cc86ee0c2a26a0b62bca2a6337..ed7cf7af4a5b2c15e06d0c982c5ae2feec85ae22 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -135,7 +135,7 @@ async def delete_system_user( ): user_id_list = user_ids.split(',') if user_ids else [] if user_id_list: - if current_user.user.user_id in user_id_list: + if current_user.user.user_id in list(map(int, user_id_list)): logger.warning('当前登录用户不能删除') return ResponseUtil.failure(msg='当前登录用户不能删除') @@ -296,7 +296,7 @@ async def change_system_user_profile_info( @Log(title='个人信息', business_type=BusinessType.UPDATE) async def reset_system_user_password( request: Request, - reset_password: ResetPasswordModel = Depends(ResetPasswordModel.as_query), + reset_password: ResetPasswordModel, query_db: AsyncSession = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user), ): diff --git a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py index 56e8c2bdf6d050cae7d551d73805dce96b99995c..4eac9535f0d8f242815f1017e2e51550d1472362 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/ruoyi-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -190,7 +190,6 @@ class EditUserModel(AddUserModel): role: Optional[List] = Field(default=[], description='角色信息') -@as_query class ResetPasswordModel(BaseModel): """ 重置密码模型 diff --git a/ruoyi-fastapi-backend/module_admin/service/config_service.py b/ruoyi-fastapi-backend/module_admin/service/config_service.py index 1d8ec7645ed553068d2d0bcb84d62434f5fc57d3..312006dbbca6baaa4559ac7b60baced8ae9ee19d 100644 --- a/ruoyi-fastapi-backend/module_admin/service/config_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -7,7 +7,8 @@ from exceptions.exception import ServiceException from module_admin.dao.config_dao import ConfigDao from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel -from utils.common_util import CamelCaseUtil, export_list2excel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil class ConfigService: @@ -207,17 +208,12 @@ class ConfigService: 'remark': '备注', } - data = config_list - - for item in data: + for item in config_list: if item.get('configType') == 'Y': item['configType'] = '是' else: item['configType'] = '否' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(config_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py index 53540833f38376d3425223331c5b4355bcbc6e40..bfe64895f94882a92342cd569392d519543efe80 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dict_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -15,7 +15,8 @@ from module_admin.entity.vo.dict_vo import ( DictTypeModel, DictTypePageQueryModel, ) -from utils.common_util import CamelCaseUtil, export_list2excel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil class DictTypeService: @@ -192,17 +193,12 @@ class DictTypeService: 'remark': '备注', } - data = dict_type_list - - for item in data: + for item in dict_type_list: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(dict_type_list, mapping_dict) return binary_data @@ -448,9 +444,7 @@ class DictDataService: 'remark': '备注', } - data = dict_data_list - - for item in data: + for item in dict_data_list: if item.get('status') == '0': item['status'] = '正常' else: @@ -459,9 +453,6 @@ class DictDataService: item['isDefault'] = '是' else: item['isDefault'] = '否' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(dict_data_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py index f4c9f376b920f7929cf3aa00dd10466c08d6af7f..596abe7b235ee486cb2bfc8bb53c6b166af6bced 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_log_service.py @@ -6,7 +6,7 @@ from module_admin.dao.job_log_dao import JobLogDao from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel from module_admin.service.dict_service import DictDataService -from utils.common_util import export_list2excel +from utils.excel_util import ExcelUtil class JobLogService: @@ -115,7 +115,6 @@ class JobLogService: 'createTime': '创建时间', } - data = job_log_list job_group_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_group' ) @@ -129,7 +128,7 @@ class JobLogService: ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} - for item in data: + for item in job_log_list: if item.get('status') == '0': item['status'] = '正常' else: @@ -138,9 +137,6 @@ class JobLogService: item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label') if str(item.get('jobExecutor')) in job_executor_option_dict.keys(): item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label') - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(job_log_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 2d06b6ff5b4508e39af00513e0407cb50d0fd263..55263c1cbed4d443e7b87012a3bec09e2fe557df 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -8,8 +8,9 @@ from module_admin.dao.job_dao import JobDao from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel from module_admin.service.dict_service import DictDataService -from utils.common_util import CamelCaseUtil, export_list2excel +from utils.common_util import CamelCaseUtil from utils.cron_util import CronUtil +from utils.excel_util import ExcelUtil from utils.string_util import StringUtil @@ -227,7 +228,6 @@ class JobService: 'remark': '备注', } - data = job_list job_group_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_job_group' ) @@ -241,7 +241,7 @@ class JobService: ] job_executor_option_dict = {item.get('value'): item for item in job_executor_option} - for item in data: + for item in job_list: if item.get('status') == '0': item['status'] = '正常' else: @@ -260,9 +260,6 @@ class JobService: item['concurrent'] = '允许' else: item['concurrent'] = '禁止' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(job_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/log_service.py b/ruoyi-fastapi-backend/module_admin/service/log_service.py index 0c80a60c45fb1cd10fc102152beda41be8f2c2c9..0983b1a271ad77e11a718244e10187d5256dc2fd 100644 --- a/ruoyi-fastapi-backend/module_admin/service/log_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/log_service.py @@ -14,7 +14,7 @@ from module_admin.entity.vo.log_vo import ( UnlockUser, ) from module_admin.service.dict_service import DictDataService -from utils.common_util import export_list2excel +from utils.excel_util import ExcelUtil class OperationLogService: @@ -122,7 +122,6 @@ class OperationLogService: 'costTime': '消耗时间(毫秒)', } - data = operation_log_list operation_type_list = await DictDataService.query_dict_data_list_from_cache_services( request.app.state.redis, dict_type='sys_oper_type' ) @@ -131,18 +130,14 @@ class OperationLogService: ] operation_type_option_dict = {item.get('value'): item for item in operation_type_option} - for item in data: + for item in operation_log_list: if item.get('status') == 0: item['status'] = '成功' else: item['status'] = '失败' if str(item.get('businessType')) in operation_type_option_dict.keys(): item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label') - - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(operation_log_list, mapping_dict) return binary_data @@ -253,16 +248,11 @@ class LoginLogService: 'loginTime': '登录日期', } - data = login_log_list - - for item in data: + for item in login_log_list: if item.get('status') == '0': item['status'] = '成功' else: item['status'] = '失败' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(login_log_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/post_service.py b/ruoyi-fastapi-backend/module_admin/service/post_service.py index 52155395cf962e35dc82fdce1dc597207eab9373..9338a9fc3041bd8aa45e4fd8cc1d4285b9a93888 100644 --- a/ruoyi-fastapi-backend/module_admin/service/post_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -5,7 +5,8 @@ from exceptions.exception import ServiceException from module_admin.dao.post_dao import PostDao from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel -from utils.common_util import CamelCaseUtil, export_list2excel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil class PostService: @@ -172,16 +173,11 @@ class PostService: 'remark': '备注', } - data = post_list - - for item in data: + for item in post_list: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(post_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/role_service.py b/ruoyi-fastapi-backend/module_admin/service/role_service.py index 24f9bee878a5bd1cc0b90fad472213cb8b36c1a3..4b633de4755396caa622bb06827d18da2eac632d 100644 --- a/ruoyi-fastapi-backend/module_admin/service/role_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -15,7 +15,8 @@ from module_admin.entity.vo.role_vo import ( from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel from module_admin.dao.role_dao import RoleDao from module_admin.dao.user_dao import UserDao -from utils.common_util import CamelCaseUtil, export_list2excel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil from utils.page_util import PageResponseModel @@ -295,17 +296,12 @@ class RoleService: 'remark': '备注', } - data = role_list - - for item in data: + for item in role_list: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(role_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py index dc7943027e17c4052f642debae9af0020ec3fc70..c149b56d914b301a8a88b60d170990e0978ffbba 100644 --- a/ruoyi-fastapi-backend/module_admin/service/user_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -31,7 +31,8 @@ from module_admin.service.config_service import ConfigService from module_admin.service.dept_service import DeptService from module_admin.service.post_service import PostService from module_admin.service.role_service import RoleService -from utils.common_util import CamelCaseUtil, export_list2excel, get_excel_template +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil from utils.page_util import PageResponseModel from utils.pwd_util import PwdUtil @@ -461,7 +462,7 @@ class UserService: header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态'] selector_header_list = ['用户性别', '帐号状态'] option_list = [{'用户性别': ['男', '女', '未知']}, {'帐号状态': ['正常', '停用']}] - binary_data = get_excel_template( + binary_data = ExcelUtil.get_excel_template( header_list=header_list, selector_header_list=selector_header_list, option_list=option_list ) @@ -492,9 +493,7 @@ class UserService: 'remark': '备注', } - data = user_list - - for item in data: + for item in user_list: if item.get('status') == '0': item['status'] = '正常' else: @@ -505,10 +504,7 @@ class UserService: item['sex'] = '女' else: item['sex'] = '未知' - new_data = [ - {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data - ] - binary_data = export_list2excel(new_data) + binary_data = ExcelUtil.export_list2excel(user_list, mapping_dict) return binary_data diff --git a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..4e227c1a1bfe5a7b8c299baad47edd910cabf5b1 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py @@ -0,0 +1,158 @@ +from datetime import datetime +from fastapi import APIRouter, Depends, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.env import GenConfig +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth, CheckUserInterfaceAuth +from module_admin.service.login_service import LoginService +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_generator.entity.vo.gen_vo import DeleteGenTableModel, EditGenTableModel, GenTablePageQueryModel +from module_generator.service.gen_service import GenTableColumnService, GenTableService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +genController = APIRouter(prefix='/tool/gen', dependencies=[Depends(LoginService.get_current_user)]) + + +@genController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +) +async def get_gen_table_list( + request: Request, + gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + gen_page_query_result = await GenTableService.get_gen_table_list_services(query_db, gen_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=gen_page_query_result) + + +@genController.get( + '/db/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))] +) +async def get_gen_db_table_list( + request: Request, + gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + gen_page_query_result = await GenTableService.get_gen_db_table_list_services(query_db, gen_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=gen_page_query_result) + + +@genController.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))]) +@Log(title='代码生成', business_type=BusinessType.IMPORT) +async def import_gen_table( + request: Request, + tables: str = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + table_names = tables.split(',') if tables else [] + add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(query_db, table_names) + add_gen_table_result = await GenTableService.import_gen_table_services(query_db, add_gen_table_list, current_user) + logger.info(add_gen_table_result.message) + + return ResponseUtil.success(msg=add_gen_table_result.message) + + +@genController.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@ValidateFields(validate_model='edit_gen_table') +@Log(title='代码生成', business_type=BusinessType.UPDATE) +async def edit_gen_table( + request: Request, + edit_gen_table: EditGenTableModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_gen_table.update_by = current_user.user.user_name + edit_gen_table.update_time = datetime.now() + await GenTableService.validate_edit(edit_gen_table) + edit_gen_result = await GenTableService.edit_gen_table_services(query_db, edit_gen_table) + logger.info(edit_gen_result.message) + + return ResponseUtil.success(msg=edit_gen_result.message) + + +@genController.delete('/{table_ids}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))]) +@Log(title='代码生成', business_type=BusinessType.DELETE) +async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_gen_table = DeleteGenTableModel(tableIds=table_ids) + delete_gen_table_result = await GenTableService.delete_gen_table_services(query_db, delete_gen_table) + logger.info(delete_gen_table_result.message) + + return ResponseUtil.success(msg=delete_gen_table_result.message) + + +@genController.post('/createTable', dependencies=[Depends(CheckRoleInterfaceAuth('admin'))]) +@Log(title='创建表', business_type=BusinessType.OTHER) +async def create_table( + request: Request, + sql: str = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + create_table_result = await GenTableService.create_table_services(query_db, sql, current_user) + logger.info(create_table_result.message) + + return ResponseUtil.success(msg=create_table_result.message) + + +@genController.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@Log(title='代码生成', business_type=BusinessType.GENCODE) +async def batch_gen_code(request: Request, tables: str = Query(), query_db: AsyncSession = Depends(get_db)): + table_names = tables.split(',') if tables else [] + batch_gen_code_result = await GenTableService.batch_gen_code_services(query_db, table_names) + logger.info('生成代码成功') + + return ResponseUtil.streaming(data=bytes2file_response(batch_gen_code_result)) + + +@genController.get('/genCode/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))]) +@Log(title='代码生成', business_type=BusinessType.GENCODE) +async def gen_code_local(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): + if not GenConfig.allow_overwrite: + logger.error('【系统预设】不允许生成文件覆盖到本地') + return ResponseUtil.error('【系统预设】不允许生成文件覆盖到本地') + gen_code_local_result = await GenTableService.generate_code_services(query_db, table_name) + logger.info(gen_code_local_result.message) + + return ResponseUtil.success(msg=gen_code_local_result.message) + + +@genController.get('/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))]) +async def query_detail_gen_table(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): + gen_table = await GenTableService.get_gen_table_by_id_services(query_db, table_id) + gen_tables = await GenTableService.get_gen_table_all_services(query_db) + gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(query_db, table_id) + gen_table_detail_result = dict(info=gen_table, rows=gen_columns, tables=gen_tables) + logger.info(f'获取table_id为{table_id}的信息成功') + + return ResponseUtil.success(data=gen_table_detail_result) + + +@genController.get('/preview/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))]) +async def preview_code(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)): + preview_code_result = await GenTableService.preview_code_services(query_db, table_id) + logger.info('获取预览代码成功') + + return ResponseUtil.success(data=preview_code_result) + + +@genController.get('/synchDb/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))]) +@Log(title='代码生成', business_type=BusinessType.UPDATE) +async def sync_db(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)): + sync_db_result = await GenTableService.sync_db_services(query_db, table_name) + logger.info(sync_db_result.message) + + return ResponseUtil.success(data=sync_db_result.message) diff --git a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py new file mode 100644 index 0000000000000000000000000000000000000000..d6e74e94c2a530758e9a5346cdfa20a055de6166 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py @@ -0,0 +1,390 @@ +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 typing import List +from config.env import DataBaseConfig +from module_generator.entity.do.gen_do import GenTable, GenTableColumn +from module_generator.entity.vo.gen_vo import ( + GenTableBaseModel, + GenTableColumnBaseModel, + GenTableColumnModel, + GenTableModel, + GenTablePageQueryModel, +) +from utils.page_util import PageUtil + + +class GenTableDao: + """ + 代码生成业务表模块数据库操作层 + """ + + @classmethod + async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int): + """ + 根据业务表id获取需要生成的业务表信息 + + :param db: orm对象 + :param table_id: 业务表id + :return: 需要生成的业务表信息对象 + """ + gen_table_info = ( + ( + await db.execute( + select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_id == table_id) + ) + ) + .scalars() + .first() + ) + + return gen_table_info + + @classmethod + async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str): + """ + 根据业务表名称获取需要生成的业务表信息 + + :param db: orm对象 + :param table_name: 业务表名称 + :return: 需要生成的业务表信息对象 + """ + gen_table_info = ( + ( + await db.execute( + select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_name == table_name) + ) + ) + .scalars() + .first() + ) + + return gen_table_info + + @classmethod + async def get_gen_table_all(cls, db: AsyncSession): + """ + 获取所有业务表信息 + + :param db: orm对象 + :return: 所有业务表信息 + """ + gen_table_all = (await db.execute(select(GenTable).options(selectinload(GenTable.columns)))).scalars().all() + + return gen_table_all + + @classmethod + async def create_table_by_sql_dao(cls, db: AsyncSession, sql: str): + """ + 根据sql语句创建表结构 + + :param db: orm对象 + :param sql: sql语句 + :return: + """ + await db.execute(text(sql)) + + @classmethod + async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取代码生成业务表列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 代码生成业务表列表信息对象 + """ + query = ( + select(GenTable) + .options(selectinload(GenTable.columns)) + .where( + func.lower(GenTable.table_name).like(f'%{query_object.table_name.lower()}%') + if query_object.table_name + else True, + func.lower(GenTable.table_comment).like(f'%{query_object.table_comment.lower()}%') + if query_object.table_comment + else True, + GenTable.create_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)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return gen_table_list + + @classmethod + async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False): + """ + 根据查询参数获取数据库列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据库列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + list_table + where + table_name not like 'apscheduler_%' + and table_name not like 'gen_%' + and table_name not in (select table_name from gen_table) + """ + else: + query_sql = """ + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + information_schema.tables + where + table_schema = (select database()) + and table_name not like 'apscheduler\_%' + and table_name not like 'gen\_%' + and table_name not in (select table_name from gen_table) + """ + if query_object.table_name: + query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))""" + if query_object.table_comment: + query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))""" + if query_object.begin_time: + if DataBaseConfig.db_type == 'postgresql': + query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')""" + else: + query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')""" + if query_object.end_time: + if DataBaseConfig.db_type == 'postgresql': + query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')""" + else: + query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')""" + query_sql += """order by create_time desc""" + query = select( + text(query_sql).bindparams( + **{ + k: v + for k, v in query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'}).items() + } + ) + ) + gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return gen_db_table_list + + @classmethod + async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: List[str]): + """ + 根据业务表名称组获取数据库列表信息 + + :param db: orm对象 + :param table_names: 业务表名称组 + :return: 数据库列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + select + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + list_table + where + table_name not like 'qrtz_%' + and table_name not like 'gen_%' + and table_name = any(:table_names) + """ + else: + query_sql = """ + select + table_name as table_name, + table_comment as table_comment, + create_time as create_time, + update_time as update_time + from + information_schema.tables + where + table_name not like 'qrtz\_%' + and table_name not like 'gen\_%' + and table_schema = (select database()) + and table_name in :table_names + """ + query = text(query_sql).bindparams(table_names=tuple(table_names)) + gen_db_table_list = (await db.execute(query)).fetchall() + + return gen_db_table_list + + @classmethod + async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + """ + 新增业务表数据库操作 + + :param db: orm对象 + :param gen_table: 业务表对象 + :return: + """ + db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump()) + db.add(db_gen_table) + await db.flush() + + return db_gen_table + + @classmethod + async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict): + """ + 编辑业务表数据库操作 + + :param db: orm对象 + :param gen_table: 需要更新的业务表字典 + :return: + """ + await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()]) + + @classmethod + async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel): + """ + 删除业务表数据库操作 + + :param db: orm对象 + :param gen_table: 业务表对象 + :return: + """ + await db.execute(delete(GenTable).where(GenTable.table_id.in_([gen_table.table_id]))) + + +class GenTableColumnDao: + """ + 代码生成业务表字段模块数据库操作层 + """ + + @classmethod + async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int): + """ + 根据业务表id获取需要生成的业务表字段列表信息 + + :param db: orm对象 + :param table_id: 业务表id + :return: 需要生成的业务表字段列表信息对象 + """ + gen_table_column_list = ( + ( + await db.execute( + select(GenTableColumn).where(GenTableColumn.table_id == table_id).order_by(GenTableColumn.sort) + ) + ) + .scalars() + .all() + ) + + return gen_table_column_list + + @classmethod + async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str): + """ + 根据业务表名称获取业务表字段列表信息 + + :param db: orm对象 + :param table_name: 业务表名称 + :return: 业务表字段列表信息对象 + """ + if DataBaseConfig.db_type == 'postgresql': + query_sql = """ + select + column_name, is_required, is_pk, sort, column_comment, is_increment, column_type + from + list_column + where + table_name = :table_name + """ + else: + query_sql = """ + select + column_name as column_name, + case + when is_nullable = 'no' and column_key != 'PRI' then '1' + else '0' + end as is_required, + case + when column_key = 'PRI' then '1' + else '0' + end as is_pk, + ordinal_position as sort, + column_comment as column_comment, + case + when extra = 'auto_increment' then '1' + else '0' + end as is_increment, + column_type as column_type + from + information_schema.columns + where + table_schema = (select database()) + and table_name = :table_name + order by + ordinal_position + """ + query = text(query_sql).bindparams(table_name=table_name) + gen_db_table_columns = (await db.execute(query)).fetchall() + + return gen_db_table_columns + + @classmethod + async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 新增业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 岗位对象 + :return: + """ + db_gen_table_column = GenTableColumn( + **GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump() + ) + db.add(db_gen_table_column) + await db.flush() + + return db_gen_table_column + + @classmethod + async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict): + """ + 编辑业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 需要更新的业务表字段字典 + :return: + """ + await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()]) + + @classmethod + async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 通过业务表id删除业务表字段数据库操作 + + :param db: orm对象 + :param gen_table_column: 业务表字段对象 + :return: + """ + await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id]))) + + @classmethod + async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel): + """ + 通过业务字段id删除业务表字段数据库操作 + + :param db: orm对象 + :param post: 业务表字段对象 + :return: + """ + await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id.in_([gen_table_column.column_id]))) diff --git a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py new file mode 100644 index 0000000000000000000000000000000000000000..e64d0bfdf934f033526e9bd5fd3fa114e6738457 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py @@ -0,0 +1,74 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String +from sqlalchemy.orm import relationship +from config.database import Base + + +class GenTable(Base): + """ + 代码生成业务表 + """ + + __tablename__ = 'gen_table' + + 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树表操作)') + tpl_web_type = Column( + String(30), nullable=True, 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='生成路径(不填默认项目路径)') + options = Column(String(1000), nullable=True, comment='其它生成选项') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + remark = Column(String(500), nullable=True, default=None, comment='备注') + + columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables') + + +class GenTableColumn(Base): + """ + 代码生成业务表字段 + """ + + __tablename__ = 'gen_table_column' + + column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号') + table_id = Column(Integer, 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='查询方式(等于、不等于、大于、小于、范围)') + html_type = Column( + String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)' + ) + dict_type = Column(String(200), nullable=True, default='', comment='字典类型') + sort = Column(Integer, nullable=True, comment='排序') + create_by = Column(String(64), default='', comment='创建者') + create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') + update_by = Column(String(64), default='', comment='更新者') + update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') + + tables = relationship('GenTable', back_populates='columns') diff --git a/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py new file mode 100644 index 0000000000000000000000000000000000000000..e5d7917ef07da349a9fe2ad59fbd2de3d39b9920 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py @@ -0,0 +1,264 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic.alias_generators import to_camel +from pydantic_validation_decorator import NotBlank +from typing import List, Literal, Optional +from config.constant import GenConstant +from module_admin.annotation.pydantic_annotation import as_query +from utils.string_util import StringUtil + + +class GenTableBaseModel(BaseModel): + """ + 代码生成业务表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + table_id: Optional[int] = Field(default=None, description='编号') + table_name: Optional[str] = Field(default=None, description='表名称') + table_comment: Optional[str] = Field(default=None, description='表描述') + sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名') + sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名') + class_name: Optional[str] = Field(default=None, description='实体类名称') + tpl_category: Optional[str] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)') + tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型(element-ui模版 element-plus模版)') + package_name: Optional[str] = Field(default=None, description='生成包路径') + module_name: Optional[str] = Field(default=None, description='生成模块名') + business_name: Optional[str] = Field(default=None, description='生成业务名') + function_name: Optional[str] = Field(default=None, description='生成功能名') + function_author: Optional[str] = Field(default=None, description='生成功能作者') + gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)') + gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)') + options: Optional[str] = Field(default=None, description='其它生成选项') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='table_name', message='表名称不能为空') + def get_table_name(self): + return self.table_name + + @NotBlank(field_name='table_comment', message='表描述不能为空') + def get_table_comment(self): + return self.table_comment + + @NotBlank(field_name='class_name', message='实体类名称不能为空') + def get_class_name(self): + return self.class_name + + @NotBlank(field_name='package_name', message='生成包路径不能为空') + def get_package_name(self): + return self.package_name + + @NotBlank(field_name='module_name', message='生成模块名不能为空') + def get_module_name(self): + return self.module_name + + @NotBlank(field_name='business_name', message='生成业务名不能为空') + def get_business_name(self): + return self.business_name + + @NotBlank(field_name='function_name', message='生成功能名不能为空') + def get_function_name(self): + return self.function_name + + @NotBlank(field_name='function_author', message='生成功能作者不能为空') + def get_function_author(self): + return self.function_author + + def validate_fields(self): + self.get_table_name() + self.get_table_comment() + self.get_class_name() + self.get_package_name() + self.get_module_name() + self.get_business_name() + self.get_function_name() + self.get_function_author() + + +class GenTableModel(GenTableBaseModel): + """ + 代码生成业务表模型 + """ + + pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息') + sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息') + columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息') + tree_code: Optional[str] = Field(default=None, description='树编码字段') + tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') + tree_name: Optional[str] = Field(default=None, description='树名称字段') + parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段') + parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段') + sub: Optional[bool] = Field(default=None, description='是否为子表') + tree: Optional[bool] = Field(default=None, description='是否为树表') + crud: Optional[bool] = Field(default=None, description='是否为单表') + + @model_validator(mode='after') + def check_some_is(self) -> 'GenTableModel': + self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False + self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False + self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False + return self + + +class EditGenTableModel(GenTableModel): + """ + 修改代码生成业务表模型 + """ + + params: Optional['GenTableParamsModel'] = Field(default=None, description='业务表参数') + + +class GenTableParamsModel(BaseModel): + """ + 代码生成业务表参数模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + tree_code: Optional[str] = Field(default=None, description='树编码字段') + tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') + tree_name: Optional[str] = Field(default=None, description='树名称字段') + parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段') + + +class GenTableQueryModel(GenTableBaseModel): + """ + 代码生成业务表不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class GenTablePageQueryModel(GenTableQueryModel): + """ + 代码生成业务表分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteGenTableModel(BaseModel): + """ + 删除代码生成业务表模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + table_ids: str = Field(description='需要删除的代码生成业务表ID') + + +class GenTableColumnBaseModel(BaseModel): + """ + 代码生成业务表字段对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + column_id: Optional[int] = Field(default=None, description='编号') + table_id: Optional[int] = Field(default=None, description='归属表编号') + column_name: Optional[str] = Field(default=None, description='列名称') + column_comment: Optional[str] = Field(default=None, description='列描述') + column_type: Optional[str] = Field(default=None, description='列类型') + python_type: Optional[str] = Field(default=None, description='PYTHON类型') + python_field: Optional[str] = Field(default=None, description='PYTHON字段名') + is_pk: Optional[str] = Field(default=None, description='是否主键(1是)') + is_increment: Optional[str] = Field(default=None, description='是否自增(1是)') + is_required: Optional[str] = Field(default=None, description='是否必填(1是)') + is_unique: Optional[str] = Field(default=None, description='是否唯一(1是)') + is_insert: Optional[str] = Field(default=None, description='是否为插入字段(1是)') + is_edit: Optional[str] = Field(default=None, description='是否编辑字段(1是)') + is_list: Optional[str] = Field(default=None, description='是否列表字段(1是)') + is_query: Optional[str] = Field(default=None, description='是否查询字段(1是)') + query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)') + html_type: Optional[str] = Field( + default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)' + ) + dict_type: Optional[str] = Field(default=None, description='字典类型') + sort: Optional[int] = Field(default=None, description='排序') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + + @NotBlank(field_name='python_field', message='Python属性不能为空') + def get_python_field(self): + return self.python_field + + def validate_fields(self): + self.get_python_field() + + +class GenTableColumnModel(GenTableColumnBaseModel): + """ + 代码生成业务表字段模型 + """ + + cap_python_field: Optional[str] = Field(default=None, description='字段大写形式') + pk: Optional[bool] = Field(default=None, description='是否主键') + increment: Optional[bool] = Field(default=None, description='是否自增') + required: Optional[bool] = Field(default=None, description='是否必填') + unique: Optional[bool] = Field(default=None, description='是否唯一') + insert: Optional[bool] = Field(default=None, description='是否为插入字段') + edit: Optional[bool] = Field(default=None, description='是否编辑字段') + list: Optional[bool] = Field(default=None, description='是否列表字段') + query: Optional[bool] = Field(default=None, description='是否查询字段') + super_column: Optional[bool] = Field(default=None, description='是否为基类字段') + usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单') + + @model_validator(mode='after') + def check_some_is(self) -> 'GenTableModel': + self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None + self.pk = True if self.is_pk and self.is_pk == '1' else False + self.increment = True if self.is_increment and self.is_increment == '1' else False + self.required = True if self.is_required and self.is_required == '1' else False + self.unique = True if self.is_unique and self.is_unique == '1' else False + self.insert = True if self.is_insert and self.is_insert == '1' else False + self.edit = True if self.is_edit and self.is_edit == '1' else False + self.list = True if self.is_list and self.is_list == '1' else False + self.query = True if self.is_query and self.is_query == '1' else False + self.super_column = ( + True + if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY) + else False + ) + self.usable_column = ( + True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False + ) + return self + + +class GenTableColumnQueryModel(GenTableColumnBaseModel): + """ + 代码生成业务表字段不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class GenTableColumnPageQueryModel(GenTableColumnQueryModel): + """ + 代码生成业务表字段分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class DeleteGenTableColumnModel(BaseModel): + """ + 删除代码生成业务表字段模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + column_ids: str = Field(description='需要删除的代码生成业务表字段ID') diff --git a/ruoyi-fastapi-backend/module_generator/service/gen_service.py b/ruoyi-fastapi-backend/module_generator/service/gen_service.py new file mode 100644 index 0000000000000000000000000000000000000000..c22019d9d1f8c59cacccb8c5d7231e3e8ee61ca6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/service/gen_service.py @@ -0,0 +1,480 @@ +import io +import json +import os +import re +import zipfile +from datetime import datetime +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import GenConstant +from config.env import GenConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_generator.entity.vo.gen_vo import ( + DeleteGenTableModel, + EditGenTableModel, + GenTableColumnModel, + GenTableModel, + GenTablePageQueryModel, +) +from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao +from utils.common_util import CamelCaseUtil +from utils.gen_util import GenUtils +from utils.template_util import TemplateInitializer, TemplateUtils + + +class GenTableService: + """ + 代码生成业务表服务层 + """ + + @classmethod + async def get_gen_table_list_services( + cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ): + """ + 获取代码生成业务表列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 代码生成业务列表信息对象 + """ + gen_table_list_result = await GenTableDao.get_gen_table_list(query_db, query_object, is_page) + + return gen_table_list_result + + @classmethod + async def get_gen_db_table_list_services( + cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False + ): + """ + 获取数据库列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据库列表信息对象 + """ + gen_db_table_list_result = await GenTableDao.get_gen_db_table_list(query_db, query_object, is_page) + + return gen_db_table_list_result + + @classmethod + async def get_gen_db_table_list_by_name_services(cls, query_db: AsyncSession, table_names: List[str]): + """ + 根据表名称组获取数据库列表信息service + + :param query_db: orm对象 + :param table_names: 表名称组 + :return: 数据库列表信息对象 + """ + gen_db_table_list_result = await GenTableDao.get_gen_db_table_list_by_names(query_db, table_names) + + return [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)] + + @classmethod + async def import_gen_table_services( + cls, query_db: AsyncSession, gen_table_list: List[GenTableModel], current_user: CurrentUserModel + ): + """ + 导入表结构service + + :param query_db: orm对象 + :param gen_table_list: 导入表列表 + :param current_user: 当前用户信息对象 + :return: 导入结果 + """ + try: + for table in gen_table_list: + table_name = table.table_name + GenUtils.init_table(table, current_user.user.user_name) + add_gen_table = await GenTableDao.add_gen_table_dao(query_db, table) + if add_gen_table: + table.table_id = add_gen_table.table_id + gen_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name) + for column in [ + GenTableColumnModel(**gen_table_column) + for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns) + ]: + GenUtils.init_column_field(column, table) + await GenTableColumnDao.add_gen_table_column_dao(query_db, column) + await query_db.commit() + return CrudResponseModel(is_success=True, message='导入成功') + except Exception as e: + await query_db.rollback() + raise ServiceException(message=f'导入失败, {str(e)}') + + @classmethod + async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel): + """ + 编辑业务表信息service + + :param query_db: orm对象 + :param page_object: 编辑业务表对象 + :return: 编辑业务表校验结果 + """ + edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True) + gen_table_info = await cls.get_gen_table_by_id_services(query_db, page_object.table_id) + if gen_table_info.table_id: + try: + edit_gen_table['options'] = json.dumps(edit_gen_table.get('params')) + await GenTableDao.edit_gen_table_dao(query_db, edit_gen_table) + for gen_table_column in page_object.columns: + gen_table_column.update_by = page_object.update_by + gen_table_column.update_time = datetime.now() + await GenTableColumnDao.edit_gen_table_column_dao( + query_db, gen_table_column.model_dump(by_alias=True) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='业务表不存在') + + @classmethod + async def delete_gen_table_services(cls, query_db: AsyncSession, page_object: DeleteGenTableModel): + """ + 删除业务表信息service + + :param query_db: orm对象 + :param page_object: 删除业务表对象 + :return: 删除业务表校验结果 + """ + if page_object.table_ids: + table_id_list = page_object.table_ids.split(',') + try: + for table_id in table_id_list: + await GenTableDao.delete_gen_table_dao(query_db, GenTableModel(tableId=table_id)) + await GenTableColumnDao.delete_gen_table_column_by_table_id_dao( + query_db, GenTableColumnModel(tableId=table_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入业务表id为空') + + @classmethod + async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int): + """ + 获取需要生成的业务表详细信息service + + :param query_db: orm对象 + :param table_id: 需要生成的业务表id + :return: 需要生成的业务表id对应的信息 + """ + gen_table = await GenTableDao.get_gen_table_by_id(query_db, table_id) + result = await cls.set_table_from_options(GenTableModel(**CamelCaseUtil.transform_result(gen_table))) + + return result + + @classmethod + async def get_gen_table_all_services(cls, query_db: AsyncSession): + """ + 获取所有业务表信息service + + :param query_db: orm对象 + :return: 所有业务表信息 + """ + gen_table_all = await GenTableDao.get_gen_table_all(query_db) + result = [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)] + + return result + + @classmethod + async def create_table_services(cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel): + """ + 创建表结构service + + :param query_db: orm对象 + :param sql: 建表语句 + :param current_user: 当前用户信息对象 + :return: 创建表结构结果 + """ + if cls.__is_valid_create_table(sql): + try: + table_names = re.findall(r'create\s+table\s+(\w+)', sql, re.IGNORECASE) + await GenTableDao.create_table_by_sql_dao(query_db, sql) + 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) + + return CrudResponseModel(is_success=True, message='创建表结构成功') + except Exception as e: + raise ServiceException(message=f'创建表结构异常,详细错误信息:{str(e)}') + else: + raise ServiceException(message='建表语句不合法') + + @classmethod + def __is_valid_create_table(cls, sql: str): + """ + 校验sql语句是否为合法的建表语句 + + :param sql: sql语句 + :return: 校验结果 + """ + create_table_pattern = r'^\s*CREATE\s+TABLE\s+' + if not re.search(create_table_pattern, sql, re.IGNORECASE): + 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 + async def preview_code_services(cls, query_db: AsyncSession, table_id: int): + """ + 预览代码service + + :param query_db: orm对象 + :param table_id: 业务表id + :return: 预览数据列表 + """ + gen_table = GenTableModel( + **CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_id(query_db, table_id)) + ) + await cls.set_sub_table(query_db, gen_table) + await cls.set_pk_column(gen_table) + env = TemplateInitializer.init_jinja2() + context = TemplateUtils.prepare_context(gen_table) + template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type) + preview_code_result = {} + for template in template_list: + render_content = env.get_template(template).render(**context) + preview_code_result[template] = render_content + return preview_code_result + + @classmethod + async def generate_code_services(cls, query_db: AsyncSession, table_name: str): + """ + 生成代码至指定路径service + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 生成代码结果 + """ + env = TemplateInitializer.init_jinja2() + render_info = await cls.__get_gen_render_info(query_db, table_name) + for template in render_info[0]: + try: + render_content = env.get_template(template).render(**render_info[2]) + gen_path = cls.__get_gen_path(render_info[3], template) + os.makedirs(os.path.dirname(gen_path), exist_ok=True) + with open(gen_path, 'w', encoding='utf-8') as f: + f.write(render_content) + except Exception as e: + raise ServiceException( + message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{str(e)}' + ) + + return CrudResponseModel(is_success=True, message='生成代码成功') + + @classmethod + async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: List[str]): + """ + 批量生成代码service + + :param query_db: orm对象 + :param table_names: 业务表名称组 + :return: 下载代码结果 + """ + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for table_name in table_names: + env = TemplateInitializer.init_jinja2() + render_info = await cls.__get_gen_render_info(query_db, table_name) + for template_file, output_file in zip(render_info[0], render_info[1]): + render_content = env.get_template(template_file).render(**render_info[2]) + zip_file.writestr(output_file, render_content) + + zip_data = zip_buffer.getvalue() + zip_buffer.close() + return zip_data + + @classmethod + async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str): + """ + 获取生成代码渲染模板相关信息 + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 生成代码渲染模板相关信息 + """ + gen_table = GenTableModel( + **CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_name(query_db, table_name)) + ) + await cls.set_sub_table(query_db, gen_table) + await cls.set_pk_column(gen_table) + context = TemplateUtils.prepare_context(gen_table) + template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type) + output_files = [TemplateUtils.get_file_name(template, gen_table) for template in template_list] + + return [template_list, output_files, context, gen_table] + + @classmethod + def __get_gen_path(cls, gen_table: GenTableModel, template: str): + """ + 根据GenTableModel对象和模板名称生成路径 + + :param gen_table: GenTableModel对象 + :param template: 模板名称 + :return: 生成的路径 + """ + gen_path = gen_table.gen_path + if gen_path == '/': + return os.path.join(os.getcwd(), GenConfig.GEN_PATH, TemplateUtils.get_file_name(template, gen_table)) + else: + return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table)) + + @classmethod + async def sync_db_services(cls, query_db: AsyncSession, table_name: str): + """ + 同步数据库service + + :param query_db: orm对象 + :param table_name: 业务表名称 + :return: 同步数据库结果 + """ + gen_table = await GenTableDao.get_gen_table_by_name(query_db, table_name) + table = GenTableModel(**CamelCaseUtil.transform_result(gen_table)) + table_columns = table.columns + table_column_map = {column.column_name: column for column in table_columns} + query_db_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name) + db_table_columns = [ + GenTableColumnModel(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns) + ] + if not db_table_columns: + raise ServiceException('同步数据失败,原表结构不存在') + db_table_column_names = [column.column_name for column in db_table_columns] + try: + for column in db_table_columns: + GenUtils.init_column_field(column, table) + if column.column_name in table_column_map: + prev_column = table_column_map[column.column_name] + column.column_id = prev_column.column_id + if column.list: + column.dict_type = prev_column.dict_type + column.query_type = prev_column.query_type + if ( + prev_column.is_required != '' + and not column.pk + and (column.insert or column.edit) + and (column.usable_column or column.super_column) + ): + column.is_required = prev_column.is_required + column.html_type = prev_column.html_type + await GenTableColumnDao.edit_gen_table_column_dao(query_db, column.model_dump(by_alias=True)) + else: + await GenTableColumnDao.add_gen_table_column_dao(query_db, column) + del_columns = [column for column in table_columns if column.column_name not in db_table_column_names] + if del_columns: + for column in del_columns: + await GenTableColumnDao.delete_gen_table_column_by_column_id_dao(query_db, column) + await query_db.commit() + return CrudResponseModel(is_success=True, message='同步成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel): + """ + 设置主子表信息 + + :param query_db: orm对象 + :param gen_table: 业务表信息 + :return: + """ + if gen_table.sub_table_name: + sub_table = await GenTableDao.get_gen_table_by_name(query_db, gen_table.sub_table_name) + gen_table.sub_table = GenTableModel(**CamelCaseUtil.transform_result(sub_table)) + + @classmethod + async def set_pk_column(cls, gen_table: GenTableModel): + """ + 设置主键列信息 + + :param gen_table: 业务表信息 + :return: + """ + for column in gen_table.columns: + if column.pk: + gen_table.pk_column = column + break + if gen_table.pk_column is None: + gen_table.pk_column = gen_table.columns[0] + if gen_table.tpl_category == GenConstant.TPL_SUB: + for column in gen_table.sub_table.columns: + if column.pk: + gen_table.sub_table.pk_column = column + break + if gen_table.sub_table.columns is None: + gen_table.sub_table.pk_column = gen_table.sub_table.columns[0] + + @classmethod + async def set_table_from_options(cls, gen_table: GenTableModel): + """ + 设置代码生成其他选项值 + + :param gen_table: 生成对象 + :return: 设置后的生成对象 + """ + params_obj = json.loads(gen_table.options) if gen_table.options else None + if params_obj: + gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE) + gen_table.tree_parent_code = params_obj.get(GenConstant.TREE_PARENT_CODE) + gen_table.tree_name = params_obj.get(GenConstant.TREE_NAME) + gen_table.parent_menu_id = params_obj.get(GenConstant.PARENT_MENU_ID) + gen_table.parent_menu_name = params_obj.get(GenConstant.PARENT_MENU_NAME) + + return gen_table + + @classmethod + async def validate_edit(cls, edit_gen_table: EditGenTableModel): + """ + 编辑保存参数校验 + + :param edit_gen_table: 编辑业务表对象 + """ + if edit_gen_table.tpl_category == GenConstant.TPL_TREE: + params_obj = edit_gen_table.params.model_dump(by_alias=True) + + if GenConstant.TREE_CODE not in params_obj: + raise ServiceException(message='树编码字段不能为空') + elif GenConstant.TREE_PARENT_CODE not in params_obj: + raise ServiceException(message='树父编码字段不能为空') + elif GenConstant.TREE_NAME not in params_obj: + raise ServiceException(message='树名称字段不能为空') + elif edit_gen_table.tpl_category == GenConstant.TPL_SUB: + if not edit_gen_table.sub_table_name: + raise ServiceException(message='关联子表的表名不能为空') + elif not edit_gen_table.sub_table_fk_name: + raise ServiceException(message='子表关联的外键名不能为空') + + +class GenTableColumnService: + """ + 代码生成业务表字段服务层 + """ + + @classmethod + async def get_gen_table_column_list_by_table_id_services(cls, query_db: AsyncSession, table_id: int): + """ + 获取业务表字段列表信息service + + :param query_db: orm对象 + :param table_id: 业务表格id + :return: 业务表字段列表信息对象 + """ + gen_table_column_list_result = await GenTableColumnDao.get_gen_table_column_list_by_table_id(query_db, table_id) + + return [ + GenTableColumnModel(**gen_table_column) + for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result) + ] diff --git a/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..3a2a5a985c1496f4ddfe26a7b309e512d0020e28 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/js/api.js.jinja2 @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询{{ functionName }}列表 +export function list{{ BusinessName }}(query) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/list', + method: 'get', + params: query + }) +} + +// 查询{{ functionName }}详细 +export function get{{ BusinessName }}({{ pkColumn.python_field }}) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, + method: 'get' + }) +} + +// 新增{{ functionName }} +export function add{{ BusinessName }}(data) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}', + method: 'post', + data: data + }) +} + +// 修改{{ functionName }} +export function update{{ BusinessName }}(data) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}', + method: 'put', + data: data + }) +} + +// 删除{{ functionName }} +export function del{{ BusinessName }}({{ pkColumn.python_field }}) { + return request({ + url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }}, + method: 'delete' + }) +} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..4d5a12e4ecbdd88a4d58d6bd56a2899b45966785 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 @@ -0,0 +1,125 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% for column in columns %} +{% if column.python_field == "createTime" %} +from datetime import datetime +{% endif %} +{% endfor %} +from fastapi import APIRouter, Depends, Form, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from {{ packageName }}.service.{{ businessName }}_service import {{ BusinessName }}Service +from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +{{ businessName }}Controller = APIRouter(prefix='/{{ moduleName }}/{{ businessName }}', dependencies=[Depends(LoginService.get_current_user)]) + + +@{{ businessName }}Controller.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:list'))] +) +async def get_{{ moduleName }}_{{ businessName }}_list( + request: Request, + {% if table.crud or table.sub %}{{ businessName }}_page_query{% elif table.tree %}{{ businessName }}_query{% endif %}: {{ BusinessName }}PageQueryModel = Depends({{ BusinessName }}PageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + {% if table.crud or table.sub %} + # 获取分页数据 + {{ businessName }}_page_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content={{ businessName }}_page_query_result) + {% elif table.tree %} + {{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_query) + logger.info('获取成功') + + return ResponseUtil.success(data={{ businessName }}_query_result) + {% endif %} + + +@{{ businessName }}Controller.post('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:add'))]) +@ValidateFields(validate_model='add_{{ businessName }}') +@Log(title='{{ functionName }}', business_type=BusinessType.INSERT) +async def add_{{ moduleName }}_{{ businessName }}( + request: Request, + add_{{ businessName }}: {{ BusinessName }}Model, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + {% for column in columns %} + {% if column.python_field == "createBy" %} + add_{{ businessName }}.create_by = current_user.user.user_name + {% elif column.python_field == "createTime" %} + add_{{ businessName }}.create_time = datetime.now() + {% elif column.python_field == "updateBy" %} + add_{{ businessName }}.update_by = current_user.user.user_name + {% elif column.python_field == "updateTime" %} + add_{{ businessName }}.update_time = datetime.now() + {% endif %} + {% endfor %} + add_{{ businessName }}_result = await {{ BusinessName }}Service.add_{{ businessName }}_services(query_db, add_{{ businessName }}) + logger.info(add_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=add_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.put('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:edit'))]) +@ValidateFields(validate_model='edit_{{ businessName }}') +@Log(title='{{ functionName }}', business_type=BusinessType.UPDATE) +async def edit_{{ moduleName }}_{{ businessName }}( + request: Request, + edit_{{ businessName }}: {{ BusinessName }}Model, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_{{ businessName }}.update_by = current_user.user.user_name + edit_{{ businessName }}.update_time = datetime.now() + edit_{{ businessName }}_result = await {{ BusinessName }}Service.edit_{{ businessName }}_services(query_db, edit_{{ businessName }}) + logger.info(edit_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=edit_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.delete('/{% raw %}{{% endraw %}{{ pk_field }}s{% raw %}}{% endraw %}', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:remove'))]) +@Log(title='{{ functionName }}', business_type=BusinessType.DELETE) +async def delete_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}s: str, query_db: AsyncSession = Depends(get_db)): + delete_{{ businessName }} = Delete{{ BusinessName }}Model({{ pkField }}s={{ pk_field }}s) + delete_{{ businessName }}_result = await {{ BusinessName }}Service.delete_{{ businessName }}_services(query_db, delete_{{ businessName }}) + logger.info(delete_{{ businessName }}_result.message) + + return ResponseUtil.success(msg=delete_{{ businessName }}_result.message) + + +@{{ businessName }}Controller.get( + '/{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}', response_model={{ BusinessName }}Model, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:query'))] +) +async def query_detail_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}: int, query_db: AsyncSession = Depends(get_db)): + {{ businessName }}_detail_result = await {{ BusinessName }}Service.{{ businessName }}_detail_services(query_db, {{ pk_field }}) + logger.info(f'获取{{ pk_field }}为{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}的信息成功') + + return ResponseUtil.success(data={{ businessName }}_detail_result) + + +@{{ businessName }}Controller.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:export'))]) +@Log(title='{{ functionName }}', business_type=BusinessType.EXPORT) +async def export_{{ moduleName }}_{{ businessName }}_list( + request: Request, + {{ businessName }}_page_query: {{ BusinessName }}PageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + {{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=False) + {{ businessName }}_export_result = await {{ BusinessName }}Service.export_{{ businessName }}_list_services({% if dicts %}request, {% endif %}{{ businessName }}_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response({{ businessName }}_export_result)) diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..cb9ba8123bef63cc3dfe7e70ad3b57f4c43a0498 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 @@ -0,0 +1,213 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% for column in columns %} +{% if column.query and column.query_type == 'BETWEEN' and column.python_field == "createTime" %} +from datetime import datetime, time +{% endif %} +{% endfor %} +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +{% if table.sub %} +from sqlalchemy.orm import selectinload +{% endif %} +{% if table.sub %} +from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }}, {{ subClassName }} +from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel, {{ subTable.business_name | capitalize }}Model +{% else %} +from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }} +from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +{% endif %} +from utils.page_util import PageUtil + + +class {{ BusinessName }}Dao: + """ + {{ functionName }}模块数据库操作层 + """ + + @classmethod + async def get_{{ businessName }}_detail_by_id(cls, db: AsyncSession, {{ pk_field }}: int): + """ + 根据{{ pk_field_comment }}获取{{ functionName }}详细信息 + + :param db: orm对象 + :param {{ pk_field }}: {{ pk_field_comment }} + :return: {{ functionName }}信息对象 + """ + {{ businessName }}_info = ( + ( + await db.execute( + {% if table.sub %} + select({{ ClassName }}) + .options(selectinload({{ ClassName }}.{{ subclassName }}_list)) + {% else %} + select({{ ClassName }}) + {% endif %} + .where( + {{ ClassName }}.{{ pk_field }} == {{ pk_field }} + ) + ) + ) + .scalars() + .first() + ) + + return {{ businessName }}_info + + @classmethod + async def get_{{ businessName }}_detail_by_info(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 根据{{ functionName }}参数获取{{ functionName }}信息 + + :param db: orm对象 + :param {{ businessName }}: {{ functionName }}参数对象 + :return: {{ functionName }}信息对象 + """ + {{ businessName }}_info = ( + ( + await db.execute( + select({{ ClassName }}).where( + {% for column in columns %} + {% if column.required %} + {{ 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 %} + ) + ) + ) + .scalars() + .first() + ) + + return {{ businessName }}_info + + @classmethod + async def get_{{ businessName }}_list(cls, db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False): + """ + 根据查询参数获取{{ functionName }}列表信息 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: {{ functionName }}列表信息对象 + """ + query = ( + {% if table.sub %} + select({{ ClassName }}) + .options(selectinload({{ ClassName }}.{{ subclassName }}_list)) + {% else %} + select({{ ClassName }}) + {% endif %} + .where( + {% for column in columns %} + {% set field = column.python_field | camel_to_snake %} + {% if column.query %} + {% if column.query_type == "EQ" %} + {{ ClassName }}.{{ field }} == query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "NE" %} + {{ ClassName }}.{{ field }} != query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "GT" %} + {{ ClassName }}.{{ field }} > query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "GTE" %} + {{ ClassName }}.{{ field }} >= query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "LT" %} + {{ ClassName }}.{{ field }} < query_object.{{ field }} if query_object.{{ field }} else True, + {% elif column.query_type == "LTE" %} + {{ 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" %} + {{ 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)), + ) + if query_object.begin_time and query_object.end_time + else True, + {% endif %} + {% endif %} + {% endfor %} + ) + .order_by({{ ClassName }}.{{ pk_field }}) + .distinct() + ) + {{ businessName }}_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return {{ businessName }}_list + + @classmethod + async def add_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 新增{{ functionName }}数据库操作 + + :param db: orm对象 + :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.add(db_{{ businessName }}) + await db.flush() + + return db_{{ businessName }} + + @classmethod + async def edit_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: dict): + """ + 编辑{{ functionName }}数据库操作 + + :param db: orm对象 + :param {{ businessName }}: 需要更新的{{ functionName }}字典 + :return: + """ + await db.execute(update({{ ClassName }}), [{{ businessName }}]) + + @classmethod + async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model): + """ + 删除{{ functionName }}数据库操作 + + :param db: orm对象 + :param {{ businessName }}: {{ functionName }}对象 + :return: + """ + await db.execute(delete({{ ClassName }}).where({{ ClassName }}.{{ pk_field }}.in_([{{ businessName }}.{{ pk_field }}]))) + + {% if table.sub %} + @classmethod + async def add_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + """ + 新增{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: {{ subTable.function_name }}对象 + :return: + """ + db_{{ subTable.business_name }} = {{ subClassName }}(**{{ subTable.business_name }}.model_dump()) + db.add(db_{{ subTable.business_name }}) + await db.flush() + + return db_{{ subTable.business_name }} + + @classmethod + async def edit_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: dict): + """ + 编辑{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: 需要更新的{{ subTable.function_name }}字典 + :return: + """ + await db.execute(update({{ subClassName }}), [{{ subTable.business_name }}]) + + @classmethod + async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model): + """ + 删除{{ subTable.function_name }}数据库操作 + + :param db: orm对象 + :param {{ subTable.business_name }}: {{ subTable.function_name }}对象 + :return: + """ + await db.execute(delete({{ subClassName }}).where({{ subClassName }}.{{ subTable.pk_column.python_field | camel_to_snake }}.in_([{{ subTable.business_name }}.{{ subTable.pk_column.python_field | camel_to_snake }}]))) + {% endif %} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..22c23c441c6bdbc7becc8bd2276b1157bcd1de25 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 @@ -0,0 +1,41 @@ +{% for do_import in doImportList %} +{{ do_import }} +{% endfor %} +{% if table.sub %} +from sqlalchemy.orm import relationship +{% endif %} +from config.database import Base + + +class {{ ClassName }}(Base): + """ + {{ functionName }}表 + """ + + __tablename__ = '{{ tableName }}' + + {% 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 }}') + {% endfor %} + + {% if table.sub %} + {{ subclassName }}_list = relationship('{{ subClassName }}', back_populates='{{ businessName }}') + {% endif %} + + +{% if table.sub %} +class {{ subClassName }}(Base): + """ + {{ subTable.function_name }}表 + """ + + __tablename__ = '{{ subTableName }}' + + {% for column in subTable.columns %} + {{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.column_name == subTableFkName %}ForeignKey('{{ tableName }}.{{ subTableFkName }}'), {% endif %}{% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required %}nullable=True{% else %}nullable=False{% endif %}, comment='{{ column.column_comment }}') + {% endfor %} + + {% if table.sub %} + {{ businessName }} = relationship('{{ ClassName }}', back_populates='{{ subclassName }}_list') + {% endif %} +{% endif %} diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..27d6979111787c77b7270924c3535daafd07c1ab --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/service.py.jinja2 @@ -0,0 +1,211 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% set pkParentheseIndex = pkColumn.column_comment.find("(") %} +{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% if dicts %} +from fastapi import Request +{% endif %} +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +{% if dicts %} +from module_admin.service.dict_service import DictDataService +{% endif %} +from {{ packageName }}.dao.{{ businessName }}_dao import {{ BusinessName }}Dao +from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel +from utils.common_util import CamelCaseUtil +from utils.excel_util import ExcelUtil + + +class {{ BusinessName }}Service: + """ + {{ functionName }}模块服务层 + """ + + @classmethod + async def get_{{ businessName }}_list_services( + cls, query_db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False + ): + """ + 获取{{ functionName }}列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: {{ functionName }}列表信息对象 + """ + {{ businessName }}_list_result = await {{ BusinessName }}Dao.get_{{ businessName }}_list(query_db, query_object, is_page) + + return {{ businessName }}_list_result + + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + @classmethod + async def check_{{ column.python_field | camel_to_snake }}_unique_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 检查{{ comment }}是否唯一service + + :param query_db: orm对象 + :param page_object: {{ functionName }}对象 + :return: 校验结果 + """ + {{ pk_field }} = -1 if page_object.{{ pk_field }} is None else page_object.{{ pk_field }} + {{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_info(query_db, {{ BusinessName }}Model({{ column.python_field }}=page_object.{{ column.python_field | camel_to_snake }})) + if {{ businessName }} and {{ businessName }}.{{ pk_field }} != {{ pk_field }}: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + @classmethod + async def add_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 新增{{ functionName }}信息service + + :param query_db: orm对象 + :param page_object: 新增{{ functionName }}对象 + :return: 新增{{ functionName }}校验结果 + """ + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object): + raise ServiceException(message=f'新增{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在') + {% endif %} + {% endfor %} + try: + {% if table.sub %} + add_{{ businessName }} = await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object) + if add_{{ businessName }}: + for sub_table in page_object.{{ subclassName }}_list: + await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table) + {% else %} + await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object) + {% endif %} + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e + + @classmethod + async def edit_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model): + """ + 编辑{{ functionName }}信息service + + :param query_db: orm对象 + :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 %}) + {{ businessName }}_info = await cls.{{ businessName }}_detail_services(query_db, page_object.{{ pk_field }}) + if {{ businessName }}_info.{{ pk_field }}: + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + {% if column.unique %} + if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object): + raise ServiceException(message=f'修改{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在') + {% endif %} + {% endfor %} + try: + await {{ BusinessName }}Dao.edit_{{ businessName }}_dao(query_db, edit_{{ businessName }}) + {% if table.sub %} + for sub_table in {{ businessName }}_info.{{ subclassName }}_list: + await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table) + for sub_table in page_object.{{ subclassName }}_list: + await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table) + {% endif %} + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='{{ functionName }}不存在') + + @classmethod + async def delete_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: Delete{{ BusinessName }}Model): + """ + 删除{{ functionName }}信息service + + :param query_db: orm对象 + :param page_object: 删除{{ functionName }}对象 + :return: 删除{{ functionName }}校验结果 + """ + if page_object.{{ pk_field }}s: + {{ pk_field }}_list = page_object.{{ pk_field }}s.split(',') + try: + for {{ pk_field }} in {{ pk_field }}_list: + {% if table.sub %} + {{ businessName }} = await cls.{{ businessName }}_detail_services(query_db, int({{ pk_field }})) + for sub_table in {{ businessName }}.{{ subclassName }}_list: + await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table) + {% endif %} + await {{ BusinessName }}Dao.delete_{{ businessName }}_dao(query_db, {{ BusinessName }}Model({{ pkField }}={{ pk_field }})) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') + except Exception as e: + await query_db.rollback() + raise e + else: + raise ServiceException(message='传入{{ pk_field_comment }}为空') + + @classmethod + async def {{ businessName }}_detail_services(cls, query_db: AsyncSession, {{ pk_field }}: int): + """ + 获取{{ functionName }}详细信息service + + :param query_db: orm对象 + :param {{ pk_field }}: {{ pk_field_comment }} + :return: {{ pk_field_comment }}对应的信息 + """ + {{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_id(query_db, {{ pk_field }}={{ pk_field }}) + if {{ businessName }}: + result = {{ BusinessName }}Model(**CamelCaseUtil.transform_result({{ businessName }})) + else: + result = {{ BusinessName }}Model(**dict()) + + return result + + @staticmethod + async def export_{{ businessName }}_list_services({% if dicts %}request: Request, {% endif %}{{ businessName }}_list: List): + """ + 导出{{ functionName }}信息service + + :param {{ businessName }}_list: {{ functionName }}信息列表 + :return: {{ functionName }}信息对应excel的二进制数据 + """ + # 创建一个映射字典,将英文键映射到中文键 + mapping_dict = { + {% for column in columns %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + '{{ column.python_field }}': '{{ comment }}', + {% endfor %} + } + {% if dicts %} + {% for dict_type in dicts.split(", ") %} + {{ dict_type[1:-1] }}_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type={{ dict_type }} + ) + {{ dict_type[1:-1] }}_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in {{ dict_type[1:-1] }}_list] + {{ dict_type[1:-1] }}_option_dict = {item.get('value'): item for item in {{ dict_type[1:-1] }}_option} + {% endfor %} + for item in {{ businessName }}_list: + {% for column in columns %} + {% if column.dict_type %} + if str(item.get('{{ column.python_field }}')) in {{ column.dict_type }}_option_dict.keys(): + item['{{ column.python_field }}'] = {{ column.dict_type }}_option_dict.get(str(item.get('{{ column.python_field }}'))).get('label') + {% endif %} + {% endfor %} + {% endif %} + binary_data = ExcelUtil.export_list2excel({{ businessName }}_list, mapping_dict) + + return binary_data diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..2b8d5b9ab2a0d0b0302afb58345092cb2335afc6 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/python/vo.py.jinja2 @@ -0,0 +1,167 @@ +{% set pkField = pkColumn.python_field %} +{% set pk_field = pkColumn.python_field | camel_to_snake %} +{% 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) %} +{% for column in columns %} +{% if column.required %} + {% set vo_field_required.has_required = True %} +{% endif %} +{% endfor %} +{% if table.sub %} +{% set sub_vo_field_required = namespace(has_required=False) %} +{% for sub_column in subTable.columns %} +{% if sub_column.required %} + {% set sub_vo_field_required.has_required = True %} +{% endif %} +{% endfor %} +{% endif %} +{% for vo_import in voImportList %} +{{ vo_import }} +{% endfor %} +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +{% if vo_field_required.has_required %} +from pydantic_validation_decorator import NotBlank +{% endif %} +{% if table.sub %} +from typing import List, Optional +{% else %} +from typing import Optional +{% endif %} +from module_admin.annotation.pydantic_annotation import as_query + + +{% if table.sub %} +class {{ BusinessName }}BaseModel(BaseModel): + """ + {{ functionName }}表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for column in columns %} + {{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}') + {% endfor %} + + {% for column in columns %} + {% if column.required %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') + def get_{{ column.column_name }}(self): + return self.{{ column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if vo_field_required.has_required %} + def validate_fields(self): + {% for column in columns %} + {% if column.required %} + self.get_{{ column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} +{% endif %} + + +class {{ BusinessName }}Model({% if table.sub %}{{ BusinessName }}BaseModel{% else %}BaseModel{% endif %}): + """ + {{ functionName }}表对应pydantic模型 + """ + {% if not table.sub %} + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for column in columns %} + {{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}') + {% endfor %} + {% endif %} + {% if table.sub %} + {{ subclassName }}_list: Optional[List['{{ subTable.business_name | capitalize }}Model']] = Field(default=None, description='子表列信息') + {% endif %} + + {% if not table.sub %} + {% for column in columns %} + {% if column.required %} + {% set parentheseIndex = column.column_comment.find("(") %} + {% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %} + @NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空') + def get_{{ column.column_name }}(self): + return self.{{ column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if vo_field_required.has_required %} + def validate_fields(self): + {% for column in columns %} + {% if column.required %} + self.get_{{ column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} + {% endif %} + + +{% if table.sub %} +class {{ subTable.business_name | capitalize }}Model(BaseModel): + """ + {{ subTable.function_name }}表对应pydantic模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) + + {% for sub_column in subTable.columns %} + {{ sub_column.column_name }}: Optional[{{ sub_column.python_type }}] = Field(default=None, description='{{ sub_column.column_comment}}') + {% endfor %} + + {% for sub_column in subTable.columns %} + {% if sub_column.required %} + {% set parentheseIndex = sub_column.column_comment.find("(") %} + {% set comment = sub_column.column_comment[:parentheseIndex] if parentheseIndex != -1 else sub_column.column_comment %} + @NotBlank(field_name='{{ sub_column.column_name }}', message='{{ comment }}不能为空') + def get_{{ sub_column.column_name }}(self): + return self.{{ sub_column.column_name }} + {% if not loop.last %}{{ "\n" }}{% endif %} + {% endif %} + {% endfor %} + + {% if sub_vo_field_required.has_required %} + def validate_fields(self): + {% for sub_column in subTable.columns %} + {% if sub_column.required %} + self.get_{{ sub_column.column_name }}() + {% endif %} + {% endfor %} + {% endif %} +{% endif %} + + +class {{ BusinessName }}QueryModel({% if table.sub %}{{ BusinessName }}BaseModel{% else %}{{ BusinessName }}Model{% endif %}): + """ + {{ functionName }}不分页查询模型 + """ + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') + + +@as_query +class {{ BusinessName }}PageQueryModel({{ BusinessName }}QueryModel): + """ + {{ functionName }}分页查询模型 + """ + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') + + +class Delete{{ BusinessName }}Model(BaseModel): + """ + 删除{{ functionName }}模型 + """ + + model_config = ConfigDict(alias_generator=to_camel) + + {{ pk_field }}s: str = Field(description='需要删除的{{ pk_field_comment }}') diff --git a/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..dd4099fd3c23f4bdbb1fe462819f2de709d57775 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/sql/sql.jinja2 @@ -0,0 +1,47 @@ +{% if dbType == 'postgresql' %} +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', current_timestamp, '', null, '{{ functionName }}菜单'); + +-- 按钮父菜单ID +select max(menu_id) from sys_menu; + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}查询', max(menu_id), '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}新增', max(menu_id), '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}修改', max(menu_id), '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}删除', max(menu_id), '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', current_timestamp, '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}导出', max(menu_id), '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', current_timestamp, '', null, ''); +{% else %} +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', sysdate(), '', null, '{{ functionName }}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('{{ functionName }}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', sysdate(), '', null, ''); +{% endif %} \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..741f13282a13abde0eecabfdb695f85b960b0a25 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/index-tree.vue.jinja2 @@ -0,0 +1,491 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..a1b7f4b03ec3f6803a6cd6ce843b5d74afbbe3b4 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/index.vue.jinja2 @@ -0,0 +1,586 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..433c162de5999e8c679dff27aea75c11410848f7 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index-tree.vue.jinja2 @@ -0,0 +1,454 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..981124f821599cce9ec5e286d06f4962dba16214 --- /dev/null +++ b/ruoyi-fastapi-backend/module_generator/templates/vue/v3/index.vue.jinja2 @@ -0,0 +1,571 @@ + + + \ No newline at end of file diff --git a/ruoyi-fastapi-backend/requirements-pg.txt b/ruoyi-fastapi-backend/requirements-pg.txt index 9e74b2fa622b8ac4a48650c6062c0442269a4462..3caa988da5f63fccebf7ce801b6e670557be217a 100644 --- a/ruoyi-fastapi-backend/requirements-pg.txt +++ b/ruoyi-fastapi-backend/requirements-pg.txt @@ -1,17 +1,17 @@ -APScheduler==3.10.4 +APScheduler==3.11.0 asyncpg==0.30.0 DateTime==5.5 -fastapi[all]==0.115.0 -loguru==0.7.2 +fastapi[all]==0.115.8 +loguru==0.7.3 openpyxl==3.1.5 -pandas==2.2.2 +pandas==2.2.3 passlib[bcrypt]==1.7.4 -Pillow==10.4.0 -psutil==6.0.0 +Pillow==11.1.0 +psutil==7.0.0 pydantic-validation-decorator==0.1.4 -PyJWT[crypto]==2.8.0 +PyJWT[crypto]==2.10.1 psycopg2==2.9.10 -redis==5.0.7 +redis==5.2.1 requests==2.32.3 -SQLAlchemy[asyncio]==2.0.31 +SQLAlchemy[asyncio]==2.0.38 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index c09d7b892ca990a786a63026d2e7f6663c73a839..7a3705b8abdcb3145cce783c9911c2cc403aea77 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -1,17 +1,17 @@ -APScheduler==3.10.4 -asyncmy==0.2.9 +APScheduler==3.11.0 +asyncmy==0.2.10 DateTime==5.5 -fastapi[all]==0.115.0 -loguru==0.7.2 +fastapi[all]==0.115.8 +loguru==0.7.3 openpyxl==3.1.5 -pandas==2.2.2 +pandas==2.2.3 passlib[bcrypt]==1.7.4 -Pillow==10.4.0 -psutil==6.0.0 +Pillow==11.1.0 +psutil==7.0.0 pydantic-validation-decorator==0.1.4 -PyJWT[crypto]==2.8.0 +PyJWT[crypto]==2.10.1 PyMySQL==1.1.1 -redis==5.0.7 +redis==5.2.1 requests==2.32.3 -SQLAlchemy[asyncio]==2.0.31 +SQLAlchemy[asyncio]==2.0.38 user-agents==2.2.0 diff --git a/ruoyi-fastapi-backend/server.py b/ruoyi-fastapi-backend/server.py index 00b4661dfc132653e08a1cfe648abf124f6af057..5c8ad9cc023d6b17f9cf137b117b609a05d60c50 100644 --- a/ruoyi-fastapi-backend/server.py +++ b/ruoyi-fastapi-backend/server.py @@ -22,6 +22,7 @@ from module_admin.controller.post_controler import postController from module_admin.controller.role_controller import roleController from module_admin.controller.server_controller import serverController from module_admin.controller.user_controller import userController +from module_generator.controller.gen_controller import genController from sub_applications.handle import handle_sub_applications from utils.common_util import worship from utils.log_util import logger @@ -77,6 +78,7 @@ controller_list = [ {'router': serverController, 'tags': ['系统监控-菜单管理']}, {'router': cacheController, 'tags': ['系统监控-缓存监控']}, {'router': commonController, 'tags': ['通用模块']}, + {'router': genController, 'tags': ['代码生成']}, ] for controller in controller_list: diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql index 79b7767c94e7bbcc8abca244dea48d60d5a12ee0..b3731aa3f42630736fe5619415a2b4203b6f1373 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql @@ -909,15 +909,16 @@ comment on table gen_table is '代码生成业务表'; drop table if exists gen_table_column; create table gen_table_column ( column_id bigserial not null, - table_id varchar(64), + table_id bigint, column_name varchar(200), column_comment varchar(500), column_type varchar(100), - java_type varchar(500), - java_field varchar(200), + python_type varchar(500), + python_field varchar(200), is_pk char(1), is_increment char(1), is_required char(1), + is_unique char(1), is_insert char(1), is_edit char(1), is_list char(1), @@ -937,11 +938,12 @@ comment on column gen_table_column.table_id is '归属表编号'; comment on column gen_table_column.column_name is '列名称'; comment on column gen_table_column.column_comment is '列描述'; comment on column gen_table_column.column_type is '列类型'; -comment on column gen_table_column.java_type is 'JAVA类型'; -comment on column gen_table_column.java_field is 'JAVA字段名'; +comment on column gen_table_column.python_type is 'PYTHON类型'; +comment on column gen_table_column.python_field is 'PYTHON字段名'; comment on column gen_table_column.is_pk is '是否主键(1是)'; comment on column gen_table_column.is_increment is '是否自增(1是)'; comment on column gen_table_column.is_required is '是否必填(1是)'; +comment on column gen_table_column.is_unique is '是否唯一(1是)'; comment on column gen_table_column.is_insert is '是否为插入字段(1是)'; comment on column gen_table_column.is_edit is '是否编辑字段(1是)'; comment on column gen_table_column.is_list is '是否列表字段(1是)'; @@ -975,3 +977,71 @@ END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; + +create or replace view list_column as +SELECT c.relname AS table_name, + a.attname AS column_name, + d.description AS column_comment, + CASE + WHEN a.attnotnull AND con.conname IS NULL THEN '1' + ELSE '0' + END AS is_required, + CASE + WHEN con.conname IS NOT NULL THEN '1' + ELSE '0' + END AS is_pk, + a.attnum AS sort, + CASE + WHEN "position"(pg_get_expr(ad.adbin, ad.adrelid), ((c.relname::text || '_'::text) || a.attname + ::text) || '_seq'::text) > 0 THEN '1' + ELSE '0' + END AS is_increment, + btrim( + CASE + WHEN t.typelem <> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text + ELSE + CASE + WHEN t.typtype = 'd'::"char" THEN format_type(t.typbasetype, NULL::integer) + ELSE format_type(a.atttypid, NULL::integer) + END + END, '"'::text) AS column_type +FROM pg_attribute a + JOIN (pg_class c + JOIN pg_namespace n ON c.relnamespace = n.oid) ON a.attrelid = c.oid + LEFT JOIN pg_description d ON d.objoid = c.oid AND a.attnum = d.objsubid + LEFT JOIN pg_constraint con ON con.conrelid = c.oid AND (a.attnum = ANY (con.conkey)) + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + LEFT JOIN pg_type t ON a.atttypid = t.oid +WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) + AND a.attnum > 0 + AND n.nspname = 'public'::name + AND not a.attisdropped + ORDER BY c.relname, a.attnum; + +create or replace view list_table as +SELECT c.relname AS table_name, + obj_description(c.oid) AS table_comment, + CURRENT_TIMESTAMP AS create_time, + CURRENT_TIMESTAMP AS update_time +FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) + AND c.relname !~~ 'spatial_%'::text AND n.nspname = 'public'::name AND n.nspname <> ''::name; + +CREATE OR REPLACE FUNCTION substring_index(varchar, varchar, integer) +RETURNS varchar AS $$ +DECLARE +tokens varchar[]; +length integer ; +indexnum integer; +BEGIN +tokens := pg_catalog.string_to_array($1, $2); +length := pg_catalog.array_upper(tokens, 1); +indexnum := length - ($3 * -1) + 1; +IF $3 >= 0 THEN +RETURN pg_catalog.array_to_string(tokens[1:$3], $2); +ELSE +RETURN pg_catalog.array_to_string(tokens[indexnum:length], $2); +END IF; +END; +$$ IMMUTABLE STRICT LANGUAGE PLPGSQL; diff --git a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql index ef5a8debddd0c2f695424ce2ca201ab6ab217c9f..d0ce11a55b1a50d9a9feeb84f8f0d4c46bc02333 100644 --- a/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql +++ b/ruoyi-fastapi-backend/sql/ruoyi-fastapi.sql @@ -691,11 +691,12 @@ create table gen_table_column ( column_name varchar(200) comment '列名称', column_comment varchar(500) comment '列描述', column_type varchar(100) comment '列类型', - java_type varchar(500) comment 'JAVA类型', - java_field varchar(200) comment 'JAVA字段名', + python_type varchar(500) comment 'PYTHON类型', + python_field varchar(200) comment 'PYTHON字段名', is_pk char(1) comment '是否主键(1是)', is_increment char(1) comment '是否自增(1是)', is_required char(1) comment '是否必填(1是)', + is_unique char(1) comment '是否唯一(1是)', is_insert char(1) comment '是否为插入字段(1是)', is_edit char(1) comment '是否编辑字段(1是)', is_list char(1) comment '是否列表字段(1是)', diff --git a/ruoyi-fastapi-backend/utils/common_util.py b/ruoyi-fastapi-backend/utils/common_util.py index 86f502e56b7b924b12f831f27aa879ba3b6a23f4..b56000e453dec419417fc4abfb02dbdcacaa20ba 100644 --- a/ruoyi-fastapi-backend/utils/common_util.py +++ b/ruoyi-fastapi-backend/utils/common_util.py @@ -7,6 +7,7 @@ from openpyxl.styles import Alignment, PatternFill 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 typing import Any, Dict, List, Literal, Union from config.database import Base from config.env import CachePathConfig @@ -58,6 +59,9 @@ class SqlalchemyUtil: if isinstance(obj, Base): base_dict = obj.__dict__.copy() base_dict.pop('_sa_instance_state', None) + for name, value in base_dict.items(): + if isinstance(value, InstrumentedList): + base_dict[name] = cls.serialize_result(value, 'snake_to_camel') elif isinstance(obj, dict): base_dict = obj.copy() if transform_case == 'snake_to_camel': diff --git a/ruoyi-fastapi-backend/utils/excel_util.py b/ruoyi-fastapi-backend/utils/excel_util.py new file mode 100644 index 0000000000000000000000000000000000000000..875a41d92f1fa3b262772401793fca452a9a7d99 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/excel_util.py @@ -0,0 +1,104 @@ +import io +import pandas as pd +from openpyxl import Workbook +from openpyxl.styles import Alignment, PatternFill +from openpyxl.utils import get_column_letter +from openpyxl.worksheet.datavalidation import DataValidation +from typing import Dict, List + + +class ExcelUtil: + """ + Excel操作类 + """ + + @classmethod + def __mapping_list(cls, list_data: List, mapping_dict: Dict): + """ + 工具方法:将list数据中的字段名映射为对应的中文字段名 + + :param list_data: 数据列表 + :param mapping_dict: 映射字典 + :return: 映射后的数据列表 + """ + mapping_data = [{mapping_dict.get(key): item.get(key) for key in mapping_dict} for item in list_data] + + return mapping_data + + @classmethod + def export_list2excel(cls, list_data: List, mapping_dict: Dict): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param list_data: 数据列表 + :param mapping_dict: 映射字典 + :return: list数据对应excel的二进制数据 + """ + mapping_data = cls.__mapping_list(list_data, mapping_dict) + df = pd.DataFrame(mapping_data) + binary_data = io.BytesIO() + df.to_excel(binary_data, index=False, engine='openpyxl') + binary_data = binary_data.getvalue() + + return binary_data + + @classmethod + def get_excel_template(cls, header_list: List, selector_header_list: List, option_list: List[Dict]): + """ + 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + + :param header_list: 表头数据列表 + :param selector_header_list: 需要设置为选择器格式的表头数据列表 + :param option_list: 选择器格式的表头预设的选项列表 + :return: 模板excel的二进制数据 + """ + # 创建Excel工作簿 + wb = Workbook() + # 选择默认的活动工作表 + ws = wb.active + + # 设置表头文字 + headers = header_list + + # 设置表头背景样式为灰色,前景色为白色 + header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid') + + # 将表头写入第一行 + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col_num) + cell.value = header + cell.fill = header_fill + # 设置列宽度为16 + ws.column_dimensions[chr(64 + col_num)].width = 12 + # 设置水平居中对齐 + cell.alignment = Alignment(horizontal='center') + + # 设置选择器的预设选项 + options = option_list + + # 获取selector_header的字母索引 + for selector_header in selector_header_list: + column_selector_header_index = headers.index(selector_header) + 1 + + # 创建数据有效性规则 + header_option = [] + for option in options: + if option.get(selector_header): + header_option = option.get(selector_header) + dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"') + # 设置数据有效性规则的起始单元格和结束单元格 + dv.add( + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576' + ) + # 添加数据有效性规则到工作表 + ws.add_data_validation(dv) + + # 保存Excel文件为字节类型的数据 + file = io.BytesIO() + wb.save(file) + file.seek(0) + + # 读取字节数据 + excel_data = file.getvalue() + + return excel_data diff --git a/ruoyi-fastapi-backend/utils/gen_util.py b/ruoyi-fastapi-backend/utils/gen_util.py new file mode 100644 index 0000000000000000000000000000000000000000..355e5d022557c757507c95c81c585e9e3b2f7647 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/gen_util.py @@ -0,0 +1,223 @@ +import re +from datetime import datetime +from typing import List +from config.constant import GenConstant +from config.env import GenConfig +from module_generator.entity.vo.gen_vo import GenTableColumnModel, GenTableModel +from utils.string_util import StringUtil + + +class GenUtils: + """代码生成器工具类""" + + @classmethod + def init_table(cls, gen_table: GenTableModel, oper_name: str) -> None: + """ + 初始化表信息 + + param gen_table: 业务表对象 + param oper_name: 操作人 + :return: + """ + gen_table.class_name = cls.convert_class_name(gen_table.table_name) + gen_table.package_name = GenConfig.package_name + gen_table.module_name = cls.get_module_name(GenConfig.package_name) + gen_table.business_name = cls.get_business_name(gen_table.table_name) + gen_table.function_name = cls.replace_text(gen_table.table_comment) + gen_table.function_author = GenConfig.author + gen_table.create_by = oper_name + gen_table.create_time = datetime.now() + gen_table.update_by = oper_name + gen_table.update_time = datetime.now() + + @classmethod + def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel) -> None: + """ + 初始化列属性字段 + + param column: 业务表字段对象 + param table: 业务表对象 + :return: + """ + data_type = cls.get_db_type(column.column_type) + column_name = column.column_name + column.table_id = table.table_id + column.create_by = table.create_by + # 设置Python字段名 + column.python_field = cls.to_camel_case(column_name) + # 设置默认类型 + column.python_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_PYTHON_TYPE_MAPPING, data_type + ) + column.query_type = GenConstant.QUERY_EQ + + if cls.arrays_contains(GenConstant.COLUMNTYPE_STR, data_type) or cls.arrays_contains( + GenConstant.COLUMNTYPE_TEXT, data_type + ): + # 字符串长度超过500设置为文本域 + column_length = cls.get_column_length(column.column_type) + html_type = ( + GenConstant.HTML_TEXTAREA + if column_length >= 500 or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type) + else GenConstant.HTML_INPUT + ) + column.html_type = html_type + elif cls.arrays_contains(GenConstant.COLUMNTYPE_TIME, data_type): + column.html_type = GenConstant.HTML_DATETIME + elif cls.arrays_contains(GenConstant.COLUMNTYPE_NUMBER, data_type): + column.html_type = GenConstant.HTML_INPUT + + # 插入字段(默认所有字段都需要插入) + column.is_insert = GenConstant.REQUIRE + + # 编辑字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not column.pk: + column.is_edit = GenConstant.REQUIRE + # 列表字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not column.pk: + column.is_list = GenConstant.REQUIRE + # 查询字段 + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not column.pk: + column.is_query = GenConstant.REQUIRE + + # 查询字段类型 + if column_name.lower().endswith('name'): + column.query_type = GenConstant.QUERY_LIKE + # 状态字段设置单选框 + if column_name.lower().endswith('status'): + column.html_type = GenConstant.HTML_RADIO + # 类型&性别字段设置下拉框 + elif column_name.lower().endswith('type') or column_name.lower().endswith('sex'): + column.html_type = GenConstant.HTML_SELECT + # 图片字段设置图片上传控件 + elif column_name.lower().endswith('image'): + column.html_type = GenConstant.HTML_IMAGE_UPLOAD + # 文件字段设置文件上传控件 + elif column_name.lower().endswith('file'): + column.html_type = GenConstant.HTML_FILE_UPLOAD + # 内容字段设置富文本控件 + elif column_name.lower().endswith('content'): + column.html_type = GenConstant.HTML_EDITOR + + column.create_by = table.create_by + column.create_time = datetime.now() + column.update_by = table.update_by + column.update_time = datetime.now() + + @classmethod + def arrays_contains(cls, arr: List[str], target_value: str) -> bool: + """ + 校验数组是否包含指定值 + + param arr: 数组 + param target_value: 需要校验的值 + :return: 校验结果 + """ + return target_value in arr + + @classmethod + def get_module_name(cls, package_name: str) -> str: + """ + 获取模块名 + + param package_name: 包名 + :return: 模块名 + """ + return package_name.split('.')[-1] + + @classmethod + def get_business_name(cls, table_name: str) -> str: + """ + 获取业务名 + + param table_name: 业务表名 + :return: 业务名 + """ + return table_name.split('_')[-1] + + @classmethod + def convert_class_name(cls, table_name: str) -> str: + """ + 表名转换成Python类名 + + param table_name: 业务表名 + :return: Python类名 + """ + auto_remove_pre = GenConfig.auto_remove_pre + table_prefix = GenConfig.table_prefix + if auto_remove_pre and table_prefix: + search_list = table_prefix.split(',') + table_name = cls.replace_first(table_name, search_list) + return StringUtil.convert_to_camel_case(table_name) + + @classmethod + def replace_first(cls, replacement: str, search_list: List[str]) -> str: + """ + 批量替换前缀 + + param replacement: 需要被替换的字符串 + param search_list: 可替换的字符串列表 + :return: 替换后的字符串 + """ + for search_string in search_list: + if replacement.startswith(search_string): + return replacement.replace(search_string, '', 1) + return replacement + + @classmethod + def replace_text(cls, text: str) -> str: + """ + 关键字替换 + + param text: 需要被替换的字符串 + :return: 替换后的字符串 + """ + return re.sub(r'(?:表|若依)', '', text) + + @classmethod + def get_db_type(cls, column_type: str) -> str: + """ + 获取数据库类型字段 + + param column_type: 字段类型 + :return: 数据库类型 + """ + if '(' in column_type: + return column_type.split('(')[0] + return column_type + + @classmethod + def get_column_length(cls, column_type: str) -> int: + """ + 获取字段长度 + + param column_type: 字段类型 + :return: 字段长度 + """ + if '(' in column_type: + length = len(column_type.split('(')[1].split(')')[0]) + return length + return 0 + + @classmethod + def split_column_type(cls, column_type: str) -> List[str]: + """ + 拆分列类型 + + param column_type: 字段类型 + :return: 拆分结果 + """ + if '(' in column_type and ')' in column_type: + return column_type.split('(')[1].split(')')[0].split(',') + return [] + + @classmethod + def to_camel_case(cls, text: str) -> str: + """ + 将字符串转换为驼峰命名 + + param text: 需要转换的字符串 + :return: 驼峰命名 + """ + parts = text.split('_') + return parts[0] + ''.join(word.capitalize() for word in parts[1:]) diff --git a/ruoyi-fastapi-backend/utils/log_util.py b/ruoyi-fastapi-backend/utils/log_util.py index e42f3938cc8435e49f02c17aac4bfa215ed866a7..f953f55159b10b686e84975964fd9e7f7a761cf6 100644 --- a/ruoyi-fastapi-backend/utils/log_util.py +++ b/ruoyi-fastapi-backend/utils/log_util.py @@ -1,11 +1,60 @@ import os +import sys import time -from loguru import logger +from loguru import logger as _logger +from typing import Dict +from middlewares.trace_middleware import TraceCtx -log_path = os.path.join(os.getcwd(), 'logs') -if not os.path.exists(log_path): - os.mkdir(log_path) -log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log') +class LoggerInitializer: + def __init__(self): + self.log_path = os.path.join(os.getcwd(), 'logs') + self.__ensure_log_directory_exists() + self.log_path_error = os.path.join(self.log_path, f'{time.strftime("%Y-%m-%d")}_error.log') -logger.add(log_path_error, rotation='50MB', encoding='utf-8', enqueue=True, compression='zip') + def __ensure_log_directory_exists(self): + """ + 确保日志目录存在,如果不存在则创建 + """ + if not os.path.exists(self.log_path): + os.mkdir(self.log_path) + + @staticmethod + def __filter(log: Dict): + """ + 自定义日志过滤器,添加trace_id + """ + log['trace_id'] = TraceCtx.get_id() + return log + + def init_log(self): + """ + 初始化日志配置 + """ + # 自定义日志格式 + format_str = ( + '{time:YYYY-MM-DD HH:mm:ss.SSS} | ' + '{trace_id} | ' + '{level: <8} | ' + '{name}:{function}:{line} - ' + '{message}' + ) + _logger.remove() + # 移除后重新添加sys.stderr, 目的: 控制台输出与文件日志内容和结构一致 + _logger.add(sys.stderr, filter=self.__filter, format=format_str, enqueue=True) + _logger.add( + self.log_path_error, + filter=self.__filter, + format=format_str, + rotation='50MB', + encoding='utf-8', + enqueue=True, + compression='zip', + ) + + return _logger + + +# 初始化日志处理器 +log_initializer = LoggerInitializer() +logger = log_initializer.init_log() diff --git a/ruoyi-fastapi-backend/utils/response_util.py b/ruoyi-fastapi-backend/utils/response_util.py index 88d2e37bfa72db23ecc3e88967ed521348e7c68c..01d463281c510a162df8713162b2206f1b808f0f 100644 --- a/ruoyi-fastapi-backend/utils/response_util.py +++ b/ruoyi-fastapi-backend/utils/response_util.py @@ -3,7 +3,8 @@ from fastapi import status from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse, Response, StreamingResponse from pydantic import BaseModel -from typing import Any, Dict, Optional +from starlette.background import BackgroundTask +from typing import Any, Dict, Mapping, Optional from config.constant import HttpStatusConstant @@ -20,6 +21,9 @@ class ResponseUtil: rows: Optional[Any] = None, dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, ) -> Response: """ 成功响应方法 @@ -29,6 +33,9 @@ class ResponseUtil: :param rows: 可选,成功响应结果中属性为rows的值 :param dict_content: 可选,dict类型,成功响应结果中自定义属性的值 :param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 成功响应结果 """ result = {'code': HttpStatusConstant.SUCCESS, 'msg': msg} @@ -44,7 +51,13 @@ class ResponseUtil: result.update({'success': True, 'time': datetime.now()}) - return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) @classmethod def failure( @@ -54,6 +67,9 @@ class ResponseUtil: rows: Optional[Any] = None, dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, ) -> Response: """ 失败响应方法 @@ -63,6 +79,9 @@ class ResponseUtil: :param rows: 可选,失败响应结果中属性为rows的值 :param dict_content: 可选,dict类型,失败响应结果中自定义属性的值 :param model_content: 可选,BaseModel类型,失败响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 失败响应结果 """ result = {'code': HttpStatusConstant.WARN, 'msg': msg} @@ -78,7 +97,13 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) @classmethod def unauthorized( @@ -88,6 +113,9 @@ class ResponseUtil: rows: Optional[Any] = None, dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, ) -> Response: """ 未认证响应方法 @@ -97,6 +125,9 @@ class ResponseUtil: :param rows: 可选,未认证响应结果中属性为rows的值 :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 未认证响应结果 """ result = {'code': HttpStatusConstant.UNAUTHORIZED, 'msg': msg} @@ -112,7 +143,13 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) @classmethod def forbidden( @@ -122,6 +159,9 @@ class ResponseUtil: rows: Optional[Any] = None, dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, ) -> Response: """ 未授权响应方法 @@ -131,6 +171,9 @@ class ResponseUtil: :param rows: 可选,未授权响应结果中属性为rows的值 :param dict_content: 可选,dict类型,未授权响应结果中自定义属性的值 :param model_content: 可选,BaseModel类型,未授权响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 未授权响应结果 """ result = {'code': HttpStatusConstant.FORBIDDEN, 'msg': msg} @@ -146,7 +189,13 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) @classmethod def error( @@ -156,6 +205,9 @@ class ResponseUtil: rows: Optional[Any] = None, dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, ) -> Response: """ 错误响应方法 @@ -165,6 +217,9 @@ class ResponseUtil: :param rows: 可选,错误响应结果中属性为rows的值 :param dict_content: 可选,dict类型,错误响应结果中自定义属性的值 :param model_content: 可选,BaseModel类型,错误响应结果中自定义属性的值 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 错误响应结果 """ result = {'code': HttpStatusConstant.ERROR, 'msg': msg} @@ -180,14 +235,32 @@ class ResponseUtil: result.update({'success': False, 'time': datetime.now()}) - return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + return JSONResponse( + status_code=status.HTTP_200_OK, + content=jsonable_encoder(result), + headers=headers, + media_type=media_type, + background=background, + ) @classmethod - def streaming(cls, *, data: Any = None): + def streaming( + cls, + *, + data: Any = None, + headers: Optional[Mapping[str, str]] = None, + media_type: Optional[str] = None, + background: Optional[BackgroundTask] = None, + ) -> Response: """ 流式响应方法 :param data: 流式传输的内容 + :param headers: 可选,响应头信息 + :param media_type: 可选,响应结果媒体类型 + :param background: 可选,响应返回后执行的后台任务 :return: 流式响应结果 """ - return StreamingResponse(status_code=status.HTTP_200_OK, content=data) + return StreamingResponse( + status_code=status.HTTP_200_OK, content=data, headers=headers, media_type=media_type, background=background + ) diff --git a/ruoyi-fastapi-backend/utils/string_util.py b/ruoyi-fastapi-backend/utils/string_util.py index 0be9e653bffdd63dace928ed6d95059dabf74ce8..7196bcf53de6530c8a3f760aee40abc893c1baa3 100644 --- a/ruoyi-fastapi-backend/utils/string_util.py +++ b/ruoyi-fastapi-backend/utils/string_util.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Dict, List from config.constant import CommonConstant @@ -36,6 +36,16 @@ class StringUtil: """ return string is None or len(string) == 0 + @classmethod + def is_not_empty(cls, string: str) -> bool: + """ + 校验字符串是否不是''和None + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + return not cls.is_empty(string) + @classmethod def is_http(cls, link: str): """ @@ -49,7 +59,7 @@ class StringUtil: @classmethod def contains_ignore_case(cls, search_str: str, compare_str: str): """ - 查找指定字符串是否包含指定字符串同时串忽略大小写 + 查找指定字符串是否包含指定字符串同时忽略大小写 :param search_str: 查找的字符串 :param compare_str: 比对的字符串 @@ -62,15 +72,40 @@ class StringUtil: @classmethod def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): """ - 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时忽略大小写 :param search_str: 查找的字符串 :param compare_str_list: 比对的字符串列表 :return: 查找结果 """ if search_str and compare_str_list: - for compare_str in compare_str_list: - return cls.contains_ignore_case(search_str, compare_str) + return any([cls.contains_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) + return False + + @classmethod + def equals_ignore_case(cls, search_str: str, compare_str: str): + """ + 比较两个字符串是否相等同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 比较结果 + """ + if search_str and compare_str: + return search_str.lower() == compare_str.lower() + return False + + @classmethod + def equals_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + """ + 比较指定字符串是否与指定字符串列表中的任意一个字符串相等同时忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 比较结果 + """ + if search_str and compare_str_list: + return any([cls.equals_ignore_case(search_str, compare_str) for compare_str in compare_str_list]) return False @classmethod @@ -96,6 +131,40 @@ class StringUtil: :return: 查找结果 """ if search_str and compare_str_list: - for compare_str in compare_str_list: - return cls.startswith_case(search_str, compare_str) + return any([cls.startswith_case(search_str, compare_str) for compare_str in compare_str_list]) return False + + @classmethod + def convert_to_camel_case(cls, name: str) -> str: + """ + 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串 + + :param name: 转换前的下划线大写方式命名的字符串 + :return: 转换后的驼峰式命名的字符串 + """ + if not name: + return '' + if '_' not in name: + return name[0].upper() + name[1:] + parts = name.split('_') + result = [] + for part in parts: + if not part: + continue + result.append(part[0].upper() + part[1:].lower()) + return ''.join(result) + + @classmethod + def get_mapping_value_by_key_ignore_case(cls, mapping: Dict[str, str], key: str) -> str: + """ + 根据忽略大小写的键获取字典中的对应的值 + + param mapping: 字典 + param key: 字典的键 + :return: 字典键对应的值 + """ + for k, v in mapping.items(): + if key.lower() == k.lower(): + return v + + return '' diff --git a/ruoyi-fastapi-backend/utils/template_util.py b/ruoyi-fastapi-backend/utils/template_util.py new file mode 100644 index 0000000000000000000000000000000000000000..55a8f879c7fd775fbeb60c6756963aa30e3e0306 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/template_util.py @@ -0,0 +1,468 @@ +import json +import os +from datetime import datetime +from jinja2 import Environment, FileSystemLoader +from typing import Dict, List, Set +from config.constant import GenConstant +from config.env import DataBaseConfig +from exceptions.exception import ServiceWarning +from module_generator.entity.vo.gen_vo import GenTableModel, GenTableColumnModel +from utils.common_util import CamelCaseUtil, SnakeCaseUtil +from utils.string_util import StringUtil + + +class TemplateInitializer: + """ + 模板引擎初始化类 + """ + + @classmethod + def init_jinja2(cls): + """ + 初始化 Jinja2 模板引擎 + + :return: Jinja2 环境对象 + """ + try: + template_dir = os.path.join(os.getcwd(), 'module_generator', 'templates') + env = Environment( + loader=FileSystemLoader(template_dir), + keep_trailing_newline=True, + trim_blocks=True, + lstrip_blocks=True, + ) + env.filters.update( + { + 'camel_to_snake': SnakeCaseUtil.camel_to_snake, + 'snake_to_camel': CamelCaseUtil.snake_to_camel, + 'get_sqlalchemy_type': TemplateUtils.get_sqlalchemy_type, + } + ) + return env + except Exception as e: + raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}') + + +class TemplateUtils: + """ + 模板工具类 + """ + + # 项目路径 + FRONTEND_PROJECT_PATH = 'frontend' + BACKEND_PROJECT_PATH = 'backend' + DEFAULT_PARENT_MENU_ID = '3' + + @classmethod + def prepare_context(cls, gen_table: GenTableModel): + """ + 准备模板变量 + + :param gen_table: 生成表的配置信息 + :return: 模板上下文字典 + """ + if not gen_table.options: + raise ServiceWarning(message='请先完善生成配置信息') + class_name = gen_table.class_name + module_name = gen_table.module_name + business_name = gen_table.business_name + package_name = gen_table.package_name + tpl_category = gen_table.tpl_category + function_name = gen_table.function_name + + context = { + 'tplCategory': tpl_category, + 'tableName': gen_table.table_name, + 'functionName': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】', + 'ClassName': class_name, + 'className': class_name.lower(), + 'moduleName': module_name, + 'BusinessName': business_name.capitalize(), + 'businessName': business_name, + 'basePackage': cls.get_package_prefix(package_name), + 'packageName': package_name, + 'author': gen_table.function_author, + 'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'pkColumn': gen_table.pk_column, + 'doImportList': cls.get_do_import_list(gen_table), + 'voImportList': cls.get_vo_import_list(gen_table), + 'permissionPrefix': cls.get_permission_prefix(module_name, business_name), + 'columns': gen_table.columns, + 'table': gen_table, + 'dicts': cls.get_dicts(gen_table), + 'dbType': DataBaseConfig.db_type, + } + + # 设置菜单、树形结构、子表的上下文 + cls.set_menu_context(context, gen_table) + if tpl_category == GenConstant.TPL_TREE: + cls.set_tree_context(context, gen_table) + if tpl_category == GenConstant.TPL_SUB: + cls.set_sub_context(context, gen_table) + + return context + + @classmethod + def set_menu_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置菜单上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + options = gen_table.options + params_obj = json.loads(options) + context['parentMenuId'] = cls.get_parent_menu_id(params_obj) + + @classmethod + def set_tree_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置树形结构上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + options = gen_table.options + params_obj = json.loads(options) + context['treeCode'] = cls.get_tree_code(params_obj) + context['treeParentCode'] = cls.get_tree_parent_code(params_obj) + context['treeName'] = cls.get_tree_name(params_obj) + context['expandColumn'] = cls.get_expand_column(gen_table) + + @classmethod + def set_sub_context(cls, context: Dict, gen_table: GenTableModel): + """ + 设置子表上下文 + + :param context: 模板上下文字典 + :param gen_table: 生成表的配置信息 + :return: 新的模板上下文字典 + """ + sub_table = gen_table.sub_table + sub_table_name = gen_table.sub_table_name + sub_table_fk_name = gen_table.sub_table_fk_name + sub_class_name = sub_table.class_name + sub_table_fk_class_name = StringUtil.convert_to_camel_case(sub_table_fk_name) + context['subTable'] = sub_table + context['subTableName'] = sub_table_name + context['subTableFkName'] = sub_table_fk_name + context['subTableFkClassName'] = sub_table_fk_class_name + context['subTableFkclassName'] = sub_table_fk_class_name.lower() + context['subClassName'] = sub_class_name + context['subclassName'] = sub_class_name.lower() + + @classmethod + def get_template_list(cls, tpl_category: str, tpl_web_type: str): + """ + 获取模板列表 + + :param tpl_category: 生成模板类型 + :param tpl_web_type: 前端类型 + :return: 模板列表 + """ + use_web_type = 'vue' + if tpl_web_type == 'element-plus': + use_web_type = 'vue/v3' + templates = [ + 'python/controller.py.jinja2', + 'python/dao.py.jinja2', + 'python/do.py.jinja2', + 'python/service.py.jinja2', + 'python/vo.py.jinja2', + 'sql/sql.jinja2', + 'js/api.js.jinja2', + ] + if tpl_category == GenConstant.TPL_CRUD: + templates.append(f'{use_web_type}/index.vue.jinja2') + elif tpl_category == GenConstant.TPL_TREE: + templates.append(f'{use_web_type}/index-tree.vue.jinja2') + elif tpl_category == GenConstant.TPL_SUB: + templates.append(f'{use_web_type}/index.vue.jinja2') + # templates.append('python/sub-domain.python.jinja2') + return templates + + @classmethod + def get_file_name(cls, template: List[str], gen_table: GenTableModel): + """ + 根据模板生成文件名 + + :param template: 模板列表 + :param gen_table: 生成表的配置信息 + :return: 模板生成文件名 + """ + package_name = gen_table.package_name + module_name = gen_table.module_name + business_name = gen_table.business_name + + vue_path = cls.FRONTEND_PROJECT_PATH + python_path = f'{cls.BACKEND_PROJECT_PATH}/{package_name.replace(".", "/")}' + + if 'controller.py.jinja2' in template: + return f'{python_path}/controller/{business_name}_controller.py' + elif 'dao.py.jinja2' in template: + return f'{python_path}/dao/{business_name}_dao.py' + elif 'do.py.jinja2' in template: + return f'{python_path}/entity/do/{business_name}_do.py' + elif 'service.py.jinja2' in template: + return f'{python_path}/service/{business_name}_service.py' + elif 'vo.py.jinja2' in template: + return f'{python_path}/entity/vo/{business_name}_vo.py' + elif 'sql.jinja2' in template: + return f'{cls.BACKEND_PROJECT_PATH}/sql/{business_name}_menu.sql' + elif 'api.js.jinja2' in template: + return f'{vue_path}/api/{module_name}/{business_name}.js' + elif 'index.vue.jinja2' in template or 'index-tree.vue.jinja2' in template: + return f'{vue_path}/views/{module_name}/{business_name}/index.vue' + return '' + + @classmethod + def get_package_prefix(cls, package_name: str): + """ + 获取包前缀 + + :param package_name: 包名 + :return: 包前缀 + """ + return package_name[: package_name.rfind('.')] + + @classmethod + def get_vo_import_list(cls, gen_table: GenTableModel): + """ + 获取vo模板导入包列表 + + :param gen_table: 生成表的配置信息 + :return: 导入包列表 + """ + columns = gen_table.columns or [] + import_list = set() + for column in columns: + if column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {column.python_type}') + elif column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + if gen_table.sub: + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + if sub_column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {sub_column.python_type}') + elif sub_column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + return cls.merge_same_imports(list(import_list), 'from datetime import') + + @classmethod + def get_do_import_list(cls, gen_table: GenTableModel): + """ + 获取do模板导入包列表 + + :param gen_table: 生成表的配置信息 + :return: 导入包列表 + """ + columns = gen_table.columns or [] + import_list = set() + import_list.add('from sqlalchemy import Column') + for column in columns: + data_type = cls.get_db_type(column.column_type) + if data_type in GenConstant.COLUMNTYPE_GEOMETRY: + import_list.add('from geoalchemy2 import Geometry') + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}' + ) + if gen_table.sub: + import_list.add('from sqlalchemy import ForeignKey') + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + data_type = cls.get_db_type(sub_column.column_type) + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}' + ) + return cls.merge_same_imports(list(import_list), 'from sqlalchemy import') + + @classmethod + def get_db_type(cls, column_type: str) -> str: + """ + 获取数据库类型字段 + + param column_type: 字段类型 + :return: 数据库类型 + """ + if '(' in column_type: + return column_type.split('(')[0] + return column_type + + @classmethod + def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]: + """ + 合并相同的导入语句 + + :param imports: 导入语句列表 + :param import_start: 导入语句的起始字符串 + :return: 合并后的导入语句列表 + """ + merged_imports = [] + _imports = [] + for import_stmt in imports: + if import_stmt.startswith(import_start): + imported_items = import_stmt.split('import')[1].strip() + _imports.extend(imported_items.split(', ')) + else: + merged_imports.append(import_stmt) + + if _imports: + merged_datetime_import = f'{import_start} {", ".join(_imports)}' + merged_imports.append(merged_datetime_import) + + return merged_imports + + @classmethod + def get_dicts(cls, gen_table: GenTableModel): + """ + 获取字典列表 + + :param gen_table: 生成表的配置信息 + :return: 字典列表 + """ + columns = gen_table.columns or [] + dicts = set() + cls.add_dicts(dicts, columns) + if gen_table.sub_table is not None: + cls.add_dicts(dicts, gen_table.sub_table.columns) + return ', '.join(dicts) + + @classmethod + def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnModel]): + """ + 添加字典列表 + + :param dicts: 字典列表 + :param columns: 字段列表 + :return: 新的字典列表 + """ + for column in columns: + if ( + not column.super_column + and StringUtil.is_not_empty(column.dict_type) + and StringUtil.equals_any_ignore_case( + column.html_type, [GenConstant.HTML_SELECT, GenConstant.HTML_RADIO, GenConstant.HTML_CHECKBOX] + ) + ): + dicts.add(f"'{column.dict_type}'") + + @classmethod + def get_permission_prefix(cls, module_name: str, business_name: str): + """ + 获取权限前缀 + + :param module_name: 模块名 + :param business_name: 业务名 + :return: 权限前缀 + """ + return f'{module_name}:{business_name}' + + @classmethod + def get_parent_menu_id(cls, params_obj: Dict): + """ + 获取上级菜单ID + + :param params_obj: 菜单参数字典 + :return: 上级菜单ID + """ + if params_obj and params_obj.get(GenConstant.PARENT_MENU_ID): + return params_obj.get(GenConstant.PARENT_MENU_ID) + return cls.DEFAULT_PARENT_MENU_ID + + @classmethod + def get_tree_code(cls, params_obj: Dict): + """ + 获取树编码 + + :param params_obj: 菜单参数字典 + :return: 树编码 + """ + if GenConstant.TREE_CODE in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_CODE)) + return '' + + @classmethod + def get_tree_parent_code(cls, params_obj: Dict): + """ + 获取树父编码 + + :param params_obj: 菜单参数字典 + :return: 树父编码 + """ + if GenConstant.TREE_PARENT_CODE in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_PARENT_CODE)) + return '' + + @classmethod + def get_tree_name(cls, params_obj: Dict): + """ + 获取树名称 + + :param params_obj: 菜单参数字典 + :return: 树名称 + """ + if GenConstant.TREE_NAME in params_obj: + return cls.to_camel_case(params_obj.get(GenConstant.TREE_NAME)) + return '' + + @classmethod + def get_expand_column(cls, gen_table: GenTableModel): + """ + 获取展开列 + + :param gen_table: 生成表的配置信息 + :return: 展开列 + """ + options = gen_table.options + params_obj = json.loads(options) + tree_name = params_obj.get(GenConstant.TREE_NAME) + num = 0 + for column in gen_table.columns or []: + if column.list: + num += 1 + if column.column_name == tree_name: + break + return num + + @classmethod + def to_camel_case(cls, text: str) -> str: + """ + 将字符串转换为驼峰命名 + + :param text: 待转换的字符串 + :return: 转换后的驼峰命名字符串 + """ + parts = text.split('_') + return parts[0] + ''.join(word.capitalize() for word in parts[1:]) + + @classmethod + def get_sqlalchemy_type(cls, column_type: str): + """ + 获取SQLAlchemy类型 + + :param column_type: 列类型 + :return: SQLAlchemy类型 + """ + if '(' in column_type: + column_type_list = column_type.split('(') + if column_type_list[0] in GenConstant.COLUMNTYPE_STR: + sqlalchemy_type = ( + StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0] + ) + + '(' + + column_type_list[1] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type + ) + + return sqlalchemy_type diff --git a/ruoyi-fastapi-frontend/.env.staging b/ruoyi-fastapi-frontend/.env.staging index 04fbfd8a3302c5262f3e8ec8e7cb64554211e787..8798e3cea1ef516bae5f18e31f21eaff6b326d58 100644 --- a/ruoyi-fastapi-frontend/.env.staging +++ b/ruoyi-fastapi-frontend/.env.staging @@ -1,6 +1,8 @@ # 页面标题 VUE_APP_TITLE = vfadmin管理系统 +BABEL_ENV = production + NODE_ENV = production # 测试环境配置 diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index d1568d304fe8c8e4e748f4d29676686cf4ca2076..2eab99b7c121faaf2113d7571409c14f1f3dde68 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -1,6 +1,6 @@ { "name": "vfadmin", - "version": "1.5.1", + "version": "1.6.0", "description": "vfadmin管理系统", "author": "insistence", "license": "MIT", @@ -54,6 +54,7 @@ "quill": "2.0.2", "screenfull": "5.0.2", "sortablejs": "1.10.2", + "splitpanes": "2.4.1", "viser-vue": "^2.4.8", "vue": "2.6.12", "vue-count-to": "1.0.13", diff --git a/ruoyi-fastapi-frontend/public/styles/theme-chalk/index.css b/ruoyi-fastapi-frontend/public/styles/theme-chalk/index.css new file mode 100644 index 0000000000000000000000000000000000000000..b78d5a9c026bc8e10fbc05917f6097be11b40bee --- /dev/null +++ b/ruoyi-fastapi-frontend/public/styles/theme-chalk/index.css @@ -0,0 +1 @@ +@charset "UTF-8";[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-ice-cream-round:before{content:"\e6a0"}.el-icon-ice-cream-square:before{content:"\e6a3"}.el-icon-lollipop:before{content:"\e6a4"}.el-icon-potato-strips:before{content:"\e6a5"}.el-icon-milk-tea:before{content:"\e6a6"}.el-icon-ice-drink:before{content:"\e6a7"}.el-icon-ice-tea:before{content:"\e6a9"}.el-icon-coffee:before{content:"\e6aa"}.el-icon-orange:before{content:"\e6ab"}.el-icon-pear:before{content:"\e6ac"}.el-icon-apple:before{content:"\e6ad"}.el-icon-cherry:before{content:"\e6ae"}.el-icon-watermelon:before{content:"\e6af"}.el-icon-grape:before{content:"\e6b0"}.el-icon-refrigerator:before{content:"\e6b1"}.el-icon-goblet-square-full:before{content:"\e6b2"}.el-icon-goblet-square:before{content:"\e6b3"}.el-icon-goblet-full:before{content:"\e6b4"}.el-icon-goblet:before{content:"\e6b5"}.el-icon-cold-drink:before{content:"\e6b6"}.el-icon-coffee-cup:before{content:"\e6b8"}.el-icon-water-cup:before{content:"\e6b9"}.el-icon-hot-water:before{content:"\e6ba"}.el-icon-ice-cream:before{content:"\e6bb"}.el-icon-dessert:before{content:"\e6bc"}.el-icon-sugar:before{content:"\e6bd"}.el-icon-tableware:before{content:"\e6be"}.el-icon-burger:before{content:"\e6bf"}.el-icon-knife-fork:before{content:"\e6c1"}.el-icon-fork-spoon:before{content:"\e6c2"}.el-icon-chicken:before{content:"\e6c3"}.el-icon-food:before{content:"\e6c4"}.el-icon-dish-1:before{content:"\e6c5"}.el-icon-dish:before{content:"\e6c6"}.el-icon-moon-night:before{content:"\e6ee"}.el-icon-moon:before{content:"\e6f0"}.el-icon-cloudy-and-sunny:before{content:"\e6f1"}.el-icon-partly-cloudy:before{content:"\e6f2"}.el-icon-cloudy:before{content:"\e6f3"}.el-icon-sunny:before{content:"\e6f6"}.el-icon-sunset:before{content:"\e6f7"}.el-icon-sunrise-1:before{content:"\e6f8"}.el-icon-sunrise:before{content:"\e6f9"}.el-icon-heavy-rain:before{content:"\e6fa"}.el-icon-lightning:before{content:"\e6fb"}.el-icon-light-rain:before{content:"\e6fc"}.el-icon-wind-power:before{content:"\e6fd"}.el-icon-baseball:before{content:"\e712"}.el-icon-soccer:before{content:"\e713"}.el-icon-football:before{content:"\e715"}.el-icon-basketball:before{content:"\e716"}.el-icon-ship:before{content:"\e73f"}.el-icon-truck:before{content:"\e740"}.el-icon-bicycle:before{content:"\e741"}.el-icon-mobile-phone:before{content:"\e6d3"}.el-icon-service:before{content:"\e6d4"}.el-icon-key:before{content:"\e6e2"}.el-icon-unlock:before{content:"\e6e4"}.el-icon-lock:before{content:"\e6e5"}.el-icon-watch:before{content:"\e6fe"}.el-icon-watch-1:before{content:"\e6ff"}.el-icon-timer:before{content:"\e702"}.el-icon-alarm-clock:before{content:"\e703"}.el-icon-map-location:before{content:"\e704"}.el-icon-delete-location:before{content:"\e705"}.el-icon-add-location:before{content:"\e706"}.el-icon-location-information:before{content:"\e707"}.el-icon-location-outline:before{content:"\e708"}.el-icon-location:before{content:"\e79e"}.el-icon-place:before{content:"\e709"}.el-icon-discover:before{content:"\e70a"}.el-icon-first-aid-kit:before{content:"\e70b"}.el-icon-trophy-1:before{content:"\e70c"}.el-icon-trophy:before{content:"\e70d"}.el-icon-medal:before{content:"\e70e"}.el-icon-medal-1:before{content:"\e70f"}.el-icon-stopwatch:before{content:"\e710"}.el-icon-mic:before{content:"\e711"}.el-icon-copy-document:before{content:"\e718"}.el-icon-full-screen:before{content:"\e719"}.el-icon-switch-button:before{content:"\e71b"}.el-icon-aim:before{content:"\e71c"}.el-icon-crop:before{content:"\e71d"}.el-icon-odometer:before{content:"\e71e"}.el-icon-time:before{content:"\e71f"}.el-icon-bangzhu:before{content:"\e724"}.el-icon-close-notification:before{content:"\e726"}.el-icon-microphone:before{content:"\e727"}.el-icon-turn-off-microphone:before{content:"\e728"}.el-icon-position:before{content:"\e729"}.el-icon-postcard:before{content:"\e72a"}.el-icon-message:before{content:"\e72b"}.el-icon-chat-line-square:before{content:"\e72d"}.el-icon-chat-dot-square:before{content:"\e72e"}.el-icon-chat-dot-round:before{content:"\e72f"}.el-icon-chat-square:before{content:"\e730"}.el-icon-chat-line-round:before{content:"\e731"}.el-icon-chat-round:before{content:"\e732"}.el-icon-set-up:before{content:"\e733"}.el-icon-turn-off:before{content:"\e734"}.el-icon-open:before{content:"\e735"}.el-icon-connection:before{content:"\e736"}.el-icon-link:before{content:"\e737"}.el-icon-cpu:before{content:"\e738"}.el-icon-thumb:before{content:"\e739"}.el-icon-female:before{content:"\e73a"}.el-icon-male:before{content:"\e73b"}.el-icon-guide:before{content:"\e73c"}.el-icon-news:before{content:"\e73e"}.el-icon-price-tag:before{content:"\e744"}.el-icon-discount:before{content:"\e745"}.el-icon-wallet:before{content:"\e747"}.el-icon-coin:before{content:"\e748"}.el-icon-money:before{content:"\e749"}.el-icon-bank-card:before{content:"\e74a"}.el-icon-box:before{content:"\e74b"}.el-icon-present:before{content:"\e74c"}.el-icon-sell:before{content:"\e6d5"}.el-icon-sold-out:before{content:"\e6d6"}.el-icon-shopping-bag-2:before{content:"\e74d"}.el-icon-shopping-bag-1:before{content:"\e74e"}.el-icon-shopping-cart-2:before{content:"\e74f"}.el-icon-shopping-cart-1:before{content:"\e750"}.el-icon-shopping-cart-full:before{content:"\e751"}.el-icon-smoking:before{content:"\e752"}.el-icon-no-smoking:before{content:"\e753"}.el-icon-house:before{content:"\e754"}.el-icon-table-lamp:before{content:"\e755"}.el-icon-school:before{content:"\e756"}.el-icon-office-building:before{content:"\e757"}.el-icon-toilet-paper:before{content:"\e758"}.el-icon-notebook-2:before{content:"\e759"}.el-icon-notebook-1:before{content:"\e75a"}.el-icon-files:before{content:"\e75b"}.el-icon-collection:before{content:"\e75c"}.el-icon-receiving:before{content:"\e75d"}.el-icon-suitcase-1:before{content:"\e760"}.el-icon-suitcase:before{content:"\e761"}.el-icon-film:before{content:"\e763"}.el-icon-collection-tag:before{content:"\e765"}.el-icon-data-analysis:before{content:"\e766"}.el-icon-pie-chart:before{content:"\e767"}.el-icon-data-board:before{content:"\e768"}.el-icon-data-line:before{content:"\e76d"}.el-icon-reading:before{content:"\e769"}.el-icon-magic-stick:before{content:"\e76a"}.el-icon-coordinate:before{content:"\e76b"}.el-icon-mouse:before{content:"\e76c"}.el-icon-brush:before{content:"\e76e"}.el-icon-headset:before{content:"\e76f"}.el-icon-umbrella:before{content:"\e770"}.el-icon-scissors:before{content:"\e771"}.el-icon-mobile:before{content:"\e773"}.el-icon-attract:before{content:"\e774"}.el-icon-monitor:before{content:"\e775"}.el-icon-search:before{content:"\e778"}.el-icon-takeaway-box:before{content:"\e77a"}.el-icon-paperclip:before{content:"\e77d"}.el-icon-printer:before{content:"\e77e"}.el-icon-document-add:before{content:"\e782"}.el-icon-document:before{content:"\e785"}.el-icon-document-checked:before{content:"\e786"}.el-icon-document-copy:before{content:"\e787"}.el-icon-document-delete:before{content:"\e788"}.el-icon-document-remove:before{content:"\e789"}.el-icon-tickets:before{content:"\e78b"}.el-icon-folder-checked:before{content:"\e77f"}.el-icon-folder-delete:before{content:"\e780"}.el-icon-folder-remove:before{content:"\e781"}.el-icon-folder-add:before{content:"\e783"}.el-icon-folder-opened:before{content:"\e784"}.el-icon-folder:before{content:"\e78a"}.el-icon-edit-outline:before{content:"\e764"}.el-icon-edit:before{content:"\e78c"}.el-icon-date:before{content:"\e78e"}.el-icon-c-scale-to-original:before{content:"\e7c6"}.el-icon-view:before{content:"\e6ce"}.el-icon-loading:before{content:"\e6cf"}.el-icon-rank:before{content:"\e6d1"}.el-icon-sort-down:before{content:"\e7c4"}.el-icon-sort-up:before{content:"\e7c5"}.el-icon-sort:before{content:"\e6d2"}.el-icon-finished:before{content:"\e6cd"}.el-icon-refresh-left:before{content:"\e6c7"}.el-icon-refresh-right:before{content:"\e6c8"}.el-icon-refresh:before{content:"\e6d0"}.el-icon-video-play:before{content:"\e7c0"}.el-icon-video-pause:before{content:"\e7c1"}.el-icon-d-arrow-right:before{content:"\e6dc"}.el-icon-d-arrow-left:before{content:"\e6dd"}.el-icon-arrow-up:before{content:"\e6e1"}.el-icon-arrow-down:before{content:"\e6df"}.el-icon-arrow-right:before{content:"\e6e0"}.el-icon-arrow-left:before{content:"\e6de"}.el-icon-top-right:before{content:"\e6e7"}.el-icon-top-left:before{content:"\e6e8"}.el-icon-top:before{content:"\e6e6"}.el-icon-bottom:before{content:"\e6eb"}.el-icon-right:before{content:"\e6e9"}.el-icon-back:before{content:"\e6ea"}.el-icon-bottom-right:before{content:"\e6ec"}.el-icon-bottom-left:before{content:"\e6ed"}.el-icon-caret-top:before{content:"\e78f"}.el-icon-caret-bottom:before{content:"\e790"}.el-icon-caret-right:before{content:"\e791"}.el-icon-caret-left:before{content:"\e792"}.el-icon-d-caret:before{content:"\e79a"}.el-icon-share:before{content:"\e793"}.el-icon-menu:before{content:"\e798"}.el-icon-s-grid:before{content:"\e7a6"}.el-icon-s-check:before{content:"\e7a7"}.el-icon-s-data:before{content:"\e7a8"}.el-icon-s-opportunity:before{content:"\e7aa"}.el-icon-s-custom:before{content:"\e7ab"}.el-icon-s-claim:before{content:"\e7ad"}.el-icon-s-finance:before{content:"\e7ae"}.el-icon-s-comment:before{content:"\e7af"}.el-icon-s-flag:before{content:"\e7b0"}.el-icon-s-marketing:before{content:"\e7b1"}.el-icon-s-shop:before{content:"\e7b4"}.el-icon-s-open:before{content:"\e7b5"}.el-icon-s-management:before{content:"\e7b6"}.el-icon-s-ticket:before{content:"\e7b7"}.el-icon-s-release:before{content:"\e7b8"}.el-icon-s-home:before{content:"\e7b9"}.el-icon-s-promotion:before{content:"\e7ba"}.el-icon-s-operation:before{content:"\e7bb"}.el-icon-s-unfold:before{content:"\e7bc"}.el-icon-s-fold:before{content:"\e7a9"}.el-icon-s-platform:before{content:"\e7bd"}.el-icon-s-order:before{content:"\e7be"}.el-icon-s-cooperation:before{content:"\e7bf"}.el-icon-bell:before{content:"\e725"}.el-icon-message-solid:before{content:"\e799"}.el-icon-video-camera:before{content:"\e772"}.el-icon-video-camera-solid:before{content:"\e796"}.el-icon-camera:before{content:"\e779"}.el-icon-camera-solid:before{content:"\e79b"}.el-icon-download:before{content:"\e77c"}.el-icon-upload2:before{content:"\e77b"}.el-icon-upload:before{content:"\e7c3"}.el-icon-picture-outline-round:before{content:"\e75f"}.el-icon-picture-outline:before{content:"\e75e"}.el-icon-picture:before{content:"\e79f"}.el-icon-close:before{content:"\e6db"}.el-icon-check:before{content:"\e6da"}.el-icon-plus:before{content:"\e6d9"}.el-icon-minus:before{content:"\e6d8"}.el-icon-help:before{content:"\e73d"}.el-icon-s-help:before{content:"\e7b3"}.el-icon-circle-close:before{content:"\e78d"}.el-icon-circle-check:before{content:"\e720"}.el-icon-circle-plus-outline:before{content:"\e723"}.el-icon-remove-outline:before{content:"\e722"}.el-icon-zoom-out:before{content:"\e776"}.el-icon-zoom-in:before{content:"\e777"}.el-icon-error:before{content:"\e79d"}.el-icon-success:before{content:"\e79c"}.el-icon-circle-plus:before{content:"\e7a0"}.el-icon-remove:before{content:"\e7a2"}.el-icon-info:before{content:"\e7a1"}.el-icon-question:before{content:"\e7a4"}.el-icon-warning-outline:before{content:"\e6c9"}.el-icon-warning:before{content:"\e7a3"}.el-icon-goods:before{content:"\e7c2"}.el-icon-s-goods:before{content:"\e7b2"}.el-icon-star-off:before{content:"\e717"}.el-icon-star-on:before{content:"\e797"}.el-icon-more-outline:before{content:"\e6cc"}.el-icon-more:before{content:"\e794"}.el-icon-phone-outline:before{content:"\e6cb"}.el-icon-phone:before{content:"\e795"}.el-icon-user:before{content:"\e6e3"}.el-icon-user-solid:before{content:"\e7a5"}.el-icon-setting:before{content:"\e6ca"}.el-icon-s-tools:before{content:"\e7ac"}.el-icon-delete:before{content:"\e6d7"}.el-icon-delete-solid:before{content:"\e7c9"}.el-icon-eleme:before{content:"\e7c7"}.el-icon-platform-eleme:before{content:"\e7ca"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}.el-pagination{white-space:nowrap;padding:2px 5px;color:#303133;font-weight:700}.el-pagination::after,.el-pagination::before{display:table;content:""}.el-pagination::after{clear:both}.el-pagination button,.el-pagination span:not([class*=suffix]){display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box}.el-pagination .el-input__inner{text-align:center;-moz-appearance:textfield;line-height:normal}.el-pagination .el-input__suffix{right:0;-webkit-transform:scale(.8);transform:scale(.8)}.el-pagination .el-select .el-input{width:100px;margin:0 5px}.el-pagination .el-select .el-input .el-input__inner{padding-right:25px;border-radius:3px}.el-pagination button{border:none;padding:0 6px;background:0 0}.el-pagination button:focus{outline:0}.el-pagination button:hover{color:#409EFF}.el-pagination button:disabled{color:#C0C4CC;background-color:#FFF;cursor:not-allowed}.el-pagination .btn-next,.el-pagination .btn-prev{background:center center no-repeat #FFF;background-size:16px;cursor:pointer;margin:0;color:#303133}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{display:block;font-size:12px;font-weight:700}.el-pagination .btn-prev{padding-right:12px}.el-pagination .btn-next{padding-left:12px}.el-pagination .el-pager li.disabled{color:#C0C4CC;cursor:not-allowed}.el-pager li,.el-pager li.btn-quicknext:hover,.el-pager li.btn-quickprev:hover{cursor:pointer}.el-pagination--small .btn-next,.el-pagination--small .btn-prev,.el-pagination--small .el-pager li,.el-pagination--small .el-pager li.btn-quicknext,.el-pagination--small .el-pager li.btn-quickprev,.el-pagination--small .el-pager li:last-child{border-color:transparent;font-size:12px;line-height:22px;height:22px;min-width:22px}.el-pagination--small .arrow.disabled{visibility:hidden}.el-pagination--small .more::before,.el-pagination--small li.more::before{line-height:24px}.el-pagination--small button,.el-pagination--small span:not([class*=suffix]){height:22px;line-height:22px}.el-pagination--small .el-pagination__editor,.el-pagination--small .el-pagination__editor.el-input .el-input__inner{height:22px}.el-pagination__sizes{margin:0 10px 0 0;font-weight:400;color:#606266}.el-pagination__sizes .el-input .el-input__inner{font-size:13px;padding-left:8px}.el-pagination__sizes .el-input .el-input__inner:hover{border-color:#409EFF}.el-pagination__total{margin-right:10px;font-weight:400;color:#606266}.el-pagination__jump{margin-left:24px;font-weight:400;color:#606266}.el-pagination__jump .el-input__inner{padding:0 3px}.el-pagination__rightwrapper{float:right}.el-pagination__editor{line-height:18px;padding:0 2px;height:28px;text-align:center;margin:0 2px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:3px}.el-pager,.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev{padding:0}.el-dialog,.el-pager li{-webkit-box-sizing:border-box}.el-pagination__editor.el-input{width:50px}.el-pagination__editor.el-input .el-input__inner{height:28px}.el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev,.el-pagination.is-background .el-pager li{margin:0 5px;background-color:#f4f4f5;color:#606266;min-width:30px;border-radius:2px}.el-pagination.is-background .btn-next.disabled,.el-pagination.is-background .btn-next:disabled,.el-pagination.is-background .btn-prev.disabled,.el-pagination.is-background .btn-prev:disabled,.el-pagination.is-background .el-pager li.disabled{color:#C0C4CC}.el-pagination.is-background .el-pager li:not(.disabled):hover{color:#409EFF}.el-pagination.is-background .el-pager li:not(.disabled).active{background-color:#409EFF;color:#FFF}.el-pagination.is-background.el-pagination--small .btn-next,.el-pagination.is-background.el-pagination--small .btn-prev,.el-pagination.is-background.el-pagination--small .el-pager li{margin:0 3px;min-width:22px}.el-pager,.el-pager li{vertical-align:top;margin:0;display:inline-block}.el-pager{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;list-style:none;font-size:0}.el-pager .more::before{line-height:30px}.el-pager li{padding:0 4px;background:#FFF;font-size:13px;min-width:35.5px;height:28px;line-height:28px;box-sizing:border-box;text-align:center}.el-pager li.btn-quicknext,.el-pager li.btn-quickprev{line-height:28px;color:#303133}.el-pager li.btn-quicknext.disabled,.el-pager li.btn-quickprev.disabled{color:#C0C4CC}.el-pager li.active+li{border-left:0}.el-pager li:hover{color:#409EFF}.el-pager li.active{color:#409EFF;cursor:default}@-webkit-keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}.el-dialog{position:relative;margin:0 auto 50px;background:#FFF;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#409EFF}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px;word-break:break-all}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-autocomplete{position:relative;display:inline-block}.el-autocomplete-suggestion{margin:5px 0;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;border:1px solid #E4E7ED;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#FFF}.el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-autocomplete-suggestion__list{margin:0;padding:0}.el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:#606266;font-size:14px;list-style:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-autocomplete-suggestion li.highlighted,.el-autocomplete-suggestion li:hover{background-color:#F5F7FA}.el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid #000}.el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:#999}.el-autocomplete-suggestion.is-loading li::after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-autocomplete-suggestion.is-loading li:hover{background-color:#FFF}.el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.el-dropdown{display:inline-block;position:relative;color:#606266;font-size:14px}.el-dropdown .el-button-group{display:block}.el-dropdown .el-button-group .el-button{float:none}.el-dropdown .el-dropdown__caret-button{padding-left:5px;padding-right:5px;position:relative;border-left:none}.el-dropdown .el-dropdown__caret-button::before{content:'';position:absolute;display:block;width:1px;top:5px;bottom:5px;left:0;background:rgba(255,255,255,.5)}.el-dropdown .el-dropdown__caret-button.el-button--default::before{background:rgba(220,223,230,.5)}.el-dropdown .el-dropdown__caret-button:hover:not(.is-disabled)::before{top:0;bottom:0}.el-dropdown .el-dropdown__caret-button .el-dropdown__icon{padding-left:0}.el-dropdown__icon{font-size:12px;margin:0 3px}.el-dropdown .el-dropdown-selfdefine:focus:active,.el-dropdown .el-dropdown-selfdefine:focus:not(.focusing){outline-width:0}.el-dropdown [disabled]{cursor:not-allowed;color:#bbb}.el-dropdown-menu{position:absolute;top:0;left:0;z-index:10;padding:10px 0;margin:5px 0;background-color:#FFF;border:1px solid #EBEEF5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-dropdown-menu__item,.el-menu-item{font-size:14px;padding:0 20px;cursor:pointer}.el-dropdown-menu__item{list-style:none;line-height:36px;margin:0;color:#606266;outline:0}.el-dropdown-menu__item:focus,.el-dropdown-menu__item:not(.is-disabled):hover{background-color:#ecf5ff;color:#66b1ff}.el-dropdown-menu__item i{margin-right:5px}.el-dropdown-menu__item--divided{position:relative;margin-top:6px;border-top:1px solid #EBEEF5}.el-dropdown-menu__item--divided:before{content:'';height:6px;display:block;margin:0 -20px;background-color:#FFF}.el-dropdown-menu__item.is-disabled{cursor:default;color:#bbb;pointer-events:none}.el-dropdown-menu--medium{padding:6px 0}.el-dropdown-menu--medium .el-dropdown-menu__item{line-height:30px;padding:0 17px;font-size:14px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:6px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:6px;margin:0 -17px}.el-dropdown-menu--small{padding:6px 0}.el-dropdown-menu--small .el-dropdown-menu__item{line-height:27px;padding:0 15px;font-size:13px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:4px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:4px;margin:0 -15px}.el-dropdown-menu--mini{padding:3px 0}.el-dropdown-menu--mini .el-dropdown-menu__item{line-height:24px;padding:0 10px;font-size:12px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:3px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:3px;margin:0 -10px}.el-menu{border-right:solid 1px #e6e6e6;list-style:none;position:relative;margin:0;padding-left:0;background-color:#FFF}.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,.el-menu--horizontal>.el-submenu .el-submenu__title:hover{background-color:#fff}.el-menu::after,.el-menu::before{display:table;content:""}.el-breadcrumb__item:last-child .el-breadcrumb__separator,.el-menu--collapse>.el-menu-item .el-submenu__icon-arrow,.el-menu--collapse>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.el-menu::after{clear:both}.el-menu.el-menu--horizontal{border-bottom:solid 1px #e6e6e6}.el-menu--horizontal{border-right:none}.el-menu--horizontal>.el-menu-item{float:left;height:60px;line-height:60px;margin:0;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-menu-item a,.el-menu--horizontal>.el-menu-item a:hover{color:inherit}.el-menu--horizontal>.el-submenu{float:left}.el-menu--horizontal>.el-submenu:focus,.el-menu--horizontal>.el-submenu:hover{outline:0}.el-menu--horizontal>.el-submenu:focus .el-submenu__title,.el-menu--horizontal>.el-submenu:hover .el-submenu__title{color:#303133}.el-menu--horizontal>.el-submenu.is-active .el-submenu__title{border-bottom:2px solid #409EFF;color:#303133}.el-menu--horizontal>.el-submenu .el-submenu__title{height:60px;line-height:60px;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-submenu .el-submenu__icon-arrow{position:static;vertical-align:middle;margin-left:8px;margin-top:-3px}.el-menu--collapse .el-submenu,.el-menu-item{position:relative}.el-menu--horizontal .el-menu .el-menu-item,.el-menu--horizontal .el-menu .el-submenu__title{background-color:#FFF;float:none;height:36px;line-height:36px;padding:0 10px;color:#909399}.el-menu--horizontal .el-menu .el-menu-item.is-active,.el-menu--horizontal .el-menu .el-submenu.is-active>.el-submenu__title{color:#303133}.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,.el-menu--horizontal .el-menu-item:not(.is-disabled):hover{outline:0;color:#303133}.el-menu--horizontal>.el-menu-item.is-active{border-bottom:2px solid #409EFF;color:#303133}.el-menu--collapse{width:64px}.el-menu--collapse>.el-menu-item [class^=el-icon-],.el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-]{margin:0;vertical-align:middle;width:24px;text-align:center}.el-menu--collapse>.el-menu-item span,.el-menu--collapse>.el-submenu>.el-submenu__title span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.el-menu-item,.el-submenu__title{height:56px;line-height:56px;list-style:none}.el-menu--collapse>.el-menu-item.is-active i{color:inherit}.el-menu--collapse .el-menu .el-submenu{min-width:200px}.el-menu--collapse .el-submenu .el-menu{position:absolute;margin-left:5px;top:0;left:100%;z-index:10;border:1px solid #E4E7ED;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu--collapse .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:none;transform:none}.el-menu--popup{z-index:100;min-width:200px;border:none;padding:5px 0;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu--popup-bottom-start{margin-top:5px}.el-menu--popup-right-start{margin-left:5px;margin-right:5px}.el-menu-item{color:#303133;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.el-radio-button__inner,.el-submenu__title{-webkit-box-sizing:border-box;position:relative;white-space:nowrap}.el-menu-item *{vertical-align:middle}.el-menu-item i{color:#909399}.el-menu-item:focus,.el-menu-item:hover{outline:0;background-color:#ecf5ff}.el-menu-item.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-menu-item [class^=el-icon-]{margin-right:5px;width:24px;text-align:center;font-size:18px;vertical-align:middle}.el-menu-item.is-active{color:#409EFF}.el-menu-item.is-active i{color:inherit}.el-submenu{list-style:none;margin:0;padding-left:0}.el-submenu__title{font-size:14px;color:#303133;padding:0 20px;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;box-sizing:border-box}.el-submenu__title *{vertical-align:middle}.el-submenu__title i{color:#909399}.el-submenu__title:focus,.el-submenu__title:hover{outline:0;background-color:#ecf5ff}.el-submenu__title.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu__title:hover{background-color:#ecf5ff}.el-submenu .el-menu{border:none}.el-submenu .el-menu-item{height:50px;line-height:50px;padding:0 45px;min-width:200px}.el-submenu__icon-arrow{position:absolute;top:50%;right:20px;margin-top:-7px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:12px}.el-submenu.is-active .el-submenu__title{border-bottom-color:#409EFF}.el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.el-submenu.is-disabled .el-menu-item,.el-submenu.is-disabled .el-submenu__title{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu [class^=el-icon-]{vertical-align:middle;margin-right:5px;width:24px;text-align:center;font-size:18px}.el-menu-item-group>ul{padding:0}.el-menu-item-group__title{padding:7px 0 7px 20px;line-height:normal;font-size:12px;color:#909399}.el-radio-button__inner,.el-radio-group{display:inline-block;line-height:1;vertical-align:middle}.horizontal-collapse-transition .el-submenu__title .el-submenu__icon-arrow{-webkit-transition:.2s;transition:.2s;opacity:0}.el-radio-group{font-size:0}.el-radio-button{position:relative;display:inline-block;outline:0}.el-radio-button__inner{background:#FFF;border:1px solid #DCDFE6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;box-sizing:border-box;outline:0;margin:0;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#409EFF}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #DCDFE6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#FFF;background-color:#409EFF;border-color:#409EFF;-webkit-box-shadow:-1px 0 0 0 #409EFF;box-shadow:-1px 0 0 0 #409EFF}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#C0C4CC;cursor:not-allowed;background-image:none;background-color:#FFF;border-color:#EBEEF5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#F2F6FC}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #409EFF;box-shadow:0 0 2px 2px #409EFF}.el-picker-panel,.el-popover,.el-select-dropdown,.el-table-filter,.el-time-panel{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;vertical-align:middle;color:#303133}.el-switch__label.is-active{color:#409EFF}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #DCDFE6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#DCDFE6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s;vertical-align:middle}.el-input__prefix,.el-input__suffix{-webkit-transition:all .3s;color:#C0C4CC}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#FFF}.el-switch.is-checked .el-switch__core{border-color:#409EFF;background-color:#409EFF}.el-switch.is-checked .el-switch__core::after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #E4E7ED;border-radius:4px;background-color:#FFF;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item{padding-right:40px}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#409EFF;background-color:#FFF}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#F5F7FA}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\e6da";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#C0C4CC;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#FFF}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#F5F7FA}.el-select-dropdown__item.selected{color:#409EFF;font-weight:700}.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#E4E7ED}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select{display:inline-block;position:relative}.el-select .el-select__tags>span{display:contents}.el-select:hover .el-input__inner{border-color:#C0C4CC}.el-select .el-input__inner{cursor:pointer;padding-right:35px}.el-select .el-input__inner:focus{border-color:#409EFF}.el-select .el-input .el-select__caret{color:#C0C4CC;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);cursor:pointer}.el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg);border-radius:100%;color:#C0C4CC;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-select .el-input .el-select__caret.is-show-close:hover{color:#909399}.el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.el-select .el-input.is-disabled .el-input__inner:hover{border-color:#E4E7ED}.el-range-editor.is-active,.el-range-editor.is-active:hover,.el-select .el-input.is-focus .el-input__inner{border-color:#409EFF}.el-select>.el-input{display:block}.el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.el-select__input.is-mini{height:14px}.el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#C0C4CC;line-height:18px;font-size:14px}.el-select__close:hover{color:#909399}.el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-select__tags-text{overflow:hidden;text-overflow:ellipsis}.el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:2px 0 2px 6px;background-color:#f0f2f5;display:-webkit-box;display:-ms-flexbox;display:flex;max-width:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-select .el-tag__close.el-icon-close{background-color:#C0C4CC;top:0;color:#FFF;-ms-flex-negative:0;flex-shrink:0}.el-select .el-tag__close.el-icon-close:hover{background-color:#909399}.el-table,.el-table__expanded-cell{background-color:#FFF}.el-select .el-tag__close.el-icon-close::before{display:block;-webkit-transform:translate(0,.5px);transform:translate(0,.5px)}.el-table{position:relative;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-ms-flex:1;flex:1;width:100%;max-width:100%;font-size:14px;color:#606266}.el-table__empty-block{min-height:60px;text-align:center;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-table__empty-text{line-height:60px;width:50%;color:#909399}.el-table__expand-column .cell{padding:0;text-align:center}.el-table__expand-icon{position:relative;cursor:pointer;color:#666;font-size:12px;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out;height:20px}.el-table__expand-icon--expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-table__expand-icon>.el-icon{position:absolute;left:50%;top:50%;margin-left:-5px;margin-top:-5px}.el-table__expanded-cell[class*=cell]{padding:20px 50px}.el-table__expanded-cell:hover{background-color:transparent!important}.el-table__placeholder{display:inline-block;width:20px}.el-table__append-wrapper{overflow:hidden}.el-table--fit{border-right:0;border-bottom:0}.el-table--fit .el-table__cell.gutter{border-right-width:1px}.el-table--scrollable-x .el-table__body-wrapper{overflow-x:auto}.el-table--scrollable-y .el-table__body-wrapper{overflow-y:auto}.el-table thead{color:#909399;font-weight:500}.el-table thead.is-group th.el-table__cell{background:#F5F7FA}.el-table .el-table__cell{padding:12px 0;min-width:0;-webkit-box-sizing:border-box;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative;text-align:left}.el-table .el-table__cell.is-center{text-align:center}.el-table .el-table__cell.is-right{text-align:right}.el-table .el-table__cell.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.el-table .el-table__cell.is-hidden>*{visibility:hidden}.el-table--medium .el-table__cell{padding:10px 0}.el-table--small{font-size:12px}.el-table--small .el-table__cell{padding:8px 0}.el-table--mini{font-size:12px}.el-table--mini .el-table__cell{padding:6px 0}.el-table tr{background-color:#FFF}.el-table tr input[type=checkbox]{margin:0}.el-table td.el-table__cell,.el-table th.el-table__cell.is-leaf{border-bottom:1px solid #EBEEF5}.el-table th.el-table__cell.is-sortable{cursor:pointer}.el-table th.el-table__cell{overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#FFF}.el-table th.el-table__cell>.cell{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;vertical-align:middle;padding-left:10px;padding-right:10px;width:100%}.el-table th.el-table__cell>.cell.highlight{color:#409EFF}.el-table th.el-table__cell.required>div::before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.el-table td.el-table__cell div{-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-table td,.el-table .cell,.el-table-filter{-webkit-box-sizing:border-box}.el-table td.el-table__cell.gutter{width:0}.el-table .cell{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding-left:10px;padding-right:10px}.el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.el-table--border,.el-table--group{border:1px solid #EBEEF5}.el-table--border::after,.el-table--group::after,.el-table::before{content:'';position:absolute;background-color:#EBEEF5;z-index:1}.el-table--border::after,.el-table--group::after{top:0;right:0;width:1px;height:100%}.el-table::before{left:0;bottom:0;width:100%;height:1px}.el-table--border{border-right:none;border-bottom:none}.el-table--border.el-loading-parent--relative{border-color:transparent}.el-table--border .el-table__cell,.el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed{border-right:1px solid #EBEEF5}.el-table--border .el-table__cell:first-child .cell{padding-left:10px}.el-table--border th.el-table__cell.gutter:last-of-type{border-bottom:1px solid #EBEEF5;border-bottom-width:1px}.el-table--border th.el-table__cell,.el-table__fixed-right-patch{border-bottom:1px solid #EBEEF5}.el-table--hidden{visibility:hidden}.el-table__fixed,.el-table__fixed-right{position:absolute;top:0;left:0;overflow-x:hidden;overflow-y:hidden;-webkit-box-shadow:0 0 10px rgba(0,0,0,.12);box-shadow:0 0 10px rgba(0,0,0,.12)}.el-table__fixed-right::before,.el-table__fixed::before{content:'';position:absolute;left:0;bottom:0;width:100%;height:1px;background-color:#EBEEF5;z-index:4}.el-table__fixed-right-patch{position:absolute;top:-1px;right:0;background-color:#FFF}.el-table__fixed-right{top:0;left:auto;right:0}.el-table__fixed-right .el-table__fixed-body-wrapper,.el-table__fixed-right .el-table__fixed-footer-wrapper,.el-table__fixed-right .el-table__fixed-header-wrapper{left:auto;right:0}.el-table__fixed-header-wrapper{position:absolute;left:0;top:0;z-index:3}.el-table__fixed-footer-wrapper{position:absolute;left:0;bottom:0;z-index:3}.el-table__fixed-footer-wrapper tbody td.el-table__cell{border-top:1px solid #EBEEF5;background-color:#F5F7FA;color:#606266}.el-table__fixed-body-wrapper{position:absolute;left:0;top:37px;overflow:hidden;z-index:3}.el-table__body-wrapper,.el-table__footer-wrapper,.el-table__header-wrapper{width:100%}.el-table__footer-wrapper{margin-top:-1px}.el-table__footer-wrapper td.el-table__cell{border-top:1px solid #EBEEF5}.el-table__body,.el-table__footer,.el-table__header{table-layout:fixed;border-collapse:separate}.el-table__footer-wrapper,.el-table__header-wrapper{overflow:hidden}.el-table__footer-wrapper tbody td.el-table__cell,.el-table__header-wrapper tbody td.el-table__cell{background-color:#F5F7FA;color:#606266}.el-table__body-wrapper{overflow:hidden;position:relative}.el-table__body-wrapper.is-scrolling-left~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed-right,.el-table__body-wrapper.is-scrolling-right~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.el-table__body-wrapper .el-table--border.is-scrolling-right~.el-table__fixed-right{border-left:1px solid #EBEEF5}.el-table .caret-wrapper{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;width:24px;vertical-align:middle;cursor:pointer;overflow:initial;position:relative}.el-table .sort-caret{width:0;height:0;border:5px solid transparent;position:absolute;left:7px}.el-table .sort-caret.ascending{border-bottom-color:#C0C4CC;top:5px}.el-table .sort-caret.descending{border-top-color:#C0C4CC;bottom:7px}.el-table .ascending .sort-caret.ascending{border-bottom-color:#409EFF}.el-table .descending .sort-caret.descending{border-top-color:#409EFF}.el-table .hidden-columns{visibility:hidden;position:absolute;z-index:-1}.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell{background:#FAFAFA}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td.el-table__cell,.el-table--striped .el-table__body tr.el-table__row--striped.selection-row td.el-table__cell{background-color:#ecf5ff}.el-table__body tr.hover-row.current-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped.current-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped.selection-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped>td.el-table__cell,.el-table__body tr.hover-row.selection-row>td.el-table__cell,.el-table__body tr.hover-row>td.el-table__cell{background-color:#F5F7FA}.el-table__body tr.current-row>td.el-table__cell,.el-table__body tr.selection-row>td.el-table__cell{background-color:#ecf5ff}.el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:1px solid #EBEEF5;z-index:10}.el-table__column-filter-trigger{display:inline-block;line-height:34px;cursor:pointer}.el-table__column-filter-trigger i{color:#909399;font-size:12px;-webkit-transform:scale(.75);transform:scale(.75)}.el-table--enable-row-transition .el-table__body td.el-table__cell{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:#F5F7FA}.el-table--fluid-height .el-table__fixed,.el-table--fluid-height .el-table__fixed-right{bottom:0;overflow:hidden}.el-table [class*=el-table__row--level] .el-table__expand-icon{display:inline-block;width:20px;line-height:20px;height:20px;text-align:center;margin-right:3px}.el-table-column--selection .cell{padding-left:14px;padding-right:14px}.el-table-filter{border:1px solid #EBEEF5;border-radius:2px;background-color:#FFF;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-sizing:border-box;margin:2px 0}.el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:14px}.el-table-filter__list-item:hover{background-color:#ecf5ff;color:#66b1ff}.el-table-filter__list-item.is-active{background-color:#409EFF;color:#FFF}.el-table-filter__content{min-width:100px}.el-table-filter__bottom{border-top:1px solid #EBEEF5;padding:8px}.el-table-filter__bottom button{background:0 0;border:none;color:#606266;cursor:pointer;font-size:13px;padding:0 3px}.el-date-table td.in-range div,.el-date-table td.in-range div:hover,.el-date-table.is-week-mode .el-date-table__row.current div,.el-date-table.is-week-mode .el-date-table__row:hover div{background-color:#F2F6FC}.el-table-filter__bottom button:hover{color:#409EFF}.el-table-filter__bottom button:focus{outline:0}.el-table-filter__bottom button.is-disabled{color:#C0C4CC;cursor:not-allowed}.el-table-filter__wrap{max-height:280px}.el-table-filter__checkbox-group{padding:10px}.el-table-filter__checkbox-group label.el-checkbox{display:block;margin-right:5px;margin-bottom:8px;margin-left:5px}.el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.el-date-table{font-size:12px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:#606266}.el-date-table.is-week-mode .el-date-table__row:hover td:first-child div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table.is-week-mode .el-date-table__row:hover td:last-child div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td{width:32px;height:30px;padding:4px 0;box-sizing:border-box;text-align:center;cursor:pointer;position:relative}.el-date-table td div{height:30px;padding:3px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-table td span{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-radius:50%}.el-date-table td.next-month,.el-date-table td.prev-month{color:#C0C4CC}.el-date-table td.today{position:relative}.el-date-table td.today span{color:#409EFF;font-weight:700}.el-date-table td.today.end-date span,.el-date-table td.today.start-date span{color:#FFF}.el-date-table td.available:hover{color:#409EFF}.el-date-table td.current:not(.disabled) span{color:#FFF;background-color:#409EFF}.el-date-table td.end-date div,.el-date-table td.start-date div{color:#FFF}.el-date-table td.end-date span,.el-date-table td.start-date span{background-color:#409EFF}.el-date-table td.start-date div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table td.end-date div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td.disabled div{background-color:#F5F7FA;opacity:1;cursor:not-allowed;color:#C0C4CC}.el-date-table td.selected div{margin-left:5px;margin-right:5px;background-color:#F2F6FC;border-radius:15px}.el-date-table td.selected div:hover{background-color:#F2F6FC}.el-date-table td.selected span{background-color:#409EFF;color:#FFF;border-radius:15px}.el-date-table td.week{font-size:80%;color:#606266}.el-month-table,.el-year-table{font-size:12px;border-collapse:collapse}.el-date-table th{padding:5px;color:#606266;font-weight:400;border-bottom:solid 1px #EBEEF5}.el-month-table{margin:-1px}.el-month-table td{text-align:center;padding:8px 0;cursor:pointer}.el-month-table td div{height:48px;padding:6px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-month-table td.today .cell{color:#409EFF;font-weight:700}.el-month-table td.today.end-date .cell,.el-month-table td.today.start-date .cell{color:#FFF}.el-month-table td.disabled .cell{background-color:#F5F7FA;cursor:not-allowed;color:#C0C4CC}.el-month-table td.disabled .cell:hover{color:#C0C4CC}.el-month-table td .cell{width:60px;height:36px;display:block;line-height:36px;color:#606266;margin:0 auto;border-radius:18px}.el-month-table td .cell:hover{color:#409EFF}.el-month-table td.in-range div,.el-month-table td.in-range div:hover{background-color:#F2F6FC}.el-month-table td.end-date div,.el-month-table td.start-date div{color:#FFF}.el-month-table td.end-date .cell,.el-month-table td.start-date .cell{color:#FFF;background-color:#409EFF}.el-month-table td.start-date div{border-top-left-radius:24px;border-bottom-left-radius:24px}.el-month-table td.end-date div{border-top-right-radius:24px;border-bottom-right-radius:24px}.el-month-table td.current:not(.disabled) .cell{color:#409EFF}.el-year-table{margin:-1px}.el-year-table .el-icon{color:#303133}.el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.el-year-table td.today .cell{color:#409EFF;font-weight:700}.el-year-table td.disabled .cell{background-color:#F5F7FA;cursor:not-allowed;color:#C0C4CC}.el-year-table td.disabled .cell:hover{color:#C0C4CC}.el-year-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#606266;margin:0 auto}.el-year-table td .cell:hover,.el-year-table td.current:not(.disabled) .cell{color:#409EFF}.el-date-range-picker{width:646px}.el-date-range-picker.has-sidebar{width:756px}.el-date-range-picker table{table-layout:fixed;width:100%}.el-date-range-picker .el-picker-panel__body{min-width:513px}.el-date-range-picker .el-picker-panel__content{margin:0}.el-date-range-picker__header{position:relative;text-align:center;height:28px}.el-date-range-picker__header [class*=arrow-left]{float:left}.el-date-range-picker__header [class*=arrow-right]{float:right}.el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.el-date-range-picker__content{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:16px}.el-date-range-picker__content.is-left{border-right:1px solid #e4e4e4}.el-date-range-picker__content .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.el-date-range-picker__editors-wrap{-webkit-box-sizing:border-box;box-sizing:border-box;display:table-cell}.el-date-range-picker__editors-wrap.is-right{text-align:right}.el-date-range-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:#303133}.el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#FFF}.el-date-picker{width:322px}.el-date-picker.has-sidebar.has-time{width:434px}.el-date-picker.has-sidebar{width:438px}.el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-picker .el-picker-panel__content{width:292px}.el-date-picker table{table-layout:fixed;width:100%}.el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-picker__header{margin:12px;text-align:center}.el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:solid 1px #EBEEF5}.el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#606266}.el-date-picker__header-label.active,.el-date-picker__header-label:hover{color:#409EFF}.el-date-picker__prev-btn{float:left}.el-date-picker__next-btn{float:right}.el-date-picker__time-wrap{padding:10px;text-align:center}.el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.time-select{margin:5px 0;min-width:0}.time-select .el-picker-panel__content{max-height:200px;margin:0}.time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.time-select-item.selected:not(.disabled){color:#409EFF;font-weight:700}.time-select-item.disabled{color:#E4E7ED;cursor:not-allowed}.time-select-item:hover{background-color:#F5F7FA;font-weight:700;cursor:pointer}.el-date-editor{position:relative;display:inline-block;text-align:left}.el-date-editor.el-input,.el-date-editor.el-input__inner{width:220px}.el-date-editor--monthrange.el-input,.el-date-editor--monthrange.el-input__inner{width:300px}.el-date-editor--daterange.el-input,.el-date-editor--daterange.el-input__inner,.el-date-editor--timerange.el-input,.el-date-editor--timerange.el-input__inner{width:350px}.el-date-editor--datetimerange.el-input,.el-date-editor--datetimerange.el-input__inner{width:400px}.el-date-editor--dates .el-input__inner{text-overflow:ellipsis;white-space:nowrap}.el-date-editor .el-icon-circle-close{cursor:pointer}.el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#C0C4CC;float:left;line-height:32px}.el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;display:inline-block;height:100%;margin:0;padding:0;width:39%;text-align:center;font-size:14px;color:#606266}.el-date-editor .el-range-input::-webkit-input-placeholder{color:#C0C4CC}.el-date-editor .el-range-input:-ms-input-placeholder{color:#C0C4CC}.el-date-editor .el-range-input::-ms-input-placeholder{color:#C0C4CC}.el-date-editor .el-range-input::placeholder{color:#C0C4CC}.el-date-editor .el-range-separator{display:inline-block;height:100%;padding:0 5px;margin:0;text-align:center;line-height:32px;font-size:14px;width:5%;color:#303133}.el-date-editor .el-range__close-icon{font-size:14px;color:#C0C4CC;width:25px;display:inline-block;float:right;line-height:32px}.el-range-editor.el-input__inner{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:3px 10px}.el-range-editor .el-range-input{line-height:1}.el-range-editor--medium.el-input__inner{height:36px}.el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.el-range-editor--medium .el-range-input{font-size:14px}.el-range-editor--medium .el-range__close-icon,.el-range-editor--medium .el-range__icon{line-height:28px}.el-range-editor--small.el-input__inner{height:32px}.el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.el-range-editor--small .el-range-input{font-size:13px}.el-range-editor--small .el-range__close-icon,.el-range-editor--small .el-range__icon{line-height:24px}.el-range-editor--mini.el-input__inner{height:28px}.el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.el-range-editor--mini .el-range-input{font-size:12px}.el-range-editor--mini .el-range__close-icon,.el-range-editor--mini .el-range__icon{line-height:20px}.el-range-editor.is-disabled{background-color:#F5F7FA;border-color:#E4E7ED;color:#C0C4CC;cursor:not-allowed}.el-range-editor.is-disabled:focus,.el-range-editor.is-disabled:hover{border-color:#E4E7ED}.el-range-editor.is-disabled input{background-color:#F5F7FA;color:#C0C4CC;cursor:not-allowed}.el-range-editor.is-disabled input::-webkit-input-placeholder{color:#C0C4CC}.el-range-editor.is-disabled input:-ms-input-placeholder{color:#C0C4CC}.el-range-editor.is-disabled input::-ms-input-placeholder{color:#C0C4CC}.el-range-editor.is-disabled input::placeholder{color:#C0C4CC}.el-range-editor.is-disabled .el-range-separator{color:#C0C4CC}.el-picker-panel{color:#606266;border:1px solid #E4E7ED;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#FFF;border-radius:4px;line-height:30px;margin:5px 0}.el-picker-panel__body-wrapper::after,.el-picker-panel__body::after{content:"";display:table;clear:both}.el-picker-panel__content{position:relative;margin:15px}.el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#FFF;position:relative;font-size:0}.el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#606266;padding-left:12px;text-align:left;outline:0;cursor:pointer}.el-picker-panel__shortcut:hover{color:#409EFF}.el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#409EFF}.el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.el-picker-panel__icon-btn{font-size:12px;color:#303133;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.el-picker-panel__icon-btn:hover{color:#409EFF}.el-picker-panel__icon-btn.is-disabled{color:#bbb}.el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.el-picker-panel__link-btn{vertical-align:middle}.el-picker-panel [slot=sidebar],.el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#FFF;overflow:auto}.el-picker-panel [slot=sidebar]+.el-picker-panel__body,.el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.el-time-spinner__input.el-input .el-input__inner,.el-time-spinner__list{padding:0;text-align:center}.el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#FFF;cursor:default}.el-time-spinner__arrow{font-size:12px;color:#909399;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.el-time-spinner__arrow:hover{color:#409EFF}.el-time-spinner__arrow.el-icon-arrow-up{top:10px}.el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.el-time-spinner__input.el-input{width:70%}.el-time-spinner__list{margin:0;list-style:none}.el-time-spinner__list::after,.el-time-spinner__list::before{content:'';display:block;width:100%;height:80px}.el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#606266}.el-time-spinner__item:hover:not(.disabled):not(.active){background:#F5F7FA;cursor:pointer}.el-time-spinner__item.active:not(.disabled){color:#303133;font-weight:700}.el-time-spinner__item.disabled{color:#C0C4CC;cursor:not-allowed}.el-time-panel{margin:5px 0;border:1px solid #E4E7ED;background-color:#FFF;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:content-box;box-sizing:content-box}.el-time-panel__content{font-size:0;position:relative;overflow:hidden}.el-time-panel__content::after,.el-time-panel__content::before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #E4E7ED;border-bottom:1px solid #E4E7ED}.el-time-panel__content::after{left:50%;margin-left:12%;margin-right:12%}.el-time-panel__content::before{padding-left:50%;margin-right:12%;margin-left:12%}.el-time-panel__content.has-seconds::after{left:calc(100% / 3 * 2)}.el-time-panel__content.has-seconds::before{padding-left:calc(100% / 3)}.el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#303133}.el-time-panel__btn.confirm{font-weight:800;color:#409EFF}.el-time-range-picker{width:354px;overflow:visible}.el-time-range-picker__content{position:relative;text-align:center;padding:10px}.el-time-range-picker__cell{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.el-time-range-picker__body{border-radius:2px;border:1px solid #E4E7ED}.el-popover{position:absolute;background:#FFF;min-width:150px;border-radius:4px;border:1px solid #EBEEF5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);word-break:break-all}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover,.el-cascader__dropdown,.el-color-picker__panel,.el-message-box,.el-notification{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}.v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-message-box{display:inline-block;width:420px;padding-bottom:10px;vertical-align:middle;background-color:#FFF;border-radius:4px;border:1px solid #EBEEF5;font-size:18px;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden}.el-message-box__wrapper{position:fixed;top:0;bottom:0;left:0;right:0;text-align:center}.el-message-box__wrapper::after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.el-message-box__header{position:relative;padding:15px 15px 10px}.el-message-box__title{padding-left:0;margin-bottom:0;font-size:18px;line-height:1;color:#303133}.el-message-box__headerbtn{position:absolute;top:15px;right:15px;padding:0;border:none;outline:0;background:0 0;font-size:16px;cursor:pointer}.el-form-item.is-error .el-input__inner,.el-form-item.is-error .el-input__inner:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus,.el-message-box__input input.invalid,.el-message-box__input input.invalid:focus{border-color:#F56C6C}.el-message-box__headerbtn .el-message-box__close{color:#909399}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:#409EFF}.el-message-box__content{padding:10px 15px;color:#606266;font-size:14px}.el-message-box__container{position:relative}.el-message-box__input{padding-top:15px}.el-message-box__status{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:24px!important}.el-message-box__status::before{padding-left:1px}.el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px}.el-message-box__status.el-icon-success{color:#67C23A}.el-message-box__status.el-icon-info{color:#909399}.el-message-box__status.el-icon-warning{color:#E6A23C}.el-message-box__status.el-icon-error{color:#F56C6C}.el-message-box__message{margin:0}.el-message-box__message p{margin:0;line-height:24px}.el-message-box__errormsg{color:#F56C6C;font-size:12px;min-height:18px;margin-top:2px}.el-message-box__btns{padding:5px 15px 0;text-align:right}.el-message-box__btns button:nth-child(2){margin-left:10px}.el-message-box__btns-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.el-message-box--center{padding-bottom:30px}.el-message-box--center .el-message-box__header{padding-top:30px}.el-message-box--center .el-message-box__title{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.el-message-box--center .el-message-box__message{margin-left:0}.el-message-box--center .el-message-box__btns,.el-message-box--center .el-message-box__content{text-align:center}.el-message-box--center .el-message-box__content{padding-left:27px;padding-right:27px}.msgbox-fade-enter-active{-webkit-animation:msgbox-fade-in .3s;animation:msgbox-fade-in .3s}.msgbox-fade-leave-active{-webkit-animation:msgbox-fade-out .3s;animation:msgbox-fade-out .3s}@-webkit-keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes msgbox-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb::after,.el-breadcrumb::before{display:table;content:""}.el-breadcrumb::after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#C0C4CC}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner a,.el-breadcrumb__inner.is-link{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner a:hover,.el-breadcrumb__inner.is-link:hover{color:#409EFF;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#606266;cursor:text}.el-form--label-left .el-form-item__label{text-align:left}.el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px}.el-form--inline .el-form-item{display:inline-block;margin-right:10px;vertical-align:top}.el-form--inline .el-form-item__label{float:none;display:inline-block}.el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.el-form--inline.el-form--label-top .el-form-item__content{display:block}.el-form-item{margin-bottom:22px}.el-form-item::after,.el-form-item::before{display:table;content:""}.el-form-item::after{clear:both}.el-form-item .el-form-item{margin-bottom:0}.el-form-item--mini.el-form-item,.el-form-item--small.el-form-item{margin-bottom:18px}.el-form-item .el-input__validateIcon{display:none}.el-form-item--medium .el-form-item__content,.el-form-item--medium .el-form-item__label{line-height:36px}.el-form-item--small .el-form-item__content,.el-form-item--small .el-form-item__label{line-height:32px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--mini .el-form-item__content,.el-form-item--mini .el-form-item__label{line-height:28px}.el-form-item--mini .el-form-item__error{padding-top:1px}.el-form-item__label-wrap{float:left}.el-form-item__label-wrap .el-form-item__label{display:inline-block;float:none}.el-form-item__label{text-align:right;vertical-align:middle;float:left;font-size:14px;color:#606266;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-form-item__content{line-height:40px;position:relative;font-size:14px}.el-form-item__content::after,.el-form-item__content::before{display:table;content:""}.el-form-item__content::after{clear:both}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:#F56C6C;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before{content:'*';color:#F56C6C;margin-right:4px}.el-form-item.is-error .el-input-group__append .el-input__inner,.el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-error .el-input__validateIcon{color:#F56C6C}.el-form-item--feedback .el-input__validateIcon{display:inline-block}.el-tabs__header{padding:0;position:relative;margin:0 0 15px}.el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:#409EFF;z-index:1;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);list-style:none}.el-tabs__new-tab{float:right;border:1px solid #d3dce6;height:18px;width:18px;line-height:18px;margin:12px 0 9px 10px;border-radius:3px;text-align:center;font-size:12px;color:#d3dce6;cursor:pointer;-webkit-transition:all .15s;transition:all .15s}.el-tabs__new-tab .el-icon-plus{-webkit-transform:scale(.8,.8);transform:scale(.8,.8)}.el-tabs__new-tab:hover{color:#409EFF}.el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.el-tabs__nav-wrap::after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:#E4E7ED;z-index:1}.el-tabs__nav-wrap.is-scrollable{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:#909399}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{white-space:nowrap;position:relative;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:left;z-index:2}.el-tabs__nav.is-stretch{min-width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.el-tabs__nav.is-stretch>*{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center}.el-tabs__item{padding:0 20px;height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:40px;display:inline-block;list-style:none;font-size:14px;font-weight:500;color:#303133;position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:0}.el-tabs__item:focus.is-active.is-focus:not(:active){-webkit-box-shadow:0 0 2px 2px #409EFF inset;box-shadow:0 0 2px 2px #409EFF inset;border-radius:3px}.el-tabs__item .el-icon-close{border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);margin-left:5px}.el-tabs__item .el-icon-close:before{-webkit-transform:scale(.9);transform:scale(.9);display:inline-block}.el-tabs--card>.el-tabs__header .el-tabs__active-bar,.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left,.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs__item .el-icon-close:hover{background-color:#C0C4CC;color:#FFF}.el-tabs__item.is-active{color:#409EFF}.el-tabs__item:hover{color:#409EFF;cursor:pointer}.el-tabs__item.is-disabled{color:#C0C4CC;cursor:default}.el-tabs__content{overflow:hidden;position:relative}.el-tabs--card>.el-tabs__header{border-bottom:1px solid #E4E7ED}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid #E4E7ED;border-bottom:none;border-radius:4px 4px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__item .el-icon-close{position:relative;font-size:12px;width:0;height:14px;vertical-align:middle;line-height:15px;overflow:hidden;top:-1px;right:-2px;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid #E4E7ED;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .el-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#FFF}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .el-icon-close{width:14px}.el-tabs--border-card{background:#FFF;border:1px solid #DCDFE6;-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04);box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:#F5F7FA;border-bottom:1px solid #E4E7ED;margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap::after{content:none}.el-tabs--border-card>.el-tabs__header .el-tabs__item{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);border:1px solid transparent;margin-top:-1px;color:#909399}.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item,.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child{margin-left:-1px}.el-col-offset-0,.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:#409EFF;background-color:#FFF;border-right-color:#DCDFE6;border-left-color:#DCDFE6}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:#409EFF}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:#C0C4CC}.el-tabs--bottom .el-tabs__item.is-bottom:nth-child(2),.el-tabs--bottom .el-tabs__item.is-top:nth-child(2),.el-tabs--top .el-tabs__item.is-bottom:nth-child(2),.el-tabs--top .el-tabs__item.is-top:nth-child(2){padding-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:last-child,.el-tabs--bottom .el-tabs__item.is-top:last-child,.el-tabs--top .el-tabs__item.is-bottom:last-child,.el-tabs--top .el-tabs__item.is-top:last-child{padding-right:0}.el-cascader-menu:last-child .el-cascader-node,.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--bottom .el-tabs__header.is-bottom{margin-bottom:0;margin-top:10px}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid #DCDFE6}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-top:-1px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid transparent}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-scroll,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{top:0;bottom:auto;width:2px;height:auto}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i{-webkit-transform:rotateZ(90deg);transform:rotateZ(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{left:auto;top:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{right:auto;bottom:0}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__nav-wrap.is-left::after{right:0;left:auto}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left::after,.el-tabs--left .el-tabs__nav-wrap.is-right::after,.el-tabs--right .el-tabs__nav-wrap.is-left::after,.el-tabs--right .el-tabs__nav-wrap.is-right::after{height:100%;width:2px;bottom:auto;top:0}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{float:none}.el-tabs--left .el-tabs__item.is-left,.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-right{display:block}.el-tabs--left .el-tabs__header.is-left{float:left;margin-bottom:0;margin-right:10px}.el-button-group>.el-button:not(:last-child),.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border-left:none;border-right:1px solid #E4E7ED;border-bottom:none;border-top:1px solid #E4E7ED;text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid #E4E7ED;border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:1px solid #E4E7ED;border-right-color:#fff;border-left:none;border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid #E4E7ED;border-right:none}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid #dfe4ed}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid transparent;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:#d1dbe5 transparent}.el-tabs--right .el-tabs__header.is-right{float:right;margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right::after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid #E4E7ED}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid #E4E7ED;border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:1px solid #E4E7ED;border-left-color:#fff;border-right:none;border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid #E4E7ED;border-left:none}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid #dfe4ed}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid transparent;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:#d1dbe5 transparent}.slideInLeft-transition,.slideInRight-transition{display:inline-block}.slideInRight-enter{-webkit-animation:slideInRight-enter .3s;animation:slideInRight-enter .3s}.slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave .3s;animation:slideInRight-leave .3s}.slideInLeft-enter{-webkit-animation:slideInLeft-enter .3s;animation:slideInLeft-enter .3s}.slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave .3s;animation:slideInLeft-leave .3s}@-webkit-keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}.el-tree{position:relative;cursor:default;background:#FFF;color:#606266}.el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.el-tree__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:#909399;font-size:14px}.el-tree__drop-indicator{position:absolute;left:0;right:0;height:1px;background-color:#409EFF}.el-tree-node{white-space:nowrap;outline:0}.el-tree-node:focus>.el-tree-node__content{background-color:#F5F7FA}.el-tree-node.is-drop-inner>.el-tree-node__content .el-tree-node__label{background-color:#409EFF;color:#fff}.el-tree-node__content:hover,.el-upload-list__item:hover{background-color:#F5F7FA}.el-tree-node__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:26px;cursor:pointer}.el-tree-node__content>.el-tree-node__expand-icon{padding:6px}.el-tree-node__content>label.el-checkbox{margin-right:8px}.el-tree.is-dragging .el-tree-node__content{cursor:move}.el-tree.is-dragging .el-tree-node__content *{pointer-events:none}.el-tree.is-dragging.is-drop-not-allow .el-tree-node__content{cursor:not-allowed}.el-tree-node__expand-icon{cursor:pointer;color:#C0C4CC;font-size:12px;-webkit-transform:rotate(0);transform:rotate(0);-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}.el-tree-node__expand-icon.expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default}.el-tree-node__label{font-size:14px}.el-tree-node__loading-icon{margin-right:8px;font-size:14px;color:#C0C4CC}.el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.el-tree-node.is-expanded>.el-tree-node__children{display:block}.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#f0f7ff}.el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#FFF;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-light .el-alert__closebtn{color:#C0C4CC}.el-alert.is-dark .el-alert__closebtn,.el-alert.is-dark .el-alert__description{color:#FFF}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success.is-light{background-color:#f0f9eb;color:#67C23A}.el-alert--success.is-light .el-alert__description{color:#67C23A}.el-alert--success.is-dark{background-color:#67C23A;color:#FFF}.el-alert--info.is-light{background-color:#f4f4f5;color:#909399}.el-alert--info.is-dark{background-color:#909399;color:#FFF}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning.is-light{background-color:#fdf6ec;color:#E6A23C}.el-alert--warning.is-light .el-alert__description{color:#E6A23C}.el-alert--warning.is-dark{background-color:#E6A23C;color:#FFF}.el-alert--error.is-light{background-color:#fef0f0;color:#F56C6C}.el-alert--error.is-light .el-alert__description{color:#F56C6C}.el-alert--error.is-dark{background-color:#F56C6C;color:#FFF}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert-fade-enter,.el-alert-fade-leave-active,.el-loading-fade-enter,.el-loading-fade-leave-active,.el-notification-fade-leave-active,.el-upload iframe{opacity:0}.el-carousel__arrow--right,.el-notification.right{right:16px}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #EBEEF5;position:fixed;background-color:#FFF;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.left{left:16px}.el-notification__group{margin-left:13px;margin-right:8px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67C23A}.el-notification .el-icon-error{color:#F56C6C}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#E6A23C}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.el-input-number .el-input{display:block}.el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.el-input-number__decrease,.el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#F5F7FA;color:#606266;cursor:pointer;font-size:13px}.el-input-number__decrease:hover,.el-input-number__increase:hover{color:#409EFF}.el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#409EFF}.el-input-number__decrease.is-disabled,.el-input-number__increase.is-disabled{color:#C0C4CC;cursor:not-allowed}.el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #DCDFE6}.el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #DCDFE6}.el-input-number.is-disabled .el-input-number__decrease,.el-input-number.is-disabled .el-input-number__increase{border-color:#E4E7ED;color:#E4E7ED}.el-input-number.is-disabled .el-input-number__decrease:hover,.el-input-number.is-disabled .el-input-number__increase:hover{color:#E4E7ED;cursor:not-allowed}.el-input-number--medium{width:200px;line-height:34px}.el-input-number--medium .el-input-number__decrease,.el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.el-input-number--small{width:130px;line-height:30px}.el-input-number--small .el-input-number__decrease,.el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.el-input-number--small .el-input-number__decrease [class*=el-icon],.el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.el-input-number--mini{width:130px;line-height:26px}.el-input-number--mini .el-input-number__decrease,.el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.el-input-number--mini .el-input-number__decrease [class*=el-icon],.el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.el-input-number.is-controls-right .el-input-number__decrease,.el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #DCDFE6}.el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #DCDFE6;border-radius:0 0 4px}.el-input-number.is-controls-right[class*=medium] [class*=decrease],.el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.el-input-number.is-controls-right[class*=small] [class*=decrease],.el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.el-input-number.is-controls-right[class*=mini] [class*=decrease],.el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.el-tooltip:focus:hover,.el-tooltip:focus:not(.focusing){outline-width:0}.el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2;min-width:10px;word-wrap:break-word}.el-tooltip__popper .popper__arrow,.el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-tooltip__popper .popper__arrow{border-width:6px}.el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.el-button-group::after,.el-button-group::before,.el-color-dropdown__main-wrapper::after,.el-link.is-underline:hover:after,.el-page-header__left::after,.el-progress-bar__inner::after,.el-row::after,.el-row::before,.el-slider::after,.el-slider::before,.el-slider__button-wrapper::after,.el-transfer-panel .el-transfer-panel__footer::after,.el-upload-cover::after,.el-upload-list--picture-card .el-upload-list__item-actions::after{content:""}.el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=right]{margin-left:12px}.el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=left]{margin-right:12px}.el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper.is-dark{background:#303133;color:#FFF}.el-tooltip__popper.is-light{background:#FFF;border:1px solid #303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#FFF}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#303133}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#FFF}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#303133}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#FFF}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#303133}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#FFF}.el-slider::after,.el-slider::before{display:table}.el-slider__button-wrapper .el-tooltip,.el-slider__button-wrapper::after{display:inline-block;vertical-align:middle}.el-slider::after{clear:both}.el-slider__runway{width:100%;height:6px;margin:16px 0;background-color:#E4E7ED;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle}.el-slider__runway.show-input{margin-right:160px;width:auto}.el-slider__runway.disabled{cursor:default}.el-slider__runway.disabled .el-slider__bar{background-color:#C0C4CC}.el-slider__runway.disabled .el-slider__button{border-color:#C0C4CC}.el-slider__runway.disabled .el-slider__button-wrapper.dragging,.el-slider__runway.disabled .el-slider__button-wrapper.hover,.el-slider__runway.disabled .el-slider__button-wrapper:hover{cursor:not-allowed}.el-slider__runway.disabled .el-slider__button.dragging,.el-slider__runway.disabled .el-slider__button.hover,.el-slider__runway.disabled .el-slider__button:hover{-webkit-transform:scale(1);transform:scale(1);cursor:not-allowed}.el-slider__button-wrapper,.el-slider__stop{-webkit-transform:translateX(-50%);position:absolute}.el-slider__input{float:right;margin-top:3px;width:130px}.el-slider__input.el-input-number--mini{margin-top:5px}.el-slider__input.el-input-number--medium{margin-top:0}.el-slider__input.el-input-number--large{margin-top:-2px}.el-slider__bar{height:6px;background-color:#409EFF;border-top-left-radius:3px;border-bottom-left-radius:3px;position:absolute}.el-slider__button-wrapper{height:36px;width:36px;z-index:1001;top:-15px;transform:translateX(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;line-height:normal}.el-image-viewer__btn,.el-slider__button,.el-step__icon-inner{-moz-user-select:none;-ms-user-select:none}.el-slider__button-wrapper::after{height:100%}.el-slider__button-wrapper.hover,.el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__button{width:16px;height:16px;border:2px solid #409EFF;background-color:#FFF;border-radius:50%;-webkit-transition:.2s;transition:.2s;-webkit-user-select:none;user-select:none}.el-slider__button.dragging,.el-slider__button.hover,.el-slider__button:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.el-slider__button.hover,.el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__stop{height:6px;width:6px;border-radius:100%;background-color:#FFF;transform:translateX(-50%)}.el-slider__marks{top:0;left:12px;width:18px;height:100%}.el-slider__marks-text{position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);font-size:14px;color:#909399;margin-top:15px}.el-slider.is-vertical{position:relative}.el-slider.is-vertical .el-slider__runway{width:6px;height:100%;margin:0 16px}.el-slider.is-vertical .el-slider__bar{width:6px;height:auto;border-radius:0 0 3px 3px}.el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:-15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.el-slider.is-vertical .el-slider__stop{-webkit-transform:translateY(50%);transform:translateY(50%)}.el-slider.is-vertical.el-slider--with-input{padding-bottom:58px}.el-slider.is-vertical.el-slider--with-input .el-slider__input{overflow:visible;float:none;position:absolute;bottom:22px;width:36px;margin-top:15px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input__inner{text-align:center;padding-left:5px;padding-right:5px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{top:32px;margin-top:-1px;border:1px solid #DCDFE6;line-height:20px;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease{width:18px;right:18px;border-bottom-left-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{width:19px;border-bottom-right-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase~.el-input .el-input__inner{border-bottom-left-radius:0;border-bottom-right-radius:0}.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__increase{border-color:#C0C4CC}.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__increase{border-color:#409EFF}.el-slider.is-vertical .el-slider__marks-text{margin-top:0;left:15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-col-pull-0,.el-col-pull-1,.el-col-pull-10,.el-col-pull-11,.el-col-pull-12,.el-col-pull-13,.el-col-pull-14,.el-col-pull-15,.el-col-pull-16,.el-col-pull-17,.el-col-pull-18,.el-col-pull-19,.el-col-pull-2,.el-col-pull-20,.el-col-pull-21,.el-col-pull-22,.el-col-pull-23,.el-col-pull-24,.el-col-pull-3,.el-col-pull-4,.el-col-pull-5,.el-col-pull-6,.el-col-pull-7,.el-col-pull-8,.el-col-pull-9,.el-col-push-0,.el-col-push-1,.el-col-push-10,.el-col-push-11,.el-col-push-12,.el-col-push-13,.el-col-push-14,.el-col-push-15,.el-col-push-16,.el-col-push-17,.el-col-push-18,.el-col-push-19,.el-col-push-2,.el-col-push-20,.el-col-push-21,.el-col-push-22,.el-col-push-23,.el-col-push-24,.el-col-push-3,.el-col-push-4,.el-col-push-5,.el-col-push-6,.el-col-push-7,.el-col-push-8,.el-col-push-9,.el-row,.el-upload-dragger,.el-upload-list__item{position:relative}.el-loading-spinner .el-loading-text{color:#409EFF;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#409EFF;stroke-linecap:round}.el-loading-spinner i{color:#409EFF}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.el-row{-webkit-box-sizing:border-box;box-sizing:border-box}.el-row::after,.el-row::before{display:table}.el-row::after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-col-0,.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-top{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}[class*=el-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.el-col-0{width:0%}.el-col-pull-0{right:0}.el-col-push-0{left:0}.el-col-1{width:4.16667%}.el-col-offset-1{margin-left:4.16667%}.el-col-pull-1{right:4.16667%}.el-col-push-1{left:4.16667%}.el-col-2{width:8.33333%}.el-col-offset-2{margin-left:8.33333%}.el-col-pull-2{right:8.33333%}.el-col-push-2{left:8.33333%}.el-col-3{width:12.5%}.el-col-offset-3{margin-left:12.5%}.el-col-pull-3{right:12.5%}.el-col-push-3{left:12.5%}.el-col-4{width:16.66667%}.el-col-offset-4{margin-left:16.66667%}.el-col-pull-4{right:16.66667%}.el-col-push-4{left:16.66667%}.el-col-5{width:20.83333%}.el-col-offset-5{margin-left:20.83333%}.el-col-pull-5{right:20.83333%}.el-col-push-5{left:20.83333%}.el-col-6{width:25%}.el-col-offset-6{margin-left:25%}.el-col-pull-6{right:25%}.el-col-push-6{left:25%}.el-col-7{width:29.16667%}.el-col-offset-7{margin-left:29.16667%}.el-col-pull-7{right:29.16667%}.el-col-push-7{left:29.16667%}.el-col-8{width:33.33333%}.el-col-offset-8{margin-left:33.33333%}.el-col-pull-8{right:33.33333%}.el-col-push-8{left:33.33333%}.el-col-9{width:37.5%}.el-col-offset-9{margin-left:37.5%}.el-col-pull-9{right:37.5%}.el-col-push-9{left:37.5%}.el-col-10{width:41.66667%}.el-col-offset-10{margin-left:41.66667%}.el-col-pull-10{right:41.66667%}.el-col-push-10{left:41.66667%}.el-col-11{width:45.83333%}.el-col-offset-11{margin-left:45.83333%}.el-col-pull-11{right:45.83333%}.el-col-push-11{left:45.83333%}.el-col-12{width:50%}.el-col-offset-12{margin-left:50%}.el-col-pull-12{right:50%}.el-col-push-12{left:50%}.el-col-13{width:54.16667%}.el-col-offset-13{margin-left:54.16667%}.el-col-pull-13{right:54.16667%}.el-col-push-13{left:54.16667%}.el-col-14{width:58.33333%}.el-col-offset-14{margin-left:58.33333%}.el-col-pull-14{right:58.33333%}.el-col-push-14{left:58.33333%}.el-col-15{width:62.5%}.el-col-offset-15{margin-left:62.5%}.el-col-pull-15{right:62.5%}.el-col-push-15{left:62.5%}.el-col-16{width:66.66667%}.el-col-offset-16{margin-left:66.66667%}.el-col-pull-16{right:66.66667%}.el-col-push-16{left:66.66667%}.el-col-17{width:70.83333%}.el-col-offset-17{margin-left:70.83333%}.el-col-pull-17{right:70.83333%}.el-col-push-17{left:70.83333%}.el-col-18{width:75%}.el-col-offset-18{margin-left:75%}.el-col-pull-18{right:75%}.el-col-push-18{left:75%}.el-col-19{width:79.16667%}.el-col-offset-19{margin-left:79.16667%}.el-col-pull-19{right:79.16667%}.el-col-push-19{left:79.16667%}.el-col-20{width:83.33333%}.el-col-offset-20{margin-left:83.33333%}.el-col-pull-20{right:83.33333%}.el-col-push-20{left:83.33333%}.el-col-21{width:87.5%}.el-col-offset-21{margin-left:87.5%}.el-col-pull-21{right:87.5%}.el-col-push-21{left:87.5%}.el-col-22{width:91.66667%}.el-col-offset-22{margin-left:91.66667%}.el-col-pull-22{right:91.66667%}.el-col-push-22{left:91.66667%}.el-col-23{width:95.83333%}.el-col-offset-23{margin-left:95.83333%}.el-col-pull-23{right:95.83333%}.el-col-push-23{left:95.83333%}.el-col-24{width:100%}.el-col-offset-24{margin-left:100%}.el-col-pull-24{right:100%}.el-col-push-24{left:100%}@media only screen and (max-width:767px){.el-col-xs-0{display:none;width:0%}.el-col-xs-offset-0{margin-left:0}.el-col-xs-pull-0{position:relative;right:0}.el-col-xs-push-0{position:relative;left:0}.el-col-xs-1{width:4.16667%}.el-col-xs-offset-1{margin-left:4.16667%}.el-col-xs-pull-1{position:relative;right:4.16667%}.el-col-xs-push-1{position:relative;left:4.16667%}.el-col-xs-2{width:8.33333%}.el-col-xs-offset-2{margin-left:8.33333%}.el-col-xs-pull-2{position:relative;right:8.33333%}.el-col-xs-push-2{position:relative;left:8.33333%}.el-col-xs-3{width:12.5%}.el-col-xs-offset-3{margin-left:12.5%}.el-col-xs-pull-3{position:relative;right:12.5%}.el-col-xs-push-3{position:relative;left:12.5%}.el-col-xs-4{width:16.66667%}.el-col-xs-offset-4{margin-left:16.66667%}.el-col-xs-pull-4{position:relative;right:16.66667%}.el-col-xs-push-4{position:relative;left:16.66667%}.el-col-xs-5{width:20.83333%}.el-col-xs-offset-5{margin-left:20.83333%}.el-col-xs-pull-5{position:relative;right:20.83333%}.el-col-xs-push-5{position:relative;left:20.83333%}.el-col-xs-6{width:25%}.el-col-xs-offset-6{margin-left:25%}.el-col-xs-pull-6{position:relative;right:25%}.el-col-xs-push-6{position:relative;left:25%}.el-col-xs-7{width:29.16667%}.el-col-xs-offset-7{margin-left:29.16667%}.el-col-xs-pull-7{position:relative;right:29.16667%}.el-col-xs-push-7{position:relative;left:29.16667%}.el-col-xs-8{width:33.33333%}.el-col-xs-offset-8{margin-left:33.33333%}.el-col-xs-pull-8{position:relative;right:33.33333%}.el-col-xs-push-8{position:relative;left:33.33333%}.el-col-xs-9{width:37.5%}.el-col-xs-offset-9{margin-left:37.5%}.el-col-xs-pull-9{position:relative;right:37.5%}.el-col-xs-push-9{position:relative;left:37.5%}.el-col-xs-10{width:41.66667%}.el-col-xs-offset-10{margin-left:41.66667%}.el-col-xs-pull-10{position:relative;right:41.66667%}.el-col-xs-push-10{position:relative;left:41.66667%}.el-col-xs-11{width:45.83333%}.el-col-xs-offset-11{margin-left:45.83333%}.el-col-xs-pull-11{position:relative;right:45.83333%}.el-col-xs-push-11{position:relative;left:45.83333%}.el-col-xs-12{width:50%}.el-col-xs-offset-12{margin-left:50%}.el-col-xs-pull-12{position:relative;right:50%}.el-col-xs-push-12{position:relative;left:50%}.el-col-xs-13{width:54.16667%}.el-col-xs-offset-13{margin-left:54.16667%}.el-col-xs-pull-13{position:relative;right:54.16667%}.el-col-xs-push-13{position:relative;left:54.16667%}.el-col-xs-14{width:58.33333%}.el-col-xs-offset-14{margin-left:58.33333%}.el-col-xs-pull-14{position:relative;right:58.33333%}.el-col-xs-push-14{position:relative;left:58.33333%}.el-col-xs-15{width:62.5%}.el-col-xs-offset-15{margin-left:62.5%}.el-col-xs-pull-15{position:relative;right:62.5%}.el-col-xs-push-15{position:relative;left:62.5%}.el-col-xs-16{width:66.66667%}.el-col-xs-offset-16{margin-left:66.66667%}.el-col-xs-pull-16{position:relative;right:66.66667%}.el-col-xs-push-16{position:relative;left:66.66667%}.el-col-xs-17{width:70.83333%}.el-col-xs-offset-17{margin-left:70.83333%}.el-col-xs-pull-17{position:relative;right:70.83333%}.el-col-xs-push-17{position:relative;left:70.83333%}.el-col-xs-18{width:75%}.el-col-xs-offset-18{margin-left:75%}.el-col-xs-pull-18{position:relative;right:75%}.el-col-xs-push-18{position:relative;left:75%}.el-col-xs-19{width:79.16667%}.el-col-xs-offset-19{margin-left:79.16667%}.el-col-xs-pull-19{position:relative;right:79.16667%}.el-col-xs-push-19{position:relative;left:79.16667%}.el-col-xs-20{width:83.33333%}.el-col-xs-offset-20{margin-left:83.33333%}.el-col-xs-pull-20{position:relative;right:83.33333%}.el-col-xs-push-20{position:relative;left:83.33333%}.el-col-xs-21{width:87.5%}.el-col-xs-offset-21{margin-left:87.5%}.el-col-xs-pull-21{position:relative;right:87.5%}.el-col-xs-push-21{position:relative;left:87.5%}.el-col-xs-22{width:91.66667%}.el-col-xs-offset-22{margin-left:91.66667%}.el-col-xs-pull-22{position:relative;right:91.66667%}.el-col-xs-push-22{position:relative;left:91.66667%}.el-col-xs-23{width:95.83333%}.el-col-xs-offset-23{margin-left:95.83333%}.el-col-xs-pull-23{position:relative;right:95.83333%}.el-col-xs-push-23{position:relative;left:95.83333%}.el-col-xs-24{width:100%}.el-col-xs-offset-24{margin-left:100%}.el-col-xs-pull-24{position:relative;right:100%}.el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.el-col-sm-0{display:none;width:0%}.el-col-sm-offset-0{margin-left:0}.el-col-sm-pull-0{position:relative;right:0}.el-col-sm-push-0{position:relative;left:0}.el-col-sm-1{width:4.16667%}.el-col-sm-offset-1{margin-left:4.16667%}.el-col-sm-pull-1{position:relative;right:4.16667%}.el-col-sm-push-1{position:relative;left:4.16667%}.el-col-sm-2{width:8.33333%}.el-col-sm-offset-2{margin-left:8.33333%}.el-col-sm-pull-2{position:relative;right:8.33333%}.el-col-sm-push-2{position:relative;left:8.33333%}.el-col-sm-3{width:12.5%}.el-col-sm-offset-3{margin-left:12.5%}.el-col-sm-pull-3{position:relative;right:12.5%}.el-col-sm-push-3{position:relative;left:12.5%}.el-col-sm-4{width:16.66667%}.el-col-sm-offset-4{margin-left:16.66667%}.el-col-sm-pull-4{position:relative;right:16.66667%}.el-col-sm-push-4{position:relative;left:16.66667%}.el-col-sm-5{width:20.83333%}.el-col-sm-offset-5{margin-left:20.83333%}.el-col-sm-pull-5{position:relative;right:20.83333%}.el-col-sm-push-5{position:relative;left:20.83333%}.el-col-sm-6{width:25%}.el-col-sm-offset-6{margin-left:25%}.el-col-sm-pull-6{position:relative;right:25%}.el-col-sm-push-6{position:relative;left:25%}.el-col-sm-7{width:29.16667%}.el-col-sm-offset-7{margin-left:29.16667%}.el-col-sm-pull-7{position:relative;right:29.16667%}.el-col-sm-push-7{position:relative;left:29.16667%}.el-col-sm-8{width:33.33333%}.el-col-sm-offset-8{margin-left:33.33333%}.el-col-sm-pull-8{position:relative;right:33.33333%}.el-col-sm-push-8{position:relative;left:33.33333%}.el-col-sm-9{width:37.5%}.el-col-sm-offset-9{margin-left:37.5%}.el-col-sm-pull-9{position:relative;right:37.5%}.el-col-sm-push-9{position:relative;left:37.5%}.el-col-sm-10{width:41.66667%}.el-col-sm-offset-10{margin-left:41.66667%}.el-col-sm-pull-10{position:relative;right:41.66667%}.el-col-sm-push-10{position:relative;left:41.66667%}.el-col-sm-11{width:45.83333%}.el-col-sm-offset-11{margin-left:45.83333%}.el-col-sm-pull-11{position:relative;right:45.83333%}.el-col-sm-push-11{position:relative;left:45.83333%}.el-col-sm-12{width:50%}.el-col-sm-offset-12{margin-left:50%}.el-col-sm-pull-12{position:relative;right:50%}.el-col-sm-push-12{position:relative;left:50%}.el-col-sm-13{width:54.16667%}.el-col-sm-offset-13{margin-left:54.16667%}.el-col-sm-pull-13{position:relative;right:54.16667%}.el-col-sm-push-13{position:relative;left:54.16667%}.el-col-sm-14{width:58.33333%}.el-col-sm-offset-14{margin-left:58.33333%}.el-col-sm-pull-14{position:relative;right:58.33333%}.el-col-sm-push-14{position:relative;left:58.33333%}.el-col-sm-15{width:62.5%}.el-col-sm-offset-15{margin-left:62.5%}.el-col-sm-pull-15{position:relative;right:62.5%}.el-col-sm-push-15{position:relative;left:62.5%}.el-col-sm-16{width:66.66667%}.el-col-sm-offset-16{margin-left:66.66667%}.el-col-sm-pull-16{position:relative;right:66.66667%}.el-col-sm-push-16{position:relative;left:66.66667%}.el-col-sm-17{width:70.83333%}.el-col-sm-offset-17{margin-left:70.83333%}.el-col-sm-pull-17{position:relative;right:70.83333%}.el-col-sm-push-17{position:relative;left:70.83333%}.el-col-sm-18{width:75%}.el-col-sm-offset-18{margin-left:75%}.el-col-sm-pull-18{position:relative;right:75%}.el-col-sm-push-18{position:relative;left:75%}.el-col-sm-19{width:79.16667%}.el-col-sm-offset-19{margin-left:79.16667%}.el-col-sm-pull-19{position:relative;right:79.16667%}.el-col-sm-push-19{position:relative;left:79.16667%}.el-col-sm-20{width:83.33333%}.el-col-sm-offset-20{margin-left:83.33333%}.el-col-sm-pull-20{position:relative;right:83.33333%}.el-col-sm-push-20{position:relative;left:83.33333%}.el-col-sm-21{width:87.5%}.el-col-sm-offset-21{margin-left:87.5%}.el-col-sm-pull-21{position:relative;right:87.5%}.el-col-sm-push-21{position:relative;left:87.5%}.el-col-sm-22{width:91.66667%}.el-col-sm-offset-22{margin-left:91.66667%}.el-col-sm-pull-22{position:relative;right:91.66667%}.el-col-sm-push-22{position:relative;left:91.66667%}.el-col-sm-23{width:95.83333%}.el-col-sm-offset-23{margin-left:95.83333%}.el-col-sm-pull-23{position:relative;right:95.83333%}.el-col-sm-push-23{position:relative;left:95.83333%}.el-col-sm-24{width:100%}.el-col-sm-offset-24{margin-left:100%}.el-col-sm-pull-24{position:relative;right:100%}.el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.el-col-md-0{display:none;width:0%}.el-col-md-offset-0{margin-left:0}.el-col-md-pull-0{position:relative;right:0}.el-col-md-push-0{position:relative;left:0}.el-col-md-1{width:4.16667%}.el-col-md-offset-1{margin-left:4.16667%}.el-col-md-pull-1{position:relative;right:4.16667%}.el-col-md-push-1{position:relative;left:4.16667%}.el-col-md-2{width:8.33333%}.el-col-md-offset-2{margin-left:8.33333%}.el-col-md-pull-2{position:relative;right:8.33333%}.el-col-md-push-2{position:relative;left:8.33333%}.el-col-md-3{width:12.5%}.el-col-md-offset-3{margin-left:12.5%}.el-col-md-pull-3{position:relative;right:12.5%}.el-col-md-push-3{position:relative;left:12.5%}.el-col-md-4{width:16.66667%}.el-col-md-offset-4{margin-left:16.66667%}.el-col-md-pull-4{position:relative;right:16.66667%}.el-col-md-push-4{position:relative;left:16.66667%}.el-col-md-5{width:20.83333%}.el-col-md-offset-5{margin-left:20.83333%}.el-col-md-pull-5{position:relative;right:20.83333%}.el-col-md-push-5{position:relative;left:20.83333%}.el-col-md-6{width:25%}.el-col-md-offset-6{margin-left:25%}.el-col-md-pull-6{position:relative;right:25%}.el-col-md-push-6{position:relative;left:25%}.el-col-md-7{width:29.16667%}.el-col-md-offset-7{margin-left:29.16667%}.el-col-md-pull-7{position:relative;right:29.16667%}.el-col-md-push-7{position:relative;left:29.16667%}.el-col-md-8{width:33.33333%}.el-col-md-offset-8{margin-left:33.33333%}.el-col-md-pull-8{position:relative;right:33.33333%}.el-col-md-push-8{position:relative;left:33.33333%}.el-col-md-9{width:37.5%}.el-col-md-offset-9{margin-left:37.5%}.el-col-md-pull-9{position:relative;right:37.5%}.el-col-md-push-9{position:relative;left:37.5%}.el-col-md-10{width:41.66667%}.el-col-md-offset-10{margin-left:41.66667%}.el-col-md-pull-10{position:relative;right:41.66667%}.el-col-md-push-10{position:relative;left:41.66667%}.el-col-md-11{width:45.83333%}.el-col-md-offset-11{margin-left:45.83333%}.el-col-md-pull-11{position:relative;right:45.83333%}.el-col-md-push-11{position:relative;left:45.83333%}.el-col-md-12{width:50%}.el-col-md-offset-12{margin-left:50%}.el-col-md-pull-12{position:relative;right:50%}.el-col-md-push-12{position:relative;left:50%}.el-col-md-13{width:54.16667%}.el-col-md-offset-13{margin-left:54.16667%}.el-col-md-pull-13{position:relative;right:54.16667%}.el-col-md-push-13{position:relative;left:54.16667%}.el-col-md-14{width:58.33333%}.el-col-md-offset-14{margin-left:58.33333%}.el-col-md-pull-14{position:relative;right:58.33333%}.el-col-md-push-14{position:relative;left:58.33333%}.el-col-md-15{width:62.5%}.el-col-md-offset-15{margin-left:62.5%}.el-col-md-pull-15{position:relative;right:62.5%}.el-col-md-push-15{position:relative;left:62.5%}.el-col-md-16{width:66.66667%}.el-col-md-offset-16{margin-left:66.66667%}.el-col-md-pull-16{position:relative;right:66.66667%}.el-col-md-push-16{position:relative;left:66.66667%}.el-col-md-17{width:70.83333%}.el-col-md-offset-17{margin-left:70.83333%}.el-col-md-pull-17{position:relative;right:70.83333%}.el-col-md-push-17{position:relative;left:70.83333%}.el-col-md-18{width:75%}.el-col-md-offset-18{margin-left:75%}.el-col-md-pull-18{position:relative;right:75%}.el-col-md-push-18{position:relative;left:75%}.el-col-md-19{width:79.16667%}.el-col-md-offset-19{margin-left:79.16667%}.el-col-md-pull-19{position:relative;right:79.16667%}.el-col-md-push-19{position:relative;left:79.16667%}.el-col-md-20{width:83.33333%}.el-col-md-offset-20{margin-left:83.33333%}.el-col-md-pull-20{position:relative;right:83.33333%}.el-col-md-push-20{position:relative;left:83.33333%}.el-col-md-21{width:87.5%}.el-col-md-offset-21{margin-left:87.5%}.el-col-md-pull-21{position:relative;right:87.5%}.el-col-md-push-21{position:relative;left:87.5%}.el-col-md-22{width:91.66667%}.el-col-md-offset-22{margin-left:91.66667%}.el-col-md-pull-22{position:relative;right:91.66667%}.el-col-md-push-22{position:relative;left:91.66667%}.el-col-md-23{width:95.83333%}.el-col-md-offset-23{margin-left:95.83333%}.el-col-md-pull-23{position:relative;right:95.83333%}.el-col-md-push-23{position:relative;left:95.83333%}.el-col-md-24{width:100%}.el-col-md-offset-24{margin-left:100%}.el-col-md-pull-24{position:relative;right:100%}.el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.el-col-lg-0{display:none;width:0%}.el-col-lg-offset-0{margin-left:0}.el-col-lg-pull-0{position:relative;right:0}.el-col-lg-push-0{position:relative;left:0}.el-col-lg-1{width:4.16667%}.el-col-lg-offset-1{margin-left:4.16667%}.el-col-lg-pull-1{position:relative;right:4.16667%}.el-col-lg-push-1{position:relative;left:4.16667%}.el-col-lg-2{width:8.33333%}.el-col-lg-offset-2{margin-left:8.33333%}.el-col-lg-pull-2{position:relative;right:8.33333%}.el-col-lg-push-2{position:relative;left:8.33333%}.el-col-lg-3{width:12.5%}.el-col-lg-offset-3{margin-left:12.5%}.el-col-lg-pull-3{position:relative;right:12.5%}.el-col-lg-push-3{position:relative;left:12.5%}.el-col-lg-4{width:16.66667%}.el-col-lg-offset-4{margin-left:16.66667%}.el-col-lg-pull-4{position:relative;right:16.66667%}.el-col-lg-push-4{position:relative;left:16.66667%}.el-col-lg-5{width:20.83333%}.el-col-lg-offset-5{margin-left:20.83333%}.el-col-lg-pull-5{position:relative;right:20.83333%}.el-col-lg-push-5{position:relative;left:20.83333%}.el-col-lg-6{width:25%}.el-col-lg-offset-6{margin-left:25%}.el-col-lg-pull-6{position:relative;right:25%}.el-col-lg-push-6{position:relative;left:25%}.el-col-lg-7{width:29.16667%}.el-col-lg-offset-7{margin-left:29.16667%}.el-col-lg-pull-7{position:relative;right:29.16667%}.el-col-lg-push-7{position:relative;left:29.16667%}.el-col-lg-8{width:33.33333%}.el-col-lg-offset-8{margin-left:33.33333%}.el-col-lg-pull-8{position:relative;right:33.33333%}.el-col-lg-push-8{position:relative;left:33.33333%}.el-col-lg-9{width:37.5%}.el-col-lg-offset-9{margin-left:37.5%}.el-col-lg-pull-9{position:relative;right:37.5%}.el-col-lg-push-9{position:relative;left:37.5%}.el-col-lg-10{width:41.66667%}.el-col-lg-offset-10{margin-left:41.66667%}.el-col-lg-pull-10{position:relative;right:41.66667%}.el-col-lg-push-10{position:relative;left:41.66667%}.el-col-lg-11{width:45.83333%}.el-col-lg-offset-11{margin-left:45.83333%}.el-col-lg-pull-11{position:relative;right:45.83333%}.el-col-lg-push-11{position:relative;left:45.83333%}.el-col-lg-12{width:50%}.el-col-lg-offset-12{margin-left:50%}.el-col-lg-pull-12{position:relative;right:50%}.el-col-lg-push-12{position:relative;left:50%}.el-col-lg-13{width:54.16667%}.el-col-lg-offset-13{margin-left:54.16667%}.el-col-lg-pull-13{position:relative;right:54.16667%}.el-col-lg-push-13{position:relative;left:54.16667%}.el-col-lg-14{width:58.33333%}.el-col-lg-offset-14{margin-left:58.33333%}.el-col-lg-pull-14{position:relative;right:58.33333%}.el-col-lg-push-14{position:relative;left:58.33333%}.el-col-lg-15{width:62.5%}.el-col-lg-offset-15{margin-left:62.5%}.el-col-lg-pull-15{position:relative;right:62.5%}.el-col-lg-push-15{position:relative;left:62.5%}.el-col-lg-16{width:66.66667%}.el-col-lg-offset-16{margin-left:66.66667%}.el-col-lg-pull-16{position:relative;right:66.66667%}.el-col-lg-push-16{position:relative;left:66.66667%}.el-col-lg-17{width:70.83333%}.el-col-lg-offset-17{margin-left:70.83333%}.el-col-lg-pull-17{position:relative;right:70.83333%}.el-col-lg-push-17{position:relative;left:70.83333%}.el-col-lg-18{width:75%}.el-col-lg-offset-18{margin-left:75%}.el-col-lg-pull-18{position:relative;right:75%}.el-col-lg-push-18{position:relative;left:75%}.el-col-lg-19{width:79.16667%}.el-col-lg-offset-19{margin-left:79.16667%}.el-col-lg-pull-19{position:relative;right:79.16667%}.el-col-lg-push-19{position:relative;left:79.16667%}.el-col-lg-20{width:83.33333%}.el-col-lg-offset-20{margin-left:83.33333%}.el-col-lg-pull-20{position:relative;right:83.33333%}.el-col-lg-push-20{position:relative;left:83.33333%}.el-col-lg-21{width:87.5%}.el-col-lg-offset-21{margin-left:87.5%}.el-col-lg-pull-21{position:relative;right:87.5%}.el-col-lg-push-21{position:relative;left:87.5%}.el-col-lg-22{width:91.66667%}.el-col-lg-offset-22{margin-left:91.66667%}.el-col-lg-pull-22{position:relative;right:91.66667%}.el-col-lg-push-22{position:relative;left:91.66667%}.el-col-lg-23{width:95.83333%}.el-col-lg-offset-23{margin-left:95.83333%}.el-col-lg-pull-23{position:relative;right:95.83333%}.el-col-lg-push-23{position:relative;left:95.83333%}.el-col-lg-24{width:100%}.el-col-lg-offset-24{margin-left:100%}.el-col-lg-pull-24{position:relative;right:100%}.el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.el-col-xl-0{display:none;width:0%}.el-col-xl-offset-0{margin-left:0}.el-col-xl-pull-0{position:relative;right:0}.el-col-xl-push-0{position:relative;left:0}.el-col-xl-1{width:4.16667%}.el-col-xl-offset-1{margin-left:4.16667%}.el-col-xl-pull-1{position:relative;right:4.16667%}.el-col-xl-push-1{position:relative;left:4.16667%}.el-col-xl-2{width:8.33333%}.el-col-xl-offset-2{margin-left:8.33333%}.el-col-xl-pull-2{position:relative;right:8.33333%}.el-col-xl-push-2{position:relative;left:8.33333%}.el-col-xl-3{width:12.5%}.el-col-xl-offset-3{margin-left:12.5%}.el-col-xl-pull-3{position:relative;right:12.5%}.el-col-xl-push-3{position:relative;left:12.5%}.el-col-xl-4{width:16.66667%}.el-col-xl-offset-4{margin-left:16.66667%}.el-col-xl-pull-4{position:relative;right:16.66667%}.el-col-xl-push-4{position:relative;left:16.66667%}.el-col-xl-5{width:20.83333%}.el-col-xl-offset-5{margin-left:20.83333%}.el-col-xl-pull-5{position:relative;right:20.83333%}.el-col-xl-push-5{position:relative;left:20.83333%}.el-col-xl-6{width:25%}.el-col-xl-offset-6{margin-left:25%}.el-col-xl-pull-6{position:relative;right:25%}.el-col-xl-push-6{position:relative;left:25%}.el-col-xl-7{width:29.16667%}.el-col-xl-offset-7{margin-left:29.16667%}.el-col-xl-pull-7{position:relative;right:29.16667%}.el-col-xl-push-7{position:relative;left:29.16667%}.el-col-xl-8{width:33.33333%}.el-col-xl-offset-8{margin-left:33.33333%}.el-col-xl-pull-8{position:relative;right:33.33333%}.el-col-xl-push-8{position:relative;left:33.33333%}.el-col-xl-9{width:37.5%}.el-col-xl-offset-9{margin-left:37.5%}.el-col-xl-pull-9{position:relative;right:37.5%}.el-col-xl-push-9{position:relative;left:37.5%}.el-col-xl-10{width:41.66667%}.el-col-xl-offset-10{margin-left:41.66667%}.el-col-xl-pull-10{position:relative;right:41.66667%}.el-col-xl-push-10{position:relative;left:41.66667%}.el-col-xl-11{width:45.83333%}.el-col-xl-offset-11{margin-left:45.83333%}.el-col-xl-pull-11{position:relative;right:45.83333%}.el-col-xl-push-11{position:relative;left:45.83333%}.el-col-xl-12{width:50%}.el-col-xl-offset-12{margin-left:50%}.el-col-xl-pull-12{position:relative;right:50%}.el-col-xl-push-12{position:relative;left:50%}.el-col-xl-13{width:54.16667%}.el-col-xl-offset-13{margin-left:54.16667%}.el-col-xl-pull-13{position:relative;right:54.16667%}.el-col-xl-push-13{position:relative;left:54.16667%}.el-col-xl-14{width:58.33333%}.el-col-xl-offset-14{margin-left:58.33333%}.el-col-xl-pull-14{position:relative;right:58.33333%}.el-col-xl-push-14{position:relative;left:58.33333%}.el-col-xl-15{width:62.5%}.el-col-xl-offset-15{margin-left:62.5%}.el-col-xl-pull-15{position:relative;right:62.5%}.el-col-xl-push-15{position:relative;left:62.5%}.el-col-xl-16{width:66.66667%}.el-col-xl-offset-16{margin-left:66.66667%}.el-col-xl-pull-16{position:relative;right:66.66667%}.el-col-xl-push-16{position:relative;left:66.66667%}.el-col-xl-17{width:70.83333%}.el-col-xl-offset-17{margin-left:70.83333%}.el-col-xl-pull-17{position:relative;right:70.83333%}.el-col-xl-push-17{position:relative;left:70.83333%}.el-col-xl-18{width:75%}.el-col-xl-offset-18{margin-left:75%}.el-col-xl-pull-18{position:relative;right:75%}.el-col-xl-push-18{position:relative;left:75%}.el-col-xl-19{width:79.16667%}.el-col-xl-offset-19{margin-left:79.16667%}.el-col-xl-pull-19{position:relative;right:79.16667%}.el-col-xl-push-19{position:relative;left:79.16667%}.el-col-xl-20{width:83.33333%}.el-col-xl-offset-20{margin-left:83.33333%}.el-col-xl-pull-20{position:relative;right:83.33333%}.el-col-xl-push-20{position:relative;left:83.33333%}.el-col-xl-21{width:87.5%}.el-col-xl-offset-21{margin-left:87.5%}.el-col-xl-pull-21{position:relative;right:87.5%}.el-col-xl-push-21{position:relative;left:87.5%}.el-col-xl-22{width:91.66667%}.el-col-xl-offset-22{margin-left:91.66667%}.el-col-xl-pull-22{position:relative;right:91.66667%}.el-col-xl-push-22{position:relative;left:91.66667%}.el-col-xl-23{width:95.83333%}.el-col-xl-offset-23{margin-left:95.83333%}.el-col-xl-pull-23{position:relative;right:95.83333%}.el-col-xl-push-23{position:relative;left:95.83333%}.el-col-xl-24{width:100%}.el-col-xl-offset-24{margin-left:100%}.el-col-xl-pull-24{position:relative;right:100%}.el-col-xl-push-24{position:relative;left:100%}}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.el-upload{display:inline-block;text-align:center;cursor:pointer;outline:0}.el-upload__input{display:none}.el-upload__tip{font-size:12px;color:#606266;margin-top:7px}.el-upload iframe{position:absolute;z-index:-1;top:0;left:0;filter:alpha(opacity=0)}.el-upload--picture-card{background-color:#fbfdff;border:1px dashed #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;cursor:pointer;line-height:146px;vertical-align:top}.el-upload--picture-card i{font-size:28px;color:#8c939d}.el-upload--picture-card:hover,.el-upload:focus{border-color:#409EFF;color:#409EFF}.el-upload:focus .el-upload-dragger{border-color:#409EFF}.el-upload-dragger{background-color:#fff;border:1px dashed #d9d9d9;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:360px;height:180px;text-align:center;cursor:pointer;overflow:hidden}.el-upload-dragger .el-icon-upload{font-size:67px;color:#C0C4CC;margin:40px 0 16px;line-height:50px}.el-upload-dragger+.el-upload__tip{text-align:center}.el-upload-dragger~.el-upload__files{border-top:1px solid #DCDFE6;margin-top:7px;padding-top:5px}.el-upload-dragger .el-upload__text{color:#606266;font-size:14px;text-align:center}.el-upload-dragger .el-upload__text em{color:#409EFF;font-style:normal}.el-upload-dragger:hover{border-color:#409EFF}.el-upload-dragger.is-dragover{background-color:rgba(32,159,255,.06);border:2px dashed #409EFF}.el-upload-list{margin:0;padding:0;list-style:none}.el-upload-list__item{-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:#606266;line-height:1.8;margin-top:5px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;width:100%}.el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.el-upload-list__item:first-child{margin-top:10px}.el-upload-list__item .el-icon-upload-success{color:#67C23A}.el-upload-list__item .el-icon-close{display:none;position:absolute;top:5px;right:5px;cursor:pointer;opacity:.75;color:#606266}.el-upload-list__item .el-icon-close:hover{opacity:1}.el-upload-list__item .el-icon-close-tip{display:none;position:absolute;top:5px;right:5px;font-size:12px;cursor:pointer;opacity:1;color:#409EFF}.el-upload-list__item:hover .el-icon-close{display:inline-block}.el-upload-list__item:hover .el-progress__text{display:none}.el-upload-list__item.is-success .el-upload-list__item-status-label{display:block}.el-upload-list__item.is-success .el-upload-list__item-name:focus,.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#409EFF;cursor:pointer}.el-upload-list__item.is-success:focus:not(:hover) .el-icon-close-tip{display:inline-block}.el-upload-list__item.is-success:active,.el-upload-list__item.is-success:not(.focusing):focus{outline-width:0}.el-upload-list__item.is-success:active .el-icon-close-tip,.el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.el-upload-list__item.is-success:hover .el-upload-list__item-status-label,.el-upload-list__item.is-success:not(.focusing):focus .el-icon-close-tip{display:none}.el-upload-list.is-disabled .el-upload-list__item:hover .el-upload-list__item-status-label{display:block}.el-upload-list__item-name{color:#606266;display:block;margin-right:40px;overflow:hidden;padding-left:4px;text-overflow:ellipsis;-webkit-transition:color .3s;transition:color .3s;white-space:nowrap}.el-upload-list__item-name [class^=el-icon]{height:100%;margin-right:7px;color:#909399;line-height:inherit}.el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none}.el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:#606266;display:none}.el-upload-list__item-delete:hover{color:#409EFF}.el-upload-list--picture-card{margin:0;display:inline;vertical-align:top}.el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;margin:0 8px 8px 0;display:inline-block}.el-upload-list--picture-card .el-upload-list__item .el-icon-check,.el-upload-list--picture-card .el-upload-list__item .el-icon-circle-check{color:#FFF}.el-upload-list--picture-card .el-upload-list__item .el-icon-close,.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{display:none}.el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture-card .el-upload-list__item-name{display:none}.el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%}.el-upload-list--picture-card .el-upload-list__item-status-label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;text-align:center;color:#fff;opacity:0;font-size:20px;background-color:rgba(0,0,0,.5);-webkit-transition:opacity .3s;transition:opacity .3s}.el-upload-list--picture-card .el-upload-list__item-actions::after{display:inline-block;height:100%;vertical-align:middle}.el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:15px}.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-block}.el-upload-list--picture-card .el-progress{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);bottom:auto;width:126px}.el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.el-upload-list--picture .el-upload-list__item{overflow:hidden;z-index:0;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:10px;padding:10px 10px 10px 90px;height:92px}.el-upload-list--picture .el-upload-list__item .el-icon-check,.el-upload-list--picture .el-upload-list__item .el-icon-circle-check{color:#FFF}.el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{background:0 0;-webkit-box-shadow:none;box-shadow:none;top:-2px;right:-12px}.el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name{line-height:70px;margin-top:0}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.el-upload-list--picture .el-upload-list__item-thumbnail{vertical-align:middle;display:inline-block;width:70px;height:70px;float:left;position:relative;z-index:1;margin-left:-80px;background-color:#FFF}.el-upload-list--picture .el-upload-list__item-name{display:block;margin-top:20px}.el-upload-list--picture .el-upload-list__item-name i{font-size:70px;line-height:1;position:absolute;left:9px;top:10px}.el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 1px 1px #ccc;box-shadow:0 1px 1px #ccc}.el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture .el-progress{position:relative;top:-7px}.el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.el-upload-cover::after{display:inline-block;height:100%;vertical-align:middle}.el-upload-cover img{display:block;width:100%;height:100%}.el-upload-cover__label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-cover__label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);color:#fff}.el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.el-upload-cover__progress+.el-upload__inner{opacity:0}.el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.72);text-align:center}.el-upload-cover__interact .btn{display:inline-block;color:#FFF;font-size:14px;cursor:pointer;vertical-align:middle;-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);margin-top:60px}.el-upload-cover__interact .btn span{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.el-upload-cover__interact .btn:hover{-webkit-transform:translateY(-13px);transform:translateY(-13px)}.el-upload-cover__interact .btn:hover span{opacity:1}.el-upload-cover__interact .btn i{color:#FFF;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#FFF;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:#303133}.el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67C23A}.el-progress.is-success .el-progress__text{color:#67C23A}.el-progress.is-warning .el-progress-bar__inner{background-color:#E6A23C}.el-badge__content,.el-progress.is-exception .el-progress-bar__inner{background-color:#F56C6C}.el-progress.is-warning .el-progress__text{color:#E6A23C}.el-progress.is-exception .el-progress__text{color:#F56C6C}.el-progress-bar{padding-right:50px;display:inline-block;vertical-align:middle;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__header,.el-message,.el-step__icon{-webkit-box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#EBEEF5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#409EFF;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-progress-bar__inner::after{display:inline-block;height:100%;vertical-align:middle}.el-progress-bar__innerText{display:inline-block;vertical-align:middle;color:#FFF;font-size:12px;margin:0 5px}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}.el-time-spinner{width:100%;white-space:nowrap}.el-spinner{display:inline-block;vertical-align:middle}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}.el-message{min-width:380px;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#EBEEF5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,transform .4s,top .4s;transition:opacity .3s,transform .4s,top .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67C23A}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#E6A23C}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#F56C6C}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__content:focus{outline-width:0}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#C0C4CC;font-size:16px}.el-message__closeBtn:focus{outline-width:0}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67C23A}.el-message .el-icon-error{color:#F56C6C}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#E6A23C}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}.el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{border-radius:10px;color:#FFF;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #FFF}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#409EFF}.el-badge__content--success{background-color:#67C23A}.el-badge__content--warning{background-color:#E6A23C}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#F56C6C}.el-card{border-radius:4px;border:1px solid #EBEEF5;background-color:#FFF;overflow:hidden;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #EBEEF5;box-sizing:border-box}.el-card__body,.el-main{padding:20px}.el-rate{height:20px;line-height:1}.el-rate:active,.el-rate:focus{outline-width:0}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#C0C4CC;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-step.is-vertical,.el-steps{display:-webkit-box;display:-ms-flexbox}.el-rate__text{font-size:14px;vertical-align:middle}.el-steps{display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#F5F7FA}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.el-step{position:relative;-ms-flex-negative:1;flex-shrink:1}.el-step:last-of-type .el-step__line{display:none}.el-step:last-of-type.is-flex{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.el-step:last-of-type .el-step__description,.el-step:last-of-type .el-step__main{padding-right:0}.el-step__head{position:relative;width:100%}.el-step__head.is-process{color:#303133;border-color:#303133}.el-step__head.is-wait{color:#C0C4CC;border-color:#C0C4CC}.el-step__head.is-success{color:#67C23A;border-color:#67C23A}.el-step__head.is-error{color:#F56C6C;border-color:#F56C6C}.el-step__head.is-finish{color:#409EFF;border-color:#409EFF}.el-step__icon{position:relative;z-index:1;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:24px;height:24px;font-size:14px;box-sizing:border-box;background:#FFF;-webkit-transition:.15s ease-out;transition:.15s ease-out}.el-step.is-horizontal,.el-step__icon-inner{display:inline-block}.el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.el-step__icon.is-icon{width:40px}.el-step__icon-inner{-webkit-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.el-step__icon-inner.is-status{-webkit-transform:translateY(1px);transform:translateY(1px)}.el-step__line{position:absolute;border-color:inherit;background-color:#C0C4CC}.el-step__line-inner{display:block;border-width:1px;border-style:solid;border-color:inherit;-webkit-transition:.15s ease-out;transition:.15s ease-out;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:0}.el-step__main{white-space:normal;text-align:left}.el-step__title{font-size:16px;line-height:38px}.el-step__title.is-process{font-weight:700;color:#303133}.el-step__title.is-wait{color:#C0C4CC}.el-step__title.is-success{color:#67C23A}.el-step__title.is-error{color:#F56C6C}.el-step__title.is-finish{color:#409EFF}.el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.el-step__description.is-process{color:#303133}.el-step__description.is-wait{color:#C0C4CC}.el-step__description.is-success{color:#67C23A}.el-step__description.is-error{color:#F56C6C}.el-step__description.is-finish{color:#409EFF}.el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.el-step.is-vertical{display:flex}.el-step.is-vertical .el-step__head{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:24px}.el-step.is-vertical .el-step__main{padding-left:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.el-step.is-vertical .el-step__icon.is-icon{width:24px}.el-step.is-center .el-step__head,.el-step.is-center .el-step__main{text-align:center}.el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.el-step.is-center .el-step__line{left:50%;right:-50%}.el-step.is-simple{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.el-step.is-simple .el-step__icon-inner.is-status{-webkit-transform:scale(.8) translateY(1px);transform:scale(.8) translateY(1px)}.el-step.is-simple .el-step__main{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.el-step.is-simple .el-step__arrow{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-step.is-simple .el-step__arrow::after,.el-step.is-simple .el-step__arrow::before{content:'';display:inline-block;position:absolute;height:15px;width:1px;background:#C0C4CC}.el-step.is-simple .el-step__arrow::before{-webkit-transform:rotate(-45deg) translateY(-4px);transform:rotate(-45deg) translateY(-4px);-webkit-transform-origin:0 0;transform-origin:0 0}.el-step.is-simple .el-step__arrow::after{-webkit-transform:rotate(45deg) translateY(4px);transform:rotate(45deg) translateY(4px);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.el-step.is-simple:last-of-type .el-step__arrow{display:none}.el-carousel{position:relative}.el-carousel--horizontal{overflow-x:hidden}.el-carousel--vertical{overflow-y:hidden}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#FFF;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;margin:0;padding:0;z-index:2}.el-carousel__indicators--horizontal{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-carousel__indicators--vertical{right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#C0C4CC;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{background-color:transparent;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{display:inline-block;padding:12px 4px}.el-carousel__indicator--vertical{padding:4px 12px}.el-carousel__indicator--vertical .el-carousel__button{width:2px;height:15px}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#FFF;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.el-carousel__item,.el-carousel__mask{height:100%;position:absolute;top:0;left:0}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#FFF;opacity:.24;-webkit-transition:.2s;transition:.2s}.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.el-fade-in-linear-enter,.el-fade-in-linear-leave,.el-fade-in-linear-leave-active{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.el-fade-in-enter,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter,.el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center top;transform-origin:center top}.el-zoom-in-top-enter,.el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.el-zoom-in-bottom-enter,.el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:top left;transform-origin:top left}.el-zoom-in-left-enter,.el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.el-list-enter,.el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.el-collapse{border-top:1px solid #EBEEF5;border-bottom:1px solid #EBEEF5}.el-collapse-item.is-disabled .el-collapse-item__header{color:#bbb;cursor:not-allowed}.el-collapse-item__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:48px;line-height:48px;background-color:#FFF;color:#303133;cursor:pointer;border-bottom:1px solid #EBEEF5;font-size:13px;font-weight:500;-webkit-transition:border-bottom-color .3s;transition:border-bottom-color .3s;outline:0}.el-collapse-item__arrow{margin:0 8px 0 auto;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-weight:300}.el-collapse-item__arrow.is-active{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-collapse-item__header.focusing:focus:not(:hover){color:#409EFF}.el-collapse-item__header.is-active{border-bottom-color:transparent}.el-collapse-item__wrap{will-change:height;background-color:#FFF;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;border-bottom:1px solid #EBEEF5}.el-cascader__search-input,.el-cascader__tags,.el-tag{-webkit-box-sizing:border-box}.el-collapse-item__content{padding-bottom:25px;font-size:13px;color:#303133;line-height:1.769230769230769}.el-collapse-item:last-child{margin-bottom:-1px}.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-cascader,.el-tag{display:inline-block}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#EBEEF5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#FFF;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#EBEEF5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#FFF}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#EBEEF5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#FFF;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#EBEEF5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#FFF}.el-tag{background-color:#ecf5ff;border-color:#d9ecff;height:32px;padding:0 10px;line-height:30px;font-size:12px;color:#409EFF;border-width:1px;border-style:solid;border-radius:4px;box-sizing:border-box;white-space:nowrap}.el-tag.is-hit{border-color:#409EFF}.el-tag .el-tag__close{color:#409eff}.el-tag .el-tag__close:hover{color:#FFF;background-color:#409eff}.el-tag.el-tag--info{background-color:#f4f4f5;border-color:#e9e9eb;color:#909399}.el-tag.el-tag--info.is-hit{border-color:#909399}.el-tag.el-tag--info .el-tag__close{color:#909399}.el-tag.el-tag--info .el-tag__close:hover{color:#FFF;background-color:#909399}.el-tag.el-tag--success{background-color:#f0f9eb;border-color:#e1f3d8;color:#67c23a}.el-tag.el-tag--success.is-hit{border-color:#67C23A}.el-tag.el-tag--success .el-tag__close{color:#67c23a}.el-tag.el-tag--success .el-tag__close:hover{color:#FFF;background-color:#67c23a}.el-tag.el-tag--warning{background-color:#fdf6ec;border-color:#faecd8;color:#e6a23c}.el-tag.el-tag--warning.is-hit{border-color:#E6A23C}.el-tag.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag.el-tag--warning .el-tag__close:hover{color:#FFF;background-color:#e6a23c}.el-tag.el-tag--danger{background-color:#fef0f0;border-color:#fde2e2;color:#f56c6c}.el-tag.el-tag--danger.is-hit{border-color:#F56C6C}.el-tag.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag.el-tag--danger .el-tag__close:hover{color:#FFF;background-color:#f56c6c}.el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:16px;width:16px;line-height:16px;vertical-align:middle;top:-1px;right:-5px}.el-tag .el-icon-close::before{display:block}.el-tag--dark{background-color:#409eff;border-color:#409eff;color:#fff}.el-tag--dark.is-hit{border-color:#409EFF}.el-tag--dark .el-tag__close{color:#fff}.el-tag--dark .el-tag__close:hover{color:#FFF;background-color:#66b1ff}.el-tag--dark.el-tag--info{background-color:#909399;border-color:#909399;color:#fff}.el-tag--dark.el-tag--info.is-hit{border-color:#909399}.el-tag--dark.el-tag--info .el-tag__close{color:#fff}.el-tag--dark.el-tag--info .el-tag__close:hover{color:#FFF;background-color:#a6a9ad}.el-tag--dark.el-tag--success{background-color:#67c23a;border-color:#67c23a;color:#fff}.el-tag--dark.el-tag--success.is-hit{border-color:#67C23A}.el-tag--dark.el-tag--success .el-tag__close{color:#fff}.el-tag--dark.el-tag--success .el-tag__close:hover{color:#FFF;background-color:#85ce61}.el-tag--dark.el-tag--warning{background-color:#e6a23c;border-color:#e6a23c;color:#fff}.el-tag--dark.el-tag--warning.is-hit{border-color:#E6A23C}.el-tag--dark.el-tag--warning .el-tag__close{color:#fff}.el-tag--dark.el-tag--warning .el-tag__close:hover{color:#FFF;background-color:#ebb563}.el-tag--dark.el-tag--danger{background-color:#f56c6c;border-color:#f56c6c;color:#fff}.el-tag--dark.el-tag--danger.is-hit{border-color:#F56C6C}.el-tag--dark.el-tag--danger .el-tag__close{color:#fff}.el-tag--dark.el-tag--danger .el-tag__close:hover{color:#FFF;background-color:#f78989}.el-tag--plain{background-color:#fff;border-color:#b3d8ff;color:#409eff}.el-tag--plain.is-hit{border-color:#409EFF}.el-tag--plain .el-tag__close{color:#409eff}.el-tag--plain .el-tag__close:hover{color:#FFF;background-color:#409eff}.el-tag--plain.el-tag--info{background-color:#fff;border-color:#d3d4d6;color:#909399}.el-tag--plain.el-tag--info.is-hit{border-color:#909399}.el-tag--plain.el-tag--info .el-tag__close{color:#909399}.el-tag--plain.el-tag--info .el-tag__close:hover{color:#FFF;background-color:#909399}.el-tag--plain.el-tag--success{background-color:#fff;border-color:#c2e7b0;color:#67c23a}.el-tag--plain.el-tag--success.is-hit{border-color:#67C23A}.el-tag--plain.el-tag--success .el-tag__close{color:#67c23a}.el-tag--plain.el-tag--success .el-tag__close:hover{color:#FFF;background-color:#67c23a}.el-tag--plain.el-tag--warning{background-color:#fff;border-color:#f5dab1;color:#e6a23c}.el-tag--plain.el-tag--warning.is-hit{border-color:#E6A23C}.el-tag--plain.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag--plain.el-tag--warning .el-tag__close:hover{color:#FFF;background-color:#e6a23c}.el-tag--plain.el-tag--danger{background-color:#fff;border-color:#fbc4c4;color:#f56c6c}.el-tag--plain.el-tag--danger.is-hit{border-color:#F56C6C}.el-tag--plain.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag--plain.el-tag--danger .el-tag__close:hover{color:#FFF;background-color:#f56c6c}.el-tag--medium{height:28px;line-height:26px}.el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--small{height:24px;padding:0 8px;line-height:22px}.el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--mini{height:20px;padding:0 5px;line-height:19px}.el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.el-cascader{position:relative;font-size:14px;line-height:40px}.el-cascader:not(.is-disabled):hover .el-input__inner{cursor:pointer;border-color:#C0C4CC}.el-cascader .el-input .el-input__inner:focus,.el-cascader .el-input.is-focus .el-input__inner{border-color:#409EFF}.el-cascader .el-input{cursor:pointer}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis}.el-cascader .el-input .el-icon-arrow-down{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:14px}.el-cascader .el-input .el-icon-arrow-down.is-reverse{-webkit-transform:rotateZ(180deg);transform:rotateZ(180deg)}.el-cascader .el-input .el-icon-circle-close:hover{color:#909399}.el-cascader--medium{font-size:14px;line-height:36px}.el-cascader--small{font-size:13px;line-height:32px}.el-cascader--mini{font-size:12px;line-height:28px}.el-cascader.is-disabled .el-cascader__label{z-index:2;color:#C0C4CC}.el-cascader__dropdown{margin:5px 0;font-size:14px;background:#FFF;border:1px solid #E4E7ED;border-radius:4px;box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-cascader__tags{position:absolute;left:0;right:30px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:normal;text-align:left;box-sizing:border-box}.el-cascader__tags .el-tag{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:#f0f2f5}.el-cascader__tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__tags .el-tag>span{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__tags .el-tag .el-icon-close{-webkit-box-flex:0;-ms-flex:none;flex:none;background-color:#C0C4CC;color:#FFF}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:#909399}.el-cascader__suggestion-panel{border-radius:4px}.el-cascader__suggestion-list{max-height:204px;margin:0;padding:6px 0;font-size:14px;color:#606266;text-align:center}.el-cascader__suggestion-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;padding:0 15px;text-align:left;outline:0;cursor:pointer}.el-cascader__suggestion-item:focus,.el-cascader__suggestion-item:hover{background:#F5F7FA}.el-cascader__suggestion-item.is-checked{color:#409EFF;font-weight:700}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__empty-text{margin:10px 0;color:#C0C4CC}.el-cascader__search-input{-webkit-box-flex:1;-ms-flex:1;flex:1;height:24px;min-width:60px;margin:2px 0 2px 15px;padding:0;color:#606266;border:none;outline:0;box-sizing:border-box}.el-cascader__search-input::-webkit-input-placeholder{color:#C0C4CC}.el-cascader__search-input:-ms-input-placeholder{color:#C0C4CC}.el-cascader__search-input::-ms-input-placeholder{color:#C0C4CC}.el-cascader__search-input::placeholder{color:#C0C4CC}.el-color-predefine{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:12px;margin-top:8px;width:280px}.el-color-predefine__colors{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-color-predefine__color-selector{margin:0 0 8px 8px;width:20px;height:20px;border-radius:4px;cursor:pointer}.el-color-predefine__color-selector:nth-child(10n+1){margin-left:0}.el-color-predefine__color-selector.selected{-webkit-box-shadow:0 0 3px 2px #409EFF;box-shadow:0 0 3px 2px #409EFF}.el-color-predefine__color-selector>div{display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;border-radius:3px}.el-color-predefine__color-selector.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-hue-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px}.el-color-hue-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);height:100%}.el-color-hue-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(to bottom,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}.el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-svpanel{position:relative;width:280px;height:180px}.el-color-svpanel__black,.el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.el-color-svpanel__white{background:-webkit-gradient(linear,left top,right top,from(#fff),to(rgba(255,255,255,0)));background:linear-gradient(to right,#fff,rgba(255,255,255,0))}.el-color-svpanel__black{background:-webkit-gradient(linear,left bottom,left top,from(#000),to(rgba(0,0,0,0)));background:linear-gradient(to top,#000,rgba(0,0,0,0))}.el-color-svpanel__cursor{position:absolute}.el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;-webkit-box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-2px,-2px);transform:translate(-2px,-2px)}.el-color-alpha-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-alpha-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%);height:100%}.el-color-alpha-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-alpha-slider.is-vertical{width:20px;height:180px}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:-webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0)),to(white));background:linear-gradient(to bottom,rgba(255,255,255,0) 0,#fff 100%)}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-dropdown{width:300px}.el-color-dropdown__main-wrapper{margin-bottom:6px}.el-color-dropdown__main-wrapper::after{display:table;clear:both}.el-color-dropdown__btns{margin-top:6px;text-align:right}.el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.el-color-dropdown__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-color-dropdown__btn[disabled]{color:#ccc;cursor:not-allowed}.el-color-dropdown__btn:hover{color:#409EFF;border-color:#409EFF}.el-color-dropdown__link-btn{cursor:pointer;color:#409EFF;text-decoration:none;padding:15px;font-size:12px}.el-color-dropdown__link-btn:hover{color:tint(#409EFF,20%)}.el-color-picker{display:inline-block;position:relative;line-height:normal;height:40px}.el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.el-color-picker--medium{height:36px}.el-color-picker--medium .el-color-picker__trigger{height:36px;width:36px}.el-color-picker--medium .el-color-picker__mask{height:34px;width:34px}.el-color-picker--small{height:32px}.el-color-picker--small .el-color-picker__trigger{height:32px;width:32px}.el-color-picker--small .el-color-picker__mask{height:30px;width:30px}.el-color-picker--small .el-color-picker__empty,.el-color-picker--small .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker--mini{height:28px}.el-color-picker--mini .el-color-picker__trigger{height:28px;width:28px}.el-color-picker--mini .el-color-picker__mask{height:26px;width:26px}.el-color-picker--mini .el-color-picker__empty,.el-color-picker--mini .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker__mask{height:38px;width:38px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:rgba(255,255,255,.7)}.el-color-picker__trigger{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px;width:40px;padding:4px;border:1px solid #e6e6e6;border-radius:4px;font-size:0;position:relative;cursor:pointer}.el-color-picker__color{position:relative;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #999;border-radius:2px;width:100%;height:100%;text-align:center}.el-color-picker__icon,.el-input,.el-textarea{display:inline-block;width:100%}.el-color-picker__color.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-picker__color-inner{position:absolute;left:0;top:0;right:0;bottom:0}.el-color-picker__empty{font-size:12px;color:#999;position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.el-color-picker__icon{position:absolute;top:50%;left:50%;-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0);color:#FFF;text-align:center;font-size:12px}.el-input__prefix,.el-input__suffix{position:absolute;top:0;text-align:center}.el-color-picker__panel{position:absolute;z-index:10;padding:6px;-webkit-box-sizing:content-box;box-sizing:content-box;background-color:#FFF;border:1px solid #EBEEF5;border-radius:4px;box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-input__inner,.el-textarea__inner,.el-transfer-panel{-webkit-box-sizing:border-box}.el-textarea{position:relative;vertical-align:bottom;font-size:14px}.el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;box-sizing:border-box;width:100%;font-size:inherit;color:#606266;background-color:#FFF;background-image:none;border:1px solid #DCDFE6;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-textarea__inner::-webkit-input-placeholder{color:#C0C4CC}.el-textarea__inner:-ms-input-placeholder{color:#C0C4CC}.el-textarea__inner::-ms-input-placeholder{color:#C0C4CC}.el-textarea__inner::placeholder{color:#C0C4CC}.el-textarea__inner:hover{border-color:#C0C4CC}.el-textarea__inner:focus{outline:0;border-color:#409EFF}.el-textarea .el-input__count{color:#909399;background:#FFF;position:absolute;font-size:12px;bottom:5px;right:10px}.el-textarea.is-disabled .el-textarea__inner{background-color:#F5F7FA;border-color:#E4E7ED;color:#C0C4CC;cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#C0C4CC}.el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#C0C4CC}.el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#C0C4CC}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:#C0C4CC}.el-textarea.is-exceed .el-textarea__inner{border-color:#F56C6C}.el-textarea.is-exceed .el-input__count{color:#F56C6C}.el-input{position:relative;font-size:14px}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.el-input::-webkit-scrollbar-corner{background:#fff}.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.el-input .el-input__clear{color:#C0C4CC;font-size:14px;cursor:pointer;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-input .el-input__clear:hover{color:#909399}.el-input .el-input__count{height:100%;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#909399;font-size:12px}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input,.el-input__inner{font-size:inherit}.el-input .el-input__count .el-input__count-inner{background:#FFF;line-height:initial;display:inline-block;padding:0 5px}.el-input__inner{-webkit-appearance:none;background-color:#FFF;background-image:none;border-radius:4px;border:1px solid #DCDFE6;box-sizing:border-box;color:#606266;display:inline-block;height:40px;line-height:40px;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.el-input__inner::-ms-reveal{display:none}.el-input__inner::-webkit-input-placeholder{color:#C0C4CC}.el-input__inner:-ms-input-placeholder{color:#C0C4CC}.el-input__inner::-ms-input-placeholder{color:#C0C4CC}.el-input__inner::placeholder{color:#C0C4CC}.el-input__inner:hover{border-color:#C0C4CC}.el-input.is-active .el-input__inner,.el-input__inner:focus{border-color:#409EFF;outline:0}.el-input__suffix{height:100%;right:5px;transition:all .3s;pointer-events:none}.el-input__suffix-inner{pointer-events:all}.el-input__prefix{height:100%;left:5px;transition:all .3s}.el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.el-input__validateIcon{pointer-events:none}.el-input.is-disabled .el-input__inner{background-color:#F5F7FA;border-color:#E4E7ED;color:#C0C4CC;cursor:not-allowed}.el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#C0C4CC}.el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#C0C4CC}.el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#C0C4CC}.el-input.is-disabled .el-input__inner::placeholder{color:#C0C4CC}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-image-viewer__btn,.el-image__preview,.el-link,.el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.el-input.is-exceed .el-input__inner{border-color:#F56C6C}.el-input.is-exceed .el-input__suffix .el-input__count{color:#F56C6C}.el-input--suffix .el-input__inner{padding-right:30px}.el-input--prefix .el-input__inner{padding-left:30px}.el-input--medium{font-size:14px}.el-input--medium .el-input__inner{height:36px;line-height:36px}.el-input--medium .el-input__icon{line-height:36px}.el-input--small{font-size:13px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input--small .el-input__icon{line-height:32px}.el-input--mini{font-size:12px}.el-input--mini .el-input__inner{height:28px;line-height:28px}.el-input--mini .el-input__icon{line-height:28px}.el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate;border-spacing:0}.el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.el-input-group__append,.el-input-group__prepend{background-color:#F5F7FA;color:#909399;vertical-align:middle;display:table-cell;position:relative;border:1px solid #DCDFE6;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.el-input-group--append .el-input__inner,.el-input-group__prepend{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:0}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:-10px -20px}.el-input-group__append button.el-button,.el-input-group__append div.el-select .el-input__inner,.el-input-group__append div.el-select:hover .el-input__inner,.el-input-group__prepend button.el-button,.el-input-group__prepend div.el-select .el-input__inner,.el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.el-input-group__prepend{border-right:0}.el-input-group__append{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--append .el-select .el-input.is-focus .el-input__inner,.el-input-group--prepend .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input-group--prepend .el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.el-input__inner::-ms-clear{display:none;width:0;height:0}.el-transfer{font-size:14px}.el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.el-transfer__button{display:block;margin:0 auto;padding:10px;border-radius:50%;color:#FFF;background-color:#409EFF;font-size:0}.el-button-group>.el-button+.el-button,.el-transfer-panel__item+.el-transfer-panel__item,.el-transfer__button [class*=el-icon-]+span{margin-left:0}.el-divider__text,.el-image__error,.el-link,.el-timeline,.el-transfer__button i,.el-transfer__button span{font-size:14px}.el-transfer__button.is-with-texts{border-radius:4px}.el-transfer__button.is-disabled,.el-transfer__button.is-disabled:hover{border:1px solid #DCDFE6;background-color:#F5F7FA;color:#C0C4CC}.el-transfer__button:first-child{margin-bottom:10px}.el-transfer__button:nth-child(2){margin:0}.el-transfer-panel{border:1px solid #EBEEF5;border-radius:4px;overflow:hidden;background:#FFF;display:inline-block;vertical-align:middle;width:200px;max-height:100%;box-sizing:border-box;position:relative}.el-transfer-panel__body{height:246px}.el-transfer-panel__body.is-with-footer{padding-bottom:40px}.el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:246px;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.el-transfer-panel__list.is-filterable{height:194px;padding-top:0}.el-transfer-panel__item{height:30px;line-height:30px;padding-left:15px;display:block!important}.el-transfer-panel__item.el-checkbox{color:#606266}.el-transfer-panel__item:hover{color:#409EFF}.el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:24px;line-height:30px}.el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.el-transfer-panel__filter{text-align:center;margin:15px;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;width:auto}.el-transfer-panel__filter .el-input__inner{height:32px;width:100%;font-size:12px;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:16px;padding-right:10px;padding-left:30px}.el-transfer-panel__filter .el-input__icon{margin-left:5px}.el-transfer-panel .el-transfer-panel__header{height:40px;line-height:40px;background:#F5F7FA;margin:0;padding-left:15px;border-bottom:1px solid #EBEEF5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#000}.el-container,.el-header{-webkit-box-sizing:border-box}.el-transfer-panel .el-transfer-panel__header .el-checkbox{display:block;line-height:40px}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:#303133;font-weight:400}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;color:#909399;font-size:12px;font-weight:400}.el-transfer-panel .el-transfer-panel__footer{height:40px;background:#FFF;margin:0;padding:0;border-top:1px solid #EBEEF5;position:absolute;bottom:0;left:0;width:100%;z-index:1}.el-transfer-panel .el-transfer-panel__footer::after{display:inline-block;height:100%;vertical-align:middle}.el-container,.el-timeline-item__node{display:-webkit-box;display:-ms-flexbox}.el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:#606266}.el-transfer-panel .el-transfer-panel__empty{margin:0;height:30px;line-height:30px;padding:6px 15px 0;color:#909399;text-align:center}.el-transfer-panel .el-checkbox__label{padding-left:8px}.el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.el-transfer-panel .el-checkbox__inner::after{height:6px;width:3px;left:4px}.el-container{display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;box-sizing:border-box;min-width:0}.el-container.is-vertical,.el-drawer,.el-empty,.el-result{-webkit-box-orient:vertical;-webkit-box-direction:normal}.el-container.is-vertical{-ms-flex-direction:column;flex-direction:column}.el-header{padding:0 20px;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}.el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}.el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}.el-timeline{margin:0;list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}.el-timeline-item{position:relative;padding-bottom:20px}.el-timeline-item__wrapper{position:relative;padding-left:28px;top:-3px}.el-timeline-item__tail{position:absolute;left:4px;height:100%;border-left:2px solid #E4E7ED}.el-timeline-item__icon{color:#FFF;font-size:13px}.el-timeline-item__node{position:absolute;background-color:#E4E7ED;border-radius:50%;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image__error,.el-timeline-item__dot{display:-webkit-box;display:-ms-flexbox}.el-timeline-item__node--normal{left:-1px;width:12px;height:12px}.el-timeline-item__node--large{left:-2px;width:14px;height:14px}.el-timeline-item__node--primary{background-color:#409EFF}.el-timeline-item__node--success{background-color:#67C23A}.el-timeline-item__node--warning{background-color:#E6A23C}.el-timeline-item__node--danger{background-color:#F56C6C}.el-timeline-item__node--info{background-color:#909399}.el-timeline-item__dot{position:absolute;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-timeline-item__content{color:#303133}.el-timeline-item__timestamp{color:#909399;line-height:1;font-size:13px}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}.el-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;vertical-align:middle;position:relative;text-decoration:none;outline:0;padding:0;font-weight:500}.el-link.is-underline:hover:after{position:absolute;left:0;right:0;height:0;bottom:0;border-bottom:1px solid #409EFF}.el-link.el-link--default:after,.el-link.el-link--primary.is-underline:hover:after,.el-link.el-link--primary:after{border-color:#409EFF}.el-link.is-disabled{cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default{color:#606266}.el-link.el-link--default:hover{color:#409EFF}.el-link.el-link--default.is-disabled{color:#C0C4CC}.el-link.el-link--primary{color:#409EFF}.el-link.el-link--primary:hover{color:#66b1ff}.el-link.el-link--primary.is-disabled{color:#a0cfff}.el-link.el-link--danger.is-underline:hover:after,.el-link.el-link--danger:after{border-color:#F56C6C}.el-link.el-link--danger{color:#F56C6C}.el-link.el-link--danger:hover{color:#f78989}.el-link.el-link--danger.is-disabled{color:#fab6b6}.el-link.el-link--success.is-underline:hover:after,.el-link.el-link--success:after{border-color:#67C23A}.el-link.el-link--success{color:#67C23A}.el-link.el-link--success:hover{color:#85ce61}.el-link.el-link--success.is-disabled{color:#b3e19d}.el-link.el-link--warning.is-underline:hover:after,.el-link.el-link--warning:after{border-color:#E6A23C}.el-link.el-link--warning{color:#E6A23C}.el-link.el-link--warning:hover{color:#ebb563}.el-link.el-link--warning.is-disabled{color:#f3d19e}.el-link.el-link--info.is-underline:hover:after,.el-link.el-link--info:after{border-color:#909399}.el-link.el-link--info{color:#909399}.el-link.el-link--info:hover{color:#a6a9ad}.el-link.el-link--info.is-disabled{color:#c8c9cc}.el-divider{background-color:#DCDFE6;position:relative}.el-divider--horizontal{display:block;height:1px;width:100%;margin:24px 0}.el-divider--vertical{display:inline-block;width:1px;height:1em;margin:0 8px;vertical-align:middle;position:relative}.el-divider__text{position:absolute;background-color:#FFF;padding:0 20px;font-weight:500;color:#303133}.el-image__error,.el-image__placeholder{background:#F5F7FA}.el-divider__text.is-left{left:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-divider__text.is-center{left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.el-divider__text.is-right{right:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-image__error,.el-image__inner,.el-image__placeholder{width:100%;height:100%}.el-image{position:relative;display:inline-block;overflow:hidden}.el-image__inner{vertical-align:top}.el-image__inner--center{position:relative;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:block}.el-image__error{display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#C0C4CC;vertical-align:middle}.el-image-viewer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0}.el-image-viewer__btn{position:absolute;z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;border-radius:50%;opacity:.8;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-user-select:none;user-select:none}.el-button,.el-checkbox,.el-checkbox-button__inner,.el-empty__image img,.el-radio{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-image-viewer__close{top:40px;right:40px;width:40px;height:40px;font-size:24px;color:#fff;background-color:#606266}.el-image-viewer__canvas{width:100%;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image-viewer__actions{left:50%;bottom:30px;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:282px;height:44px;padding:0 23px;background-color:#606266;border-color:#fff;border-radius:22px}.el-image-viewer__actions__inner{width:100%;height:100%;text-align:justify;cursor:default;font-size:23px;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.el-image-viewer__next,.el-image-viewer__prev{width:44px;height:44px;font-size:24px;color:#fff;background-color:#606266;border-color:#fff;top:50%}.el-image-viewer__prev{-webkit-transform:translateY(-50%);transform:translateY(-50%);left:40px}.el-image-viewer__next{-webkit-transform:translateY(-50%);transform:translateY(-50%);right:40px;text-indent:2px}.el-image-viewer__mask{position:absolute;width:100%;height:100%;top:0;left:0;opacity:.5;background:#000}.viewer-fade-enter-active{-webkit-animation:viewer-fade-in .3s;animation:viewer-fade-in .3s}.viewer-fade-leave-active{-webkit-animation:viewer-fade-out .3s;animation:viewer-fade-out .3s}@-webkit-keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes viewer-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes viewer-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#FFF;border:1px solid #DCDFE6;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;padding:12px 20px;font-size:14px;border-radius:4px}.el-button+.el-button,.el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.el-button:focus,.el-button:hover{color:#409EFF;border-color:#c6e2ff;background-color:#ecf5ff}.el-button:active{color:#3a8ee6;border-color:#3a8ee6;outline:0}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon-]+span{margin-left:5px}.el-button.is-plain:focus,.el-button.is-plain:hover{background:#FFF;border-color:#409EFF;color:#409EFF}.el-button.is-active,.el-button.is-plain:active{color:#3a8ee6;border-color:#3a8ee6}.el-button.is-plain:active{background:#FFF;outline:0}.el-button.is-disabled,.el-button.is-disabled:focus,.el-button.is-disabled:hover{color:#C0C4CC;cursor:not-allowed;background-image:none;background-color:#FFF;border-color:#EBEEF5}.el-button.is-disabled.el-button--text{background-color:transparent}.el-button.is-disabled.is-plain,.el-button.is-disabled.is-plain:focus,.el-button.is-disabled.is-plain:hover{background-color:#FFF;border-color:#EBEEF5;color:#C0C4CC}.el-button.is-loading{position:relative;pointer-events:none}.el-button.is-loading:before{pointer-events:none;content:'';position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:rgba(255,255,255,.35)}.el-button.is-round{border-radius:20px;padding:12px 23px}.el-button.is-circle{border-radius:50%;padding:12px}.el-button--primary{color:#FFF;background-color:#409EFF;border-color:#409EFF}.el-button--primary:focus,.el-button--primary:hover{background:#66b1ff;border-color:#66b1ff;color:#FFF}.el-button--primary.is-active,.el-button--primary:active{background:#3a8ee6;border-color:#3a8ee6;color:#FFF}.el-button--primary:active{outline:0}.el-button--primary.is-disabled,.el-button--primary.is-disabled:active,.el-button--primary.is-disabled:focus,.el-button--primary.is-disabled:hover{color:#FFF;background-color:#a0cfff;border-color:#a0cfff}.el-button--primary.is-plain{color:#409EFF;background:#ecf5ff;border-color:#b3d8ff}.el-button--primary.is-plain:focus,.el-button--primary.is-plain:hover{background:#409EFF;border-color:#409EFF;color:#FFF}.el-button--primary.is-plain:active{background:#3a8ee6;border-color:#3a8ee6;color:#FFF;outline:0}.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover{color:#8cc5ff;background-color:#ecf5ff;border-color:#d9ecff}.el-button--success{color:#FFF;background-color:#67C23A;border-color:#67C23A}.el-button--success:focus,.el-button--success:hover{background:#85ce61;border-color:#85ce61;color:#FFF}.el-button--success.is-active,.el-button--success:active{background:#5daf34;border-color:#5daf34;color:#FFF}.el-button--success:active{outline:0}.el-button--success.is-disabled,.el-button--success.is-disabled:active,.el-button--success.is-disabled:focus,.el-button--success.is-disabled:hover{color:#FFF;background-color:#b3e19d;border-color:#b3e19d}.el-button--success.is-plain{color:#67C23A;background:#f0f9eb;border-color:#c2e7b0}.el-button--success.is-plain:focus,.el-button--success.is-plain:hover{background:#67C23A;border-color:#67C23A;color:#FFF}.el-button--success.is-plain:active{background:#5daf34;border-color:#5daf34;color:#FFF;outline:0}.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover{color:#a4da89;background-color:#f0f9eb;border-color:#e1f3d8}.el-button--warning{color:#FFF;background-color:#E6A23C;border-color:#E6A23C}.el-button--warning:focus,.el-button--warning:hover{background:#ebb563;border-color:#ebb563;color:#FFF}.el-button--warning.is-active,.el-button--warning:active{background:#cf9236;border-color:#cf9236;color:#FFF}.el-button--warning:active{outline:0}.el-button--warning.is-disabled,.el-button--warning.is-disabled:active,.el-button--warning.is-disabled:focus,.el-button--warning.is-disabled:hover{color:#FFF;background-color:#f3d19e;border-color:#f3d19e}.el-button--warning.is-plain{color:#E6A23C;background:#fdf6ec;border-color:#f5dab1}.el-button--warning.is-plain:focus,.el-button--warning.is-plain:hover{background:#E6A23C;border-color:#E6A23C;color:#FFF}.el-button--warning.is-plain:active{background:#cf9236;border-color:#cf9236;color:#FFF;outline:0}.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover{color:#f0c78a;background-color:#fdf6ec;border-color:#faecd8}.el-button--danger{color:#FFF;background-color:#F56C6C;border-color:#F56C6C}.el-button--danger:focus,.el-button--danger:hover{background:#f78989;border-color:#f78989;color:#FFF}.el-button--danger.is-active,.el-button--danger:active{background:#dd6161;border-color:#dd6161;color:#FFF}.el-button--danger:active{outline:0}.el-button--danger.is-disabled,.el-button--danger.is-disabled:active,.el-button--danger.is-disabled:focus,.el-button--danger.is-disabled:hover{color:#FFF;background-color:#fab6b6;border-color:#fab6b6}.el-button--danger.is-plain{color:#F56C6C;background:#fef0f0;border-color:#fbc4c4}.el-button--danger.is-plain:focus,.el-button--danger.is-plain:hover{background:#F56C6C;border-color:#F56C6C;color:#FFF}.el-button--danger.is-plain:active{background:#dd6161;border-color:#dd6161;color:#FFF;outline:0}.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover{color:#f9a7a7;background-color:#fef0f0;border-color:#fde2e2}.el-button--info{color:#FFF;background-color:#909399;border-color:#909399}.el-button--info:focus,.el-button--info:hover{background:#a6a9ad;border-color:#a6a9ad;color:#FFF}.el-button--info.is-active,.el-button--info:active{background:#82848a;border-color:#82848a;color:#FFF}.el-button--info:active{outline:0}.el-button--info.is-disabled,.el-button--info.is-disabled:active,.el-button--info.is-disabled:focus,.el-button--info.is-disabled:hover{color:#FFF;background-color:#c8c9cc;border-color:#c8c9cc}.el-button--info.is-plain{color:#909399;background:#f4f4f5;border-color:#d3d4d6}.el-button--info.is-plain:focus,.el-button--info.is-plain:hover{background:#909399;border-color:#909399;color:#FFF}.el-button--info.is-plain:active{background:#82848a;border-color:#82848a;color:#FFF;outline:0}.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover{color:#bcbec2;background-color:#f4f4f5;border-color:#e9e9eb}.el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.el-button--medium.is-round{padding:10px 20px}.el-button--medium.is-circle{padding:10px}.el-button--small{padding:9px 15px;font-size:12px;border-radius:3px}.el-button--small.is-round{padding:9px 15px}.el-button--small.is-circle{padding:9px}.el-button--mini,.el-button--mini.is-round{padding:7px 15px}.el-button--mini{font-size:12px;border-radius:3px}.el-button--mini.is-circle{padding:7px}.el-button--text{border-color:transparent;color:#409EFF;background:0 0;padding-left:0;padding-right:0}.el-button--text:focus,.el-button--text:hover{color:#66b1ff;border-color:transparent;background-color:transparent}.el-button--text:active{color:#3a8ee6;border-color:transparent;background-color:transparent}.el-button--text.is-disabled,.el-button--text.is-disabled:focus,.el-button--text.is-disabled:hover{border-color:transparent}.el-button-group .el-button--danger:last-child,.el-button-group .el-button--danger:not(:first-child):not(:last-child),.el-button-group .el-button--info:last-child,.el-button-group .el-button--info:not(:first-child):not(:last-child),.el-button-group .el-button--primary:last-child,.el-button-group .el-button--primary:not(:first-child):not(:last-child),.el-button-group .el-button--success:last-child,.el-button-group .el-button--success:not(:first-child):not(:last-child),.el-button-group .el-button--warning:last-child,.el-button-group .el-button--warning:not(:first-child):not(:last-child),.el-button-group>.el-dropdown>.el-button{border-left-color:rgba(255,255,255,.5)}.el-button-group .el-button--danger:first-child,.el-button-group .el-button--danger:not(:first-child):not(:last-child),.el-button-group .el-button--info:first-child,.el-button-group .el-button--info:not(:first-child):not(:last-child),.el-button-group .el-button--primary:first-child,.el-button-group .el-button--primary:not(:first-child):not(:last-child),.el-button-group .el-button--success:first-child,.el-button-group .el-button--success:not(:first-child):not(:last-child),.el-button-group .el-button--warning:first-child,.el-button-group .el-button--warning:not(:first-child):not(:last-child){border-right-color:rgba(255,255,255,.5)}.el-button-group{display:inline-block;vertical-align:middle}.el-button-group::after,.el-button-group::before{display:table}.el-button-group::after{clear:both}.el-button-group>.el-button{float:left;position:relative}.el-button-group>.el-button.is-disabled{z-index:1}.el-button-group>.el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.el-button-group>.el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group>.el-button:first-child:last-child{border-radius:4px}.el-button-group>.el-button:first-child:last-child.is-round{border-radius:20px}.el-button-group>.el-button:first-child:last-child.is-circle{border-radius:50%}.el-button-group>.el-button:not(:first-child):not(:last-child){border-radius:0}.el-button-group>.el-button.is-active,.el-button-group>.el-button:not(.is-disabled):active,.el-button-group>.el-button:not(.is-disabled):focus,.el-button-group>.el-button:not(.is-disabled):hover{z-index:1}.el-button-group>.el-dropdown>.el-button{border-top-left-radius:0;border-bottom-left-radius:0}.el-calendar{background-color:#fff}.el-calendar__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:12px 20px;border-bottom:1px solid #EBEEF5}.el-backtop,.el-page-header{display:-webkit-box;display:-ms-flexbox}.el-calendar__title{color:#000;-ms-flex-item-align:center;align-self:center}.el-calendar__body{padding:12px 20px 35px}.el-calendar-table{table-layout:fixed;width:100%}.el-calendar-table thead th{padding:12px 0;color:#606266;font-weight:400}.el-calendar-table:not(.is-range) td.next,.el-calendar-table:not(.is-range) td.prev{color:#C0C4CC}.el-backtop,.el-calendar-table td.is-today{color:#409EFF}.el-calendar-table td{border-bottom:1px solid #EBEEF5;border-right:1px solid #EBEEF5;vertical-align:top;-webkit-transition:background-color .2s ease;transition:background-color .2s ease}.el-calendar-table td.is-selected{background-color:#F2F8FE}.el-calendar-table tr:first-child td{border-top:1px solid #EBEEF5}.el-calendar-table tr td:first-child{border-left:1px solid #EBEEF5}.el-calendar-table tr.el-calendar-table__row--hide-border td{border-top:none}.el-calendar-table .el-calendar-day{-webkit-box-sizing:border-box;box-sizing:border-box;padding:8px;height:85px}.el-calendar-table .el-calendar-day:hover{cursor:pointer;background-color:#F2F8FE}.el-backtop{position:fixed;background-color:#FFF;width:40px;height:40px;border-radius:50%;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:20px;-webkit-box-shadow:0 0 6px rgba(0,0,0,.12);box-shadow:0 0 6px rgba(0,0,0,.12);cursor:pointer;z-index:5}.el-backtop:hover{background-color:#F2F6FC}.el-page-header{display:flex;line-height:24px}.el-page-header__left{display:-webkit-box;display:-ms-flexbox;display:flex;cursor:pointer;margin-right:40px;position:relative}.el-page-header__left::after{position:absolute;width:1px;height:16px;right:-20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);background-color:#DCDFE6}.el-checkbox,.el-checkbox__input{display:inline-block;position:relative;white-space:nowrap}.el-page-header__left .el-icon-back{font-size:18px;margin-right:6px;-ms-flex-item-align:center;align-self:center}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{font-size:18px;color:#303133}.el-checkbox{color:#606266;font-weight:500;font-size:14px;cursor:pointer;user-select:none;margin-right:30px}.el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #DCDFE6;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:normal;height:40px}.el-checkbox.is-bordered.is-checked{border-color:#409EFF}.el-checkbox.is-bordered.is-disabled{border-color:#EBEEF5;cursor:not-allowed}.el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px;height:36px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.el-checkbox.is-bordered.el-checkbox--small{padding:5px 15px 5px 10px;border-radius:3px;height:32px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner::after{height:6px;width:2px}.el-checkbox.is-bordered.el-checkbox--mini{padding:3px 15px 3px 10px;border-radius:3px;height:28px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner::after{height:6px;width:2px}.el-checkbox__input{cursor:pointer;outline:0;line-height:1;vertical-align:middle}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#DCDFE6;cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner::after{cursor:not-allowed;border-color:#C0C4CC}.el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#F2F6FC;border-color:#DCDFE6}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after{border-color:#C0C4CC}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#F2F6FC;border-color:#DCDFE6}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before{background-color:#C0C4CC;border-color:#C0C4CC}.el-checkbox__input.is-checked .el-checkbox__inner,.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#409EFF;border-color:#409EFF}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:#C0C4CC;cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner::after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.el-checkbox__input.is-checked+.el-checkbox__label{color:#409EFF}.el-checkbox__input.is-focus .el-checkbox__inner{border-color:#409EFF}.el-checkbox__input.is-indeterminate .el-checkbox__inner::before{content:'';position:absolute;display:block;background-color:#FFF;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.el-checkbox__input.is-indeterminate .el-checkbox__inner::after{display:none}.el-checkbox__inner{display:inline-block;position:relative;border:1px solid #DCDFE6;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#FFF;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.el-checkbox__inner:hover{border-color:#409EFF}.el-checkbox__inner::after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #FFF;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s ease-in .05s;transition:-webkit-transform .15s ease-in .05s;transition:transform .15s ease-in .05s;transition:transform .15s ease-in .05s,-webkit-transform .15s ease-in .05s;-webkit-transform-origin:center;transform-origin:center}.el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;z-index:-1}.el-checkbox-button,.el-checkbox-button__inner{display:inline-block;position:relative}.el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.el-checkbox:last-of-type{margin-right:0}.el-checkbox-button__inner{line-height:1;font-weight:500;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#FFF;border:1px solid #DCDFE6;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-checkbox-button__inner.is-round{padding:12px 20px}.el-checkbox-button__inner:hover{color:#409EFF}.el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;z-index:-1}.el-radio,.el-radio__inner,.el-radio__input{position:relative;display:inline-block}.el-checkbox-button.is-checked .el-checkbox-button__inner{color:#FFF;background-color:#409EFF;border-color:#409EFF;-webkit-box-shadow:-1px 0 0 0 #8cc5ff;box-shadow:-1px 0 0 0 #8cc5ff}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner{border-left-color:#409EFF}.el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#C0C4CC;cursor:not-allowed;background-image:none;background-color:#FFF;border-color:#EBEEF5;-webkit-box-shadow:none;box-shadow:none}.el-checkbox-button.is-disabled:first-child .el-checkbox-button__inner{border-left-color:#EBEEF5}.el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #DCDFE6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#409EFF}.el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.el-checkbox-group{font-size:0}.el-avatar,.el-cascader-panel,.el-radio,.el-radio--medium.is-bordered .el-radio__label,.el-radio__label{font-size:14px}.el-radio{color:#606266;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;outline:0;margin-right:30px}.el-cascader-node>.el-radio,.el-radio:last-child{margin-right:0}.el-radio.is-bordered{padding:12px 20px 0 10px;border-radius:4px;border:1px solid #DCDFE6;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px}.el-cascader-menu,.el-cascader-menu__list,.el-radio__inner{-webkit-box-sizing:border-box}.el-radio.is-bordered.is-checked{border-color:#409EFF}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#EBEEF5}.el-radio__input.is-disabled .el-radio__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#F5F7FA;border-color:#E4E7ED}.el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.el-radio--medium.is-bordered{padding:10px 20px 0 10px;border-radius:4px;height:36px}.el-radio--mini.is-bordered .el-radio__label,.el-radio--small.is-bordered .el-radio__label{font-size:12px}.el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.el-radio--small.is-bordered{padding:8px 15px 0 10px;border-radius:3px;height:32px}.el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio--mini.is-bordered{padding:6px 15px 0 10px;border-radius:3px;height:28px}.el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio__input{white-space:nowrap;cursor:pointer;outline:0;line-height:1;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner::after{cursor:not-allowed;background-color:#F5F7FA}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#C0C4CC}.el-radio__input.is-disabled+span.el-radio__label{color:#C0C4CC;cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:#409EFF;background:#409EFF}.el-radio__input.is-checked .el-radio__inner::after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:#409EFF}.el-radio__input.is-focus .el-radio__inner{border-color:#409EFF}.el-radio__inner{border:1px solid #DCDFE6;border-radius:100%;width:14px;height:14px;background-color:#FFF;cursor:pointer;box-sizing:border-box}.el-radio__inner:hover{border-color:#409EFF}.el-radio__inner::after{width:4px;height:4px;border-radius:100%;background-color:#FFF;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #409EFF;box-shadow:0 0 2px 2px #409EFF}.el-radio__label{padding-left:10px}.el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-cascader-panel{display:-webkit-box;display:-ms-flexbox;display:flex;border-radius:4px}.el-cascader-panel.is-bordered{border:1px solid #E4E7ED;border-radius:4px}.el-cascader-menu{min-width:180px;box-sizing:border-box;color:#606266;border-right:solid 1px #E4E7ED}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu__wrap{height:204px}.el-cascader-menu__list{position:relative;min-height:100%;margin:0;padding:6px 0;list-style:none;box-sizing:border-box}.el-cascader-menu__hover-zone{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.el-cascader-menu__empty-text{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-align:center;color:#C0C4CC}.el-cascader-node{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 30px 0 20px;height:34px;line-height:34px;outline:0}.el-cascader-node.is-selectable.in-active-path{color:#606266}.el-cascader-node.in-active-path,.el-cascader-node.is-active,.el-cascader-node.is-selectable.in-checked-path{color:#409EFF;font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):focus,.el-cascader-node:not(.is-disabled):hover{background:#F5F7FA}.el-cascader-node.is-disabled{color:#C0C4CC;cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-avatar{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden;color:#fff;background:#C0C4CC;width:40px;height:40px;line-height:40px}.el-drawer,.el-drawer__body>*{-webkit-box-sizing:border-box}.el-avatar>img{display:block;height:100%;vertical-align:middle}.el-empty__image img,.el-empty__image svg{vertical-align:top;height:100%;width:100%}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:4px}.el-avatar--icon{font-size:18px}.el-avatar--large{width:40px;height:40px;line-height:40px}.el-avatar--medium{width:36px;height:36px;line-height:36px}.el-avatar--small{width:28px;height:28px;line-height:28px}@-webkit-keyframes el-drawer-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes el-drawer-fade-in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%,0);transform:translate(100%,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%,0);transform:translate(100%,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes rtl-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(100%,0);transform:translate(100%,0)}}@keyframes rtl-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(100%,0);transform:translate(100%,0)}}@-webkit-keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes ltr-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}}@keyframes ltr-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(-100%,0);transform:translate(-100%,0)}}@-webkit-keyframes ttb-drawer-in{0%{-webkit-transform:translate(0,-100%);transform:translate(0,-100%)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes ttb-drawer-in{0%{-webkit-transform:translate(0,-100%);transform:translate(0,-100%)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes ttb-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(0,-100%);transform:translate(0,-100%)}}@keyframes ttb-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(0,-100%);transform:translate(0,-100%)}}@-webkit-keyframes btt-drawer-in{0%{-webkit-transform:translate(0,100%);transform:translate(0,100%)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes btt-drawer-in{0%{-webkit-transform:translate(0,100%);transform:translate(0,100%)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes btt-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(0,100%);transform:translate(0,100%)}}@keyframes btt-drawer-out{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}100%{-webkit-transform:translate(0,100%);transform:translate(0,100%)}}.el-drawer{position:absolute;box-sizing:border-box;background-color:#FFF;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-webkit-box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);overflow:hidden;outline:0}.el-drawer.rtl{-webkit-animation:rtl-drawer-out .3s;animation:rtl-drawer-out .3s;right:0}.el-drawer__open .el-drawer.rtl{-webkit-animation:rtl-drawer-in .3s 1ms;animation:rtl-drawer-in .3s 1ms}.el-drawer.ltr{-webkit-animation:ltr-drawer-out .3s;animation:ltr-drawer-out .3s;left:0}.el-drawer__open .el-drawer.ltr{-webkit-animation:ltr-drawer-in .3s 1ms;animation:ltr-drawer-in .3s 1ms}.el-drawer.ttb{-webkit-animation:ttb-drawer-out .3s;animation:ttb-drawer-out .3s;top:0}.el-drawer__open .el-drawer.ttb{-webkit-animation:ttb-drawer-in .3s 1ms;animation:ttb-drawer-in .3s 1ms}.el-drawer.btt{-webkit-animation:btt-drawer-out .3s;animation:btt-drawer-out .3s;bottom:0}.el-drawer__open .el-drawer.btt{-webkit-animation:btt-drawer-in .3s 1ms;animation:btt-drawer-in .3s 1ms}.el-drawer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:hidden;margin:0}.el-drawer__header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#72767b;display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:32px;padding:20px 20px 0}.el-drawer__header>:first-child{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-drawer__title{margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;line-height:inherit;font-size:1rem}.el-drawer__close-btn{border:none;cursor:pointer;font-size:20px;color:inherit;background-color:transparent}.el-drawer__body{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:auto}.el-drawer__body>*{box-sizing:border-box}.el-drawer.ltr,.el-drawer.rtl{height:100%;top:0;bottom:0}.el-drawer.btt,.el-drawer.ttb{width:100%;left:0;right:0}.el-drawer__container{position:relative;left:0;right:0;top:0;bottom:0;height:100%;width:100%}.el-drawer-fade-enter-active{-webkit-animation:el-drawer-fade-in .3s;animation:el-drawer-fade-in .3s}.el-drawer-fade-leave-active{animation:el-drawer-fade-in .3s reverse}.el-statistic{width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:#000;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:"tnum";font-feature-settings:"tnum";text-align:center}.el-statistic .head{margin-bottom:4px;color:#606266;font-size:13px}.el-statistic .con{font-family:Sans-serif;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#303133}.el-statistic .con .number{font-size:20px;padding:0 4px}.el-statistic .con span{display:inline-block;margin:0;line-height:100%}.el-popconfirm__main,.el-skeleton__image{display:-ms-flexbox;-webkit-box-align:center;display:-webkit-box}.el-popconfirm__main{display:flex;-ms-flex-align:center;align-items:center}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin:0}@-webkit-keyframes el-skeleton-loading{0%{background-position:100% 50%}100%{background-position:0 50%}}@keyframes el-skeleton-loading{0%{background-position:100% 50%}100%{background-position:0 50%}}.el-skeleton{width:100%}.el-skeleton__first-line,.el-skeleton__paragraph{height:16px;margin-top:16px;background:#f2f2f2}.el-skeleton.is-animated .el-skeleton__item{background:-webkit-gradient(linear,left top,right top,color-stop(25%,#f2f2f2),color-stop(37%,#e6e6e6),color-stop(63%,#f2f2f2));background:linear-gradient(90deg,#f2f2f2 25%,#e6e6e6 37%,#f2f2f2 63%);background-size:400% 100%;-webkit-animation:el-skeleton-loading 1.4s ease infinite;animation:el-skeleton-loading 1.4s ease infinite}.el-skeleton__item{background:#f2f2f2;display:inline-block;height:16px;border-radius:4px;width:100%}.el-skeleton__circle{border-radius:50%;width:36px;height:36px;line-height:36px}.el-skeleton__circle--lg{width:40px;height:40px;line-height:40px}.el-skeleton__circle--md{width:28px;height:28px;line-height:28px}.el-skeleton__button{height:40px;width:64px;border-radius:4px}.el-skeleton__p{width:100%}.el-skeleton__p.is-last{width:61%}.el-skeleton__p.is-first{width:33%}.el-skeleton__text{width:100%;height:13px}.el-skeleton__caption{height:12px}.el-skeleton__h1{height:20px}.el-skeleton__h3{height:18px}.el-skeleton__h5{height:16px}.el-skeleton__image{width:unset;display:flex;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;border-radius:0}.el-skeleton__image svg{fill:#DCDDE0;width:22%;height:22%}.el-empty{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-direction:column;flex-direction:column;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;padding:40px 0}.el-empty__image{width:160px}.el-empty__image img{user-select:none;-o-object-fit:contain;object-fit:contain}.el-empty__image svg{fill:#DCDDE0}.el-empty__description{margin-top:20px}.el-empty__description p{margin:0;font-size:14px;color:#909399}.el-empty__bottom,.el-result__title{margin-top:20px}.el-descriptions{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;color:#303133}.el-descriptions__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:20px}.el-descriptions__title{font-size:16px;font-weight:700}.el-descriptions--mini,.el-descriptions--small{font-size:12px}.el-descriptions__body{color:#606266;background-color:#FFF}.el-descriptions__body .el-descriptions__table{border-collapse:collapse;width:100%;table-layout:fixed}.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:left;font-weight:400;line-height:1.5}.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-left{text-align:left}.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-center{text-align:center}.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-right{text-align:right}.el-descriptions .is-bordered{table-layout:auto}.el-descriptions .is-bordered .el-descriptions-item__cell{border:1px solid #EBEEF5;padding:12px 10px}.el-descriptions :not(.is-bordered) .el-descriptions-item__cell{padding-bottom:12px}.el-descriptions--medium.is-bordered .el-descriptions-item__cell{padding:10px}.el-descriptions--medium:not(.is-bordered) .el-descriptions-item__cell{padding-bottom:10px}.el-descriptions--small.is-bordered .el-descriptions-item__cell{padding:8px 10px}.el-descriptions--small:not(.is-bordered) .el-descriptions-item__cell{padding-bottom:8px}.el-descriptions--mini.is-bordered .el-descriptions-item__cell{padding:6px 10px}.el-descriptions--mini:not(.is-bordered) .el-descriptions-item__cell{padding-bottom:6px}.el-descriptions-item{vertical-align:top}.el-descriptions-item__container{display:-webkit-box;display:-ms-flexbox;display:flex}.el-descriptions-item__container .el-descriptions-item__content,.el-descriptions-item__container .el-descriptions-item__label{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-descriptions-item__container .el-descriptions-item__content{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-descriptions-item__label.has-colon::after{content:':';position:relative;top:-.5px}.el-descriptions-item__label.is-bordered-label{font-weight:700;color:#909399;background:#fafafa}.el-descriptions-item__label:not(.is-bordered-label){margin-right:10px}.el-descriptions-item__content{word-break:break-word;overflow-wrap:break-word}.el-result{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-direction:column;flex-direction:column;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;padding:40px 30px}.el-result__icon svg{width:64px;height:64px}.el-result__title p{margin:0;font-size:20px;color:#303133;line-height:1.3}.el-result__subtitle{margin-top:10px}.el-result__subtitle p{margin:0;font-size:14px;color:#606266;line-height:1.3}.el-result__extra{margin-top:30px }.el-result .icon-success{fill:#67C23A}.el-result .icon-error{fill:#F56C6C}.el-result .icon-info{fill:#909399}.el-result .icon-warning{fill:#E6A23C} \ No newline at end of file diff --git a/ruoyi-fastapi-frontend/src/api/system/user.js b/ruoyi-fastapi-frontend/src/api/system/user.js index 9b0211a5fe3db21e68c659a9e9c0dfd539b677a7..b5e3edd81925eff98df34bf86e605938e192e2ac 100644 --- a/ruoyi-fastapi-frontend/src/api/system/user.js +++ b/ruoyi-fastapi-frontend/src/api/system/user.js @@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) { return request({ url: '/system/user/profile/updatePwd', method: 'put', - params: data + data: data }) } diff --git a/ruoyi-fastapi-frontend/src/api/tool/gen.js b/ruoyi-fastapi-frontend/src/api/tool/gen.js index 45069278fa93ac8cd748ffae138dc2777cd5bd00..207567727c84718863a22137b1b82e3587664300 100644 --- a/ruoyi-fastapi-frontend/src/api/tool/gen.js +++ b/ruoyi-fastapi-frontend/src/api/tool/gen.js @@ -43,6 +43,15 @@ export function importTable(data) { }) } +// 创建表 +export function createTable(data) { + return request({ + url: '/tool/gen/createTable', + method: 'post', + params: data + }) +} + // 预览生成代码 export function previewTable(tableId) { return request({ diff --git a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss index 4e298744c689179b3f5d173ce5fb00cdd665ff2d..7e44513cc5b0649a9a154d365787e7efcfb31154 100644 --- a/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss +++ b/ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss @@ -118,7 +118,7 @@ /** 表格布局 **/ .pagination-container { position: relative; - height: 25px; + height: 32px; margin-bottom: 10px; margin-top: 15px; padding: 10px 20px !important; @@ -289,3 +289,8 @@ position: relative; float: right; } + +/* 分割面板样式 */ +.splitpanes.default-theme .splitpanes__pane { + background-color: #fff!important; +} diff --git a/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue index 1696f54719db39bc2ace0cfde2cbb0183ec91745..080595a4ec14290cc2cfa2d4b066453a20414fae 100644 --- a/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue +++ b/ruoyi-fastapi-frontend/src/components/Breadcrumb/index.vue @@ -1,7 +1,7 @@ diff --git a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue index 82ba40786ae2ec60ba78a72cec94c4b364001d7c..fabc61e9499ee74cc1cf50cc03b9f4d8d783efc0 100644 --- a/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue +++ b/ruoyi-fastapi-frontend/src/layout/components/Sidebar/SidebarItem.vue @@ -62,11 +62,10 @@ export default { const showingChildren = children.filter(item => { if (item.hidden) { return false - } else { - // Temp set(will be used if only has one showing child) - this.onlyOneChild = item - return true } + // Temp set(will be used if only has one showing child) + this.onlyOneChild = item + return true }) // When there is only one child router, the child router is displayed by default diff --git a/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue b/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue index 96585a5b1ff36e9946773500b6ecd0fbad0d0521..39ded08cc72ee385ef7a6671a0aed3b5b7c5226d 100644 --- a/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue +++ b/ruoyi-fastapi-frontend/src/layout/components/TagsView/index.vue @@ -133,11 +133,7 @@ export default { const { name } = this.$route if (name) { this.$store.dispatch('tagsView/addView', this.$route) - if (this.$route.meta.link) { - this.$store.dispatch('tagsView/addIframeView', this.$route) - } } - return false }, moveToCurrentTag() { const tags = this.$refs.tag diff --git a/ruoyi-fastapi-frontend/src/permission.js b/ruoyi-fastapi-frontend/src/permission.js index c56897902f829417c0c936d056066afe894e9215..b66190b378b2fde08587b2ae48113591e199a75d 100644 --- a/ruoyi-fastapi-frontend/src/permission.js +++ b/ruoyi-fastapi-frontend/src/permission.js @@ -4,12 +4,17 @@ import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getToken } from '@/utils/auth' +import { isPathMatch } from '@/utils/validate' import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) const whiteList = ['/login', '/register'] +const isWhiteList = (path) => { + return whiteList.some(pattern => isPathMatch(pattern, path)) +} + router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { @@ -18,7 +23,7 @@ router.beforeEach((to, from, next) => { if (to.path === '/login') { next({ path: '/' }) NProgress.done() - } else if (whiteList.indexOf(to.path) !== -1) { + } else if (isWhiteList(to.path)) { next() } else { if (store.getters.roles.length === 0) { @@ -43,7 +48,7 @@ router.beforeEach((to, from, next) => { } } else { // 没有token - if (whiteList.indexOf(to.path) !== -1) { + if (isWhiteList(to.path)) { // 在免登录白名单,直接进入 next() } else { diff --git a/ruoyi-fastapi-frontend/src/plugins/cache.js b/ruoyi-fastapi-frontend/src/plugins/cache.js index 6b5c00b9ebb89ff928369086ec9e8cd4165e2d3c..b67d453faeb16a6baab3576f8d211a714168f6a2 100644 --- a/ruoyi-fastapi-frontend/src/plugins/cache.js +++ b/ruoyi-fastapi-frontend/src/plugins/cache.js @@ -26,6 +26,7 @@ const sessionCache = { if (value != null) { return JSON.parse(value) } + return null }, remove (key) { sessionStorage.removeItem(key); @@ -59,6 +60,7 @@ const localCache = { if (value != null) { return JSON.parse(value) } + return null }, remove (key) { localStorage.removeItem(key); diff --git a/ruoyi-fastapi-frontend/src/store/modules/user.js b/ruoyi-fastapi-frontend/src/store/modules/user.js index cdbab1e9dc1fe25edb6f3f6e824c25a381d77d68..63e6ba2ec960ba66a2c64c786710c4c667fc7fa2 100644 --- a/ruoyi-fastapi-frontend/src/store/modules/user.js +++ b/ruoyi-fastapi-frontend/src/store/modules/user.js @@ -1,5 +1,7 @@ import { login, logout, getInfo } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' +import { isHttp, isEmpty } from "@/utils/validate" +import defAva from '@/assets/images/profile.jpg' const user = { state: { @@ -55,7 +57,10 @@ const user = { return new Promise((resolve, reject) => { getInfo().then(res => { const user = res.user - const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; + let avatar = user.avatar || "" + if (!isHttp(avatar)) { + avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar + } if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 commit('SET_ROLES', res.roles) commit('SET_PERMISSIONS', res.permissions) diff --git a/ruoyi-fastapi-frontend/src/utils/validate.js b/ruoyi-fastapi-frontend/src/utils/validate.js index 57a568e9f1b5413d9b8c7a4c071734d2be0d764e..6a4c0c5d1883d1c1e4cad2d0c24a0f914d286872 100644 --- a/ruoyi-fastapi-frontend/src/utils/validate.js +++ b/ruoyi-fastapi-frontend/src/utils/validate.js @@ -1,4 +1,38 @@ /** + * 路径匹配器 + * @param {string} pattern + * @param {string} path + * @returns {Boolean} + */ +export function isPathMatch(pattern, path) { + const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*') + const regex = new RegExp(`^${regexPattern}$`) + return regex.test(path) +} + +/** + * 判断value字符串是否为空 + * @param {string} value + * @returns {Boolean} + */ +export function isEmpty(value) { + if (value == null || value == "" || value == undefined || value == "undefined") { + return true + } + return false +} + +/** + * 判断url是否是http或https + * @param {string} url + * @returns {Boolean} + */ +export function isHttp(url) { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 +} + +/** + * 判断path是否为外链 * @param {string} path * @returns {Boolean} */ @@ -65,7 +99,7 @@ export function validEmail(email) { * @returns {Boolean} */ export function isString(str) { - return typeof str === 'string' || str instanceof String; + return typeof str === 'string' || str instanceof String } /** diff --git a/ruoyi-fastapi-frontend/src/views/system/config/index.vue b/ruoyi-fastapi-frontend/src/views/system/config/index.vue index 3ab81fc20f4462f82a7c24cc0d8510e38da7dc62..6bde2ee0c39d5340602b41b0b442eca8d544d91e 100644 --- a/ruoyi-fastapi-frontend/src/views/system/config/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/config/index.vue @@ -157,7 +157,7 @@ - + diff --git a/ruoyi-fastapi-frontend/src/views/system/menu/index.vue b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue index 619c9e2dac937932b66494146ce022cbeb2f0109..79393141b67152c02c9ec69d77f0cd321d44f480 100644 --- a/ruoyi-fastapi-frontend/src/views/system/menu/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/menu/index.vue @@ -117,6 +117,8 @@ /> + + @@ -126,7 +128,9 @@ - + + + - - - - - + + - - - - - - - 路由名称 - - + + + + + @@ -193,6 +190,21 @@ + + + + + + + + + 路由名称 + + + + + + @@ -215,6 +227,8 @@ + + @@ -240,6 +254,8 @@ + + diff --git a/ruoyi-fastapi-frontend/src/views/system/role/index.vue b/ruoyi-fastapi-frontend/src/views/system/role/index.vue index fb3b5ef044bc60b62e53485e701297a093fc87bd..47419baabc663c51552a2b2062c305f30edb29d0 100644 --- a/ruoyi-fastapi-frontend/src/views/system/role/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/role/index.vue @@ -522,8 +522,8 @@ export default { }) }); }); - this.title = "修改角色"; }); + this.title = "修改角色"; }, /** 选择角色权限范围触发 */ dataScopeSelectChange(value) { @@ -543,8 +543,8 @@ export default { this.$refs.dept.setCheckedKeys(res.checkedKeys); }); }); - this.title = "分配数据权限"; }); + this.title = "分配数据权限"; }, /** 分配用户操作 */ handleAuthUser: function(row) { diff --git a/ruoyi-fastapi-frontend/src/views/system/user/index.vue b/ruoyi-fastapi-frontend/src/views/system/user/index.vue index 793409559a3446db36baef9f27d906b9a43a195c..2fe3a251ce871d1a097d503a14c66027688ecb3b 100644 --- a/ruoyi-fastapi-frontend/src/views/system/user/index.vue +++ b/ruoyi-fastapi-frontend/src/views/system/user/index.vue @@ -1,205 +1,209 @@ + + +