Compare commits
3 Commits
10010fc56f
...
2132e07d2f
| Author | SHA1 | Date |
|---|---|---|
|
|
2132e07d2f | 1 week ago |
|
|
fe8eca1480 | 1 week ago |
|
|
9af85065aa | 1 week ago |
29 changed files with 5162 additions and 21 deletions
@ -0,0 +1,88 @@ |
|||
import cv2 |
|||
import numpy as np |
|||
import sys |
|||
|
|||
def convert_to_red_pseudo_color(input_path, output_path): |
|||
cap = cv2.VideoCapture(input_path) |
|||
if not cap.isOpened(): |
|||
print("❌ 无法打开视频") |
|||
return |
|||
|
|||
fps = cap.get(cv2.CAP_PROP_FPS) |
|||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) |
|||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) |
|||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) |
|||
|
|||
fourcc = cv2.VideoWriter_fourcc(*'mp4v') |
|||
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) |
|||
|
|||
# 基础色 #f21762 (BGR 顺序) |
|||
#base_b, base_g, base_r = 98, 23, 242 |
|||
# f80b5b |
|||
base_b, base_g, base_r = 91, 11, 248 |
|||
# f31347 |
|||
#base_b, base_g, base_r = 71, 19, 243 |
|||
##b60430 |
|||
#base_b, base_g, base_r = 48, 4, 182 |
|||
|
|||
|
|||
# # 锐化核(锐度70) |
|||
# sharpen_kernel = np.array([[-0.7, -0.7, -0.7], |
|||
# [-0.7, 6.6, -0.7], |
|||
# [-0.7, -0.7, -0.7]], dtype=np.float32) |
|||
|
|||
frame_count = 0 |
|||
while True: |
|||
ret, frame = cap.read() |
|||
if not ret: |
|||
break |
|||
|
|||
# 1. 灰度 + 明暗反转(亮变暗,暗变亮) |
|||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) |
|||
gray_inverted = 255 - gray |
|||
|
|||
# 2. 归一化亮度 0~1 |
|||
t = gray_inverted / 255.0 |
|||
|
|||
# 3. 关键:双向渐变 黑色 → #f21762 → 白色 |
|||
# t < 0.5: 黑色到基础色的渐变 |
|||
# t >= 0.5: 基础色到白色的渐变 |
|||
pseudo = np.zeros_like(frame, dtype=np.float32) |
|||
|
|||
# 4. 亮度提升100% + 对比度70 |
|||
pseudo = cv2.convertScaleAbs(pseudo, alpha=2.0, beta=1000) |
|||
|
|||
# 双向插值公式 |
|||
mask_low = t < 0.5 # 暗区:黑 → 基础色 |
|||
mask_high = t >= 0.5 # 亮区:基础色 → 白 |
|||
|
|||
# 暗区:从黑色(0)渐变到基础色 |
|||
pseudo[mask_low, 0] = base_b * (t[mask_low] / 0.5) |
|||
pseudo[mask_low, 1] = base_g * (t[mask_low] / 0.5) |
|||
pseudo[mask_low, 2] = base_r * (t[mask_low] / 0.5) |
|||
|
|||
# 亮区:从基础色渐变到白色(255) |
|||
t_high = (t[mask_high] - 0.5) / 0.5 # 重新映射到 0~1 |
|||
pseudo[mask_high, 0] = base_b + t_high * (255 - base_b) |
|||
pseudo[mask_high, 1] = base_g + t_high * (255 - base_g) |
|||
pseudo[mask_high, 2] = base_r + t_high * (255 - base_r) |
|||
|
|||
pseudo = np.clip(pseudo, 0, 255).astype(np.uint8) |
|||
|
|||
# 5. 锐度70 |
|||
# pseudo = cv2.filter2D(pseudo, -1, sharpen_kernel) |
|||
|
|||
out.write(pseudo) |
|||
frame_count += 1 |
|||
if frame_count % 100 == 0: |
|||
print(f"处理进度: {frame_count}/{total_frames}") |
|||
|
|||
cap.release() |
|||
out.release() |
|||
print("🎉 完成 | #f80b5b 平滑双向渐变 + 反转 + 亮度/对比度/锐度") |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) != 3: |
|||
print("用法:python 脚本.py 输入.mp4 输出.mp4") |
|||
else: |
|||
convert_to_red_pseudo_color(sys.argv[1], sys.argv[2]) |
|||
@ -0,0 +1,655 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
""" |
|||
Python API脚手架生成器 |
|||
基于FastAPI + SQLAlchemy + Pydantic的三层架构代码生成 |
|||
复用Java脚手架的config.yml配置文件 |
|||
|
|||
@author: Auto Generated |
|||
@date: 2024 |
|||
""" |
|||
|
|||
import os |
|||
import sys |
|||
import yaml |
|||
from urllib.parse import quote_plus |
|||
import argparse |
|||
from datetime import datetime |
|||
from typing import List, Dict, Any, Optional |
|||
from jinja2 import Environment, FileSystemLoader |
|||
|
|||
# 添加当前目录到路径 |
|||
sys.path.insert(0, os.path.dirname(__file__)) |
|||
|
|||
# 字段类型映射 (MySQL -> Python/SQLAlchemy) |
|||
TYPE_MAPPING = { |
|||
"int": {"python": "int", "sqlalchemy": "Integer"}, |
|||
"bigint": {"python": "int", "sqlalchemy": "BigInteger"}, |
|||
"smallint": {"python": "int", "sqlalchemy": "SmallInteger"}, |
|||
"tinyint": {"python": "int", "sqlalchemy": "SmallInteger"}, |
|||
"float": {"python": "float", "sqlalchemy": "Float"}, |
|||
"double": {"python": "float", "sqlalchemy": "Float"}, |
|||
"decimal": {"python": "float", "sqlalchemy": "Numeric"}, |
|||
"varchar": {"python": "str", "sqlalchemy": "String"}, |
|||
"char": {"python": "str", "sqlalchemy": "String"}, |
|||
"text": {"python": "str", "sqlalchemy": "Text"}, |
|||
"longtext": {"python": "str", "sqlalchemy": "Text"}, |
|||
"date": {"python": "datetime", "sqlalchemy": "Date"}, |
|||
"datetime": {"python": "datetime", "sqlalchemy": "DateTime"}, |
|||
"timestamp": {"python": "datetime", "sqlalchemy": "DateTime"}, |
|||
"boolean": {"python": "bool", "sqlalchemy": "Boolean"}, |
|||
"bool": {"python": "bool", "sqlalchemy": "Boolean"}, |
|||
"json": {"python": "dict", "sqlalchemy": "JSON"}, |
|||
} |
|||
|
|||
# 需要排除的系统字段 |
|||
EXCLUDE_FIELDS = [ |
|||
"id", |
|||
"created_at", |
|||
"created_by", |
|||
"updated_at", |
|||
"updated_by", |
|||
"deleted_flag", |
|||
"is_deleted", |
|||
] |
|||
|
|||
|
|||
class DatabaseInspector: |
|||
"""数据库结构检查器""" |
|||
|
|||
def __init__(self, host: str, port: int, user: str, password: str, database: str): |
|||
self.host = host |
|||
self.port = port |
|||
self.user = user |
|||
self.password = password |
|||
self.database = database |
|||
self.connection = None |
|||
|
|||
def connect(self): |
|||
"""连接数据库""" |
|||
try: |
|||
import pymysql |
|||
|
|||
self.connection = pymysql.connect( |
|||
host=self.host, |
|||
port=self.port, |
|||
user=self.user, |
|||
password=self.password, |
|||
database=self.database, |
|||
charset="utf8mb4", |
|||
) |
|||
print(f"成功连接到数据库: {self.host}:{self.port}/{self.database}") |
|||
return True |
|||
except ImportError: |
|||
print("错误: 请安装 pymysql: pip install pymysql") |
|||
return False |
|||
except Exception as e: |
|||
print(f"数据库连接失败: {e}") |
|||
return False |
|||
|
|||
def get_tables(self, table_names: List[str] = None) -> List[Dict[str, Any]]: |
|||
"""获取表信息""" |
|||
if not self.connection: |
|||
return [] |
|||
|
|||
tables = [] |
|||
cursor = self.connection.cursor() |
|||
|
|||
try: |
|||
# 获取所有表 |
|||
if table_names: |
|||
placeholders = ",".join(["%s"] * len(table_names)) |
|||
cursor.execute( |
|||
f""" |
|||
SELECT TABLE_NAME, TABLE_COMMENT |
|||
FROM information_schema.TABLES |
|||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME IN ({placeholders}) |
|||
""", |
|||
[self.database] + table_names, |
|||
) |
|||
else: |
|||
cursor.execute( |
|||
""" |
|||
SELECT TABLE_NAME, TABLE_COMMENT |
|||
FROM information_schema.TABLES |
|||
WHERE TABLE_SCHEMA = %s |
|||
""", |
|||
[self.database], |
|||
) |
|||
|
|||
for table_name, table_comment in cursor.fetchall(): |
|||
fields = self._get_table_fields(table_name) |
|||
tables.append( |
|||
{ |
|||
"name": table_name, |
|||
"comment": table_comment or table_name, |
|||
"fields": fields, |
|||
} |
|||
) |
|||
|
|||
except Exception as e: |
|||
print(f"获取表信息失败: {e}") |
|||
finally: |
|||
cursor.close() |
|||
|
|||
return tables |
|||
|
|||
def _get_table_fields(self, table_name: str) -> List[Dict[str, Any]]: |
|||
"""获取表字段信息""" |
|||
cursor = self.connection.cursor() |
|||
fields = [] |
|||
|
|||
try: |
|||
cursor.execute( |
|||
""" |
|||
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT, IS_NULLABLE, COLUMN_KEY |
|||
FROM information_schema.COLUMNS |
|||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s |
|||
ORDER BY ORDINAL_POSITION |
|||
""", |
|||
[self.database, table_name], |
|||
) |
|||
|
|||
for ( |
|||
col_name, |
|||
col_type, |
|||
col_comment, |
|||
is_nullable, |
|||
col_key, |
|||
) in cursor.fetchall(): |
|||
# 排除系统字段 |
|||
if col_name.lower() in EXCLUDE_FIELDS: |
|||
continue |
|||
|
|||
# 解析类型 |
|||
base_type = col_type.split("(")[0].lower() |
|||
type_info = TYPE_MAPPING.get( |
|||
base_type, {"python": "str", "sqlalchemy": "String"} |
|||
) |
|||
|
|||
fields.append( |
|||
{ |
|||
"name": col_name, |
|||
"type": col_type, |
|||
"comment": col_comment or col_name, |
|||
"nullable": is_nullable == "YES", |
|||
"is_primary": col_key == "PRI", |
|||
"searchable": col_key != "PRI", # 非主键默认可搜索 |
|||
"python_type": type_info["python"], |
|||
"sqlalchemy_type": self._get_sqlalchemy_type(col_type), |
|||
} |
|||
) |
|||
|
|||
except Exception as e: |
|||
print(f"获取字段信息失败 {table_name}: {e}") |
|||
finally: |
|||
cursor.close() |
|||
|
|||
return fields |
|||
|
|||
@staticmethod |
|||
def _get_sqlalchemy_type(db_type: str) -> str: |
|||
"""获取SQLAlchemy类型字符串""" |
|||
base_type = db_type.lower().split("(")[0] |
|||
|
|||
if "(" in db_type and base_type in ["varchar", "char"]: |
|||
length = db_type.split("(")[1].rstrip(")") |
|||
return f"String({length})" |
|||
elif base_type in ["decimal", "numeric"]: |
|||
return "Numeric(precision=10, scale=2)" |
|||
else: |
|||
type_info = TYPE_MAPPING.get( |
|||
base_type, {"python": "str", "sqlalchemy": "String"} |
|||
) |
|||
return type_info["sqlalchemy"] |
|||
|
|||
def close(self): |
|||
"""关闭连接""" |
|||
if self.connection: |
|||
self.connection.close() |
|||
|
|||
|
|||
class PythonScaffoldingGenerator: |
|||
"""Python API脚手架生成器""" |
|||
|
|||
def __init__(self, template_dir: str = None): |
|||
"""初始化生成器""" |
|||
if template_dir is None: |
|||
template_dir = os.path.join(os.path.dirname(__file__), "templates", "py") |
|||
self.template_dir = template_dir |
|||
self.env = Environment( |
|||
loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True |
|||
) |
|||
# 添加自定义过滤器 |
|||
self.env.filters["snake_case"] = self._to_snake_case |
|||
self.env.filters["camel_case"] = self._to_camel_case |
|||
self.env.filters["pascal_case"] = self._to_pascal_case |
|||
|
|||
@staticmethod |
|||
def _to_snake_case(name: str) -> str: |
|||
"""转换为snake_case""" |
|||
result = [] |
|||
for i, char in enumerate(name): |
|||
if char.isupper() and i > 0: |
|||
result.append("_") |
|||
result.append(char.lower()) |
|||
return "".join(result) |
|||
|
|||
@staticmethod |
|||
def _to_camel_case(name: str) -> str: |
|||
"""转换为camelCase""" |
|||
parts = name.split("_") |
|||
return parts[0].lower() + "".join(p.capitalize() for p in parts[1:]) |
|||
|
|||
@staticmethod |
|||
def _to_pascal_case(name: str) -> str: |
|||
"""转换为PascalCase""" |
|||
return "".join(p.capitalize() for p in name.split("_")) |
|||
|
|||
def _process_table(self, table: Dict[str, Any]) -> Dict[str, Any]: |
|||
"""处理表信息""" |
|||
name = table.get("name", "") |
|||
return { |
|||
**table, |
|||
"entity": self._to_pascal_case(name), |
|||
"name_lower": name.lower(), |
|||
"comment": table.get("comment", name), |
|||
} |
|||
|
|||
def generate_project( |
|||
self, |
|||
project_name: str, |
|||
module_name: str, |
|||
tables: List[Dict[str, Any]], |
|||
output_dir: str, |
|||
author: str = "Auto Generated", |
|||
db_config: Dict[str, Any] = None, |
|||
**kwargs, |
|||
): |
|||
"""生成完整项目""" |
|||
print(f"开始生成Python API项目: {project_name}/{module_name}") |
|||
|
|||
# 创建项目目录 |
|||
project_dir = os.path.join(output_dir, project_name, module_name) |
|||
os.makedirs(project_dir, exist_ok=True) |
|||
|
|||
# 创建子目录 |
|||
dirs = [ |
|||
"app", |
|||
"app/models", |
|||
"app/schemas", |
|||
"app/services", |
|||
"app/routers", |
|||
"app/utils", |
|||
"tests", |
|||
] |
|||
for d in dirs: |
|||
os.makedirs(os.path.join(project_dir, d), exist_ok=True) |
|||
|
|||
# 处理表信息 |
|||
processed_tables = [self._process_table(t) for t in tables] |
|||
|
|||
# 构建数据库URL |
|||
if db_config: |
|||
db_host = db_config.get("host", "localhost") |
|||
db_port = db_config.get("port", 3306) |
|||
db_user = db_config.get("user", "root") |
|||
db_password = db_config.get("password", "") |
|||
db_database = db_config.get("database", "") |
|||
database_url = f"mysql+pymysql://{db_user}:{quote_plus(db_password)}@{db_host}:{db_port}/{db_database}?charset=utf8mb4" |
|||
else: |
|||
database_url = "sqlite:///./sql_app.db" |
|||
|
|||
# 通用上下文 |
|||
context = { |
|||
"project_name": project_name, |
|||
"module_name": module_name, |
|||
"author": author, |
|||
"date": datetime.now().strftime("%Y-%m-%d"), |
|||
"database_url": database_url, |
|||
"database_type": "mysql" if db_config else "sqlite", |
|||
"tables": processed_tables, |
|||
**kwargs, |
|||
} |
|||
|
|||
# 生成主文件 |
|||
self._render_template( |
|||
"main.py.j2", context, os.path.join(project_dir, "main.py") |
|||
) |
|||
self._render_template( |
|||
"config.py.j2", context, os.path.join(project_dir, "app", "config.py") |
|||
) |
|||
self._render_template( |
|||
"database.py.j2", context, os.path.join(project_dir, "app", "database.py") |
|||
) |
|||
self._render_template( |
|||
"requirements.txt.j2", |
|||
context, |
|||
os.path.join(project_dir, "requirements.txt"), |
|||
) |
|||
self._render_template( |
|||
"result.py.j2", |
|||
context, |
|||
os.path.join(project_dir, "app", "utils", "result.py"), |
|||
) |
|||
|
|||
# 生成__init__.py文件 |
|||
init_dirs = [ |
|||
"app", |
|||
"app/models", |
|||
"app/schemas", |
|||
"app/services", |
|||
"app/routers", |
|||
"app/utils", |
|||
"tests", |
|||
] |
|||
for d in init_dirs: |
|||
self._render_template( |
|||
"__init__.py.j2", |
|||
{"package_name": d.replace("/", ".")}, |
|||
os.path.join(project_dir, d, "__init__.py"), |
|||
) |
|||
|
|||
# 为每个表生成代码 |
|||
for table in processed_tables: |
|||
table_context = { |
|||
**context, |
|||
"table": table, |
|||
"fields": table.get("fields", []), |
|||
} |
|||
|
|||
# 生成Model |
|||
self._render_template( |
|||
"model.py.j2", |
|||
table_context, |
|||
os.path.join(project_dir, "app", "models", f"{table['name_lower']}.py"), |
|||
) |
|||
|
|||
# 生成Schema |
|||
self._render_template( |
|||
"schema.py.j2", |
|||
table_context, |
|||
os.path.join( |
|||
project_dir, "app", "schemas", f"{table['name_lower']}.py" |
|||
), |
|||
) |
|||
|
|||
# 生成Service |
|||
self._render_template( |
|||
"service.py.j2", |
|||
table_context, |
|||
os.path.join( |
|||
project_dir, "app", "services", f"{table['name_lower']}_service.py" |
|||
), |
|||
) |
|||
|
|||
# 生成Router |
|||
self._render_template( |
|||
"router.py.j2", |
|||
table_context, |
|||
os.path.join( |
|||
project_dir, "app", "routers", f"{table['name_lower']}.py" |
|||
), |
|||
) |
|||
|
|||
print(f"项目生成完成: {project_dir}") |
|||
print(f"\n项目结构:") |
|||
self._print_tree(project_dir) |
|||
return project_dir |
|||
|
|||
def _render_template( |
|||
self, template_name: str, context: Dict[str, Any], output_path: str |
|||
): |
|||
"""渲染模板并写入文件""" |
|||
try: |
|||
template = self.env.get_template(template_name) |
|||
content = template.render(**context) |
|||
with open(output_path, "w", encoding="utf-8") as f: |
|||
f.write(content) |
|||
print( |
|||
f" 生成: {os.path.relpath(output_path, os.path.dirname(output_path))}" |
|||
) |
|||
except Exception as e: |
|||
print(f" 生成失败 {template_name}: {str(e)}") |
|||
|
|||
@staticmethod |
|||
def _print_tree(directory: str, prefix: str = ""): |
|||
"""打印目录树""" |
|||
entries = sorted(os.listdir(directory)) |
|||
entries = [e for e in entries if not e.startswith("__pycache__")] |
|||
|
|||
for i, entry in enumerate(entries): |
|||
path = os.path.join(directory, entry) |
|||
is_last = i == len(entries) - 1 |
|||
connector = "└── " if is_last else "├── " |
|||
print(f"{prefix}{connector}{entry}") |
|||
|
|||
if os.path.isdir(path): |
|||
extension = " " if is_last else "│ " |
|||
PythonScaffoldingGenerator._print_tree(path, prefix + extension) |
|||
|
|||
|
|||
def load_config(config_path: str) -> Dict[str, Any]: |
|||
"""加载YAML配置文件""" |
|||
with open(config_path, "r", encoding="utf-8") as f: |
|||
config = yaml.safe_load(f) |
|||
return config |
|||
|
|||
|
|||
def resolve_variables(config: Dict[str, Any]) -> Dict[str, Any]: |
|||
"""解析配置中的变量引用""" |
|||
config_str = yaml.dump(config) |
|||
|
|||
# 简单的变量替换 |
|||
replacements = { |
|||
"${mainModule}": config.get("mainModule", ""), |
|||
"${moduleName}": config.get("moduleName", ""), |
|||
"${package.Base}": config.get("package", {}).get("Base", ""), |
|||
"${package.Models}": config.get("package", {}).get("Models", ""), |
|||
} |
|||
|
|||
for key, value in replacements.items(): |
|||
config_str = config_str.replace(key, value) |
|||
|
|||
return yaml.safe_load(config_str) |
|||
|
|||
|
|||
def main(): |
|||
"""主函数""" |
|||
parser = argparse.ArgumentParser( |
|||
description="Python API脚手架生成器 - 复用Java配置" |
|||
) |
|||
parser.add_argument( |
|||
"-c", "--config", default="config.yml", help="配置文件路径(默认: config.yml)" |
|||
) |
|||
parser.add_argument( |
|||
"-o", "--output", default="./output", help="输出目录(默认: ./output)" |
|||
) |
|||
parser.add_argument( |
|||
"-t", "--tables", nargs="+", help="指定要生成的表名(默认: 全部)" |
|||
) |
|||
parser.add_argument( |
|||
"--no-db", action="store_true", help="不连接数据库,使用示例数据" |
|||
) |
|||
|
|||
args = parser.parse_args() |
|||
|
|||
# 检查配置文件 |
|||
if not os.path.exists(args.config): |
|||
print(f"错误: 配置文件不存在: {args.config}") |
|||
return |
|||
|
|||
# 加载配置 |
|||
print(f"加载配置文件: {args.config}") |
|||
config = load_config(args.config) |
|||
config = resolve_variables(config) |
|||
|
|||
# 获取基础信息 |
|||
main_module = config.get("mainModule", "project") |
|||
module_name = config.get("moduleName", "api") |
|||
author = config.get("author", "Auto Generated") |
|||
db_config = config.get("db", {}) |
|||
app_config = config.get("application", {}) |
|||
|
|||
print(f"项目: {main_module}/{module_name}") |
|||
print(f"作者: {author}") |
|||
|
|||
# 获取表结构 |
|||
tables = [] |
|||
if not args.no_db and db_config: |
|||
print(f"\n连接数据库: {db_config.get('host')}:{db_config.get('port')}") |
|||
inspector = DatabaseInspector( |
|||
host=db_config.get("host", "localhost"), |
|||
port=db_config.get("port", 3306), |
|||
user=db_config.get("user", "root"), |
|||
password=db_config.get("password", ""), |
|||
database=db_config.get("database", ""), |
|||
) |
|||
|
|||
if inspector.connect(): |
|||
tables = inspector.get_tables(args.tables) |
|||
inspector.close() |
|||
print(f"获取到 {len(tables)} 个表") |
|||
else: |
|||
print("数据库连接失败,使用示例数据") |
|||
tables = _get_sample_tables() |
|||
else: |
|||
print("\n使用示例数据") |
|||
tables = _get_sample_tables() |
|||
|
|||
if not tables: |
|||
print("没有要生成的表") |
|||
return |
|||
|
|||
# 初始化生成器 |
|||
generator = PythonScaffoldingGenerator() |
|||
|
|||
# 生成项目 |
|||
generator.generate_project( |
|||
project_name=main_module, |
|||
module_name=module_name, |
|||
tables=tables, |
|||
output_dir=args.output, |
|||
author=author, |
|||
db_config=db_config if not args.no_db else None, |
|||
redis_config=app_config.get("redis"), |
|||
minio_config=app_config.get("minio"), |
|||
) |
|||
|
|||
print("\n生成完成!") |
|||
print(f"\n下一步:") |
|||
print(f" 1. cd {args.output}/{main_module}/{module_name}") |
|||
print(f" 2. pip install -r requirements.txt") |
|||
print(f" 3. python main.py") |
|||
|
|||
|
|||
def _get_sample_tables() -> List[Dict[str, Any]]: |
|||
"""获取示例表数据""" |
|||
return [ |
|||
{ |
|||
"name": "user", |
|||
"comment": "用户表", |
|||
"fields": [ |
|||
{ |
|||
"name": "username", |
|||
"type": "varchar(50)", |
|||
"comment": "用户名", |
|||
"nullable": False, |
|||
"searchable": True, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "String(50)", |
|||
}, |
|||
{ |
|||
"name": "password", |
|||
"type": "varchar(100)", |
|||
"comment": "密码", |
|||
"nullable": False, |
|||
"searchable": False, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "String(100)", |
|||
}, |
|||
{ |
|||
"name": "email", |
|||
"type": "varchar(100)", |
|||
"comment": "邮箱", |
|||
"nullable": True, |
|||
"searchable": True, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "String(100)", |
|||
}, |
|||
{ |
|||
"name": "phone", |
|||
"type": "varchar(20)", |
|||
"comment": "手机号", |
|||
"nullable": True, |
|||
"searchable": True, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "String(20)", |
|||
}, |
|||
{ |
|||
"name": "status", |
|||
"type": "int", |
|||
"comment": "状态", |
|||
"nullable": True, |
|||
"searchable": True, |
|||
"python_type": "int", |
|||
"sqlalchemy_type": "Integer", |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
"name": "product", |
|||
"comment": "商品表", |
|||
"fields": [ |
|||
{ |
|||
"name": "name", |
|||
"type": "varchar(100)", |
|||
"comment": "商品名称", |
|||
"nullable": False, |
|||
"searchable": True, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "String(100)", |
|||
}, |
|||
{ |
|||
"name": "description", |
|||
"type": "text", |
|||
"comment": "商品描述", |
|||
"nullable": True, |
|||
"searchable": False, |
|||
"python_type": "str", |
|||
"sqlalchemy_type": "Text", |
|||
}, |
|||
{ |
|||
"name": "price", |
|||
"type": "decimal(10,2)", |
|||
"comment": "价格", |
|||
"nullable": False, |
|||
"searchable": True, |
|||
"python_type": "float", |
|||
"sqlalchemy_type": "Numeric(precision=10, scale=2)", |
|||
}, |
|||
{ |
|||
"name": "stock", |
|||
"type": "int", |
|||
"comment": "库存", |
|||
"nullable": True, |
|||
"searchable": True, |
|||
"python_type": "int", |
|||
"sqlalchemy_type": "Integer", |
|||
}, |
|||
{ |
|||
"name": "category_id", |
|||
"type": "int", |
|||
"comment": "分类ID", |
|||
"nullable": True, |
|||
"searchable": True, |
|||
"python_type": "int", |
|||
"sqlalchemy_type": "Integer", |
|||
}, |
|||
], |
|||
}, |
|||
] |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
@ -0,0 +1 @@ |
|||
# {{ package_name }} package |
|||
@ -0,0 +1,31 @@ |
|||
""" |
|||
Application Configuration |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
import os |
|||
from pydantic_settings import BaseSettings |
|||
|
|||
class Settings(BaseSettings): |
|||
"""Application Settings""" |
|||
APP_NAME: str = "{{ project_name }}" |
|||
APP_VERSION: str = "1.0.0" |
|||
DEBUG: bool = True |
|||
|
|||
# Database |
|||
DATABASE_URL: str = "{{ database_url | default('sqlite:///./sql_app.db') }}" |
|||
|
|||
# JWT |
|||
SECRET_KEY: str = "{{ secret_key | default('your-secret-key-change-in-production') }}" |
|||
ALGORITHM: str = "HS256" |
|||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 |
|||
|
|||
# CORS |
|||
ALLOWED_ORIGINS: list = ["*"] |
|||
|
|||
class Config: |
|||
env_file = ".env" |
|||
case_sensitive = True |
|||
|
|||
settings = Settings() |
|||
@ -0,0 +1,33 @@ |
|||
""" |
|||
Database configuration |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from sqlalchemy import create_engine |
|||
from sqlalchemy.ext.declarative import declarative_base |
|||
from sqlalchemy.orm import sessionmaker |
|||
|
|||
# Database URL - configure according to your database |
|||
SQLALCHEMY_DATABASE_URL = "{{ database_url | default('sqlite:///./sql_app.db') }}" |
|||
|
|||
engine = create_engine( |
|||
SQLALCHEMY_DATABASE_URL, |
|||
{% if database_url and 'sqlite' not in database_url %} |
|||
pool_pre_ping=True, |
|||
pool_size=10, |
|||
max_overflow=20 |
|||
{% endif %} |
|||
) |
|||
|
|||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
|||
|
|||
Base = declarative_base() |
|||
|
|||
def get_db(): |
|||
"""Dependency to get database session""" |
|||
db = SessionLocal() |
|||
try: |
|||
yield db |
|||
finally: |
|||
db.close() |
|||
@ -0,0 +1,44 @@ |
|||
""" |
|||
{{ project_name }} - FastAPI Application |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from fastapi import FastAPI |
|||
from fastapi.middleware.cors import CORSMiddleware |
|||
|
|||
from app.database import engine, Base |
|||
{% for table in tables %} |
|||
from app.routers import {{ table.name_lower }} |
|||
{% endfor %} |
|||
|
|||
# Create database tables |
|||
Base.metadata.create_all(bind=engine) |
|||
|
|||
app = FastAPI( |
|||
title="{{ project_name }}", |
|||
description="{{ project_description }}", |
|||
version="1.0.0" |
|||
) |
|||
|
|||
# CORS middleware |
|||
app.add_middleware( |
|||
CORSMiddleware, |
|||
allow_origins=["*"], |
|||
allow_credentials=True, |
|||
allow_methods=["*"], |
|||
allow_headers=["*"], |
|||
) |
|||
|
|||
# Include routers |
|||
{% for table in tables %} |
|||
app.include_router({{ table.name_lower }}.router, prefix="/api/{{ table.name_lower }}", tags=["{{ table.comment }}"]) |
|||
{% endfor %} |
|||
|
|||
@app.get("/") |
|||
async def root(): |
|||
return {"message": "Welcome to {{ project_name }}"} |
|||
|
|||
if __name__ == "__main__": |
|||
import uvicorn |
|||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) |
|||
@ -0,0 +1,26 @@ |
|||
""" |
|||
{{ table.comment }} Model - 对齐JAVA Entity |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from sqlalchemy import Column, Integer, BigInteger, SmallInteger, String, DateTime, Boolean, Float, Text, Numeric, JSON, ForeignKey |
|||
from sqlalchemy.sql import func |
|||
|
|||
from app.database import Base |
|||
|
|||
class {{ table.entity }}Model(Base): |
|||
""" |
|||
{{ table.comment }} - 对齐JAVA Entity |
|||
""" |
|||
__tablename__ = "{{ table.name }}" |
|||
|
|||
id = Column(Integer, primary_key=True, index=True, autoincrement=True, comment="主键ID") |
|||
{% for field in fields %} |
|||
{{ field.name }} = Column({{ field.sqlalchemy_type }}, {% if field.nullable %}nullable=True{% else %}nullable=False{% endif %}, comment="{{ field.comment }}") |
|||
{% endfor %} |
|||
created_at = Column(DateTime, server_default=func.now(), comment="创建时间") |
|||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") |
|||
created_by = Column(String(50), comment="创建人") |
|||
updated_by = Column(String(50), comment="更新人") |
|||
is_deleted = Column(Boolean, default=False, comment="是否删除") |
|||
@ -0,0 +1,22 @@ |
|||
# {{ project_name }} Dependencies |
|||
# @author: {{ author }} |
|||
# @date: {{ date }} |
|||
|
|||
fastapi==0.104.1 |
|||
uvicorn[standard]==0.24.0 |
|||
sqlalchemy==2.0.23 |
|||
pydantic==2.5.2 |
|||
python-multipart==0.0.6 |
|||
{% if database_type == 'mysql' %} |
|||
pymysql==1.1.0 |
|||
cryptography==41.0.7 |
|||
{% elif database_type == 'postgresql' %} |
|||
psycopg2-binary==2.9.9 |
|||
{% elif database_type == 'sqlite' %} |
|||
# SQLite is included in Python standard library |
|||
{% endif %} |
|||
python-jose[cryptography]==3.3.0 |
|||
passlib[bcrypt]==1.7.4 |
|||
httpx==0.25.2 |
|||
pytest==7.4.3 |
|||
pytest-asyncio==0.23.2 |
|||
@ -0,0 +1,53 @@ |
|||
""" |
|||
统一返回结果类 - 对齐JAVA Result |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from typing import TypeVar, Generic, Optional, Any |
|||
from pydantic import BaseModel, Field |
|||
from datetime import datetime |
|||
import time |
|||
|
|||
T = TypeVar('T') |
|||
|
|||
class Result(BaseModel, Generic[T]): |
|||
"""接口返回数据格式 - 对齐JAVA Result<T>""" |
|||
success: bool = Field(True, description="成功标志") |
|||
message: str = Field("", description="返回处理消息") |
|||
code: int = Field(0, description="返回代码, 0=成功, 500=错误") |
|||
result: Optional[T] = Field(None, description="返回数据对象") |
|||
timestamp: int = Field(default_factory=lambda: int(time.time() * 1000), description="时间戳") |
|||
|
|||
@staticmethod |
|||
def OK(data: Any = None, message: str = "") -> dict: |
|||
"""成功返回""" |
|||
return { |
|||
"success": True, |
|||
"message": message, |
|||
"code": 200, |
|||
"result": data, |
|||
"timestamp": int(time.time() * 1000) |
|||
} |
|||
|
|||
@staticmethod |
|||
def error(message: str, code: int = 500) -> dict: |
|||
"""错误返回""" |
|||
return { |
|||
"success": False, |
|||
"message": message, |
|||
"code": code, |
|||
"result": None, |
|||
"timestamp": int(time.time() * 1000) |
|||
} |
|||
|
|||
@staticmethod |
|||
def noauth(message: str = "无权限访问") -> dict: |
|||
"""无权限返回""" |
|||
return { |
|||
"success": False, |
|||
"message": message, |
|||
"code": 210, |
|||
"result": None, |
|||
"timestamp": int(time.time() * 1000) |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
""" |
|||
{{ table.comment }} Router - 对齐JAVA Controller |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from datetime import datetime |
|||
from fastapi import APIRouter, Depends, HTTPException, Header |
|||
from sqlalchemy.orm import Session |
|||
from typing import List, Optional |
|||
|
|||
from app.database import get_db |
|||
from app.schemas.{{ table.name_lower }} import {{ table.entity }} |
|||
from app.services.{{ table.name_lower }}_service import {{ table.entity }}Service |
|||
from app.utils.result import Result |
|||
|
|||
router = APIRouter() |
|||
|
|||
@router.post("/page", summary="{{ table.comment }}分页列表查询") |
|||
async def page( |
|||
param: {{ table.entity }}, |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}分页列表查询 - 对齐JAVA: @PostMapping("/page")""" |
|||
if not token: |
|||
return Result.error("token不能为空") |
|||
|
|||
page_num = param.pageNum if param.pageNum else 1 |
|||
page_size = param.pageSize if param.pageSize else 10 |
|||
|
|||
items, total = {{ table.entity }}Service.get_list( |
|||
db=db, |
|||
param=param, |
|||
page=page_num, |
|||
page_size=page_size |
|||
) |
|||
|
|||
page_data = { |
|||
"records": [item.__dict__ for item in items], |
|||
"total": total, |
|||
"size": page_size, |
|||
"current": page_num, |
|||
"pages": (total + page_size - 1) // page_size |
|||
} |
|||
return Result.OK(page_data) |
|||
|
|||
|
|||
@router.post("/info", summary="{{ table.comment }}根据条件查询") |
|||
async def info( |
|||
param: {{ table.entity }}, |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}根据条件查询 - 对齐JAVA: @PostMapping("/info")""" |
|||
if not token: |
|||
return Result.error("token不能为空") |
|||
|
|||
data = {{ table.entity }}Service.info(db=db, param=param) |
|||
return Result.OK(data) |
|||
|
|||
|
|||
@router.post("/add", summary="{{ table.comment }}新增") |
|||
async def add( |
|||
param: {{ table.entity }}, |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}新增 - 对齐JAVA: @PostMapping("/add")""" |
|||
if not token: |
|||
return Result.error("token不能为空") |
|||
|
|||
{{ table.entity }}Service.add(db=db, param=param) |
|||
return Result.OK() |
|||
|
|||
|
|||
@router.post("/modify", summary="{{ table.comment }}修改") |
|||
async def modify( |
|||
param: {{ table.entity }}, |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}修改 - 对齐JAVA: @PostMapping("/modify")""" |
|||
if not token: |
|||
return Result.error("token不能为空") |
|||
|
|||
info = {{ table.entity }}Service.get_by_id(db=db, id=param.id) |
|||
if info is None: |
|||
return Result.error(f"[{param.id}]记录不存在") |
|||
|
|||
{{ table.entity }}Service.modify(db=db, param=param) |
|||
return Result.OK() |
|||
|
|||
|
|||
@router.get("/remove/{id}", summary="{{ table.comment }}删除(单个条目)") |
|||
async def remove( |
|||
id: int, |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}删除(单个条目) - 对齐JAVA: @GetMapping("/remove/{id}")""" |
|||
{{ table.entity }}Service.remove(db=db, id=id) |
|||
return Result.OK() |
|||
|
|||
|
|||
@router.post("/removes", summary="{{ table.comment }}删除(多个条目)") |
|||
async def removes( |
|||
ids: List[int], |
|||
token: str = Header(..., description="认证token"), |
|||
version: str = Header("1.0", description="版本号"), |
|||
db: Session = Depends(get_db) |
|||
): |
|||
"""{{ table.comment }}删除(多个条目) - 对齐JAVA: @PostMapping("/removes")""" |
|||
{{ table.entity }}Service.removes(db=db, ids=ids) |
|||
return Result.OK() |
|||
@ -0,0 +1,33 @@ |
|||
""" |
|||
{{ table.comment }} Pydantic Schemas - 对齐JAVA Entity |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from pydantic import BaseModel, Field |
|||
from typing import Optional, List |
|||
from datetime import datetime |
|||
|
|||
class {{ table.entity }}(BaseModel): |
|||
""" |
|||
{{ table.comment }} - 对齐JAVA Entity |
|||
所有接口共用同一个Schema作为入参 |
|||
""" |
|||
id: Optional[int] = Field(None, description="主键ID") |
|||
{% for field in fields %} |
|||
{{ field.name }}: Optional[{{ field.python_type }}] = Field(None, description="{{ field.comment }}") |
|||
{% endfor %} |
|||
# 分页字段 - 对齐JAVA BaseEntity |
|||
pageNum: Optional[int] = Field(None, description="当前页码") |
|||
pageSize: Optional[int] = Field(None, description="每页数量") |
|||
# 审计字段 |
|||
created_at: Optional[datetime] = Field(None, description="创建时间") |
|||
updated_at: Optional[datetime] = Field(None, description="更新时间") |
|||
created_by: Optional[str] = Field(None, description="创建人") |
|||
updated_by: Optional[str] = Field(None, description="更新人") |
|||
is_deleted: Optional[bool] = Field(False, description="是否删除") |
|||
# 用户信息 |
|||
user_id: Optional[str] = Field(None, description="用户ID") |
|||
|
|||
class Config: |
|||
from_attributes = True |
|||
@ -0,0 +1,132 @@ |
|||
""" |
|||
{{ table.comment }} Service - 对齐JAVA Service |
|||
|
|||
@author: {{ author }} |
|||
@date: {{ date }} |
|||
""" |
|||
from sqlalchemy.orm import Session |
|||
from sqlalchemy import and_ |
|||
from typing import List, Optional, Tuple |
|||
from datetime import datetime |
|||
|
|||
from app.models.{{ table.name_lower }} import {{ table.entity }}Model |
|||
from app.schemas.{{ table.name_lower }} import {{ table.entity }} |
|||
|
|||
class {{ table.entity }}Service: |
|||
"""{{ table.comment }} Service Class - 对齐JAVA Service""" |
|||
|
|||
@staticmethod |
|||
def get_by_id(db: Session, id: int) -> Optional[{{ table.entity }}Model]: |
|||
"""根据ID查询{{ table.comment }}""" |
|||
return db.query({{ table.entity }}Model).filter( |
|||
and_({{ table.entity }}Model.id == id, {{ table.entity }}Model.is_deleted == False) |
|||
).first() |
|||
|
|||
@staticmethod |
|||
def info(db: Session, param: {{ table.entity }}) -> Optional[{{ table.entity }}Model]: |
|||
"""{{ table.comment }}详情 - 对齐JAVA: info({{ table.entity }} param)""" |
|||
query = db.query({{ table.entity }}Model).filter({{ table.entity }}Model.is_deleted == False) |
|||
|
|||
if param.id is not None: |
|||
query = query.filter({{ table.entity }}Model.id == param.id) |
|||
{% for field in fields %} |
|||
{% if field.searchable %} |
|||
if param.{{ field.name }} is not None: |
|||
query = query.filter({{ table.entity }}Model.{{ field.name }} == param.{{ field.name }}) |
|||
{% endif %} |
|||
{% endfor %} |
|||
|
|||
return query.first() |
|||
|
|||
@staticmethod |
|||
def get_list( |
|||
db: Session, |
|||
param: {{ table.entity }}, |
|||
page: int = 1, |
|||
page_size: int = 10 |
|||
) -> Tuple[List[{{ table.entity }}Model], int]: |
|||
"""{{ table.comment }}分页列表 - 对齐JAVA: page({{ table.entity }} param)""" |
|||
query = db.query({{ table.entity }}Model).filter({{ table.entity }}Model.is_deleted == False) |
|||
|
|||
# 根据参数条件过滤 - 对齐JAVA MPJLambdaWrapper条件查询 |
|||
{% for field in fields %} |
|||
{% if field.searchable %} |
|||
if param.{{ field.name }} is not None: |
|||
query = query.filter({{ table.entity }}Model.{{ field.name }} == param.{{ field.name }}) |
|||
{% endif %} |
|||
{% endfor %} |
|||
|
|||
total = query.count() |
|||
items = query.offset((page - 1) * page_size).limit(page_size).all() |
|||
return items, total |
|||
|
|||
@staticmethod |
|||
def list(db: Session, param: {{ table.entity }}) -> List[{{ table.entity }}Model]: |
|||
"""{{ table.comment }}列表 - 对齐JAVA: list({{ table.entity }} param)""" |
|||
query = db.query({{ table.entity }}Model).filter({{ table.entity }}Model.is_deleted == False) |
|||
|
|||
{% for field in fields %} |
|||
{% if field.searchable %} |
|||
if param.{{ field.name }} is not None: |
|||
query = query.filter({{ table.entity }}Model.{{ field.name }} == param.{{ field.name }}) |
|||
{% endif %} |
|||
{% endfor %} |
|||
|
|||
return query.all() |
|||
|
|||
@staticmethod |
|||
def add(db: Session, param: {{ table.entity }}) -> None: |
|||
"""{{ table.comment }}新增 - 对齐JAVA: add({{ table.entity }} param)""" |
|||
db_obj = {{ table.entity }}Model( |
|||
{% for field in fields %} |
|||
{{ field.name }}=param.{{ field.name }}, |
|||
{% endfor %} |
|||
created_by=param.user_id, |
|||
created_at=datetime.now() |
|||
) |
|||
db.add(db_obj) |
|||
db.commit() |
|||
db.refresh(db_obj) |
|||
|
|||
@staticmethod |
|||
def modify(db: Session, param: {{ table.entity }}) -> None: |
|||
"""{{ table.comment }}修改 - 对齐JAVA: modify({{ table.entity }} param)""" |
|||
db_obj = db.query({{ table.entity }}Model).filter( |
|||
and_({{ table.entity }}Model.id == param.id, {{ table.entity }}Model.is_deleted == False) |
|||
).first() |
|||
|
|||
if db_obj is None: |
|||
return |
|||
|
|||
# 更新非空字段 |
|||
{% for field in fields %} |
|||
if param.{{ field.name }} is not None: |
|||
db_obj.{{ field.name }} = param.{{ field.name }} |
|||
{% endfor %} |
|||
|
|||
db_obj.updated_by = param.user_id |
|||
db_obj.updated_at = datetime.now() |
|||
db.commit() |
|||
|
|||
@staticmethod |
|||
def remove(db: Session, id: int) -> None: |
|||
"""{{ table.comment }}删除(单个条目) - 对齐JAVA: remove(Integer id)""" |
|||
db_obj = db.query({{ table.entity }}Model).filter( |
|||
and_({{ table.entity }}Model.id == id, {{ table.entity }}Model.is_deleted == False) |
|||
).first() |
|||
|
|||
if db_obj: |
|||
db_obj.is_deleted = True |
|||
db_obj.updated_at = datetime.now() |
|||
db.commit() |
|||
|
|||
@staticmethod |
|||
def removes(db: Session, ids: List[int]) -> None: |
|||
"""{{ table.comment }}删除(多个条目) - 对齐JAVA: removes(List<Integer> ids)""" |
|||
db.query({{ table.entity }}Model).filter( |
|||
and_({{ table.entity }}Model.id.in_(ids), {{ table.entity }}Model.is_deleted == False) |
|||
).update({ |
|||
{{ table.entity }}Model.is_deleted: True, |
|||
{{ table.entity }}Model.updated_at: datetime.now() |
|||
}, synchronize_session=False) |
|||
db.commit() |
|||
@ -0,0 +1,9 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2024-present, Vben |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
@ -0,0 +1,531 @@ |
|||
/** |
|||
* 自动生成 Mock 数据 |
|||
*/ |
|||
|
|||
import { MockMethod } from 'vite-plugin-mock'; |
|||
|
|||
const list = [ |
|||
|
|||
|
|||
{ |
|||
|
|||
id: 'id_0', |
|||
|
|||
funParentId: 'funParentId_0', |
|||
|
|||
funName: 'funName_0', |
|||
|
|||
funCode: 'funCode_0', |
|||
|
|||
funValue: 'funValue_0', |
|||
|
|||
funWarnLevel: 'funWarnLevel_0', |
|||
|
|||
funWarnType: 'funWarnType_0', |
|||
|
|||
funIndex: 'funIndex_0', |
|||
|
|||
createdAt: 'createdAt_0', |
|||
|
|||
createdBy: 'createdBy_0', |
|||
|
|||
updatedAt: 'updatedAt_0', |
|||
|
|||
updatedBy: 'updatedBy_0', |
|||
|
|||
deletedFlag: 'deletedFlag_0', |
|||
|
|||
userId: 'userId_0', |
|||
|
|||
deviceCode: 'deviceCode_0', |
|||
|
|||
funImg: 'funImg_0', |
|||
|
|||
deviceType: 'deviceType_0', |
|||
|
|||
funStatus: 'funStatus_0', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_0', |
|||
|
|||
funMsgTitle: 'funMsgTitle_0', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_0', |
|||
|
|||
funShortMsg: 'funShortMsg_0', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_1', |
|||
|
|||
funParentId: 'funParentId_1', |
|||
|
|||
funName: 'funName_1', |
|||
|
|||
funCode: 'funCode_1', |
|||
|
|||
funValue: 'funValue_1', |
|||
|
|||
funWarnLevel: 'funWarnLevel_1', |
|||
|
|||
funWarnType: 'funWarnType_1', |
|||
|
|||
funIndex: 'funIndex_1', |
|||
|
|||
createdAt: 'createdAt_1', |
|||
|
|||
createdBy: 'createdBy_1', |
|||
|
|||
updatedAt: 'updatedAt_1', |
|||
|
|||
updatedBy: 'updatedBy_1', |
|||
|
|||
deletedFlag: 'deletedFlag_1', |
|||
|
|||
userId: 'userId_1', |
|||
|
|||
deviceCode: 'deviceCode_1', |
|||
|
|||
funImg: 'funImg_1', |
|||
|
|||
deviceType: 'deviceType_1', |
|||
|
|||
funStatus: 'funStatus_1', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_1', |
|||
|
|||
funMsgTitle: 'funMsgTitle_1', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_1', |
|||
|
|||
funShortMsg: 'funShortMsg_1', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_2', |
|||
|
|||
funParentId: 'funParentId_2', |
|||
|
|||
funName: 'funName_2', |
|||
|
|||
funCode: 'funCode_2', |
|||
|
|||
funValue: 'funValue_2', |
|||
|
|||
funWarnLevel: 'funWarnLevel_2', |
|||
|
|||
funWarnType: 'funWarnType_2', |
|||
|
|||
funIndex: 'funIndex_2', |
|||
|
|||
createdAt: 'createdAt_2', |
|||
|
|||
createdBy: 'createdBy_2', |
|||
|
|||
updatedAt: 'updatedAt_2', |
|||
|
|||
updatedBy: 'updatedBy_2', |
|||
|
|||
deletedFlag: 'deletedFlag_2', |
|||
|
|||
userId: 'userId_2', |
|||
|
|||
deviceCode: 'deviceCode_2', |
|||
|
|||
funImg: 'funImg_2', |
|||
|
|||
deviceType: 'deviceType_2', |
|||
|
|||
funStatus: 'funStatus_2', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_2', |
|||
|
|||
funMsgTitle: 'funMsgTitle_2', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_2', |
|||
|
|||
funShortMsg: 'funShortMsg_2', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_3', |
|||
|
|||
funParentId: 'funParentId_3', |
|||
|
|||
funName: 'funName_3', |
|||
|
|||
funCode: 'funCode_3', |
|||
|
|||
funValue: 'funValue_3', |
|||
|
|||
funWarnLevel: 'funWarnLevel_3', |
|||
|
|||
funWarnType: 'funWarnType_3', |
|||
|
|||
funIndex: 'funIndex_3', |
|||
|
|||
createdAt: 'createdAt_3', |
|||
|
|||
createdBy: 'createdBy_3', |
|||
|
|||
updatedAt: 'updatedAt_3', |
|||
|
|||
updatedBy: 'updatedBy_3', |
|||
|
|||
deletedFlag: 'deletedFlag_3', |
|||
|
|||
userId: 'userId_3', |
|||
|
|||
deviceCode: 'deviceCode_3', |
|||
|
|||
funImg: 'funImg_3', |
|||
|
|||
deviceType: 'deviceType_3', |
|||
|
|||
funStatus: 'funStatus_3', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_3', |
|||
|
|||
funMsgTitle: 'funMsgTitle_3', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_3', |
|||
|
|||
funShortMsg: 'funShortMsg_3', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_4', |
|||
|
|||
funParentId: 'funParentId_4', |
|||
|
|||
funName: 'funName_4', |
|||
|
|||
funCode: 'funCode_4', |
|||
|
|||
funValue: 'funValue_4', |
|||
|
|||
funWarnLevel: 'funWarnLevel_4', |
|||
|
|||
funWarnType: 'funWarnType_4', |
|||
|
|||
funIndex: 'funIndex_4', |
|||
|
|||
createdAt: 'createdAt_4', |
|||
|
|||
createdBy: 'createdBy_4', |
|||
|
|||
updatedAt: 'updatedAt_4', |
|||
|
|||
updatedBy: 'updatedBy_4', |
|||
|
|||
deletedFlag: 'deletedFlag_4', |
|||
|
|||
userId: 'userId_4', |
|||
|
|||
deviceCode: 'deviceCode_4', |
|||
|
|||
funImg: 'funImg_4', |
|||
|
|||
deviceType: 'deviceType_4', |
|||
|
|||
funStatus: 'funStatus_4', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_4', |
|||
|
|||
funMsgTitle: 'funMsgTitle_4', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_4', |
|||
|
|||
funShortMsg: 'funShortMsg_4', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_5', |
|||
|
|||
funParentId: 'funParentId_5', |
|||
|
|||
funName: 'funName_5', |
|||
|
|||
funCode: 'funCode_5', |
|||
|
|||
funValue: 'funValue_5', |
|||
|
|||
funWarnLevel: 'funWarnLevel_5', |
|||
|
|||
funWarnType: 'funWarnType_5', |
|||
|
|||
funIndex: 'funIndex_5', |
|||
|
|||
createdAt: 'createdAt_5', |
|||
|
|||
createdBy: 'createdBy_5', |
|||
|
|||
updatedAt: 'updatedAt_5', |
|||
|
|||
updatedBy: 'updatedBy_5', |
|||
|
|||
deletedFlag: 'deletedFlag_5', |
|||
|
|||
userId: 'userId_5', |
|||
|
|||
deviceCode: 'deviceCode_5', |
|||
|
|||
funImg: 'funImg_5', |
|||
|
|||
deviceType: 'deviceType_5', |
|||
|
|||
funStatus: 'funStatus_5', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_5', |
|||
|
|||
funMsgTitle: 'funMsgTitle_5', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_5', |
|||
|
|||
funShortMsg: 'funShortMsg_5', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_6', |
|||
|
|||
funParentId: 'funParentId_6', |
|||
|
|||
funName: 'funName_6', |
|||
|
|||
funCode: 'funCode_6', |
|||
|
|||
funValue: 'funValue_6', |
|||
|
|||
funWarnLevel: 'funWarnLevel_6', |
|||
|
|||
funWarnType: 'funWarnType_6', |
|||
|
|||
funIndex: 'funIndex_6', |
|||
|
|||
createdAt: 'createdAt_6', |
|||
|
|||
createdBy: 'createdBy_6', |
|||
|
|||
updatedAt: 'updatedAt_6', |
|||
|
|||
updatedBy: 'updatedBy_6', |
|||
|
|||
deletedFlag: 'deletedFlag_6', |
|||
|
|||
userId: 'userId_6', |
|||
|
|||
deviceCode: 'deviceCode_6', |
|||
|
|||
funImg: 'funImg_6', |
|||
|
|||
deviceType: 'deviceType_6', |
|||
|
|||
funStatus: 'funStatus_6', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_6', |
|||
|
|||
funMsgTitle: 'funMsgTitle_6', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_6', |
|||
|
|||
funShortMsg: 'funShortMsg_6', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_7', |
|||
|
|||
funParentId: 'funParentId_7', |
|||
|
|||
funName: 'funName_7', |
|||
|
|||
funCode: 'funCode_7', |
|||
|
|||
funValue: 'funValue_7', |
|||
|
|||
funWarnLevel: 'funWarnLevel_7', |
|||
|
|||
funWarnType: 'funWarnType_7', |
|||
|
|||
funIndex: 'funIndex_7', |
|||
|
|||
createdAt: 'createdAt_7', |
|||
|
|||
createdBy: 'createdBy_7', |
|||
|
|||
updatedAt: 'updatedAt_7', |
|||
|
|||
updatedBy: 'updatedBy_7', |
|||
|
|||
deletedFlag: 'deletedFlag_7', |
|||
|
|||
userId: 'userId_7', |
|||
|
|||
deviceCode: 'deviceCode_7', |
|||
|
|||
funImg: 'funImg_7', |
|||
|
|||
deviceType: 'deviceType_7', |
|||
|
|||
funStatus: 'funStatus_7', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_7', |
|||
|
|||
funMsgTitle: 'funMsgTitle_7', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_7', |
|||
|
|||
funShortMsg: 'funShortMsg_7', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_8', |
|||
|
|||
funParentId: 'funParentId_8', |
|||
|
|||
funName: 'funName_8', |
|||
|
|||
funCode: 'funCode_8', |
|||
|
|||
funValue: 'funValue_8', |
|||
|
|||
funWarnLevel: 'funWarnLevel_8', |
|||
|
|||
funWarnType: 'funWarnType_8', |
|||
|
|||
funIndex: 'funIndex_8', |
|||
|
|||
createdAt: 'createdAt_8', |
|||
|
|||
createdBy: 'createdBy_8', |
|||
|
|||
updatedAt: 'updatedAt_8', |
|||
|
|||
updatedBy: 'updatedBy_8', |
|||
|
|||
deletedFlag: 'deletedFlag_8', |
|||
|
|||
userId: 'userId_8', |
|||
|
|||
deviceCode: 'deviceCode_8', |
|||
|
|||
funImg: 'funImg_8', |
|||
|
|||
deviceType: 'deviceType_8', |
|||
|
|||
funStatus: 'funStatus_8', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_8', |
|||
|
|||
funMsgTitle: 'funMsgTitle_8', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_8', |
|||
|
|||
funShortMsg: 'funShortMsg_8', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_9', |
|||
|
|||
funParentId: 'funParentId_9', |
|||
|
|||
funName: 'funName_9', |
|||
|
|||
funCode: 'funCode_9', |
|||
|
|||
funValue: 'funValue_9', |
|||
|
|||
funWarnLevel: 'funWarnLevel_9', |
|||
|
|||
funWarnType: 'funWarnType_9', |
|||
|
|||
funIndex: 'funIndex_9', |
|||
|
|||
createdAt: 'createdAt_9', |
|||
|
|||
createdBy: 'createdBy_9', |
|||
|
|||
updatedAt: 'updatedAt_9', |
|||
|
|||
updatedBy: 'updatedBy_9', |
|||
|
|||
deletedFlag: 'deletedFlag_9', |
|||
|
|||
userId: 'userId_9', |
|||
|
|||
deviceCode: 'deviceCode_9', |
|||
|
|||
funImg: 'funImg_9', |
|||
|
|||
deviceType: 'deviceType_9', |
|||
|
|||
funStatus: 'funStatus_9', |
|||
|
|||
graduallyIntervalTime: 'graduallyIntervalTime_9', |
|||
|
|||
funMsgTitle: 'funMsgTitle_9', |
|||
|
|||
funStatisticsTimes: 'funStatisticsTimes_9', |
|||
|
|||
funShortMsg: 'funShortMsg_9', |
|||
|
|||
}, |
|||
|
|||
|
|||
]; |
|||
|
|||
export default [ |
|||
|
|||
{ |
|||
url: '/api/health_fun/page', |
|||
method: 'get', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
data: { |
|||
records: list, |
|||
total: list.length, |
|||
}, |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
{ |
|||
url: '/api/health_fun', |
|||
method: 'post', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
message: 'success', |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
{ |
|||
url: '/api/health_fun/:id', |
|||
method: 'delete', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
message: 'deleted', |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
] as MockMethod[]; |
|||
@ -0,0 +1,511 @@ |
|||
/** |
|||
* 自动生成 Mock 数据 |
|||
*/ |
|||
|
|||
import { MockMethod } from 'vite-plugin-mock'; |
|||
|
|||
const list = [ |
|||
|
|||
|
|||
{ |
|||
|
|||
id: 'id_0', |
|||
|
|||
msgNote: 'msgNote_0', |
|||
|
|||
msgType: 'msgType_0', |
|||
|
|||
sceneId: 'sceneId_0', |
|||
|
|||
userId: 'userId_0', |
|||
|
|||
url: 'url_0', |
|||
|
|||
messageSendStatus: 'messageSendStatus_0', |
|||
|
|||
createdAt: 'createdAt_0', |
|||
|
|||
createdBy: 'createdBy_0', |
|||
|
|||
updatedAt: 'updatedAt_0', |
|||
|
|||
updatedBy: 'updatedBy_0', |
|||
|
|||
deletedFlag: 'deletedFlag_0', |
|||
|
|||
deviceId: 'deviceId_0', |
|||
|
|||
roomId: 'roomId_0', |
|||
|
|||
msgRead: 'msgRead_0', |
|||
|
|||
funId: 'funId_0', |
|||
|
|||
funParentId: 'funParentId_0', |
|||
|
|||
funWarnLevel: 'funWarnLevel_0', |
|||
|
|||
familyId: 'familyId_0', |
|||
|
|||
statisticsDuration: 'statisticsDuration_0', |
|||
|
|||
msgSourceId: 'msgSourceId_0', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_1', |
|||
|
|||
msgNote: 'msgNote_1', |
|||
|
|||
msgType: 'msgType_1', |
|||
|
|||
sceneId: 'sceneId_1', |
|||
|
|||
userId: 'userId_1', |
|||
|
|||
url: 'url_1', |
|||
|
|||
messageSendStatus: 'messageSendStatus_1', |
|||
|
|||
createdAt: 'createdAt_1', |
|||
|
|||
createdBy: 'createdBy_1', |
|||
|
|||
updatedAt: 'updatedAt_1', |
|||
|
|||
updatedBy: 'updatedBy_1', |
|||
|
|||
deletedFlag: 'deletedFlag_1', |
|||
|
|||
deviceId: 'deviceId_1', |
|||
|
|||
roomId: 'roomId_1', |
|||
|
|||
msgRead: 'msgRead_1', |
|||
|
|||
funId: 'funId_1', |
|||
|
|||
funParentId: 'funParentId_1', |
|||
|
|||
funWarnLevel: 'funWarnLevel_1', |
|||
|
|||
familyId: 'familyId_1', |
|||
|
|||
statisticsDuration: 'statisticsDuration_1', |
|||
|
|||
msgSourceId: 'msgSourceId_1', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_2', |
|||
|
|||
msgNote: 'msgNote_2', |
|||
|
|||
msgType: 'msgType_2', |
|||
|
|||
sceneId: 'sceneId_2', |
|||
|
|||
userId: 'userId_2', |
|||
|
|||
url: 'url_2', |
|||
|
|||
messageSendStatus: 'messageSendStatus_2', |
|||
|
|||
createdAt: 'createdAt_2', |
|||
|
|||
createdBy: 'createdBy_2', |
|||
|
|||
updatedAt: 'updatedAt_2', |
|||
|
|||
updatedBy: 'updatedBy_2', |
|||
|
|||
deletedFlag: 'deletedFlag_2', |
|||
|
|||
deviceId: 'deviceId_2', |
|||
|
|||
roomId: 'roomId_2', |
|||
|
|||
msgRead: 'msgRead_2', |
|||
|
|||
funId: 'funId_2', |
|||
|
|||
funParentId: 'funParentId_2', |
|||
|
|||
funWarnLevel: 'funWarnLevel_2', |
|||
|
|||
familyId: 'familyId_2', |
|||
|
|||
statisticsDuration: 'statisticsDuration_2', |
|||
|
|||
msgSourceId: 'msgSourceId_2', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_3', |
|||
|
|||
msgNote: 'msgNote_3', |
|||
|
|||
msgType: 'msgType_3', |
|||
|
|||
sceneId: 'sceneId_3', |
|||
|
|||
userId: 'userId_3', |
|||
|
|||
url: 'url_3', |
|||
|
|||
messageSendStatus: 'messageSendStatus_3', |
|||
|
|||
createdAt: 'createdAt_3', |
|||
|
|||
createdBy: 'createdBy_3', |
|||
|
|||
updatedAt: 'updatedAt_3', |
|||
|
|||
updatedBy: 'updatedBy_3', |
|||
|
|||
deletedFlag: 'deletedFlag_3', |
|||
|
|||
deviceId: 'deviceId_3', |
|||
|
|||
roomId: 'roomId_3', |
|||
|
|||
msgRead: 'msgRead_3', |
|||
|
|||
funId: 'funId_3', |
|||
|
|||
funParentId: 'funParentId_3', |
|||
|
|||
funWarnLevel: 'funWarnLevel_3', |
|||
|
|||
familyId: 'familyId_3', |
|||
|
|||
statisticsDuration: 'statisticsDuration_3', |
|||
|
|||
msgSourceId: 'msgSourceId_3', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_4', |
|||
|
|||
msgNote: 'msgNote_4', |
|||
|
|||
msgType: 'msgType_4', |
|||
|
|||
sceneId: 'sceneId_4', |
|||
|
|||
userId: 'userId_4', |
|||
|
|||
url: 'url_4', |
|||
|
|||
messageSendStatus: 'messageSendStatus_4', |
|||
|
|||
createdAt: 'createdAt_4', |
|||
|
|||
createdBy: 'createdBy_4', |
|||
|
|||
updatedAt: 'updatedAt_4', |
|||
|
|||
updatedBy: 'updatedBy_4', |
|||
|
|||
deletedFlag: 'deletedFlag_4', |
|||
|
|||
deviceId: 'deviceId_4', |
|||
|
|||
roomId: 'roomId_4', |
|||
|
|||
msgRead: 'msgRead_4', |
|||
|
|||
funId: 'funId_4', |
|||
|
|||
funParentId: 'funParentId_4', |
|||
|
|||
funWarnLevel: 'funWarnLevel_4', |
|||
|
|||
familyId: 'familyId_4', |
|||
|
|||
statisticsDuration: 'statisticsDuration_4', |
|||
|
|||
msgSourceId: 'msgSourceId_4', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_5', |
|||
|
|||
msgNote: 'msgNote_5', |
|||
|
|||
msgType: 'msgType_5', |
|||
|
|||
sceneId: 'sceneId_5', |
|||
|
|||
userId: 'userId_5', |
|||
|
|||
url: 'url_5', |
|||
|
|||
messageSendStatus: 'messageSendStatus_5', |
|||
|
|||
createdAt: 'createdAt_5', |
|||
|
|||
createdBy: 'createdBy_5', |
|||
|
|||
updatedAt: 'updatedAt_5', |
|||
|
|||
updatedBy: 'updatedBy_5', |
|||
|
|||
deletedFlag: 'deletedFlag_5', |
|||
|
|||
deviceId: 'deviceId_5', |
|||
|
|||
roomId: 'roomId_5', |
|||
|
|||
msgRead: 'msgRead_5', |
|||
|
|||
funId: 'funId_5', |
|||
|
|||
funParentId: 'funParentId_5', |
|||
|
|||
funWarnLevel: 'funWarnLevel_5', |
|||
|
|||
familyId: 'familyId_5', |
|||
|
|||
statisticsDuration: 'statisticsDuration_5', |
|||
|
|||
msgSourceId: 'msgSourceId_5', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_6', |
|||
|
|||
msgNote: 'msgNote_6', |
|||
|
|||
msgType: 'msgType_6', |
|||
|
|||
sceneId: 'sceneId_6', |
|||
|
|||
userId: 'userId_6', |
|||
|
|||
url: 'url_6', |
|||
|
|||
messageSendStatus: 'messageSendStatus_6', |
|||
|
|||
createdAt: 'createdAt_6', |
|||
|
|||
createdBy: 'createdBy_6', |
|||
|
|||
updatedAt: 'updatedAt_6', |
|||
|
|||
updatedBy: 'updatedBy_6', |
|||
|
|||
deletedFlag: 'deletedFlag_6', |
|||
|
|||
deviceId: 'deviceId_6', |
|||
|
|||
roomId: 'roomId_6', |
|||
|
|||
msgRead: 'msgRead_6', |
|||
|
|||
funId: 'funId_6', |
|||
|
|||
funParentId: 'funParentId_6', |
|||
|
|||
funWarnLevel: 'funWarnLevel_6', |
|||
|
|||
familyId: 'familyId_6', |
|||
|
|||
statisticsDuration: 'statisticsDuration_6', |
|||
|
|||
msgSourceId: 'msgSourceId_6', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_7', |
|||
|
|||
msgNote: 'msgNote_7', |
|||
|
|||
msgType: 'msgType_7', |
|||
|
|||
sceneId: 'sceneId_7', |
|||
|
|||
userId: 'userId_7', |
|||
|
|||
url: 'url_7', |
|||
|
|||
messageSendStatus: 'messageSendStatus_7', |
|||
|
|||
createdAt: 'createdAt_7', |
|||
|
|||
createdBy: 'createdBy_7', |
|||
|
|||
updatedAt: 'updatedAt_7', |
|||
|
|||
updatedBy: 'updatedBy_7', |
|||
|
|||
deletedFlag: 'deletedFlag_7', |
|||
|
|||
deviceId: 'deviceId_7', |
|||
|
|||
roomId: 'roomId_7', |
|||
|
|||
msgRead: 'msgRead_7', |
|||
|
|||
funId: 'funId_7', |
|||
|
|||
funParentId: 'funParentId_7', |
|||
|
|||
funWarnLevel: 'funWarnLevel_7', |
|||
|
|||
familyId: 'familyId_7', |
|||
|
|||
statisticsDuration: 'statisticsDuration_7', |
|||
|
|||
msgSourceId: 'msgSourceId_7', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_8', |
|||
|
|||
msgNote: 'msgNote_8', |
|||
|
|||
msgType: 'msgType_8', |
|||
|
|||
sceneId: 'sceneId_8', |
|||
|
|||
userId: 'userId_8', |
|||
|
|||
url: 'url_8', |
|||
|
|||
messageSendStatus: 'messageSendStatus_8', |
|||
|
|||
createdAt: 'createdAt_8', |
|||
|
|||
createdBy: 'createdBy_8', |
|||
|
|||
updatedAt: 'updatedAt_8', |
|||
|
|||
updatedBy: 'updatedBy_8', |
|||
|
|||
deletedFlag: 'deletedFlag_8', |
|||
|
|||
deviceId: 'deviceId_8', |
|||
|
|||
roomId: 'roomId_8', |
|||
|
|||
msgRead: 'msgRead_8', |
|||
|
|||
funId: 'funId_8', |
|||
|
|||
funParentId: 'funParentId_8', |
|||
|
|||
funWarnLevel: 'funWarnLevel_8', |
|||
|
|||
familyId: 'familyId_8', |
|||
|
|||
statisticsDuration: 'statisticsDuration_8', |
|||
|
|||
msgSourceId: 'msgSourceId_8', |
|||
|
|||
}, |
|||
|
|||
{ |
|||
|
|||
id: 'id_9', |
|||
|
|||
msgNote: 'msgNote_9', |
|||
|
|||
msgType: 'msgType_9', |
|||
|
|||
sceneId: 'sceneId_9', |
|||
|
|||
userId: 'userId_9', |
|||
|
|||
url: 'url_9', |
|||
|
|||
messageSendStatus: 'messageSendStatus_9', |
|||
|
|||
createdAt: 'createdAt_9', |
|||
|
|||
createdBy: 'createdBy_9', |
|||
|
|||
updatedAt: 'updatedAt_9', |
|||
|
|||
updatedBy: 'updatedBy_9', |
|||
|
|||
deletedFlag: 'deletedFlag_9', |
|||
|
|||
deviceId: 'deviceId_9', |
|||
|
|||
roomId: 'roomId_9', |
|||
|
|||
msgRead: 'msgRead_9', |
|||
|
|||
funId: 'funId_9', |
|||
|
|||
funParentId: 'funParentId_9', |
|||
|
|||
funWarnLevel: 'funWarnLevel_9', |
|||
|
|||
familyId: 'familyId_9', |
|||
|
|||
statisticsDuration: 'statisticsDuration_9', |
|||
|
|||
msgSourceId: 'msgSourceId_9', |
|||
|
|||
}, |
|||
|
|||
|
|||
]; |
|||
|
|||
export default [ |
|||
|
|||
{ |
|||
url: '/api/health_msg/page', |
|||
method: 'get', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
data: { |
|||
records: list, |
|||
total: list.length, |
|||
}, |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
{ |
|||
url: '/api/health_msg', |
|||
method: 'post', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
message: 'success', |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
{ |
|||
url: '/api/health_msg/:id', |
|||
method: 'delete', |
|||
response: () => { |
|||
return { |
|||
code: 0, |
|||
message: 'deleted', |
|||
}; |
|||
}, |
|||
}, |
|||
|
|||
] as MockMethod[]; |
|||
@ -0,0 +1,69 @@ |
|||
/** |
|||
* 自动生成 API |
|||
* 负责调用后端接口 |
|||
*/ |
|||
|
|||
import { requestClient } from '#/api/request'; |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { useAccessStore } from '@vben/stores'; |
|||
|
|||
export namespace health_deviceApi { |
|||
|
|||
const applicationConfig = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
console.log('=== 接口域名 ===', applicationConfig.javaURL) |
|||
|
|||
/** |
|||
* 分页查询 |
|||
*/ |
|||
export function page(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-device/page', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 获取详情 |
|||
*/ |
|||
export function get(id: number) { |
|||
return requestClient.get(applicationConfig.javaURL+'/health-device/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 新增 |
|||
*/ |
|||
export function add(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-device/add', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 修改 |
|||
*/ |
|||
export function save(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-device/modify', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 删除 |
|||
*/ |
|||
export function remove(id: number) { |
|||
return requestClient.delete(applicationConfig.javaURL+'/health-device/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 枚举列表 |
|||
*/ |
|||
export function enumList(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-enums/optionList', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 上传图片 |
|||
*/ |
|||
export function upload(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/file/up', params, |
|||
{ headers: {'Content-Type': 'multipart/form-data', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
/** |
|||
* 自动生成 API |
|||
* 负责调用后端接口 |
|||
*/ |
|||
|
|||
import { requestClient } from '#/api/request'; |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { useAccessStore } from '@vben/stores'; |
|||
|
|||
export namespace health_funApi { |
|||
|
|||
const applicationConfig = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
console.log('=== 接口域名 ===', applicationConfig.javaURL) |
|||
|
|||
/** |
|||
* 分页查询 |
|||
*/ |
|||
export function page(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-fun/page', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 获取详情 |
|||
*/ |
|||
export function get(id: number) { |
|||
return requestClient.get(applicationConfig.javaURL+'/health-fun/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 新增 |
|||
*/ |
|||
export function add(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-fun/add', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 修改 |
|||
*/ |
|||
export function save(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-fun/modify', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 删除 |
|||
*/ |
|||
export function remove(id: number) { |
|||
return requestClient.delete(applicationConfig.javaURL+'/health-fun/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 枚举列表 |
|||
*/ |
|||
export function enumList(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-enums/optionList', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 上传图片 |
|||
*/ |
|||
export function upload(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/file/up', params, |
|||
{ headers: {'Content-Type': 'multipart/form-data', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
/** |
|||
* 自动生成 API |
|||
* 负责调用后端接口 |
|||
*/ |
|||
|
|||
import { requestClient } from '#/api/request'; |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { useAccessStore } from '@vben/stores'; |
|||
|
|||
export namespace health_msgApi { |
|||
|
|||
const applicationConfig = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
console.log('=== 接口域名 ===', applicationConfig.javaURL) |
|||
|
|||
/** |
|||
* 分页查询 |
|||
*/ |
|||
export function page(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-msg/page', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 获取详情 |
|||
*/ |
|||
export function get(id: number) { |
|||
return requestClient.get(applicationConfig.javaURL+'/health-msg/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 新增 |
|||
*/ |
|||
export function add(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-msg/add', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 修改 |
|||
*/ |
|||
export function save(data: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-msg/modify', data, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 删除 |
|||
*/ |
|||
export function remove(id: number) { |
|||
return requestClient.delete(applicationConfig.javaURL+'/health-msg/' + id); |
|||
} |
|||
|
|||
/** |
|||
* 枚举列表 |
|||
*/ |
|||
export function enumList(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/health-enums/optionList', params, |
|||
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
/** |
|||
* 上传图片 |
|||
*/ |
|||
export function upload(params: any) { |
|||
return requestClient.post(applicationConfig.javaURL+'/file/up', params, |
|||
{ headers: {'Content-Type': 'multipart/form-data', Token: useAccessStore().accessToken, version: '1.0.1'}}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* 自动生成路由 |
|||
*/ |
|||
|
|||
import type { RouteRecordRaw } from 'vue-router'; |
|||
|
|||
const routes: RouteRecordRaw[] = [ |
|||
|
|||
{ |
|||
path: '/health_device', |
|||
name: '设备表模块', |
|||
meta: { |
|||
icon: 'ic:baseline-view-list', |
|||
order: 1000, |
|||
keepAlive: true, |
|||
title: '设备表', |
|||
}, |
|||
children: [ |
|||
{ |
|||
meta: { |
|||
title: "设备表列表", |
|||
icon: 'ic:baseline-menu', |
|||
}, |
|||
name: 'health_deviceList', |
|||
path: '/health_device', |
|||
component: () => import('#/views/health_device/index.vue'), |
|||
}, |
|||
], |
|||
} |
|||
]; |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* 自动生成路由 |
|||
*/ |
|||
|
|||
import type { RouteRecordRaw } from 'vue-router'; |
|||
|
|||
const routes: RouteRecordRaw[] = [ |
|||
|
|||
{ |
|||
path: '/health_fun', |
|||
name: '功能配置表模块', |
|||
meta: { |
|||
icon: 'ic:baseline-view-list', |
|||
order: 1000, |
|||
keepAlive: true, |
|||
title: '功能配置表', |
|||
}, |
|||
children: [ |
|||
{ |
|||
meta: { |
|||
title: "功能配置表列表", |
|||
icon: 'ic:baseline-menu', |
|||
}, |
|||
name: 'health_funList', |
|||
path: '/health_fun', |
|||
component: () => import('#/views/health_fun/index.vue'), |
|||
}, |
|||
], |
|||
} |
|||
]; |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* 自动生成路由 |
|||
*/ |
|||
|
|||
import type { RouteRecordRaw } from 'vue-router'; |
|||
|
|||
const routes: RouteRecordRaw[] = [ |
|||
|
|||
{ |
|||
path: '/health_msg', |
|||
name: '推送消息表(每个人场景维度保存,这里的UID是送达的用户)模块', |
|||
meta: { |
|||
icon: 'ic:baseline-view-list', |
|||
order: 1000, |
|||
keepAlive: true, |
|||
title: '推送消息表(每个人场景维度保存,这里的UID是送达的用户)', |
|||
}, |
|||
children: [ |
|||
{ |
|||
meta: { |
|||
title: "推送消息表(每个人场景维度保存,这里的UID是送达的用户)列表", |
|||
icon: 'ic:baseline-menu', |
|||
}, |
|||
name: 'health_msgList', |
|||
path: '/health_msg', |
|||
component: () => import('#/views/health_msg/index.vue'), |
|||
}, |
|||
], |
|||
} |
|||
]; |
|||
@ -0,0 +1,253 @@ |
|||
/** |
|||
* 表格列配置 |
|||
*/ |
|||
|
|||
import type { VxeGridProps } from '#/adapter/vxe-table'; |
|||
|
|||
export const columns: VxeGridProps['columns'] = [ |
|||
|
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '主键ID', |
|||
|
|||
// 对应字段
|
|||
field: 'id', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '父类ID', |
|||
|
|||
// 对应字段
|
|||
field: 'funParentId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能名称', |
|||
|
|||
// 对应字段
|
|||
field: 'funName', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能码:功能码对外做映射用', |
|||
|
|||
// 对应字段
|
|||
field: 'funCode', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能数值', |
|||
|
|||
// 对应字段
|
|||
field: 'funValue', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '告警级别: 0默认,1级,2级,3级', |
|||
|
|||
// 对应字段
|
|||
field: 'funWarnLevel', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '用位运算组合', |
|||
|
|||
// 对应字段
|
|||
field: 'funWarnType', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '首页展示: 0展示,1不展示', |
|||
|
|||
// 对应字段
|
|||
field: 'funIndex', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '创建时间', |
|||
|
|||
// 对应字段
|
|||
field: 'createdAt', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '创建人', |
|||
|
|||
// 对应字段
|
|||
field: 'createdBy', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '更新时间', |
|||
|
|||
// 对应字段
|
|||
field: 'updatedAt', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '更新人', |
|||
|
|||
// 对应字段
|
|||
field: 'updatedBy', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '删除: 0 默认 1删除', |
|||
|
|||
// 对应字段
|
|||
field: 'deletedFlag', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '用户ID', |
|||
|
|||
// 对应字段
|
|||
field: 'userId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '设备唯一标识', |
|||
|
|||
// 对应字段
|
|||
field: 'deviceCode', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能图', |
|||
|
|||
// 对应字段
|
|||
field: 'funImg', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '设备类型', |
|||
|
|||
// 对应字段
|
|||
field: 'deviceType', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能状态: 默认 0开通,1关闭,2隐藏', |
|||
|
|||
// 对应字段
|
|||
field: 'funStatus', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '逐级告警:完成10,30,60逐级提升告警持续', |
|||
|
|||
// 对应字段
|
|||
field: 'graduallyIntervalTime', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '消息展示语', |
|||
|
|||
// 对应字段
|
|||
field: 'funMsgTitle', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '持续时间默认值', |
|||
|
|||
// 对应字段
|
|||
field: 'funStatisticsTimes', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '提示语言:短消息提示语言', |
|||
|
|||
// 对应字段
|
|||
field: 'funShortMsg', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
|
|||
]; |
|||
@ -0,0 +1,276 @@ |
|||
/** |
|||
* 表单 schema |
|||
* component 类型来自 parse_component() |
|||
*/ |
|||
|
|||
import type { VbenFormSchema } from '#/adapter/form'; |
|||
|
|||
export const formSchema: VbenFormSchema[] = [ |
|||
|
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'id', |
|||
|
|||
// label
|
|||
label: '主键ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funParentId', |
|||
|
|||
// label
|
|||
label: '父类ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funName', |
|||
|
|||
// label
|
|||
label: '功能名称', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funCode', |
|||
|
|||
// label
|
|||
label: '功能码:功能码对外做映射用', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funValue', |
|||
|
|||
// label
|
|||
label: '功能数值', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funWarnLevel', |
|||
|
|||
// label
|
|||
label: '告警级别: 0默认,1级,2级,3级', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funWarnType', |
|||
|
|||
// label
|
|||
label: '用位运算组合', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funIndex', |
|||
|
|||
// label
|
|||
label: '首页展示: 0展示,1不展示', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'createdAt', |
|||
|
|||
// label
|
|||
label: '创建时间', |
|||
|
|||
// 自动组件
|
|||
component: 'DatePicker' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'createdBy', |
|||
|
|||
// label
|
|||
label: '创建人', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'updatedAt', |
|||
|
|||
// label
|
|||
label: '更新时间', |
|||
|
|||
// 自动组件
|
|||
component: 'DatePicker' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'updatedBy', |
|||
|
|||
// label
|
|||
label: '更新人', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'deletedFlag', |
|||
|
|||
// label
|
|||
label: '删除: 0 默认 1删除', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'userId', |
|||
|
|||
// label
|
|||
label: '用户ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'deviceCode', |
|||
|
|||
// label
|
|||
label: '设备唯一标识', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funImg', |
|||
|
|||
// label
|
|||
label: '功能图', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'deviceType', |
|||
|
|||
// label
|
|||
label: '设备类型', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funStatus', |
|||
|
|||
// label
|
|||
label: '功能状态: 默认 0开通,1关闭,2隐藏', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'graduallyIntervalTime', |
|||
|
|||
// label
|
|||
label: '逐级告警:完成10,30,60逐级提升告警持续', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funMsgTitle', |
|||
|
|||
// label
|
|||
label: '消息展示语', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funStatisticsTimes', |
|||
|
|||
// label
|
|||
label: '持续时间默认值', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funShortMsg', |
|||
|
|||
// label
|
|||
label: '提示语言:短消息提示语言', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
|
|||
]; |
|||
@ -0,0 +1,691 @@ |
|||
<script setup lang="ts"> |
|||
/** |
|||
* health_fun管理页面 |
|||
*/ |
|||
import {ref, reactive, onMounted, watch, h} from 'vue'; |
|||
import {useVbenVxeGrid} from '#/adapter/vxe-table'; |
|||
import {useVbenModal} from '@vben/common-ui'; |
|||
import {useVbenForm} from '#/adapter/form'; |
|||
import {Upload, message, Image} from 'ant-design-vue'; |
|||
import dayjs from 'dayjs'; |
|||
import {columns} from './data'; |
|||
import {formSchema} from './form'; |
|||
import { health_funApi } from '#/api/health_fun'; |
|||
// ========== 获取 API 基础 URL ========== |
|||
const API_BASE_URL = import.meta.env.VITE_GLOB_API_URL || ''; |
|||
|
|||
// ========== 状态变量 ========== |
|||
const currentRow = ref(null); |
|||
const isEdit = ref(false); |
|||
const modalTitle = ref('新增'); |
|||
const uploadFieldName = ref(''); |
|||
const uploadImageUrl = ref(''); |
|||
const uploadedUrl = ref(''); // 上传成功后返回的 |
|||
const formUploadUrls = ref<Record<string, string>>({}); // 存储新增弹窗中各字段的上传 URL |
|||
|
|||
// ========== 动态生成的查询表单 Schema ========== |
|||
const querySchema = ref([]); |
|||
// ========== 枚举数据配置 ========== |
|||
const enumData = reactive({ |
|||
loading: true, |
|||
list: [], // 存储接口返回的原始枚举数据 |
|||
}); |
|||
//todo 枚举数据在查询里面不展示的配置 |
|||
const hiddenColumns = ref(['fun_code', 'graduallyIntervalTime', 'funMsgTitle', 'funImg', 'createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"]) |
|||
const editFields = ['userId','createdAt','createdBy','updatedAt','updatedBy','deletedFlag']; |
|||
|
|||
// ========== 判断是否是图片字段 ========== |
|||
function isImageField(field: string) { |
|||
return /img|face|picture/i.test(field); |
|||
} |
|||
// ========== 初始化枚举数据和查询表单 ========== |
|||
async function initEnumData() { |
|||
try { |
|||
enumData.loading = true; |
|||
|
|||
// 调用枚举列表接口 |
|||
const res = await health_funApi.enumList({}); |
|||
const enums = res.result || res; |
|||
|
|||
// 保存原始枚举数据 |
|||
enumData.list = enums; |
|||
|
|||
// 根据 queryFields 和枚举数据动态生成查询表单 schema` |
|||
querySchema.value = formSchema.filter(formItem => { |
|||
// 2. 排除隐藏的字段 |
|||
if (hiddenColumns.value.includes(formItem.fieldName)) { |
|||
console.log('跳过隐藏字段:', formItem.fieldName) |
|||
return false; |
|||
} |
|||
return true; |
|||
}).map(formSchemaTmp => { |
|||
// 在枚举列表中查找匹配的字段 |
|||
const matchedEnums = enums.filter(item => item.fieldName === formSchemaTmp.fieldName); |
|||
|
|||
// 合并相同 fieldName 的所有 options |
|||
const allOptions = matchedEnums.reduce((acc, item) => { |
|||
if (item.options && Array.isArray(item.options)) { |
|||
return [...acc, ...item.options]; |
|||
} |
|||
return acc; |
|||
}, []); |
|||
// 判断是否有匹配的枚举选项 |
|||
if (allOptions.length > 0) { |
|||
return { |
|||
component: 'Select', |
|||
fieldName: formSchemaTmp.fieldName, |
|||
label: formSchemaTmp.label, |
|||
componentProps: { |
|||
placeholder: `请选择`, |
|||
allowClear: true, |
|||
options: allOptions.map(opt => ({ |
|||
label: opt.label, |
|||
value: String(opt.value), // 确保 value 是字符串 |
|||
})), |
|||
}, |
|||
}; |
|||
} else { |
|||
// ❌ 未匹配到枚举 → 使用 Input 组件 |
|||
return { |
|||
component: 'Input', |
|||
fieldName: formSchemaTmp.fieldName, |
|||
label: formSchemaTmp.label, |
|||
componentProps: { |
|||
placeholder: `请输入${formSchemaTmp.label}`, |
|||
allowClear: true, |
|||
}, |
|||
}; |
|||
} |
|||
}); |
|||
} catch (error) { |
|||
} finally { |
|||
enumData.loading = false; |
|||
} |
|||
} |
|||
|
|||
// 页面加载时自动执行初始化 |
|||
onMounted(() => { |
|||
initEnumData(); |
|||
}); |
|||
|
|||
// ========== 查询表单配置 (初始为空 schema) ========== |
|||
const [QueryForm, queryFormApi] = useVbenForm({ |
|||
schema: [], // 初始化为空数组 |
|||
layout: 'inline', |
|||
showDefaultActions: false, |
|||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5', |
|||
}) |
|||
// ========== 监听 querySchema 变化并更新表单 ========== |
|||
watch( |
|||
querySchema, |
|||
(newSchema) => { |
|||
if (newSchema && newSchema.length > 0) { |
|||
console.log('🔄 更新查询表单 schema:', newSchema); |
|||
// ✅ 使用 setState 方法更新 schema |
|||
queryFormApi.setState({ |
|||
schema: newSchema, |
|||
}); |
|||
} |
|||
}, |
|||
{immediate: false} |
|||
); |
|||
|
|||
|
|||
// ========== 表格配置 ========== |
|||
function getFullUrl(url: string) { |
|||
if (!url) return ''; |
|||
if (url.startsWith('http')) return url; |
|||
return `${API_BASE_URL}${url}`; |
|||
} |
|||
const gridOptions = { |
|||
columns: [ |
|||
...columns.map(col => { |
|||
const baseCol = { |
|||
...col, |
|||
fixed: col.fixed ?? null, |
|||
}; |
|||
// ✅ 如果是图片字段 (包含 img, face, picture),使用图片渲染 + 上传按钮 |
|||
if (isImageField(col.field)) { |
|||
return { |
|||
...baseCol, |
|||
width: 200, |
|||
align: 'center', |
|||
slots: { |
|||
default: ({row}: any) => { |
|||
const imgUrl = row[col.field]; |
|||
const handleClick = (e: Event) => { |
|||
e.stopPropagation(); |
|||
currentRow.value = row; |
|||
isEdit.value = true; |
|||
openUploadDialog(col.field, imgUrl); |
|||
}; |
|||
if (!imgUrl) { |
|||
return h('div', { class: 'flex items-center justify-center gap-2 h-[50px]' }, [ |
|||
h('span', { class: 'text-gray-400 text-xs' }, '无图片'), |
|||
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '上传'), |
|||
]); |
|||
} |
|||
return h('div', { class: 'flex items-center gap-2 justify-center' }, [ |
|||
h(Image, { |
|||
src: getFullUrl(imgUrl), |
|||
width: 80, |
|||
height: 60, |
|||
preview: true, |
|||
}), |
|||
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '更换'), |
|||
]); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
return baseCol; |
|||
}), |
|||
{ |
|||
field: 'action', |
|||
title: '操作', |
|||
width: 120, |
|||
fixed: 'right', |
|||
slots: {default: 'action'}, |
|||
}, |
|||
], |
|||
customConfig: { |
|||
storage: true, // 已有功能:列显示/隐藏缓存 |
|||
allowFixed: true, // 🔥 开启“冻结列”功能(关键) |
|||
}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({page}) => { |
|||
try { |
|||
// 获取查询表单的值 |
|||
const queryValues = await queryFormApi.getValues(); |
|||
const res = await health_funApi.page({ |
|||
pageNum: page.currentPage || 1, |
|||
pageSize: page?.pageSize || 10, |
|||
...queryValues, // 携带查询条件 |
|||
}) |
|||
|
|||
const data = res.error?.result || res.result || res |
|||
return { |
|||
items: data.records || [], |
|||
total: data.total || 0 |
|||
} |
|||
} catch (error) { |
|||
console.error('查询列表失败:', error) |
|||
throw error |
|||
} |
|||
} |
|||
}, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total' |
|||
} |
|||
}, |
|||
pagerConfig: { |
|||
enabled: true, |
|||
pageSize: 10, |
|||
}, |
|||
showOverflow: true, |
|||
minHeight: '100%', |
|||
maxHeight: 'auto', |
|||
showHeaderOverflow: true, |
|||
} |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({gridOptions}) |
|||
// ========== 过滤后的编辑表单 Schema ========== |
|||
const editFormSchema = formSchema.filter(item => !editFields.includes(item.fieldName)); |
|||
// ========== 弹窗配置 ========== |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
centered: true, |
|||
closable: true, |
|||
maskClosable: false, |
|||
draggable: true, |
|||
class: 'w-[60vw]', |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onConfirm: async () => { |
|||
let loadingMessage: any = null; |
|||
try { |
|||
const values = await formApi.validateAndSubmitForm(); |
|||
if (!values) return; |
|||
|
|||
// 合并上传的 URL 到提交数据中(只在新增模式下) |
|||
const submitValues = !isEdit.value |
|||
? {...values, ...formUploadUrls.value} |
|||
: values; |
|||
|
|||
// 处理日期格式,将 Day.js 对象转换为字符串 |
|||
const finalSubmitValues = { |
|||
...submitValues, |
|||
userBirthday: submitValues.userBirthday ? dayjs(submitValues.userBirthday).format('YYYY-MM-DD') : null, |
|||
}; |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading(isEdit.value ? '保存中...' : '新增中...', 0); |
|||
|
|||
if (isEdit.value && currentRow.value?.id) { |
|||
await health_funApi.save({...finalSubmitValues, id: currentRow.value.id}); |
|||
Object.assign(currentRow.value, finalSubmitValues); |
|||
modalApi.close(); |
|||
loadingMessage(); |
|||
isEdit.value = false; |
|||
message.success('保存成功!'); |
|||
} else { |
|||
await health_funApi.add(finalSubmitValues); |
|||
// 新增才 reload(必须) |
|||
modalApi.close(); |
|||
gridApi.reload(); |
|||
formUploadUrls.value = {}; |
|||
loadingMessage(); |
|||
message.success('新增成功!'); |
|||
} |
|||
} catch (error) { |
|||
console.error('保存失败:', error); |
|||
if (loadingMessage) { |
|||
loadingMessage(); |
|||
} |
|||
} |
|||
}, |
|||
onOpenChange(isOpen: boolean) { |
|||
if (!isOpen && !isEdit.value) { |
|||
formApi.resetForm(); |
|||
formUploadUrls.value = {}; |
|||
} |
|||
}, |
|||
}) |
|||
|
|||
|
|||
// ========== 上传弹窗配置 ========== |
|||
const uploadFileList = ref<any[]>([]); |
|||
const [UploadModal, uploadModalApi] = useVbenModal({ |
|||
centered: true, |
|||
closable: true, |
|||
maskClosable: false, |
|||
draggable: true, |
|||
width: 600, |
|||
title: '上传图片', |
|||
onCancel() { |
|||
uploadModalApi.close(); |
|||
uploadFileList.value = []; |
|||
uploadedUrl.value = ''; |
|||
}, |
|||
onConfirm: async () => { |
|||
let loadingMessage: any = null; |
|||
console.log('=== 点击确认保存 ===', { |
|||
uploadedUrl: uploadedUrl.value, |
|||
isEdit: isEdit.value, |
|||
currentRowId: currentRow.value?.id, |
|||
uploadFieldName: uploadFieldName.value, |
|||
currentRowData: currentRow.value, |
|||
}); |
|||
|
|||
// 如果有上传成功的 URL,更新到当前行并保存 |
|||
if (uploadedUrl.value) { |
|||
try { |
|||
// 更新当前行的图片 URL |
|||
if (currentRow.value) { |
|||
currentRow.value[uploadFieldName.value] = uploadedUrl.value; |
|||
console.log('✅ 已更新当前行图片字段:', { |
|||
fieldName: uploadFieldName.value, |
|||
newUrl: uploadedUrl.value, |
|||
updatedRow: currentRow.value, |
|||
}); |
|||
} |
|||
|
|||
// 调用 save 接口保存修改(只在编辑模式下) |
|||
if (isEdit.value && currentRow.value?.id) { |
|||
const submitData = { |
|||
...currentRow.value, |
|||
id: currentRow.value.id |
|||
}; |
|||
console.log('📤 提交保存数据:', submitData); |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading('保存图片中...', 0); |
|||
await health_funApi.save(submitData); |
|||
message.success('保存成功!'); |
|||
|
|||
// 关闭弹窗并刷新表格 |
|||
uploadModalApi.close(); |
|||
// gridApi.reload(); |
|||
loadingMessage() |
|||
// 重置状态 |
|||
uploadFileList.value = []; |
|||
uploadedUrl.value = ''; |
|||
} else { |
|||
console.warn('⚠️ 不满足保存条件:', { |
|||
isEdit: isEdit.value, |
|||
hasId: !!currentRow.value?.id, |
|||
}); |
|||
message.warning('非编辑模式或无 ID,仅更新预览'); |
|||
|
|||
// 只更新预览,不保存 |
|||
uploadModalApi.close(); |
|||
// gridApi.reload(); |
|||
} |
|||
|
|||
} catch (error: any) { |
|||
console.error('❌ 保存失败:', error); |
|||
if (loadingMessage) { |
|||
loadingMessage(); |
|||
} |
|||
message.error(error?.message || '保存失败'); |
|||
} |
|||
} else { |
|||
message.warning('请先选择并上传图片'); |
|||
} |
|||
}, |
|||
}) |
|||
|
|||
// ========== 动态计算编辑表单 Schema ========== |
|||
// 新增时:过滤掉 id 字段,并为图片字段添加上传按钮 |
|||
// 编辑时:包含 id 字段 |
|||
const getEditFormSchema = () => { |
|||
return formSchema.filter(item => { |
|||
// 不在 editFields 中的才显示 |
|||
if (!editFields.includes(item.fieldName)) { |
|||
// 新增时排除 id 字段 |
|||
if (!isEdit.value && item.fieldName === 'id') { |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
return false; |
|||
}).map(item => { |
|||
// 只在新增模式下,且是图片字段,添加上传组件 |
|||
if (!isEdit.value && isImageField(item.fieldName)) { |
|||
// 优先使用 formUploadUrls 中存储的 URL,其次使用表单值 |
|||
const currentImageUrl = formUploadUrls.value[item.fieldName] || ''; |
|||
return { |
|||
...item, |
|||
component: 'Upload', |
|||
componentProps: { |
|||
class: 'w-full', |
|||
accept: 'image/*', |
|||
maxCount: 1, |
|||
listType: 'picture-card', |
|||
fileList: currentImageUrl && typeof currentImageUrl === 'string' ? [{ |
|||
uid: '-1', |
|||
name: 'image.png', |
|||
status: 'done', |
|||
url: currentImageUrl, |
|||
}] : (Array.isArray(currentImageUrl) ? currentImageUrl : []), |
|||
beforeUpload: async (file: any) => { |
|||
console.log('📦 新增表单内上传文件:', file); |
|||
|
|||
try { |
|||
const resultUrl = await uploadFile(file); |
|||
console.log(item.fieldName + ' 新增表单内上传成功,URL:', resultUrl); |
|||
message.success('上传成功!'); |
|||
|
|||
// 保存上传返回的 URL 到专用存储 |
|||
formUploadUrls.value[item.fieldName] = resultUrl; |
|||
|
|||
// 上传成功后,重新生成 schema 以更新 fileList 显示 |
|||
setTimeout(() => { |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
}, 0); |
|||
|
|||
// Deleted:formApi.resetForm(); |
|||
// 返回 false 阻止默认上传行为 |
|||
return false; |
|||
} catch (error: any) { |
|||
console.error('❌ 新增表单内上传失败:', error); |
|||
message.error(error?.message || '上传失败'); |
|||
return false; |
|||
} |
|||
}, |
|||
}, |
|||
}; |
|||
} |
|||
return item; |
|||
}); |
|||
}; |
|||
// ========== 表单配置 ========== |
|||
const [Form, formApi] = useVbenForm({ |
|||
schema: editFormSchema, |
|||
showDefaultActions: false, |
|||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', |
|||
}) |
|||
|
|||
// ========== 上传文件 ========== |
|||
|
|||
async function uploadFile(file: File) { |
|||
let loadingMessage: any = null; |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading('图片上传中...', 0); |
|||
const formData = new FormData(); |
|||
formData.append('file', file); |
|||
const res = await health_funApi.upload(formData); |
|||
loadingMessage(); |
|||
return res.result || res.message || res.url || res; |
|||
} |
|||
|
|||
// ========== 打开上传对话框 ========== |
|||
function openUploadDialog(fieldName: string, currentUrl: string) { |
|||
console.log('=== 打开上传对话框 ===', { |
|||
fieldName, |
|||
currentUrl, |
|||
isEdit: isEdit.value, |
|||
currentRowId: currentRow.value?.id, |
|||
currentRowFull: currentRow.value, |
|||
}); |
|||
|
|||
uploadFieldName.value = fieldName; |
|||
uploadImageUrl.value = currentUrl || ''; |
|||
uploadedUrl.value = ''; // 重置上传后的 URL |
|||
uploadFileList.value = []; // 清空文件列表 |
|||
uploadModalApi.open(); |
|||
} |
|||
|
|||
// ========== 处理图片上传 - 使用 custom-request ========== |
|||
async function handleCustomRequest(options: any) { |
|||
const {file, onSuccess, onError} = options; |
|||
|
|||
try { |
|||
console.log('📦 开始上传文件:', file); |
|||
console.log('📦 文件详情:', { |
|||
name: file.name, |
|||
type: file.type, |
|||
size: file.size, |
|||
lastModified: file.lastModified, |
|||
}); |
|||
const resultUrl = await uploadFile(file); |
|||
console.log('✅ 上传成功,URL:', resultUrl); |
|||
message.success('上传成功!'); |
|||
// 保存上传返回的 URL |
|||
uploadedUrl.value = resultUrl; |
|||
console.log('=== uploadedUrl 赋值后 ===', uploadedUrl.value); |
|||
|
|||
// 添加到文件列表用于显示 |
|||
uploadFileList.value = [{ |
|||
uid: file.uid, |
|||
name: file.name, |
|||
status: 'done', |
|||
url: resultUrl, |
|||
}]; |
|||
|
|||
// 通知组件上传成功 |
|||
onSuccess({url: resultUrl}); |
|||
|
|||
} catch (error: any) { |
|||
console.error('❌ 上传失败:', error); |
|||
message.error(error?.message || '上传失败'); |
|||
onError(error); |
|||
} |
|||
} |
|||
|
|||
// ========== 打开新增弹窗 ========== |
|||
function handleAdd() { |
|||
isEdit.value = false; |
|||
modalTitle.value = '功能配置表新增'; |
|||
// 重置上传 URL 记录 |
|||
formUploadUrls.value = {}; |
|||
// 更新表单 schema (不包含 id) |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
formApi.resetForm(); |
|||
modalApi.open(); |
|||
} |
|||
|
|||
// ========== 打开编辑弹窗 ========== |
|||
function handleEdit(row: any) { |
|||
isEdit.value = true; |
|||
currentRow.value = row; |
|||
modalTitle.value = '功能配置表编辑'; |
|||
// 更新表单 schema (包含 id) |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
// 设置表单值,只设置 editFields 中包含的字段 |
|||
const formValues: any = {}; |
|||
editFormSchema.forEach(item => { |
|||
const field = item.fieldName; |
|||
if (row[field] !== undefined && row[field] !== null) { |
|||
formValues[field] = row[field]; |
|||
} |
|||
}); |
|||
|
|||
formApi.setValues(formValues); |
|||
modalApi.open(); |
|||
} |
|||
|
|||
// ========== 删除确认 ========== |
|||
function handleDelete(row: any) { |
|||
if (!row.id) return; |
|||
window.confirm(`确定要删除 "${row.userName}" 吗?`) && |
|||
health_funApi.remove(row.id).then(() => { |
|||
gridApi.reload(); |
|||
}); |
|||
} |
|||
|
|||
// ========== 查询功能 ========== |
|||
function handleSearch() { |
|||
console.log('=== 点击查询按钮 ===') |
|||
// 先重置到第一页,然后执行查询 |
|||
gridApi.query(); |
|||
} |
|||
|
|||
// ========== 重置查询 ========== |
|||
function handleReset() { |
|||
queryFormApi.resetForm(); |
|||
gridApi.reload(); |
|||
} |
|||
|
|||
// ========== 列设置 ========== |
|||
function openColumnSetting() { |
|||
const $grid = gridApi?.grid || gridApi?.getVxeGrid?.(); |
|||
$grid?.openCustom(); |
|||
} |
|||
</script> |
|||
|
|||
|
|||
<template> |
|||
<div |
|||
style="height: 100vh; padding: 16px; box-sizing: border-box; display: flex; flex-direction: column;"> |
|||
<!-- 查询表单 --> |
|||
<div class="bg-card mb-4 p-4 rounded shadow flex-shrink-0"> |
|||
<h3 class="text-lg font-semibold mb-3">查询条件</h3> |
|||
<div v-if="enumData.loading" class="text-center py-4 text-gray-500"> |
|||
正在加载查询条件... |
|||
</div> |
|||
<QueryForm v-else/> |
|||
<div class="mt-3 flex gap-2"> |
|||
<button @click="handleSearch" |
|||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"> |
|||
🔍 查询 |
|||
</button> |
|||
<button @click="handleReset" |
|||
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"> |
|||
🔄 重置 |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 数据表格 (可滚动区域) --> |
|||
<div class="flex-1 overflow-hidden bg-card rounded shadow"> |
|||
<Grid> |
|||
<template #toolbar-tools> |
|||
<button @click="handleAdd" class="vxe-button type--button size--small is--circle" title="新增"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> |
|||
<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"></path> |
|||
</svg> |
|||
</button> |
|||
<button @click="openColumnSetting" class="vxe-button type--button vxe-toolbar-custom-target size--small is--circle" title="列设置"> |
|||
<i class="vxe-button--item vxe-button--prefix-icon vxe-table-icon-custom-column"></i> |
|||
</button> |
|||
<button @click="() => gridApi.reload()" class="vxe-button type--button size--small is--circle" title="刷新" type="button"> |
|||
<i class="vxe-button--item vxe-button--prefix-icon vxe-table-icon-repeat"></i> |
|||
</button> |
|||
</template> |
|||
|
|||
<template #action="{ row }"> |
|||
<div class="flex gap-2"> |
|||
<button @click="() => handleEdit(row)" class="text-blue-500 hover:text-blue-700">✏️ 编辑 |
|||
</button> |
|||
<button @click="() => handleDelete(row)" class="text-red-500 hover:text-red-700">🗑️ 删除 |
|||
</button> |
|||
</div> |
|||
</template> |
|||
</Grid> |
|||
</div> |
|||
|
|||
<Modal :title="modalTitle"> |
|||
<Form/> |
|||
</Modal> |
|||
<!-- 上传对话框 --> |
|||
<UploadModal> |
|||
<div style="padding: 20px;margin-top: 16px; text-align: center;"> |
|||
<Upload |
|||
name="file" |
|||
:file-list="uploadFileList" |
|||
:custom-request="handleCustomRequest" |
|||
:show-upload-list="true" |
|||
accept="image/*" style="width: 100%;" |
|||
> |
|||
<div |
|||
style="width: 100%; height: 150px; border: 2px dashed #d9d9d9; border-radius: 8px; background: #fafafa; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer;"> |
|||
<!-- 使用 SVG 图标代替 --> |
|||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1890ff" |
|||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" |
|||
style="margin-bottom: 8px;"> |
|||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
|||
<polyline points="17 8 12 3 7 8"></polyline> |
|||
<line x1="12" y1="3" x2="12" y2="15"></line> |
|||
</svg> |
|||
<span style="color: #666;">点击或拖拽图片到此上传</span> |
|||
<span style="color: #999; font-size: 12px; margin-top: 4px;">支持 JPG、PNG 格式</span> |
|||
</div> |
|||
</Upload> |
|||
|
|||
<!-- 展示上传成功后的图片 --> |
|||
<div v-if="uploadedUrl" style="margin-top: 16px; text-align: center;"> |
|||
<p style="color: #67c23a; font-size: 14px; margin-bottom: 8px;">✅ 上传成功!</p> |
|||
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">新图片预览:</p> |
|||
<Image |
|||
:src="uploadedUrl" |
|||
style="max-width: 200px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);" |
|||
:preview="true" |
|||
/> |
|||
<p style="color: #999; font-size: 12px; margin-top: 8px; word-break: break-all;"></p> |
|||
</div> |
|||
|
|||
<!-- 展示原图(如果有) --> |
|||
<div v-if="uploadImageUrl && !uploadedUrl" style="margin-top: 16px; text-align: center;"> |
|||
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">当前图片:</p> |
|||
<Image |
|||
:src="getFullUrl(uploadImageUrl)" |
|||
style="max-width: 200px; border-radius: 4px;" |
|||
:preview="true" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</UploadModal> |
|||
</div> |
|||
</template> |
|||
<style scoped> |
|||
:deep(.vxe-pager--wrapper) { |
|||
margin-bottom: 0.5rem; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,242 @@ |
|||
/** |
|||
* 表格列配置 |
|||
*/ |
|||
|
|||
import type { VxeGridProps } from '#/adapter/vxe-table'; |
|||
|
|||
export const columns: VxeGridProps['columns'] = [ |
|||
|
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '主键ID', |
|||
|
|||
// 对应字段
|
|||
field: 'id', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '信息详情', |
|||
|
|||
// 对应字段
|
|||
field: 'msgNote', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '消息类型: 0 告警信息 1恢复消息,2推送消息', |
|||
|
|||
// 对应字段
|
|||
field: 'msgType', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '场景ID', |
|||
|
|||
// 对应字段
|
|||
field: 'sceneId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '用户ID', |
|||
|
|||
// 对应字段
|
|||
field: 'userId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '图片路径', |
|||
|
|||
// 对应字段
|
|||
field: 'url', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '推送状态 0 未推送 1成功 2失败', |
|||
|
|||
// 对应字段
|
|||
field: 'messageSendStatus', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '创建时间', |
|||
|
|||
// 对应字段
|
|||
field: 'createdAt', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '创建人', |
|||
|
|||
// 对应字段
|
|||
field: 'createdBy', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '更新时间', |
|||
|
|||
// 对应字段
|
|||
field: 'updatedAt', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '更新人', |
|||
|
|||
// 对应字段
|
|||
field: 'updatedBy', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '删除 0 默认 1删除', |
|||
|
|||
// 对应字段
|
|||
field: 'deletedFlag', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '设备ID', |
|||
|
|||
// 对应字段
|
|||
field: 'deviceId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '房间ID', |
|||
|
|||
// 对应字段
|
|||
field: 'roomId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '0默认未读;已读1', |
|||
|
|||
// 对应字段
|
|||
field: 'msgRead', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能方法ID', |
|||
|
|||
// 对应字段
|
|||
field: 'funId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能父类ID', |
|||
|
|||
// 对应字段
|
|||
field: 'funParentId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '功能告警等级', |
|||
|
|||
// 对应字段
|
|||
field: 'funWarnLevel', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '家庭ID', |
|||
|
|||
// 对应字段
|
|||
field: 'familyId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '统计时长/毫秒', |
|||
|
|||
// 对应字段
|
|||
field: 'statisticsDuration', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
{ |
|||
// 列标题
|
|||
title: '消息原表ID', |
|||
|
|||
// 对应字段
|
|||
field: 'msgSourceId', |
|||
|
|||
// 宽度
|
|||
width: 150 |
|||
}, |
|||
|
|||
|
|||
]; |
|||
@ -0,0 +1,264 @@ |
|||
/** |
|||
* 表单 schema |
|||
* component 类型来自 parse_component() |
|||
*/ |
|||
|
|||
import type { VbenFormSchema } from '#/adapter/form'; |
|||
|
|||
export const formSchema: VbenFormSchema[] = [ |
|||
|
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'id', |
|||
|
|||
// label
|
|||
label: '主键ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'msgNote', |
|||
|
|||
// label
|
|||
label: '信息详情', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'msgType', |
|||
|
|||
// label
|
|||
label: '消息类型: 0 告警信息 1恢复消息,2推送消息', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'sceneId', |
|||
|
|||
// label
|
|||
label: '场景ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'userId', |
|||
|
|||
// label
|
|||
label: '用户ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'url', |
|||
|
|||
// label
|
|||
label: '图片路径', |
|||
|
|||
// 自动组件
|
|||
component: 'Input' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'messageSendStatus', |
|||
|
|||
// label
|
|||
label: '推送状态 0 未推送 1成功 2失败', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'createdAt', |
|||
|
|||
// label
|
|||
label: '创建时间', |
|||
|
|||
// 自动组件
|
|||
component: 'DatePicker' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'createdBy', |
|||
|
|||
// label
|
|||
label: '创建人', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'updatedAt', |
|||
|
|||
// label
|
|||
label: '更新时间', |
|||
|
|||
// 自动组件
|
|||
component: 'DatePicker' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'updatedBy', |
|||
|
|||
// label
|
|||
label: '更新人', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'deletedFlag', |
|||
|
|||
// label
|
|||
label: '删除 0 默认 1删除', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'deviceId', |
|||
|
|||
// label
|
|||
label: '设备ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'roomId', |
|||
|
|||
// label
|
|||
label: '房间ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'msgRead', |
|||
|
|||
// label
|
|||
label: '0默认未读;已读1', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funId', |
|||
|
|||
// label
|
|||
label: '功能方法ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funParentId', |
|||
|
|||
// label
|
|||
label: '功能父类ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'funWarnLevel', |
|||
|
|||
// label
|
|||
label: '功能告警等级', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'familyId', |
|||
|
|||
// label
|
|||
label: '家庭ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'statisticsDuration', |
|||
|
|||
// label
|
|||
label: '统计时长/毫秒', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
{ |
|||
// 字段名
|
|||
fieldName: 'msgSourceId', |
|||
|
|||
// label
|
|||
label: '消息原表ID', |
|||
|
|||
// 自动组件
|
|||
component: 'InputNumber' |
|||
|
|||
}, |
|||
|
|||
|
|||
]; |
|||
@ -0,0 +1,691 @@ |
|||
<script setup lang="ts"> |
|||
/** |
|||
* health_msg管理页面 |
|||
*/ |
|||
import {ref, reactive, onMounted, watch, h} from 'vue'; |
|||
import {useVbenVxeGrid} from '#/adapter/vxe-table'; |
|||
import {useVbenModal} from '@vben/common-ui'; |
|||
import {useVbenForm} from '#/adapter/form'; |
|||
import {Upload, message, Image} from 'ant-design-vue'; |
|||
import dayjs from 'dayjs'; |
|||
import {columns} from './data'; |
|||
import {formSchema} from './form'; |
|||
import { health_msgApi } from '#/api/health_msg'; |
|||
// ========== 获取 API 基础 URL ========== |
|||
const API_BASE_URL = import.meta.env.VITE_GLOB_API_URL || ''; |
|||
|
|||
// ========== 状态变量 ========== |
|||
const currentRow = ref(null); |
|||
const isEdit = ref(false); |
|||
const modalTitle = ref('新增'); |
|||
const uploadFieldName = ref(''); |
|||
const uploadImageUrl = ref(''); |
|||
const uploadedUrl = ref(''); // 上传成功后返回的 |
|||
const formUploadUrls = ref<Record<string, string>>({}); // 存储新增弹窗中各字段的上传 URL |
|||
|
|||
// ========== 动态生成的查询表单 Schema ========== |
|||
const querySchema = ref([]); |
|||
// ========== 枚举数据配置 ========== |
|||
const enumData = reactive({ |
|||
loading: true, |
|||
list: [], // 存储接口返回的原始枚举数据 |
|||
}); |
|||
//todo 枚举数据在查询里面不展示的配置 |
|||
const hiddenColumns = ref(['fun_code', 'graduallyIntervalTime', 'funMsgTitle', 'funImg', 'createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"]) |
|||
const editFields = ['userId','createdAt','createdBy','updatedAt','updatedBy','deletedFlag']; |
|||
|
|||
// ========== 判断是否是图片字段 ========== |
|||
function isImageField(field: string) { |
|||
return /img|face|picture/i.test(field); |
|||
} |
|||
// ========== 初始化枚举数据和查询表单 ========== |
|||
async function initEnumData() { |
|||
try { |
|||
enumData.loading = true; |
|||
|
|||
// 调用枚举列表接口 |
|||
const res = await health_msgApi.enumList({}); |
|||
const enums = res.result || res; |
|||
|
|||
// 保存原始枚举数据 |
|||
enumData.list = enums; |
|||
|
|||
// 根据 queryFields 和枚举数据动态生成查询表单 schema` |
|||
querySchema.value = formSchema.filter(formItem => { |
|||
// 2. 排除隐藏的字段 |
|||
if (hiddenColumns.value.includes(formItem.fieldName)) { |
|||
console.log('跳过隐藏字段:', formItem.fieldName) |
|||
return false; |
|||
} |
|||
return true; |
|||
}).map(formSchemaTmp => { |
|||
// 在枚举列表中查找匹配的字段 |
|||
const matchedEnums = enums.filter(item => item.fieldName === formSchemaTmp.fieldName); |
|||
|
|||
// 合并相同 fieldName 的所有 options |
|||
const allOptions = matchedEnums.reduce((acc, item) => { |
|||
if (item.options && Array.isArray(item.options)) { |
|||
return [...acc, ...item.options]; |
|||
} |
|||
return acc; |
|||
}, []); |
|||
// 判断是否有匹配的枚举选项 |
|||
if (allOptions.length > 0) { |
|||
return { |
|||
component: 'Select', |
|||
fieldName: formSchemaTmp.fieldName, |
|||
label: formSchemaTmp.label, |
|||
componentProps: { |
|||
placeholder: `请选择`, |
|||
allowClear: true, |
|||
options: allOptions.map(opt => ({ |
|||
label: opt.label, |
|||
value: String(opt.value), // 确保 value 是字符串 |
|||
})), |
|||
}, |
|||
}; |
|||
} else { |
|||
// ❌ 未匹配到枚举 → 使用 Input 组件 |
|||
return { |
|||
component: 'Input', |
|||
fieldName: formSchemaTmp.fieldName, |
|||
label: formSchemaTmp.label, |
|||
componentProps: { |
|||
placeholder: `请输入${formSchemaTmp.label}`, |
|||
allowClear: true, |
|||
}, |
|||
}; |
|||
} |
|||
}); |
|||
} catch (error) { |
|||
} finally { |
|||
enumData.loading = false; |
|||
} |
|||
} |
|||
|
|||
// 页面加载时自动执行初始化 |
|||
onMounted(() => { |
|||
initEnumData(); |
|||
}); |
|||
|
|||
// ========== 查询表单配置 (初始为空 schema) ========== |
|||
const [QueryForm, queryFormApi] = useVbenForm({ |
|||
schema: [], // 初始化为空数组 |
|||
layout: 'inline', |
|||
showDefaultActions: false, |
|||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5', |
|||
}) |
|||
// ========== 监听 querySchema 变化并更新表单 ========== |
|||
watch( |
|||
querySchema, |
|||
(newSchema) => { |
|||
if (newSchema && newSchema.length > 0) { |
|||
console.log('🔄 更新查询表单 schema:', newSchema); |
|||
// ✅ 使用 setState 方法更新 schema |
|||
queryFormApi.setState({ |
|||
schema: newSchema, |
|||
}); |
|||
} |
|||
}, |
|||
{immediate: false} |
|||
); |
|||
|
|||
|
|||
// ========== 表格配置 ========== |
|||
function getFullUrl(url: string) { |
|||
if (!url) return ''; |
|||
if (url.startsWith('http')) return url; |
|||
return `${API_BASE_URL}${url}`; |
|||
} |
|||
const gridOptions = { |
|||
columns: [ |
|||
...columns.map(col => { |
|||
const baseCol = { |
|||
...col, |
|||
fixed: col.fixed ?? null, |
|||
}; |
|||
// ✅ 如果是图片字段 (包含 img, face, picture),使用图片渲染 + 上传按钮 |
|||
if (isImageField(col.field)) { |
|||
return { |
|||
...baseCol, |
|||
width: 200, |
|||
align: 'center', |
|||
slots: { |
|||
default: ({row}: any) => { |
|||
const imgUrl = row[col.field]; |
|||
const handleClick = (e: Event) => { |
|||
e.stopPropagation(); |
|||
currentRow.value = row; |
|||
isEdit.value = true; |
|||
openUploadDialog(col.field, imgUrl); |
|||
}; |
|||
if (!imgUrl) { |
|||
return h('div', { class: 'flex items-center justify-center gap-2 h-[50px]' }, [ |
|||
h('span', { class: 'text-gray-400 text-xs' }, '无图片'), |
|||
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '上传'), |
|||
]); |
|||
} |
|||
return h('div', { class: 'flex items-center gap-2 justify-center' }, [ |
|||
h(Image, { |
|||
src: getFullUrl(imgUrl), |
|||
width: 80, |
|||
height: 60, |
|||
preview: true, |
|||
}), |
|||
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '更换'), |
|||
]); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
return baseCol; |
|||
}), |
|||
{ |
|||
field: 'action', |
|||
title: '操作', |
|||
width: 120, |
|||
fixed: 'right', |
|||
slots: {default: 'action'}, |
|||
}, |
|||
], |
|||
customConfig: { |
|||
storage: true, // 已有功能:列显示/隐藏缓存 |
|||
allowFixed: true, // 🔥 开启“冻结列”功能(关键) |
|||
}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({page}) => { |
|||
try { |
|||
// 获取查询表单的值 |
|||
const queryValues = await queryFormApi.getValues(); |
|||
const res = await health_msgApi.page({ |
|||
pageNum: page.currentPage || 1, |
|||
pageSize: page?.pageSize || 10, |
|||
...queryValues, // 携带查询条件 |
|||
}) |
|||
|
|||
const data = res.error?.result || res.result || res |
|||
return { |
|||
items: data.records || [], |
|||
total: data.total || 0 |
|||
} |
|||
} catch (error) { |
|||
console.error('查询列表失败:', error) |
|||
throw error |
|||
} |
|||
} |
|||
}, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total' |
|||
} |
|||
}, |
|||
pagerConfig: { |
|||
enabled: true, |
|||
pageSize: 10, |
|||
}, |
|||
showOverflow: true, |
|||
minHeight: '100%', |
|||
maxHeight: 'auto', |
|||
showHeaderOverflow: true, |
|||
} |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({gridOptions}) |
|||
// ========== 过滤后的编辑表单 Schema ========== |
|||
const editFormSchema = formSchema.filter(item => !editFields.includes(item.fieldName)); |
|||
// ========== 弹窗配置 ========== |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
centered: true, |
|||
closable: true, |
|||
maskClosable: false, |
|||
draggable: true, |
|||
class: 'w-[60vw]', |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onConfirm: async () => { |
|||
let loadingMessage: any = null; |
|||
try { |
|||
const values = await formApi.validateAndSubmitForm(); |
|||
if (!values) return; |
|||
|
|||
// 合并上传的 URL 到提交数据中(只在新增模式下) |
|||
const submitValues = !isEdit.value |
|||
? {...values, ...formUploadUrls.value} |
|||
: values; |
|||
|
|||
// 处理日期格式,将 Day.js 对象转换为字符串 |
|||
const finalSubmitValues = { |
|||
...submitValues, |
|||
userBirthday: submitValues.userBirthday ? dayjs(submitValues.userBirthday).format('YYYY-MM-DD') : null, |
|||
}; |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading(isEdit.value ? '保存中...' : '新增中...', 0); |
|||
|
|||
if (isEdit.value && currentRow.value?.id) { |
|||
await health_msgApi.save({...finalSubmitValues, id: currentRow.value.id}); |
|||
Object.assign(currentRow.value, finalSubmitValues); |
|||
modalApi.close(); |
|||
loadingMessage(); |
|||
isEdit.value = false; |
|||
message.success('保存成功!'); |
|||
} else { |
|||
await health_msgApi.add(finalSubmitValues); |
|||
// 新增才 reload(必须) |
|||
modalApi.close(); |
|||
gridApi.reload(); |
|||
formUploadUrls.value = {}; |
|||
loadingMessage(); |
|||
message.success('新增成功!'); |
|||
} |
|||
} catch (error) { |
|||
console.error('保存失败:', error); |
|||
if (loadingMessage) { |
|||
loadingMessage(); |
|||
} |
|||
} |
|||
}, |
|||
onOpenChange(isOpen: boolean) { |
|||
if (!isOpen && !isEdit.value) { |
|||
formApi.resetForm(); |
|||
formUploadUrls.value = {}; |
|||
} |
|||
}, |
|||
}) |
|||
|
|||
|
|||
// ========== 上传弹窗配置 ========== |
|||
const uploadFileList = ref<any[]>([]); |
|||
const [UploadModal, uploadModalApi] = useVbenModal({ |
|||
centered: true, |
|||
closable: true, |
|||
maskClosable: false, |
|||
draggable: true, |
|||
width: 600, |
|||
title: '上传图片', |
|||
onCancel() { |
|||
uploadModalApi.close(); |
|||
uploadFileList.value = []; |
|||
uploadedUrl.value = ''; |
|||
}, |
|||
onConfirm: async () => { |
|||
let loadingMessage: any = null; |
|||
console.log('=== 点击确认保存 ===', { |
|||
uploadedUrl: uploadedUrl.value, |
|||
isEdit: isEdit.value, |
|||
currentRowId: currentRow.value?.id, |
|||
uploadFieldName: uploadFieldName.value, |
|||
currentRowData: currentRow.value, |
|||
}); |
|||
|
|||
// 如果有上传成功的 URL,更新到当前行并保存 |
|||
if (uploadedUrl.value) { |
|||
try { |
|||
// 更新当前行的图片 URL |
|||
if (currentRow.value) { |
|||
currentRow.value[uploadFieldName.value] = uploadedUrl.value; |
|||
console.log('✅ 已更新当前行图片字段:', { |
|||
fieldName: uploadFieldName.value, |
|||
newUrl: uploadedUrl.value, |
|||
updatedRow: currentRow.value, |
|||
}); |
|||
} |
|||
|
|||
// 调用 save 接口保存修改(只在编辑模式下) |
|||
if (isEdit.value && currentRow.value?.id) { |
|||
const submitData = { |
|||
...currentRow.value, |
|||
id: currentRow.value.id |
|||
}; |
|||
console.log('📤 提交保存数据:', submitData); |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading('保存图片中...', 0); |
|||
await health_msgApi.save(submitData); |
|||
message.success('保存成功!'); |
|||
|
|||
// 关闭弹窗并刷新表格 |
|||
uploadModalApi.close(); |
|||
// gridApi.reload(); |
|||
loadingMessage() |
|||
// 重置状态 |
|||
uploadFileList.value = []; |
|||
uploadedUrl.value = ''; |
|||
} else { |
|||
console.warn('⚠️ 不满足保存条件:', { |
|||
isEdit: isEdit.value, |
|||
hasId: !!currentRow.value?.id, |
|||
}); |
|||
message.warning('非编辑模式或无 ID,仅更新预览'); |
|||
|
|||
// 只更新预览,不保存 |
|||
uploadModalApi.close(); |
|||
// gridApi.reload(); |
|||
} |
|||
|
|||
} catch (error: any) { |
|||
console.error('❌ 保存失败:', error); |
|||
if (loadingMessage) { |
|||
loadingMessage(); |
|||
} |
|||
message.error(error?.message || '保存失败'); |
|||
} |
|||
} else { |
|||
message.warning('请先选择并上传图片'); |
|||
} |
|||
}, |
|||
}) |
|||
|
|||
// ========== 动态计算编辑表单 Schema ========== |
|||
// 新增时:过滤掉 id 字段,并为图片字段添加上传按钮 |
|||
// 编辑时:包含 id 字段 |
|||
const getEditFormSchema = () => { |
|||
return formSchema.filter(item => { |
|||
// 不在 editFields 中的才显示 |
|||
if (!editFields.includes(item.fieldName)) { |
|||
// 新增时排除 id 字段 |
|||
if (!isEdit.value && item.fieldName === 'id') { |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
return false; |
|||
}).map(item => { |
|||
// 只在新增模式下,且是图片字段,添加上传组件 |
|||
if (!isEdit.value && isImageField(item.fieldName)) { |
|||
// 优先使用 formUploadUrls 中存储的 URL,其次使用表单值 |
|||
const currentImageUrl = formUploadUrls.value[item.fieldName] || ''; |
|||
return { |
|||
...item, |
|||
component: 'Upload', |
|||
componentProps: { |
|||
class: 'w-full', |
|||
accept: 'image/*', |
|||
maxCount: 1, |
|||
listType: 'picture-card', |
|||
fileList: currentImageUrl && typeof currentImageUrl === 'string' ? [{ |
|||
uid: '-1', |
|||
name: 'image.png', |
|||
status: 'done', |
|||
url: currentImageUrl, |
|||
}] : (Array.isArray(currentImageUrl) ? currentImageUrl : []), |
|||
beforeUpload: async (file: any) => { |
|||
console.log('📦 新增表单内上传文件:', file); |
|||
|
|||
try { |
|||
const resultUrl = await uploadFile(file); |
|||
console.log(item.fieldName + ' 新增表单内上传成功,URL:', resultUrl); |
|||
message.success('上传成功!'); |
|||
|
|||
// 保存上传返回的 URL 到专用存储 |
|||
formUploadUrls.value[item.fieldName] = resultUrl; |
|||
|
|||
// 上传成功后,重新生成 schema 以更新 fileList 显示 |
|||
setTimeout(() => { |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
}, 0); |
|||
|
|||
// Deleted:formApi.resetForm(); |
|||
// 返回 false 阻止默认上传行为 |
|||
return false; |
|||
} catch (error: any) { |
|||
console.error('❌ 新增表单内上传失败:', error); |
|||
message.error(error?.message || '上传失败'); |
|||
return false; |
|||
} |
|||
}, |
|||
}, |
|||
}; |
|||
} |
|||
return item; |
|||
}); |
|||
}; |
|||
// ========== 表单配置 ========== |
|||
const [Form, formApi] = useVbenForm({ |
|||
schema: editFormSchema, |
|||
showDefaultActions: false, |
|||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', |
|||
}) |
|||
|
|||
// ========== 上传文件 ========== |
|||
|
|||
async function uploadFile(file: File) { |
|||
let loadingMessage: any = null; |
|||
// 显示 loading 提示 |
|||
loadingMessage = message.loading('图片上传中...', 0); |
|||
const formData = new FormData(); |
|||
formData.append('file', file); |
|||
const res = await health_msgApi.upload(formData); |
|||
loadingMessage(); |
|||
return res.result || res.message || res.url || res; |
|||
} |
|||
|
|||
// ========== 打开上传对话框 ========== |
|||
function openUploadDialog(fieldName: string, currentUrl: string) { |
|||
console.log('=== 打开上传对话框 ===', { |
|||
fieldName, |
|||
currentUrl, |
|||
isEdit: isEdit.value, |
|||
currentRowId: currentRow.value?.id, |
|||
currentRowFull: currentRow.value, |
|||
}); |
|||
|
|||
uploadFieldName.value = fieldName; |
|||
uploadImageUrl.value = currentUrl || ''; |
|||
uploadedUrl.value = ''; // 重置上传后的 URL |
|||
uploadFileList.value = []; // 清空文件列表 |
|||
uploadModalApi.open(); |
|||
} |
|||
|
|||
// ========== 处理图片上传 - 使用 custom-request ========== |
|||
async function handleCustomRequest(options: any) { |
|||
const {file, onSuccess, onError} = options; |
|||
|
|||
try { |
|||
console.log('📦 开始上传文件:', file); |
|||
console.log('📦 文件详情:', { |
|||
name: file.name, |
|||
type: file.type, |
|||
size: file.size, |
|||
lastModified: file.lastModified, |
|||
}); |
|||
const resultUrl = await uploadFile(file); |
|||
console.log('✅ 上传成功,URL:', resultUrl); |
|||
message.success('上传成功!'); |
|||
// 保存上传返回的 URL |
|||
uploadedUrl.value = resultUrl; |
|||
console.log('=== uploadedUrl 赋值后 ===', uploadedUrl.value); |
|||
|
|||
// 添加到文件列表用于显示 |
|||
uploadFileList.value = [{ |
|||
uid: file.uid, |
|||
name: file.name, |
|||
status: 'done', |
|||
url: resultUrl, |
|||
}]; |
|||
|
|||
// 通知组件上传成功 |
|||
onSuccess({url: resultUrl}); |
|||
|
|||
} catch (error: any) { |
|||
console.error('❌ 上传失败:', error); |
|||
message.error(error?.message || '上传失败'); |
|||
onError(error); |
|||
} |
|||
} |
|||
|
|||
// ========== 打开新增弹窗 ========== |
|||
function handleAdd() { |
|||
isEdit.value = false; |
|||
modalTitle.value = '功能配置表新增'; |
|||
// 重置上传 URL 记录 |
|||
formUploadUrls.value = {}; |
|||
// 更新表单 schema (不包含 id) |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
formApi.resetForm(); |
|||
modalApi.open(); |
|||
} |
|||
|
|||
// ========== 打开编辑弹窗 ========== |
|||
function handleEdit(row: any) { |
|||
isEdit.value = true; |
|||
currentRow.value = row; |
|||
modalTitle.value = '功能配置表编辑'; |
|||
// 更新表单 schema (包含 id) |
|||
formApi.setState({ |
|||
schema: getEditFormSchema(), |
|||
}); |
|||
// 设置表单值,只设置 editFields 中包含的字段 |
|||
const formValues: any = {}; |
|||
editFormSchema.forEach(item => { |
|||
const field = item.fieldName; |
|||
if (row[field] !== undefined && row[field] !== null) { |
|||
formValues[field] = row[field]; |
|||
} |
|||
}); |
|||
|
|||
formApi.setValues(formValues); |
|||
modalApi.open(); |
|||
} |
|||
|
|||
// ========== 删除确认 ========== |
|||
function handleDelete(row: any) { |
|||
if (!row.id) return; |
|||
window.confirm(`确定要删除 "${row.userName}" 吗?`) && |
|||
health_msgApi.remove(row.id).then(() => { |
|||
gridApi.reload(); |
|||
}); |
|||
} |
|||
|
|||
// ========== 查询功能 ========== |
|||
function handleSearch() { |
|||
console.log('=== 点击查询按钮 ===') |
|||
// 先重置到第一页,然后执行查询 |
|||
gridApi.query(); |
|||
} |
|||
|
|||
// ========== 重置查询 ========== |
|||
function handleReset() { |
|||
queryFormApi.resetForm(); |
|||
gridApi.reload(); |
|||
} |
|||
|
|||
// ========== 列设置 ========== |
|||
function openColumnSetting() { |
|||
const $grid = gridApi?.grid || gridApi?.getVxeGrid?.(); |
|||
$grid?.openCustom(); |
|||
} |
|||
</script> |
|||
|
|||
|
|||
<template> |
|||
<div |
|||
style="height: 100vh; padding: 16px; box-sizing: border-box; display: flex; flex-direction: column;"> |
|||
<!-- 查询表单 --> |
|||
<div class="bg-card mb-4 p-4 rounded shadow flex-shrink-0"> |
|||
<h3 class="text-lg font-semibold mb-3">查询条件</h3> |
|||
<div v-if="enumData.loading" class="text-center py-4 text-gray-500"> |
|||
正在加载查询条件... |
|||
</div> |
|||
<QueryForm v-else/> |
|||
<div class="mt-3 flex gap-2"> |
|||
<button @click="handleSearch" |
|||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"> |
|||
🔍 查询 |
|||
</button> |
|||
<button @click="handleReset" |
|||
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"> |
|||
🔄 重置 |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 数据表格 (可滚动区域) --> |
|||
<div class="flex-1 overflow-hidden bg-card rounded shadow"> |
|||
<Grid> |
|||
<template #toolbar-tools> |
|||
<button @click="handleAdd" class="vxe-button type--button size--small is--circle" title="新增"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> |
|||
<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"></path> |
|||
</svg> |
|||
</button> |
|||
<button @click="openColumnSetting" class="vxe-button type--button vxe-toolbar-custom-target size--small is--circle" title="列设置"> |
|||
<i class="vxe-button--item vxe-button--prefix-icon vxe-table-icon-custom-column"></i> |
|||
</button> |
|||
<button @click="() => gridApi.reload()" class="vxe-button type--button size--small is--circle" title="刷新" type="button"> |
|||
<i class="vxe-button--item vxe-button--prefix-icon vxe-table-icon-repeat"></i> |
|||
</button> |
|||
</template> |
|||
|
|||
<template #action="{ row }"> |
|||
<div class="flex gap-2"> |
|||
<button @click="() => handleEdit(row)" class="text-blue-500 hover:text-blue-700">✏️ 编辑 |
|||
</button> |
|||
<button @click="() => handleDelete(row)" class="text-red-500 hover:text-red-700">🗑️ 删除 |
|||
</button> |
|||
</div> |
|||
</template> |
|||
</Grid> |
|||
</div> |
|||
|
|||
<Modal :title="modalTitle"> |
|||
<Form/> |
|||
</Modal> |
|||
<!-- 上传对话框 --> |
|||
<UploadModal> |
|||
<div style="padding: 20px;margin-top: 16px; text-align: center;"> |
|||
<Upload |
|||
name="file" |
|||
:file-list="uploadFileList" |
|||
:custom-request="handleCustomRequest" |
|||
:show-upload-list="true" |
|||
accept="image/*" style="width: 100%;" |
|||
> |
|||
<div |
|||
style="width: 100%; height: 150px; border: 2px dashed #d9d9d9; border-radius: 8px; background: #fafafa; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer;"> |
|||
<!-- 使用 SVG 图标代替 --> |
|||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1890ff" |
|||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" |
|||
style="margin-bottom: 8px;"> |
|||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
|||
<polyline points="17 8 12 3 7 8"></polyline> |
|||
<line x1="12" y1="3" x2="12" y2="15"></line> |
|||
</svg> |
|||
<span style="color: #666;">点击或拖拽图片到此上传</span> |
|||
<span style="color: #999; font-size: 12px; margin-top: 4px;">支持 JPG、PNG 格式</span> |
|||
</div> |
|||
</Upload> |
|||
|
|||
<!-- 展示上传成功后的图片 --> |
|||
<div v-if="uploadedUrl" style="margin-top: 16px; text-align: center;"> |
|||
<p style="color: #67c23a; font-size: 14px; margin-bottom: 8px;">✅ 上传成功!</p> |
|||
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">新图片预览:</p> |
|||
<Image |
|||
:src="uploadedUrl" |
|||
style="max-width: 200px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);" |
|||
:preview="true" |
|||
/> |
|||
<p style="color: #999; font-size: 12px; margin-top: 8px; word-break: break-all;"></p> |
|||
</div> |
|||
|
|||
<!-- 展示原图(如果有) --> |
|||
<div v-if="uploadImageUrl && !uploadedUrl" style="margin-top: 16px; text-align: center;"> |
|||
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">当前图片:</p> |
|||
<Image |
|||
:src="getFullUrl(uploadImageUrl)" |
|||
style="max-width: 200px; border-radius: 4px;" |
|||
:preview="true" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</UploadModal> |
|||
</div> |
|||
</template> |
|||
<style scoped> |
|||
:deep(.vxe-pager--wrapper) { |
|||
margin-bottom: 0.5rem; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue