上传全量
This commit is contained in:
parent
aeda78b8dc
commit
f007460b1e
@ -6,7 +6,7 @@ from . import *
|
|||||||
from .views import blus
|
from .views import blus
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__, static_folder='D:\\2025\\fraud-detection-ml\\uploads')
|
app = Flask(__name__)
|
||||||
# 注册蓝图
|
# 注册蓝图
|
||||||
app.register_blueprint(blueprint=blus)
|
app.register_blueprint(blueprint=blus)
|
||||||
# MySQL所在主机名,默认127.0.0.1
|
# MySQL所在主机名,默认127.0.0.1
|
||||||
|
@ -52,8 +52,9 @@ class FinancialTransaction(db.Model):
|
|||||||
browser_info = db.Column(db.String(255), nullable=True)
|
browser_info = db.Column(db.String(255), nullable=True)
|
||||||
mobile = db.Column(db.Integer, nullable=True)
|
mobile = db.Column(db.Integer, nullable=True)
|
||||||
is_fraud = db.Column(db.Boolean, nullable=False)
|
is_fraud = db.Column(db.Boolean, nullable=False)
|
||||||
|
status = db.Column(db.Boolean, nullable=False)
|
||||||
def __init__(self, user_name, transaction_amount, transaction_time, transaction_location, device_info, ip_address,transaction_status,mobile,
|
def __init__(self, user_name, transaction_amount, transaction_time, transaction_location, device_info, ip_address,transaction_status,mobile,
|
||||||
browser_info, is_fraud):
|
browser_info, is_fraud,status):
|
||||||
self.user_name = user_name
|
self.user_name = user_name
|
||||||
self.transaction_amount = transaction_amount
|
self.transaction_amount = transaction_amount
|
||||||
self.transaction_time = transaction_time
|
self.transaction_time = transaction_time
|
||||||
@ -64,10 +65,12 @@ class FinancialTransaction(db.Model):
|
|||||||
self.is_fraud = is_fraud
|
self.is_fraud = is_fraud
|
||||||
self.transaction_status = transaction_status
|
self.transaction_status = transaction_status
|
||||||
self.mobile = mobile
|
self.mobile = mobile
|
||||||
|
self.status = status
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'transaction_id': self.transaction_id,
|
'transaction_id': self.transaction_id,
|
||||||
|
'status': self.status,
|
||||||
'user_name': self.user_name,
|
'user_name': self.user_name,
|
||||||
'transaction_amount': self.transaction_amount,
|
'transaction_amount': self.transaction_amount,
|
||||||
'transaction_time': self.transaction_time.strftime('%Y-%d-%m %H:%M:%S'),
|
'transaction_time': self.transaction_time.strftime('%Y-%d-%m %H:%M:%S'),
|
||||||
|
88
App/views.py
88
App/views.py
@ -7,14 +7,16 @@ from flask import Blueprint
|
|||||||
import hashlib
|
import hashlib
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
|
from sqlalchemy import and_
|
||||||
|
|
||||||
from .utils.api_utils import APIUtils
|
from .utils.api_utils import APIUtils
|
||||||
from .models import *
|
from .models import *
|
||||||
blus = Blueprint("user", __name__)
|
blus = Blueprint("user", __name__)
|
||||||
db_config = {
|
db_config = {
|
||||||
'host': 'localhost',
|
'host': '192.168.15.2',
|
||||||
'user': 'root',
|
'user': 'root',
|
||||||
'password': '123456',
|
'password': 'minxianrui',
|
||||||
'database': 'job',
|
'database': 'fraud_detection_ml',
|
||||||
'charset': 'utf8mb4'
|
'charset': 'utf8mb4'
|
||||||
}
|
}
|
||||||
# 注册
|
# 注册
|
||||||
@ -112,14 +114,11 @@ def get_users():
|
|||||||
|
|
||||||
# 获取 username 参数,如果没有则为 None
|
# 获取 username 参数,如果没有则为 None
|
||||||
username = request.args.get('username', type=str)
|
username = request.args.get('username', type=str)
|
||||||
|
|
||||||
# 构建查询,先查询所有用户
|
# 构建查询,先查询所有用户
|
||||||
query = User.query
|
query = User.query
|
||||||
|
|
||||||
# 如果提供了 username,则根据 username 进行筛选
|
# 如果提供了 username,则根据 username 进行筛选
|
||||||
if username:
|
if username:
|
||||||
query = query.filter(User.username.like(f'%{username}%'))
|
query = query.filter(User.username.like(f'%{username}%'))
|
||||||
|
|
||||||
# 执行分页查询
|
# 执行分页查询
|
||||||
users_pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
users_pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
@ -150,42 +149,10 @@ def get_users():
|
|||||||
return APIUtils.success_response(data=response, message="获取用户列表成功")
|
return APIUtils.success_response(data=response, message="获取用户列表成功")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 文件上传
|
|
||||||
@blus.route('/api/upload', methods=['POST'])
|
|
||||||
def upload():
|
|
||||||
# 检查是否有文件上传
|
|
||||||
if 'file' not in request.files:
|
|
||||||
return APIUtils.error_response(message="没有上传文件!")
|
|
||||||
file = request.files['file']
|
|
||||||
# 如果用户没有选择文件,浏览器也会提交一个空文件
|
|
||||||
if file.filename == '':
|
|
||||||
return APIUtils.error_response(message="没有上传文件!")
|
|
||||||
# 保存文件
|
|
||||||
upload_folder = "uploads"
|
|
||||||
if not os.path.exists(upload_folder):
|
|
||||||
os.makedirs(upload_folder) # 如果不存在则创建目录
|
|
||||||
# 保存文件
|
|
||||||
file_path = os.path.join(upload_folder, file.filename)
|
|
||||||
file.save(file_path)
|
|
||||||
|
|
||||||
# 构建文件的可访问 URL
|
|
||||||
file_url = f"http://127.0.0.1:5000/{upload_folder}/{file.filename}"
|
|
||||||
|
|
||||||
# 返回上传路径和文件名
|
|
||||||
response_data = {
|
|
||||||
"name": file.filename.split(".")[0],
|
|
||||||
"path": file_path, # 保存的完整路径
|
|
||||||
"url": file_url # 可访问的 URL
|
|
||||||
}
|
|
||||||
return APIUtils.success_response(data=response_data, message="上传成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 增:添加新的交易记录
|
# 增:添加新的交易记录
|
||||||
@blus.route('/api/transactions', methods=['POST'])
|
@blus.route('/api/transactions', methods=['POST'])
|
||||||
def add_transaction():
|
def add_transaction():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
new_transaction = FinancialTransaction(
|
new_transaction = FinancialTransaction(
|
||||||
user_id=data['user_id'],
|
user_id=data['user_id'],
|
||||||
transaction_amount=data['transaction_amount'],
|
transaction_amount=data['transaction_amount'],
|
||||||
@ -196,7 +163,6 @@ def add_transaction():
|
|||||||
browser_info=data.get('browser_info', ''),
|
browser_info=data.get('browser_info', ''),
|
||||||
is_fraud=data['is_fraud']
|
is_fraud=data['is_fraud']
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(new_transaction)
|
db.session.add(new_transaction)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@ -209,16 +175,27 @@ def get_transactions():
|
|||||||
page = request.args.get('page', 1, type=int) # 默认第一页
|
page = request.args.get('page', 1, type=int) # 默认第一页
|
||||||
page_size = request.args.get('page_size', 10, type=int) # 默认每页10条
|
page_size = request.args.get('page_size', 10, type=int) # 默认每页10条
|
||||||
|
|
||||||
|
query = FinancialTransaction.query
|
||||||
|
transactionStatus = request.args.get('transactionStatus')
|
||||||
|
status = request.args.get('status')
|
||||||
|
|
||||||
|
|
||||||
|
if transactionStatus and status:
|
||||||
|
query = query.filter(and_(
|
||||||
|
FinancialTransaction.is_fraud.like(f'%{transactionStatus}%'),
|
||||||
|
FinancialTransaction.status.like(f'%{status}%')
|
||||||
|
))
|
||||||
|
elif transactionStatus:
|
||||||
|
query = query.filter(FinancialTransaction.is_fraud.like(f'%{transactionStatus}%'))
|
||||||
|
elif status:
|
||||||
|
query = query.filter(FinancialTransaction.status.like(f'%{status}%'))
|
||||||
# 计算分页偏移量
|
# 计算分页偏移量
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
# 查询交易记录,使用 limit 和 offset 实现分页
|
# 查询交易记录,使用 limit 和 offset 实现分页
|
||||||
transactions = FinancialTransaction.query.offset(offset).limit(page_size).all()
|
transactions = query.offset(offset).limit(page_size).all()
|
||||||
|
|
||||||
# 获取总记录数,用于计算总页数
|
# 获取总记录数,用于计算总页数
|
||||||
total_count = FinancialTransaction.query.count()
|
total_count = query.count()
|
||||||
total_pages = ceil(total_count / page_size)
|
total_pages = ceil(total_count / page_size)
|
||||||
|
|
||||||
# 构建响应数据,包括分页信息
|
# 构建响应数据,包括分页信息
|
||||||
response = {
|
response = {
|
||||||
'data': [transaction.to_dict() for transaction in transactions],
|
'data': [transaction.to_dict() for transaction in transactions],
|
||||||
@ -229,15 +206,11 @@ def get_transactions():
|
|||||||
"total_pages": total_pages
|
"total_pages": total_pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 返回分页数据,包括当前页的记录和总信息
|
# 返回分页数据,包括当前页的记录和总信息
|
||||||
return APIUtils.success_response(
|
return APIUtils.success_response(
|
||||||
data=response,
|
data=response,
|
||||||
message="成功",
|
message="成功",
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 查:获取单个交易记录
|
# 查:获取单个交易记录
|
||||||
@blus.route('/api/transactions/<int:transaction_id>', methods=['GET'])
|
@blus.route('/api/transactions/<int:transaction_id>', methods=['GET'])
|
||||||
def get_transaction(transaction_id):
|
def get_transaction(transaction_id):
|
||||||
@ -252,12 +225,9 @@ def get_transaction(transaction_id):
|
|||||||
@blus.route('/api/transactions/<int:transaction_id>', methods=['PUT'])
|
@blus.route('/api/transactions/<int:transaction_id>', methods=['PUT'])
|
||||||
def update_transaction(transaction_id):
|
def update_transaction(transaction_id):
|
||||||
transaction = FinancialTransaction.query.get(transaction_id)
|
transaction = FinancialTransaction.query.get(transaction_id)
|
||||||
|
|
||||||
if transaction is None:
|
if transaction is None:
|
||||||
return jsonify({'message': 'Transaction not found'}), 404
|
return jsonify({'message': 'Transaction not found'}), 404
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
transaction.user_id = data.get('user_id', transaction.user_id)
|
transaction.user_id = data.get('user_id', transaction.user_id)
|
||||||
transaction.transaction_amount = data.get('transaction_amount', transaction.transaction_amount)
|
transaction.transaction_amount = data.get('transaction_amount', transaction.transaction_amount)
|
||||||
transaction.transaction_time = data.get('transaction_time', transaction.transaction_time)
|
transaction.transaction_time = data.get('transaction_time', transaction.transaction_time)
|
||||||
@ -266,12 +236,23 @@ def update_transaction(transaction_id):
|
|||||||
transaction.ip_address = data.get('ip_address', transaction.ip_address)
|
transaction.ip_address = data.get('ip_address', transaction.ip_address)
|
||||||
transaction.browser_info = data.get('browser_info', transaction.browser_info)
|
transaction.browser_info = data.get('browser_info', transaction.browser_info)
|
||||||
transaction.is_fraud = data.get('is_fraud', transaction.is_fraud)
|
transaction.is_fraud = data.get('is_fraud', transaction.is_fraud)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify(transaction.to_dict())
|
return jsonify(transaction.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 改:更新交易记录
|
||||||
|
@blus.route('/api/utransactions/<int:transaction_id>', methods=['PUT'])
|
||||||
|
def update_transaction1(transaction_id):
|
||||||
|
transaction = FinancialTransaction.query.get(transaction_id)
|
||||||
|
|
||||||
|
print(transaction_id)
|
||||||
|
transaction.status = 1
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(transaction.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 删:删除交易记录
|
# 删:删除交易记录
|
||||||
@blus.route('/api/transactions/<int:transaction_id>', methods=['DELETE'])
|
@blus.route('/api/transactions/<int:transaction_id>', methods=['DELETE'])
|
||||||
def delete_transaction(transaction_id):
|
def delete_transaction(transaction_id):
|
||||||
@ -289,7 +270,6 @@ def delete_transaction(transaction_id):
|
|||||||
@blus.route('/api/mysql', methods=['POST'])
|
@blus.route('/api/mysql', methods=['POST'])
|
||||||
def mysql():
|
def mysql():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
# 检查 SQL 参数是否存在
|
# 检查 SQL 参数是否存在
|
||||||
if not data['sql']:
|
if not data['sql']:
|
||||||
return APIUtils.error_response(message="没有sql参数")
|
return APIUtils.error_response(message="没有sql参数")
|
||||||
|
158
data/model.py
Normal file
158
data/model.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
from sklearn.linear_model import LogisticRegression # 逻辑回归
|
||||||
|
from sklearn.tree import DecisionTreeClassifier # 决策树
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.preprocessing import StandardScaler
|
||||||
|
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
|
||||||
|
import joblib
|
||||||
|
|
||||||
|
# pandas的显示设置来增加可以显示的列数
|
||||||
|
pd.set_option('display.max_columns', None)
|
||||||
|
# 读取数据
|
||||||
|
data = pd.read_csv('creditcard.csv')
|
||||||
|
# 查看默认的前5行数据
|
||||||
|
data.head(5)
|
||||||
|
|
||||||
|
# 查看数据的信息
|
||||||
|
print(data.shape)
|
||||||
|
data.info()
|
||||||
|
# 查看数据的描述
|
||||||
|
data.describe()
|
||||||
|
# 检查是否有空置
|
||||||
|
data.isnull().sum()
|
||||||
|
# 查看没类的个数
|
||||||
|
data['Class'].value_counts()
|
||||||
|
# 划分特征和标签
|
||||||
|
X = data.drop('Class', axis=1)
|
||||||
|
y = data['Class']
|
||||||
|
# 划分训练集和测试集
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
||||||
|
# 特征标准化
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_train = scaler.fit_transform(X_train)
|
||||||
|
X_test = scaler.transform(X_test)
|
||||||
|
print("原始数据训练-----")
|
||||||
|
# 使用逻辑回归算法进行训练和评估
|
||||||
|
print("\nLogistic Regression:")
|
||||||
|
# 初始化逻辑回归模型
|
||||||
|
model = LogisticRegression(max_iter=1000, random_state=42)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
|
||||||
|
print("\nDecisionTreeClassifier:")
|
||||||
|
# 初始化决策树模型
|
||||||
|
model = DecisionTreeClassifier()
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
|
||||||
|
from imblearn.under_sampling import RandomUnderSampler
|
||||||
|
|
||||||
|
print("下采样以平衡数据")
|
||||||
|
rus = RandomUnderSampler(random_state=42)
|
||||||
|
X_res, y_res = rus.fit_resample(X, y)
|
||||||
|
|
||||||
|
# 分割数据为训练集和测试集
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_res, y_res, test_size=0.2, random_state=42)
|
||||||
|
|
||||||
|
# 特征缩放
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_train = scaler.fit_transform(X_train)
|
||||||
|
X_test = scaler.transform(X_test)
|
||||||
|
|
||||||
|
# 使用逻辑回归算法进行训练和评估
|
||||||
|
print("\nLogistic Regression:")
|
||||||
|
# 初始化逻辑回归模型
|
||||||
|
model = LogisticRegression(max_iter=1000, random_state=42)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
|
||||||
|
print("\nDecisionTreeClassifier:")
|
||||||
|
# 初始化决策树模型
|
||||||
|
model = DecisionTreeClassifier()
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
|
||||||
|
from imblearn.over_sampling import SMOTE
|
||||||
|
|
||||||
|
print("上采样以平衡数据")
|
||||||
|
smote = SMOTE(random_state=42)
|
||||||
|
X_res, y_res = smote.fit_resample(X, y)
|
||||||
|
# 分割数据为训练集和测试集
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_res, y_res,
|
||||||
|
test_size=0.2, random_state=42)
|
||||||
|
# 特征缩放
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_train = scaler.fit_transform(X_train)
|
||||||
|
X_test = scaler.transform(X_test)
|
||||||
|
|
||||||
|
# 模型调优
|
||||||
|
print("\nDecisionTreeClassifier:")
|
||||||
|
# 初始化决策树模型
|
||||||
|
model = DecisionTreeClassifier()
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
|
||||||
|
# 使用逻辑回归算法进行训练和评估
|
||||||
|
print("\nLogistic Regression:")
|
||||||
|
# 初始化逻辑回归模型
|
||||||
|
model = LogisticRegression(max_iter=1000, random_state=42)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
# 打印性能指标
|
||||||
|
print(f'Accuracy: {accuracy_score(y_test, y_pred)}')
|
||||||
|
print(f'Precision: {precision_score(y_test, y_pred)}')
|
||||||
|
print(f'Recall: {recall_score(y_test, y_pred)}')
|
||||||
|
print(f'F1 Score: {f1_score(y_test, y_pred)}')
|
||||||
|
from sklearn.model_selection import learning_curve
|
||||||
|
# 获取学习曲线数据
|
||||||
|
train_sizes, train_scores, test_scores = learning_curve(model, X, y, cv=5, n_jobs=-1,
|
||||||
|
train_sizes=np.linspace(0.1, 1.0, 5))
|
||||||
|
# 计算训练和测试分数的平均值与标准差
|
||||||
|
train_scores_mean = np.mean(train_scores, axis=1)
|
||||||
|
train_scores_std = np.std(train_scores, axis=1)
|
||||||
|
test_scores_mean = np.mean(test_scores, axis=1)
|
||||||
|
test_scores_std = np.std(test_scores, axis=1)
|
||||||
|
|
||||||
|
# 绘制学习曲线
|
||||||
|
plt.figure()
|
||||||
|
plt.title("Learning Curve")
|
||||||
|
plt.xlabel("Training Examples")
|
||||||
|
plt.ylabel("Score")
|
||||||
|
plt.grid()
|
||||||
|
plt.plot(train_sizes, train_scores_mean, 'o-', color="r", label="Training Score")
|
||||||
|
plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Cross-validation Score")
|
||||||
|
|
||||||
|
plt.legend(loc="best")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# 加载逻辑回归模型
|
||||||
|
logistic_model = joblib.load('logistic_regression_model.pkl')
|
||||||
|
# 加载决策树模型
|
||||||
|
decision_tree_model = joblib.load('decision_tree_model.pkl')
|
@ -78,10 +78,8 @@ const state = reactive({
|
|||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
logoutAdmin().then(()=>{
|
toast.success("退出成功~")
|
||||||
toast.success("退出成功~")
|
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 增、查交易记录的按钮 -->
|
<!-- 同步交易记录的按钮 -->
|
||||||
<el-button type="primary" @click="openAddDialog">同步交易记录</el-button>
|
<el-button type="primary" @click="openAddDialog">同步交易记录</el-button>
|
||||||
|
|
||||||
|
<!-- 交易记录表格 -->
|
||||||
<el-table :data="state.getList">
|
<el-table :data="state.getList">
|
||||||
<el-table-column prop="transaction_id" label="交易ID" />
|
<el-table-column prop="transaction_id" label="交易ID" />
|
||||||
<el-table-column prop="user_name" label="姓名" />
|
<el-table-column prop="user_name" label="姓名" />
|
||||||
<el-table-column prop="mobile" label="联系方式"/>
|
<el-table-column prop="mobile" label="联系方式" />
|
||||||
<el-table-column prop="transaction_amount" label="交易金额" />
|
<el-table-column prop="transaction_amount" label="交易金额" />
|
||||||
<el-table-column prop="transaction_time" label="交易时间" width="200" />
|
<el-table-column prop="transaction_time" label="交易时间" width="200" />
|
||||||
<el-table-column prop="transaction_location" label="交易城市" />
|
<el-table-column prop="transaction_location" label="交易城市" />
|
||||||
<el-table-column prop="transaction_status" label="交易状态" />
|
<el-table-column prop="transaction_status" label="交易状态" />
|
||||||
<el-table-column prop="ip_address" label="ip地址" width="180" />
|
<el-table-column prop="ip_address" label="IP地址" width="180" />
|
||||||
<!-- <el-table-column prop="is_fraud" label="是否欺诈" width="180" />-->
|
|
||||||
<el-table-column label="操作">
|
<el-table-column label="操作">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button @click="editTransaction(scope.row)" size="small">编辑</el-button>
|
<!-- <el-button @click="editTransaction(scope.row)" size="small">编辑</el-button>-->
|
||||||
<el-button @click="deleteTransaction(scope.row.transaction_id)" type="danger" size="small">删除</el-button>
|
<el-button @click="deleteTransaction(scope.row.transaction_id)" type="danger" size="small">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页控件 -->
|
<!-- 分页控件 -->
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-if="state.totalCount > 0"
|
v-if="state.totalCount > 0"
|
||||||
@ -28,8 +30,9 @@
|
|||||||
layout="total, prev, pager, jumper"
|
layout="total, prev, pager, jumper"
|
||||||
@current-change="handlePageChange"
|
@current-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<el-dialog v-model="state.dialogVisible" title="新增交易记录" width="50%">
|
<el-dialog v-model="state.dialogVisible" :title="formData.transaction_id ? '编辑交易记录' : '新增交易记录'" width="50%">
|
||||||
<el-form :model="formData" ref="form" label-width="100px">
|
<el-form :model="formData" ref="form" label-width="100px">
|
||||||
<el-form-item label="用户ID" prop="user_id" :rules="[{ required: true, message: '请输入用户ID', trigger: 'blur' }]">
|
<el-form-item label="用户ID" prop="user_id" :rules="[{ required: true, message: '请输入用户ID', trigger: 'blur' }]">
|
||||||
<el-input v-model="formData.user_id" />
|
<el-input v-model="formData.user_id" />
|
||||||
@ -38,7 +41,12 @@
|
|||||||
<el-input v-model="formData.transaction_amount" />
|
<el-input v-model="formData.transaction_amount" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="交易时间" prop="transaction_time" :rules="[{ required: true, message: '请输入交易时间', trigger: 'blur' }]">
|
<el-form-item label="交易时间" prop="transaction_time" :rules="[{ required: true, message: '请输入交易时间', trigger: 'blur' }]">
|
||||||
<el-input v-model="formData.transaction_time" />
|
<el-date-picker
|
||||||
|
v-model="formData.transaction_time"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择交易时间"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否欺诈" prop="is_fraud" :rules="[{ required: true, message: '请选择是否欺诈', trigger: 'blur' }]">
|
<el-form-item label="是否欺诈" prop="is_fraud" :rules="[{ required: true, message: '请选择是否欺诈', trigger: 'blur' }]">
|
||||||
<el-switch v-model="formData.is_fraud" active-text="是" inactive-text="否" />
|
<el-switch v-model="formData.is_fraud" active-text="是" inactive-text="否" />
|
||||||
@ -54,9 +62,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted } from 'vue'
|
import { reactive, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
// 交易记录数据
|
// 交易记录数据
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dialogVisible:false,
|
dialogVisible: false,
|
||||||
getList: [],
|
getList: [],
|
||||||
totalCount: 0, // 总记录数
|
totalCount: 0, // 总记录数
|
||||||
page: 1, // 当前页码
|
page: 1, // 当前页码
|
||||||
@ -69,31 +79,34 @@ const formData = reactive({
|
|||||||
user_id: '',
|
user_id: '',
|
||||||
transaction_amount: '',
|
transaction_amount: '',
|
||||||
transaction_time: '',
|
transaction_time: '',
|
||||||
is_fraud: false
|
is_fraud: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取交易记录
|
// 获取交易记录
|
||||||
const init = () => {
|
const init = async () => {
|
||||||
adminRequest.get('/api/transactions', {
|
try {
|
||||||
params: {
|
const res = await adminRequest.get('/api/transactions', {
|
||||||
page: state.page,
|
params: {
|
||||||
page_size: state.pageSize,
|
page: state.page,
|
||||||
}
|
page_size: state.pageSize,
|
||||||
}).then(res => {
|
},
|
||||||
|
})
|
||||||
state.getList = res.data.data
|
state.getList = res.data.data
|
||||||
state.totalCount = res.data.page.total_count
|
state.totalCount = res.data.page.total_count
|
||||||
})
|
} catch (error) {
|
||||||
|
ElMessage.error('获取交易记录失败')
|
||||||
|
console.error('Error fetching transactions:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增交易记录
|
// 打开新增对话框
|
||||||
const openAddDialog = () => {
|
const openAddDialog = () => {
|
||||||
ElMessage.success("同步成功!")
|
formData.transaction_id = null
|
||||||
// formData.transaction_id = null
|
formData.user_id = ''
|
||||||
// formData.user_id = ''
|
formData.transaction_amount = ''
|
||||||
// formData.transaction_amount = ''
|
formData.transaction_time = ''
|
||||||
// formData.transaction_time = ''
|
formData.is_fraud = false
|
||||||
// formData.is_fraud = false
|
state.dialogVisible = true
|
||||||
// state.dialogVisible = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑交易记录
|
// 编辑交易记录
|
||||||
@ -108,23 +121,35 @@ const editTransaction = (row: any) => {
|
|||||||
|
|
||||||
// 保存交易记录
|
// 保存交易记录
|
||||||
const saveTransaction = async () => {
|
const saveTransaction = async () => {
|
||||||
if (formData.transaction_id) {
|
try {
|
||||||
// 更新交易记录
|
if (formData.transaction_id) {
|
||||||
await adminRequest.put(`/api/transactions/${formData.transaction_id}`, formData)
|
// 更新交易记录
|
||||||
} else {
|
await adminRequest.put(`/api/transactions/${formData.transaction_id}`, formData)
|
||||||
// 新增交易记录
|
ElMessage.success('更新成功')
|
||||||
await adminRequest.post('api/transactions', formData)
|
} else {
|
||||||
|
// 新增交易记录
|
||||||
|
await adminRequest.post('/api/transactions', formData)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
state.dialogVisible = false
|
||||||
|
init() // 重新加载数据
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('保存失败')
|
||||||
|
console.error('Error saving transaction:', error)
|
||||||
}
|
}
|
||||||
init()
|
|
||||||
state.dialogVisible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除交易记录
|
// 删除交易记录
|
||||||
const deleteTransaction = async (transaction_id: number) => {
|
const deleteTransaction = async (transaction_id: number) => {
|
||||||
try {
|
try {
|
||||||
await adminRequest.delete(`api/transactions/${transaction_id}`)
|
await ElMessageBox.confirm('确定删除该交易记录吗?', '提示', {
|
||||||
init()
|
type: 'warning',
|
||||||
|
})
|
||||||
|
await adminRequest.delete(`/api/transactions/${transaction_id}`)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
init() // 重新加载数据
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
console.error('Error deleting transaction:', error)
|
console.error('Error deleting transaction:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,6 +165,7 @@ onMounted(() => {
|
|||||||
init()
|
init()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
text-align: right;
|
text-align: right;
|
99
ui/src/pages/admin/view.vue
Normal file
99
ui/src/pages/admin/view.vue
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<el-row v-loading="loading">
|
||||||
|
<!-- 折线图 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<v-chart class="chart" :option="demandByRegionOption" autoresize />
|
||||||
|
</el-col>
|
||||||
|
<!-- 饼图 -->
|
||||||
|
<el-col :span="12">
|
||||||
|
<v-chart class="chart" :option="pieChartOption" autoresize />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import VChart from 'vue-echarts'
|
||||||
|
import { BarChart, PieChart, LineChart } from 'echarts/charts'
|
||||||
|
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
|
||||||
|
|
||||||
|
// 引入并使用图表组件
|
||||||
|
use([CanvasRenderer, BarChart, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||||
|
|
||||||
|
// 定义折线图配置
|
||||||
|
const demandByRegionOption = ref({
|
||||||
|
title: { text: '交易地区占比(折线图)' },
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { data: ['交易量'] },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [] // 初始为空,等待接口数据
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '交易量',
|
||||||
|
type: 'line',
|
||||||
|
data: [] // 初始为空,等待接口数据
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义饼图配置
|
||||||
|
const pieChartOption = ref({
|
||||||
|
title: { text: '交易地区占比(饼图)' },
|
||||||
|
tooltip: { trigger: 'item' },
|
||||||
|
legend: { bottom: '10%', left: 'center' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '交易量',
|
||||||
|
type: 'pie',
|
||||||
|
radius: '50%',
|
||||||
|
data: [], // 初始为空,等待接口数据
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
// 在 onMounted 中获取数据并更新图表
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
// 调用接口获取数据
|
||||||
|
const response = await adminRequest.post('/api/mysql', {
|
||||||
|
sql: "SELECT transaction_location name, count(*) value FROM fraud_detection_ml.tb_financial_transactions GROUP BY transaction_location"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新折线图配置
|
||||||
|
demandByRegionOption.value.xAxis.data = response.map(item => item.name)
|
||||||
|
demandByRegionOption.value.series[0].data = response.map(item => item.value)
|
||||||
|
|
||||||
|
// 更新饼图配置
|
||||||
|
pieChartOption.value.series[0].data = response.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false // 关闭加载状态
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- 销售情况按地区分布 -->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<v-chart class="chart" :option="salesByRegionOption" autoresize />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 地区需求对比 -->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<v-chart class="chart" :option="demandByRegionOption" autoresize />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 客户购买频次分析 -->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<v-chart class="chart" :option="mapOption" autoresize />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import VChart from 'vue-echarts'
|
|
||||||
import { BarChart, PieChart, LineChart } from 'echarts/charts'
|
|
||||||
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
|
||||||
import { use } from 'echarts/core'
|
|
||||||
|
|
||||||
// 引入并使用图表组件
|
|
||||||
use([CanvasRenderer, BarChart, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
|
||||||
|
|
||||||
// 数据模拟:各地区销售、需求、频次等数据
|
|
||||||
const regionSalesData = ref([
|
|
||||||
{ region: '华北', sales: 1500, demand: 2000, frequency: 30 },
|
|
||||||
{ region: '华东', sales: 2500, demand: 3000, frequency: 40 },
|
|
||||||
{ region: '华南', sales: 1200, demand: 1500, frequency: 25 },
|
|
||||||
{ region: '西南', sales: 800, demand: 1000, frequency: 15 },
|
|
||||||
{ region: '西北', sales: 600, demand: 900, frequency: 10 },
|
|
||||||
{ region: '东北', sales: 900, demand: 1100, frequency: 20 },
|
|
||||||
{ region: '华中', sales: 1800, demand: 2200, frequency: 35 },
|
|
||||||
{ region: '西部', sales: 700, demand: 900, frequency: 18 },
|
|
||||||
{ region: '东南', sales: 2200, demand: 2700, frequency: 50 }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 销售情况按地区分布(柱状图)
|
|
||||||
const salesByRegionOption = ref({
|
|
||||||
title: { text: '各地区销售情况' },
|
|
||||||
tooltip: { trigger: 'axis' },
|
|
||||||
legend: { data: ['销售量'] },
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: regionSalesData.value.map(item => item.region)
|
|
||||||
},
|
|
||||||
yAxis: { type: 'value' },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '销售量',
|
|
||||||
type: 'bar',
|
|
||||||
data: regionSalesData.value.map(item => item.sales)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 地区需求对比(折线图)
|
|
||||||
const demandByRegionOption = ref({
|
|
||||||
title: { text: '各地区需求对比' },
|
|
||||||
tooltip: { trigger: 'axis' },
|
|
||||||
legend: { data: ['需求量'] },
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: regionSalesData.value.map(item => item.region)
|
|
||||||
},
|
|
||||||
yAxis: { type: 'value' },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '需求量',
|
|
||||||
type: 'line',
|
|
||||||
data: regionSalesData.value.map(item => item.demand)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 客户购买频次分析(饼图)
|
|
||||||
const purchaseFrequencyOption = ref({
|
|
||||||
title: {
|
|
||||||
text: '各地区客户购买频次分析',
|
|
||||||
left: 'center' // 将标题居中
|
|
||||||
},
|
|
||||||
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
|
||||||
legend: {
|
|
||||||
orient: 'vertical',
|
|
||||||
left: 'left',
|
|
||||||
data: regionSalesData.value.map(item => item.region)
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '购买频次',
|
|
||||||
type: 'pie',
|
|
||||||
radius: '55%',
|
|
||||||
center: ['50%', '60%'],
|
|
||||||
data: regionSalesData.value.map(item => ({ value: item.frequency, name: item.region })),
|
|
||||||
emphasis: {
|
|
||||||
itemStyle: {
|
|
||||||
shadowBlur: 10,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.chart {
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,173 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- 销售热力图 -->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<v-chart class="chart" :option="heatmapOption" autoresize />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<div style="height: 20px"></div>
|
|
||||||
<!-- 产品销售表现雷达图 -->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<v-chart class="chart" :option="radarOption" autoresize />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import VChart from 'vue-echarts'
|
|
||||||
import { HeatmapChart, RadarChart } from 'echarts/charts'
|
|
||||||
import { TitleComponent, TooltipComponent, GridComponent, VisualMapComponent } from 'echarts/components'
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
|
||||||
import { use } from 'echarts/core'
|
|
||||||
|
|
||||||
// 引入并使用图表组件
|
|
||||||
use([CanvasRenderer, HeatmapChart, RadarChart, TitleComponent, TooltipComponent, GridComponent, VisualMapComponent])
|
|
||||||
|
|
||||||
// 数据模拟:液化气瓶的销售数据
|
|
||||||
const productSalesData = ref([
|
|
||||||
{ type: '液化气瓶A', specification: '5L', quality: '优质', sales: 200 },
|
|
||||||
{ type: '液化气瓶A', specification: '10L', quality: '优质', sales: 150 },
|
|
||||||
{ type: '液化气瓶A', specification: '20L', quality: '中等', sales: 180 },
|
|
||||||
{ type: '液化气瓶B', specification: '5L', quality: '良好', sales: 130 },
|
|
||||||
{ type: '液化气瓶B', specification: '10L', quality: '中等', sales: 220 },
|
|
||||||
{ type: '液化气瓶B', specification: '20L', quality: '优质', sales: 250 },
|
|
||||||
{ type: '液化气瓶C', specification: '5L', quality: '优质', sales: 190 },
|
|
||||||
{ type: '液化气瓶C', specification: '10L', quality: '良好', sales: 160 },
|
|
||||||
{ type: '液化气瓶C', specification: '20L', quality: '中等', sales: 200 },
|
|
||||||
{ type: '液化气瓶D', specification: '5L', quality: '中等', sales: 140 },
|
|
||||||
{ type: '液化气瓶D', specification: '10L', quality: '优质', sales: 270 },
|
|
||||||
{ type: '液化气瓶D', specification: '20L', quality: '良好', sales: 230 },
|
|
||||||
{ type: '液化气瓶E', specification: '5L', quality: '良好', sales: 110 },
|
|
||||||
{ type: '液化气瓶E', specification: '10L', quality: '中等', sales: 130 },
|
|
||||||
{ type: '液化气瓶E', specification: '20L', quality: '优质', sales: 220 },
|
|
||||||
{ type: '液化气瓶F', specification: '5L', quality: '中等', sales: 180 },
|
|
||||||
{ type: '液化气瓶F', specification: '10L', quality: '良好', sales: 160 },
|
|
||||||
{ type: '液化气瓶F', specification: '20L', quality: '优质', sales: 200 },
|
|
||||||
{ type: '液化气瓶G', specification: '5L', quality: '优质', sales: 210 },
|
|
||||||
{ type: '液化气瓶G', specification: '10L', quality: '优质', sales: 230 },
|
|
||||||
{ type: '液化气瓶G', specification: '20L', quality: '中等', sales: 220 }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 销售热力图:展示不同规格和质量等级下的销售情况
|
|
||||||
const heatmapOption = ref({
|
|
||||||
title: { text: '销售热力图' },
|
|
||||||
tooltip: { position: 'top' },
|
|
||||||
grid: {
|
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: ['液化气瓶A', '液化气瓶B', '液化气瓶C', '液化气瓶D', '液化气瓶E', '液化气瓶F', '液化气瓶G']
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: ['5L', '10L', '20L']
|
|
||||||
},
|
|
||||||
visualMap: {
|
|
||||||
min: 0,
|
|
||||||
max: 250, // 根据实际数据设置最大值
|
|
||||||
calculable: true,
|
|
||||||
orient: 'horizontal',
|
|
||||||
left: 'center',
|
|
||||||
inRange: {
|
|
||||||
color: ['#FFFFFF', '#FF0000'] // 从白色到红色的渐变色,表示从低到高的热力值
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '销售量',
|
|
||||||
type: 'heatmap',
|
|
||||||
data: [
|
|
||||||
[0, 0, 200], // [x, y, value] 表示对应的销售量数据
|
|
||||||
[0, 1, 150],
|
|
||||||
[0, 2, 180],
|
|
||||||
[1, 0, 130],
|
|
||||||
[1, 1, 220],
|
|
||||||
[1, 2, 250],
|
|
||||||
[2, 0, 190],
|
|
||||||
[2, 1, 160],
|
|
||||||
[2, 2, 200],
|
|
||||||
[3, 0, 140],
|
|
||||||
[3, 1, 210],
|
|
||||||
[3, 2, 180],
|
|
||||||
[4, 0, 170],
|
|
||||||
[4, 1, 200],
|
|
||||||
[4, 2, 230],
|
|
||||||
[5, 0, 120],
|
|
||||||
[5, 1, 180],
|
|
||||||
[5, 2, 160],
|
|
||||||
[6, 0, 250],
|
|
||||||
[6, 1, 170],
|
|
||||||
[6, 2, 190]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 销售表现雷达图:展示各类型液化气瓶的销售表现
|
|
||||||
const radarOption = ref({
|
|
||||||
title: {
|
|
||||||
text: '液化气产品销售表现',
|
|
||||||
left: 'center'
|
|
||||||
},
|
|
||||||
tooltip: {},
|
|
||||||
radar: {
|
|
||||||
indicator: [
|
|
||||||
{ name: '液化气瓶A', max: 300 },
|
|
||||||
{ name: '液化气瓶B', max: 300 },
|
|
||||||
{ name: '液化气瓶C', max: 300 },
|
|
||||||
{ name: '液化气瓶D', max: 300 },
|
|
||||||
{ name: '液化气瓶E', max: 300 },
|
|
||||||
{ name: '液化气瓶F', max: 300 },
|
|
||||||
{ name: '液化气瓶G', max: 300 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: '销售表现',
|
|
||||||
type: 'radar',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶A').map(item => item.sales),
|
|
||||||
name: '液化气瓶A'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶B').map(item => item.sales),
|
|
||||||
name: '液化气瓶B'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶C').map(item => item.sales),
|
|
||||||
name: '液化气瓶C'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶D').map(item => item.sales),
|
|
||||||
name: '液化气瓶D'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶E').map(item => item.sales),
|
|
||||||
name: '液化气瓶E'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶F').map(item => item.sales),
|
|
||||||
name: '液化气瓶F'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: productSalesData.value.filter(item => item.type === '液化气瓶G').map(item => item.sales),
|
|
||||||
name: '液化气瓶G'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.chart {
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,15 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 销售额与毛利对比 -->
|
<!-- 销售情况按地区分布 -->
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<v-chart class="chart" :option="salesMarginOption" ref="salesMarginChart" autoresize />
|
<v-chart class="chart" :option="salesByRegionOption" autoresize />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- 线上与线下收入对比 -->
|
<!-- 地区需求对比 -->
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<v-chart class="chart" :option="revenueSourceOption" ref="revenueSourceChart" autoresize />
|
<v-chart class="chart" :option="demandByRegionOption" autoresize />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 客户购买频次分析 -->
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<v-chart class="chart" :option="mapOption" autoresize />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
@ -17,100 +24,92 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import VChart from 'vue-echarts'
|
import VChart from 'vue-echarts'
|
||||||
import { LineChart, BarChart } from 'echarts/charts'
|
import { BarChart, PieChart, LineChart } from 'echarts/charts'
|
||||||
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent } from 'echarts/components'
|
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { use } from 'echarts/core'
|
import { use } from 'echarts/core'
|
||||||
|
|
||||||
// 引入并使用图表组件
|
// 引入并使用图表组件
|
||||||
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent])
|
use([CanvasRenderer, BarChart, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||||
|
|
||||||
// 模拟数据
|
// 数据模拟:各地区销售、需求、频次等数据
|
||||||
const salesData = ref([
|
const regionSalesData = ref([
|
||||||
{ date: '2024-01-01', salesAmount: 5000, profit: 1200, onlineRevenue: 2500, offlineRevenue: 2500 },
|
{ region: '华北', sales: 1500, demand: 2000, frequency: 30 },
|
||||||
{ date: '2024-01-02', salesAmount: 6000, profit: 1500, onlineRevenue: 3000, offlineRevenue: 3000 },
|
{ region: '华东', sales: 2500, demand: 3000, frequency: 40 },
|
||||||
{ date: '2024-01-03', salesAmount: 4000, profit: 1000, onlineRevenue: 2000, offlineRevenue: 2000 },
|
{ region: '华南', sales: 1200, demand: 1500, frequency: 25 },
|
||||||
{ date: '2024-01-04', salesAmount: 7000, profit: 1800, onlineRevenue: 3500, offlineRevenue: 3500 },
|
{ region: '西南', sales: 800, demand: 1000, frequency: 15 },
|
||||||
{ date: '2024-01-05', salesAmount: 5500, profit: 1300, onlineRevenue: 2700, offlineRevenue: 2800 },
|
{ region: '西北', sales: 600, demand: 900, frequency: 10 },
|
||||||
// 更多数据...
|
{ region: '东北', sales: 900, demand: 1100, frequency: 20 },
|
||||||
|
{ region: '华中', sales: 1800, demand: 2200, frequency: 35 },
|
||||||
|
{ region: '西部', sales: 700, demand: 900, frequency: 18 },
|
||||||
|
{ region: '东南', sales: 2200, demand: 2700, frequency: 50 }
|
||||||
])
|
])
|
||||||
|
|
||||||
// 销售额与毛利对比(折线图和柱状图结合)
|
// 销售情况按地区分布(柱状图)
|
||||||
const salesMarginOption = ref({
|
const salesByRegionOption = ref({
|
||||||
title: { text: '销售额与毛利对比' },
|
title: { text: '各地区销售情况' },
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: { trigger: 'axis' },
|
||||||
legend: { data: ['销售额', '毛利'] },
|
legend: { data: ['销售量'] },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: salesData.value.map(item => item.date)
|
data: regionSalesData.value.map(item => item.region)
|
||||||
},
|
},
|
||||||
yAxis: { type: 'value' },
|
yAxis: { type: 'value' },
|
||||||
toolbox: {
|
|
||||||
show: true,
|
|
||||||
feature: {
|
|
||||||
magicType: { show: true, type: ['line', 'bar'] },
|
|
||||||
saveAsImage: {
|
|
||||||
show: true,
|
|
||||||
title: '保存为图片',
|
|
||||||
type: 'png',
|
|
||||||
pixelRatio: 2, // 控制图片清晰度
|
|
||||||
backgroundColor: '#ffffff' // 背景色设置为白色,避免透明背景
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '销售额',
|
name: '销售量',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: salesData.value.map(item => item.salesAmount),
|
data: regionSalesData.value.map(item => item.sales)
|
||||||
itemStyle: { color: 'rgba(255, 127, 80, 0.6)' } // 显色不明显
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '毛利',
|
|
||||||
type: 'line',
|
|
||||||
data: salesData.value.map(item => item.profit),
|
|
||||||
itemStyle: { color: 'rgba(135, 206, 250, 0.6)' }, // 显色不明显
|
|
||||||
emphasis: { itemStyle: { color: '#87cefa' } }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 线上与线下收入对比(柱状图)
|
// 地区需求对比(折线图)
|
||||||
const revenueSourceOption = ref({
|
const demandByRegionOption = ref({
|
||||||
title: { text: '线上与线下收入对比' },
|
title: { text: '各地区需求对比' },
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: { trigger: 'axis' },
|
||||||
legend: { data: ['线上收入', '线下收入'] },
|
legend: { data: ['需求量'] },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: salesData.value.map(item => item.date)
|
data: regionSalesData.value.map(item => item.region)
|
||||||
},
|
},
|
||||||
yAxis: { type: 'value' },
|
yAxis: { type: 'value' },
|
||||||
toolbox: {
|
series: [
|
||||||
show: true,
|
{
|
||||||
feature: {
|
name: '需求量',
|
||||||
magicType: { show: true, type: ['line', 'bar'] },
|
type: 'line',
|
||||||
saveAsImage: {
|
data: regionSalesData.value.map(item => item.demand)
|
||||||
show: true,
|
|
||||||
title: '保存为图片',
|
|
||||||
type: 'png',
|
|
||||||
pixelRatio: 2, // 控制图片清晰度
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 客户购买频次分析(饼图)
|
||||||
|
const purchaseFrequencyOption = ref({
|
||||||
|
title: {
|
||||||
|
text: '各地区客户购买频次分析',
|
||||||
|
left: 'center' // 将标题居中
|
||||||
|
},
|
||||||
|
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
left: 'left',
|
||||||
|
data: regionSalesData.value.map(item => item.region)
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '线上收入',
|
name: '购买频次',
|
||||||
type: 'bar',
|
type: 'pie',
|
||||||
data: salesData.value.map(item => item.onlineRevenue),
|
radius: '55%',
|
||||||
itemStyle: { color: 'rgba(50, 205, 50, 0.6)' } // 显色不明显
|
center: ['50%', '60%'],
|
||||||
},
|
data: regionSalesData.value.map(item => ({ value: item.frequency, name: item.region })),
|
||||||
{
|
emphasis: {
|
||||||
name: '线下收入',
|
itemStyle: {
|
||||||
type: 'bar',
|
shadowBlur: 10,
|
||||||
data: salesData.value.map(item => item.offlineRevenue),
|
shadowOffsetX: 0,
|
||||||
itemStyle: { color: 'rgba(255, 99, 71, 0.6)' } // 显色不明显
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
188
ui/src/pages/admin/waring.vue
Normal file
188
ui/src/pages/admin/waring.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fraud-detection-container">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<h2>欺诈检测预警系统</h2>
|
||||||
|
<!-- 筛选条件 -->
|
||||||
|
<el-row :gutter="20" class="filter-row">
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-select v-model="state.status" placeholder="交易状态">
|
||||||
|
<el-option label="未处理" :value=0 />
|
||||||
|
<el-option label="已处理" :value=1 />
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-button type="primary" @click="fetchData">筛选</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
|
||||||
|
<el-row :gutter="20" class="chart-row">
|
||||||
|
<!-- <el-col :span="24">-->
|
||||||
|
<!-- <v-chart class="chart" :option="pieChartOption" autoresize />-->
|
||||||
|
<!-- </el-col>-->
|
||||||
|
<el-col :span="24">
|
||||||
|
<v-chart class="chart" :option="barChartOption" autoresize />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<el-table :data="state.getList" v-loading="loading">
|
||||||
|
<el-table-column prop="transaction_id" label="交易ID" />
|
||||||
|
<el-table-column prop="user_name" label="姓名" />
|
||||||
|
<el-table-column prop="mobile" label="联系方式" />
|
||||||
|
<el-table-column prop="transaction_amount" label="交易金额" />
|
||||||
|
<el-table-column prop="transaction_time" label="交易时间" width="200" />
|
||||||
|
<el-table-column prop="transaction_location" label="交易城市" />
|
||||||
|
<el-table-column prop="status" label="处理状态" >
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.status" type="success">已处理</el-tag>
|
||||||
|
<el-tag v-else type="warning">未处理</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button v-if="scope.row.status ==0" size="small" @click="markAsProcessed(scope.row)">标记为已处理</el-button>
|
||||||
|
<!-- <el-button size="small" type="danger" @click="deleteTransaction(scope.row)">删除</el-button>-->
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页控件 -->
|
||||||
|
<!-- 分页控件 -->
|
||||||
|
<el-pagination
|
||||||
|
v-if="state.totalCount > 0"
|
||||||
|
:current-page="state.page"
|
||||||
|
:page-size="state.pageSize"
|
||||||
|
:total="state.totalCount"
|
||||||
|
layout="total, prev, pager, jumper"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref, onMounted, reactive} from 'vue'
|
||||||
|
import VChart from 'vue-echarts'
|
||||||
|
import { PieChart, BarChart } from 'echarts/charts'
|
||||||
|
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
// 引入并使用图表组件
|
||||||
|
use([CanvasRenderer, PieChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dialogVisible: false,
|
||||||
|
getList: [],
|
||||||
|
totalCount: 0, // 总记录数
|
||||||
|
page: 1, // 当前页码
|
||||||
|
pageSize: 10, // 每页显示的记录数
|
||||||
|
transactionStatus:1,
|
||||||
|
status:1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
const res = await adminRequest.get('/api/transactions', {
|
||||||
|
params: {
|
||||||
|
page: state.page,
|
||||||
|
page_size: state.pageSize,
|
||||||
|
transactionStatus: state.transactionStatus,
|
||||||
|
status: state.status,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
state.getList = res.data.data
|
||||||
|
state.totalCount = res.data.page.total_count
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('获取交易记录失败')
|
||||||
|
console.error('Error fetching transactions:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
// 柱状图配置
|
||||||
|
const barChartOption = ref({
|
||||||
|
title: { text: '已欺诈交易趋势' },
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '欺诈交易',
|
||||||
|
type: 'bar',
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为已处理
|
||||||
|
const markAsProcessed = async (row) => {
|
||||||
|
try {
|
||||||
|
await adminRequest.put(`/api/utransactions/${row.transaction_id}`)
|
||||||
|
init()
|
||||||
|
ElMessage.success("修改成功")
|
||||||
|
} catch (error) {
|
||||||
|
console.error('标记失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页码改变时的处理函数
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
state.page = page
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
// 初始化数据
|
||||||
|
onMounted(async () => {
|
||||||
|
init()
|
||||||
|
fetchData()
|
||||||
|
try {
|
||||||
|
// 调用接口获取数据
|
||||||
|
const response = await adminRequest.post('/api/mysql', {
|
||||||
|
sql: "SELECT transaction_location name, count(*) value FROM fraud_detection_ml.tb_financial_transactions GROUP BY transaction_location"
|
||||||
|
})
|
||||||
|
// 更新折线图配置
|
||||||
|
barChartOption.value.xAxis.data = response.map(item => item.name)
|
||||||
|
barChartOption.value.series[0].data = response.map(item => item.value)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false // 关闭加载状态
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fraud-detection-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@ -12,5 +11,4 @@ onMounted(()=>{
|
|||||||
router.push("/admin")
|
router.push("/admin")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -17,20 +17,21 @@ export const getAdminList = () => {
|
|||||||
"icon": "House",
|
"icon": "House",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/admin/view1",
|
"path": "/admin/data",
|
||||||
"name": "交易记录管理",
|
|
||||||
"icon": "DataAnalysis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/admin/view2",
|
|
||||||
"name": "欺诈检测预警",
|
|
||||||
"icon": "DataAnalysis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/admin/view3",
|
|
||||||
"name": "数据管理",
|
"name": "数据管理",
|
||||||
"icon": "DataAnalysis",
|
"icon": "DataAnalysis",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "/admin/waring",
|
||||||
|
"name": "欺诈检测预警",
|
||||||
|
"icon": "DataAnalysis",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/admin/view",
|
||||||
|
"name": "交易数据分析",
|
||||||
|
"icon": "DataAnalysis",
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user