上传全量

This commit is contained in:
闵宪瑞 2025-02-26 22:25:44 +08:00
parent aeda78b8dc
commit f007460b1e
13 changed files with 631 additions and 476 deletions

View File

@ -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

View File

@ -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'),

View File

@ -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
View 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')

View File

@ -78,10 +78,8 @@ const state = reactive({
* 退出登录 * 退出登录
*/ */
const logout = () => { const logout = () => {
logoutAdmin().then(()=>{ toast.success("退出成功~")
toast.success("退出成功~")
router.push('/login'); router.push('/login');
})
} }
/** /**

View File

@ -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;

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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)'
}
}
} }
] ]
}) })

View 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>

View File

@ -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>

View File

@ -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;
} }