【Flask】測試平臺開發,數據看板開發-第二十一篇

概述:

在前面我們已經實現了我們的產品創建管理,應用管理管理,需求提測管理但是每周提測了多少需求,創建了哪些產品,我們是不是看著不是很直觀,接下來我們就需要開發一個數據看板功能,實現能夠看到產品下創建的需求,提測數據

先看看實現效果

后端接口源碼:

# -*- coding:utf-8 -*-
# application.py
import datetime
import tracebackfrom flask import Blueprint, current_app
from dbutils.pooled_db import PooledDB
from apis.product import connectDB
from configs import config, format
from flask import request
import pymysql.cursors
import json
# from utils.jwt import login_required# 使用數據庫連接池的方式鏈接數據庫,提高資源利用率
pool = PooledDB(pymysql, mincached=2, maxcached=5, host=config.MYSQL_HOST, port=config.MYSQL_PORT,user=config.MYSQL_USER, passwd=config.MYSQL_PASSWORD, database=config.MYSQL_DATABASE,cursorclass=pymysql.cursors.DictCursor)test_dashboard = Blueprint("test_dashboard", __name__)@test_dashboard.route("/api/dashboard/stacked", methods=['POST'])
def get_request_stacked():connection = Nonetry:connection = pool.connection()with connection.cursor() as cursor:sql_select = '''SELECT DATE_FORMAT(request.createDate,"%Y%u") weeks, apps.note, COUNT(apps.id) counts FROM request LEFT JOIN apps ON request.appId = apps.id GROUP BY weeks, apps.note;'''cursor.execute(sql_select)table_data = cursor.fetchall()  # 數據庫返回的結果,包含 note 為 NULL 的行# === 核心修復:處理 NULL 值 ===weeks = []notes = []key_value = {}for row in table_data:# 1. 處理 weeks 可能為 NULL 的情況(如 createDate 為 NULL 時)week = row['weeks'] or 'No_Week'  # 轉為默認字符串# 2. 處理 note 為 NULL 的情況(關鍵修復!)note = row['note'] or 'No_App'    # 將 NULL 轉為 'Unknown_App'counts = row['counts'] or 0            # 確保 counts 不為 NULL# 后續邏輯保持不變,但使用處理后的 week 和 noteif week not in weeks:weeks.append(week)if note not in notes:notes.append(note)# 使用處理后的 week 和 note 拼接鍵名,避免 NULL 導致的 TypeErrorkey_value[f"{week}_{note}"] = counts  # 建議用下劃線分隔,避免歧義(如 week=202534, note=23 變為 20253423)weeks.sort()  # 排序周數# 生成 series 數據(保持不變,但 note 已無 NULL)series = {}for note in notes:series[note] = []for week in weeks:# 使用處理后的鍵名(帶下劃線)series[note].append(key_value.get(f"{week}_{note}", 0))resp_data = {'weeks': weeks,'notes': notes,  # 已包含處理后的 'Unknown_App''series': series}resp = format.resp_format_successresp['data'] = resp_datareturn respexcept Exception as e:# current_app.logger.error(f"Error in get_request_stacked: {str(e)}")  # 記錄錯誤日志,方便調試resp = format.resp_format_errorresp['message'] = "Failed to process stacked data"return resp, 500finally:if connection:connection.close()  # 釋放連接from datetime import datetime  # 正確的導入方式
@test_dashboard.route("/api/dashboard/metadata", methods=['POST'])
def get_request_stacked_metadata():connection = Nonetry:# === 1. 解析請求體 ===if not request.data:return {"code": 40001, "message": "Request body is empty", "data": [], "total": 0}, 400body = request.get_json()if body is None:return {"code": 40002, "message": "Invalid JSON format", "data": [], "total": 0}, 400current_app.logger.info(f"Request body: {body}")date_range = body.get('date', [])start_date_param = body.get('start_date')end_date_param = body.get('end_date')# 初始化變量start_date = Noneend_date = Nonevalid = False# === 2. 日期參數處理 ===if date_range and len(date_range) == 2:start_str, end_str = date_range[0], date_range[1]date_format = '%Y-%m-%d %H:%M:%S'try:# 使用正確的 datetime.datetime.strptimedatetime.strptime(start_str, date_format)datetime.strptime(end_str, date_format)if start_str <= end_str:start_date = start_strend_date = end_strvalid = Truecurrent_app.logger.info(f"Valid date range: {start_date} to {end_date}")except ValueError:current_app.logger.warning("Invalid date format in date_range")valid = Falseelif start_date_param and end_date_param:date_format = '%Y-%m-%d %H:%M:%S'try:datetime.strptime(start_date_param, date_format)datetime.strptime(end_date_param, date_format)if start_date_param <= end_date_param:start_date = start_date_paramend_date = end_date_paramvalid = Truecurrent_app.logger.info(f"Valid date params: {start_date} to {end_date}")except ValueError:current_app.logger.warning("Invalid date format in start_date/end_date")valid = Falseelse:current_app.logger.info("No date filter applied, querying all data")# === 3. 構建SQL查詢 ===connection = pool.connection()with connection.cursor() as cursor:# 臨時禁用ONLY_FULL_GROUP_BYtry:cursor.execute("SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''))")except Exception as mode_error:current_app.logger.warning(f"Could not modify SQL mode: {mode_error}")if valid and start_date and end_date:# 帶日期過濾的查詢sql = """SELECT CONCAT(YEAR(r.createDate), '-', LPAD(WEEK(r.createDate), 2, '0')) as week_key, \COALESCE(a.note, 'No_App')                                   AS app_name, \COUNT(*)                                                          as request_countFROM request rLEFT JOIN apps a ON r.appId = a.idWHERE r.createDate BETWEEN %s AND %sGROUP BY YEAR (r.createDate), WEEK(r.createDate), a.noteORDER BY week_key, app_name \"""current_app.logger.info(f"Executing filtered query: {start_date} to {end_date}")cursor.execute(sql, (start_date, end_date))else:# 查詢所有數據的查詢sql = """SELECT CONCAT(YEAR(r.createDate), '-', LPAD(WEEK(r.createDate), 2, '0')) as week_key, \COALESCE(a.note, 'No_App')                                   AS app_name, \COUNT(*)                                                          as request_countFROM request rLEFT JOIN apps a ON r.appId = a.idGROUP BY YEAR (r.createDate), WEEK(r.createDate), a.noteORDER BY week_key, app_name \"""current_app.logger.info("Executing full data query")cursor.execute(sql)table_data = cursor.fetchall()current_app.logger.info(f"Query returned {len(table_data)} rows")# === 4. 處理返回數據 ===cleaned_data = []for row in table_data:cleaned_data.append({'weeks': row.get('week_key', 'No_Week'),'note': row.get('app_name', 'No_App'),'counts': row.get('request_count', 0)})# 成功響應return {"code": 20000,"message": "success","data": cleaned_data,"total": len(cleaned_data)}except json.JSONDecodeError:return {"code": 40000, "message": "Invalid JSON format", "data": [], "total": 0}, 400except Exception as e:current_app.logger.error(f"Metadata API Error:\n"f"Error: {str(e)}\n"f"Stacktrace: {traceback.format_exc()}")return {"code": 50000,"message": "Internal server error","data": [],"total": 0}, 500finally:if connection:try:connection.close()except Exception:pass

前端圖標部分主要分別是 Echats G2Plot 組件,從個人使用上來講前者應用更廣、自定義開發更靈活,后者使用上更簡單尤其是在數據綁的格式和方式上更友好,在我們使用 Element vue admin 集成分支項目中有關圖表的例子基礎就是Echats,比如其中的混合圖表(柱形+折線)

對應源代碼中代碼位置依據可從 /views/chats 看到導入的是 echats 也就是說此組件的使用方式,同樣是通過添加依賴和導入使用。

結合提測平臺的后臺數據,接下來就體驗下 Echarts 的圖表的如何使用。

VUE項目使用步驟

步驟一:項目進行依賴安裝

npm install echarts --save

執行完成后可以在 package.json 的 dependencies 配置項目看到 "echarts": "^5.2.2"引依賴被添加。

步驟二:頁面添加組件引用和定義一個容器

<template><div class="app-container"><div ref="pieChartDemo" style="width: 600px;height:400px;"></div></div>
</template><script>
import * as echarts from 'echarts'
...
</script>

步驟三:使用 echarts.init 方法初始化一個 echarts 實例并通過setOption方法生成一個簡單餅圖,余下配置代碼如下(注意查看幾處注解說明):

export default {name: 'EchartsDemo',// 使用mounted在頁面控件加載在完成后mounted方法進行echart初始化非createdmounted() {this.initPieChart()},methods: {initPieChart() {// 采用的是vue ref的方式獲取容器var chartDom = this.$refs['pieChartDemo']var myChart = echarts.init(chartDom)var option = {title: {text: '測試開發',subtext: '文章類型分布',left: 'center'},tooltip: {trigger: 'item'},legend: {orient: 'vertical',left: 'left'},series: [{name: 'Access From',type: 'pie',radius: '50%',data: [{ value: 20, name: '提測平臺' },{ value: 2, name: '性能測試' },{ value: 1, name: '流量' },{ value: 3, name: '分享' },{ value: 5, name: '其他' }]}]}option && myChart.setOption(option);}}
}

堆疊面積圖

在掌握了Echar表的渲染方法和接口數據格式化的充分準備之后,就可以直接上在 src/views/dashboard/index.vue 編寫代碼,注意實現里有個額外的 series 數據處理,已經標注在代碼注解里了。

<template><div class="dashboard-container"><div ref="LineChartBoard" style="width: 95%;height:500px;"></div></div>
</template><script>
import * as echarts from 'echarts'
import { requestStacked } from '@/api/board'export default {name: 'Dashboard',mounted() {this.getApList()},methods: {getApList() {requestStacked().then(resp => {this.initStackedChart(resp.data)})},initStackedChart(data) {const chartDom = this.$refs['LineChartBoard']const myChart = echarts.init(chartDom)const series = []// 唯一處理需要額外邏輯處理的地方,根據接口數據動態生成series數據for (var key in data.series) {series.push({name: key,type: 'line',stack: 'Total',areaStyle: {},emphasis: {focus: 'series'},data: data.series[key]})}var option = {title: {text: '周需求提測趨勢'},tooltip: {trigger: 'axis',axisPointer: {type: 'cross',label: {backgroundColor: '#6a7985'}}},legend: {// 數據標題展示data: data.note},toolbox: {feature: {saveAsImage: {}}},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: [{type: 'category',boundaryGap: false,data: data.weeks}],yAxis: [{type: 'value'}],series: series}option && myChart.setOption(option)}}
}
</script>

完整的vue前端源碼

<template><div class="dashboard-container"><div class="filter-container"><el-form :inline="true" :model="searchValue"><el-form-item label="日期選擇"><el-date-pickerv-model="searchValue.date"type="daterange"value-format="yyyy-MM-dd HH:mm:ss"range-separator="至"start-placeholder="開始日期"end-placeholder="結束日期"></el-date-picker></el-form-item><el-form-item><el-button type="primary" @click="searchBoard">刷新查詢</el-button></el-form-item><el-form-item><el-switchv-model="stackedColumnMode"@change="changeBoardMode"active-text="分組模式"inactive-text="累積模式"></el-switch></el-form-item></el-form></div><el-card class="box-card"><div slot="header" class="clearfix"><span>周需求分組量</span></div><div id="ColumnBoard" style="width: 95%;height:360px;" /></el-card><br><el-card class="box-card"><div ref="LineChartBoard" style="width: 95%;height:500px;" /></el-card></div>
</template><script>
import * as echarts from 'echarts'
import { Column } from '@antv/g2plot'import { requestStacked, requestMetaData } from '@/api/board'export default {name: 'Dashboard',created() {this.getAppList()this.getMetaDate()},mounted() {this.stackedColumnPlot = new Column('ColumnBoard', {data: this.stackedColumnData,xField: 'weeks',yField: 'counts',seriesField: 'note',isGroup: this.stackedColumnMode ? 'true' : 'false',columnStyle: {radius: [20, 20, 0, 0]}})this.stackedColumnPlot.render()},data() {return {stackedColumnPlot: undefined,stackedColumnData: [],stackedColumnMode: true,searchValue: {date: []}}},methods: {getAppList() {requestStacked().then(resp => {this.initStackedChart(resp.data)})},getMetaDate() {const params = {date: this.searchValue.date}requestMetaData(params).then(resp => {this.stackedColumnData = resp.datathis.stackedColumnPlot.changeData(this.stackedColumnData)this.initStackedColumn(resp.data)})},// initStackedColumn(data) {//   const stackedColumnPlot = new Column('ColumnBoard', {//     data,//     xField: 'weeks',//     yField: 'counts',//     seriesField: 'note',//     isGroup: 'true',//     columnStyle: {//       radius: [20, 20, 0, 0]//     }//   })//   stackedColumnPlot.render()// },initStackedChart(data) {const chartDom = this.$refs['LineChartBoard']const myChart = echarts.init(chartDom)const series = []// 唯一處理需要額外邏輯處理的地方,根據接口數據動態生成series數據for (var key in data.series) {series.push({name: key,type: 'line',stack: 'Total',areaStyle: {},emphasis: {focus: 'series'},data: data.series[key]})}var option = {title: {text: '周需求提測趨勢'},tooltip: {trigger: 'axis',axisPointer: {type: 'cross',label: {backgroundColor: '#6a7985'}}},legend: {data: data.note},toolbox: {feature: {saveAsImage: {}}},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: [{type: 'category',boundaryGap: false,data: data.weeks}],yAxis: [{type: 'value'}],series: series}option && myChart.setOption(option)},searchBoard() {this.getMetaDate()},// 更改顯示類型changeBoardMode() {const options = {isGroup: this.stackedColumnMode}this.stackedColumnPlot.update(options)}}
}
</script><style lang="scss" scoped>
.dashboard {&-container {margin: 30px;}&-text {font-size: 30px;line-height: 46px;}
}
</style>

最終實現后就是我們一開始截圖后的實現效果

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96133.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96133.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96133.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我是程序員,不是程序猿:請別把我當猴耍——拒絕被低估,用專業贏得尊重

摘要 本文旨在深度剖析“程序員”與“程序猿”一字之差背后所反映的職業尊嚴與身份認同問題。我們生活在一個技術驅動的時代&#xff0c;但對技術創造者的認知卻常常被“程序猿”、“碼農”等標簽簡單化、甚至矮化。本文將從正名開始&#xff0c;辨析“程序員”的專業內涵&…

C++中vector刪除操作的安全隱患與最佳實踐

std::vector 是C標準模板庫&#xff08;STL&#xff09;中最常用的動態數組容器&#xff0c;提供了高效的隨機訪問和動態擴容能力。然而&#xff0c;其刪除操作如果使用不當&#xff0c;會引入嚴重的安全隱患&#xff0c;包括未定義行為、內存泄漏和數據競爭等問題。本文將深入…

Unix/Linux 系統中的 `writev` 系統調用

<摘要> 本文對 Unix/Linux 系統中的 writev 系統調用進行了全面深入的解析。內容涵蓋了其產生的背景&#xff08;從傳統 write 的局限性到分散/聚集 I/O 概念的引入&#xff09;、核心概念&#xff08;如 struct iovec、系統調用流程&#xff09;。重點剖析了其設計意圖&…

深入理解 Android targetSdkVersion:從 Google Play 政策到依賴沖突

深入理解 Android targetSdkVersion&#xff1a;從 Google Play 政策到依賴沖突 作為 Android 開發者&#xff0c;你很可能在 Android Studio 中見過這條提示&#xff1a;Google Play requires that apps target API level 33 or higher。它像一個盡職的提醒者&#xff0c;時常…

灰匣(GrayBox)1.0.0 發布【提升系統權限APP】

灰匣是一個提升系統權限的工具&#xff0c;可以配合Root、三方軟件&#xff08;Shizuku&#xff09;以及【設備管理員】&#xff08;設備所有者&#xff09;實現一些高級功能及底層接口&#xff0c;可以自動隔離&#xff08;凍結/禁用&#xff09;不必要的應用&#xff0c;如某…

PAT 1104 Sum of Number Segments

這一題的大意就是找一個數組中的所有子數組&#xff0c;它們的累加和為多少&#xff0c; 題目上給出的數據范圍是O(n^5)那么只能遍歷一次&#xff0c;不能用暴力的方法求出。 看到這一題我有兩個思路&#xff1a; 1.試圖用雙指針和滑動窗口來把O&#xff08;n^2)的時間復雜度降…

[萬字長文]AJAX入門-常用請求方法和數據提交、HTTP協議-報文、接口文檔、案例實戰

本系列可作為前端學習系列的筆記&#xff0c;代碼的運行環境是在VS code中&#xff0c;小編會將代碼復制下來&#xff0c;大家復制下來就可以練習了&#xff0c;方便大家學習。 HTML、CSS、JavaScript系列文章 已經收錄在前端專欄&#xff0c;有需要的寶寶們可以點擊前端專欄查…

Codesy中的UDP發送信息

Codesy UDP通訊 概述 CAA Net Base Services UDP通訊的建立 發送UDP 狀態控制 效果 概述 Codesys中默認安裝的通訊支持很多,不安裝其他的軟件也可以實現TCP通訊。但是,在使用UDP通訊時,因為我們的PLC有兩個網卡,一般我們把第一個網口做編程和HMI用,把的個網口做外部通訊,…

神經網絡之深入理解偏置

&#x1f50d; 1. 表達能力&#xff1a;無偏模型不能表示全體函數族 ? 有偏線性變換&#xff1a; yWxb&#xff08;仿射變換&#xff09; y Wx b \quad \text{&#xff08;仿射變換&#xff09;} yWxb&#xff08;仿射變換&#xff09; 能表示任意線性函數 平移是仿射空間的…

小白必看:AI智能體零基礎搭建全攻略!

寫在前面&#xff1a;別怕&#xff0c;真的不需要技術背景&#xff01; 你是不是經常聽到"AI智能體"、"大模型"這些高大上的詞&#xff0c;總覺得那是技術大牛的專利&#xff1f;別擔心&#xff0c;這篇教程就是為你準備的&#xff01;我們將用最通俗的語…

React state在setInterval里未獲取最新值的問題

目錄 一、問題描述 二、解決方案 方案一&#xff0c;使用函數式更新 方案二&#xff0c;使用 useRef 保存最新值 一、問題描述 在 React 中&#xff0c;當在 setInterval或setTimeout 中使用 setState 時&#xff0c;經常會遇到狀態不是最新值的問題。這是因為閉包導致的&a…

x86 架構 Docker 鏡像遷移至 ARM 環境的詳細指南

目錄 一、問題背景與分析 二、解決步驟 &#xff08;一&#xff09;檢查 docker-compose 版本 &#xff08;二&#xff09;升級 docker-compose 1. 對于 Linux 系統 2. 對于 Windows 系統 &#xff08;三&#xff09;驗證升級 &#xff08;四&#xff09;重新運行 dock…

零代碼部署工業數據平臺:TRAE + TDengine IDMP 實踐

對于編程初學者來說&#xff0c;軟件開發流程中的開發環境配置、安裝異常或報錯往往需要花費大量時間查閱資料和反復試錯&#xff0c;才能正常安裝和啟動某些軟件工具。現在&#xff0c;在 TRAE 的幫助下&#xff0c;即使完全沒有接觸過編程&#xff0c;也能通過自然語言直接表…

史上最全Flink面試題(完整版)

1、簡單介紹一下 FlinkFlink 是一個框架和分布式處理引擎&#xff0c;用于對無界和有界數據流進行有狀態計算。并且 Flink 提供了數據分布、容錯機制以及資源管理等核心功能。Flink提供了諸多高抽象層的API以便用戶編寫分布式任務&#xff1a;DataSet API&#xff0c; 對靜態數…

C# .NET中使用log4Net日志框架指南

C# .NET中使用log4Net日志框架指南 log4Net是Apache基金會開發的一款高效、靈活的日志記錄框架&#xff0c;廣泛應用于.NET生態系統中。它支持多種日志輸出目標&#xff08;如文件、數據庫、控制臺&#xff09;&#xff0c;并提供細粒度的日志級別控制&#xff0c;幫助開發者監…

每日算法刷題Day68:9.10:leetcode 最短路6道題,用時2h30min

一. 單源最短路&#xff1a;Dijkstra 算法 1.套路 1.Dijkstra 算法介紹 (1)定義 g[i][j] 表示節點 i 到節點 j 這條邊的邊權。如果沒有 i 到 j 的邊&#xff0c;則 g[i][j]∞。 (2)定義 dis[i] 表示起點 k 到節點 i 的最短路長度&#xff0c;一開始 dis[k]0&#xff0c;其余 …

Spring Boot + Apache Tika 從文件或文件流中提取文本內容

應用效果&#xff1a;1、安裝 Apache Tika 依賴pom.xml<!-- Apache Tika 從文件中提取結構化文本和元數據 --><dependency><groupId>org.apache.tika</groupId><artifactId>tika-core</artifactId><version>2.9.2</version>&l…

qqq數據結構補充

1.緒論1.存儲方式順序存儲&#xff1a;邏輯相鄰&#xff0c;物理相鄰鏈式存儲&#xff1a;邏輯相鄰&#xff0c;物理不一定相鄰2.線性表1.順序表1.不可擴容數組寫一個順序表1.在頭文件中應有#pragam once&#xff0c;防止頭文件多次編譯&#xff1b;如果頭文件多次編譯&#x…

Anaconda與Jupyter 安裝和使用

Anaconda內部集成了很多科學計算包&#xff0c;并且可以實現環境隔離 1. 安裝Anaconda 定義&#xff1a;Anaconda是一個集成的Python發行版&#xff0c;專為數據科學、機器學習和AI開發而設計。它包含了常用的Python庫、包管理工具&#xff08;Conda&#xff09;和Jupyter No…

5.后臺運行設置和包設計與實現

程序的入口點(想讓其后臺默認.exe進程運行)也可以不通過vs設置也可以通過定義預處理設置第三種就是沒有窗口的變成后臺運行的了 處理client傳來的數據包 第一步&#xff1a;咱們怎么設計一種包呢&#xff1f;FEFF在網絡環境里面出現的概率低所以就采用這個 自己數據包截斷了&am…