当前位置:首页>python>从零开始:用Python打造A股股票K线图网站(完整代码)

从零开始:用Python打造A股股票K线图网站(完整代码)

  • 2026-09-08 14:28:23
从零开始:用Python打造A股股票K线图网站(完整代码)

风险提示:本文仅用于学习编程实战,不构成任何投资建议。

接下来,我们将从零起步,一步步搭建一个功能完整的A股K线图网站。所有代码均已实测通过,复制即可运行。 完整源代码:https://github.com/ICodeWR/kline


准备工作

1. 环境要求

  • Python 3.8+
  • 推荐使用虚拟环境

2. 安装依赖包

pip install -r requirements.txt

3. 数据源说明

本软件支持两种数据源:(二选一即可)

数据源
特点
是否需要注册
akshare
(默认)
开源免费,无需注册,开箱即用
否
tushare
需注册获取token,数据更全
是
  • akshare:无需任何配置,安装后直接可用
  • tushare:访问 tushare.pro 注册账号,在"个人主页"获取token,通过环境变量 TUSHARE_TOKEN 配置

项目结构

kline/├── app.py                  # Flask 主应用├── stock_data.py           # 股票数据获取模块├── requirements.txt        # 项目依赖├── test_app.py             # Flask 应用单元测试├── test_stock_data.py      # 数据模块单元测试├── .gitignore              # Git 忽略规则├── LICENSE                 # MIT 开源许可证├── README.md               # 项目说明文档├── CONTRIBUTING.md         # 贡献指南├── CHANGELOG.md            # 更新日志├── templates/│   ├── base.html           # 基础模板(导航栏、页脚)│   ├── index.html          # 首页(搜索表单 + K线图)│   └── 404.html            # 404/500错误页面├── static/│   └── css/│       └── style.css       # 自定义样式└── 教程.md                 # 本教程

第一步:创建数据获取模块(stock_data.py)

stock_data.py 是数据获取核心模块,封装了所有与股票数据源的交互。主要特性:

  • 双数据源支持:优先使用 akshare(开源免费),可选 tushare
  • 自动市场识别:根据股票代码前缀自动判断沪深市场(6开头→沪市,其他→深市)
  • 股票列表缓存:避免重复请求
import tushare as tsimport pandas as pdimport akshare as akfrom datetime import datetime, timedeltafrom typing import Optional, Tuple, ListclassStockDataFetcher:"""A股股票数据获取类,支持tushare和akshare双数据源"""def__init__(self, token: Optional[str] = None):        self.pro = Noneif token:try:                ts.set_token(token)                self.pro = ts.pro_api()except Exception as e:                print(f"tushare初始化失败,将使用akshare: {e}")                self.pro = None        self._stock_cache = Nonedef_is_shanghai(self, code: str) -> bool:"""判断是否为沪市股票(6或9开头)"""return code.startswith(('6', '9'))def_get_ts_code(self, code: str) -> str:"""生成tushare格式的股票代码"""        suffix = '.SH'if self._is_shanghai(code) else'.SZ'returnf"{code}{suffix}"defget_stock_list(self) -> pd.DataFrame:"""获取A股股票列表,带缓存"""if self._stock_cache isnotNone:return self._stock_cachetry:            df = ak.stock_info_a_code_name()            df = df.rename(columns={'code': 'code', 'name': 'name'})            self._stock_cache = df[['code', 'name']]return self._stock_cacheexcept Exception:pass# 网络失败时返回常用股票备选列表        fallback = pd.DataFrame({'code': ['000001', '600000', '000002', '600036', '000858','600519', '000333', '601318', '600276', '002415'],'name': ['平安银行', '浦发银行', '万科A', '招商银行', '五粮液','贵州茅台', '美的集团', '中国平安', '恒瑞医药', '海康威视']        })        self._stock_cache = fallbackreturn fallbackdefget_historical_data(self, stock_code: str, days: int = 250) -> Tuple[List[str], List[List[float]]]:"""获取股票历史OHLC数据"""try:            end_date = datetime.now().strftime('%Y%m%d')            start_date = (datetime.now() - timedelta(days=days * 2)).strftime('%Y%m%d')            df = Noneif self.pro:try:                    symbol = self._get_ts_code(stock_code)                    df = self.pro.daily(ts_code=symbol, start_date=start_date,                                        end_date=end_date,                                        fields='trade_date,open,high,low,close')except Exception:passif df isNoneor df.empty:                df = ak.stock_zh_a_hist(symbol=stock_code, period="daily",                                        start_date=start_date, end_date=end_date,                                        adjust="qfq")                df = df.rename(columns={'日期': 'trade_date', '开盘': 'open','最高': 'high', '最低': 'low', '收盘': 'close'                })                df['trade_date'] = df['trade_date'].astype(str).str.replace('-', '')if df.empty:return [], []            df = df.sort_values('trade_date', ascending=True)            df = df.tail(days)            dates = df['trade_date'].tolist()            ohlc_data = df[['open', 'high', 'low', 'close']].values.tolist()return dates, ohlc_dataexcept Exception as e:            print(f"获取股票数据失败: {e}")return [], []defsearch_stock(self, keyword: str) -> Tuple[int, Optional[list]]:"""搜索股票,返回 (状态码, [代码, 名称])"""        stock_df = self.get_stock_list()for code, name in stock_df.values.tolist():if keyword in str(code) or keyword in str(name):return1, [str(code), str(name)]return0, None

第二步:创建Flask应用(app.py)

app.py 是Web服务的主入口,包含路由定义和K线图生成逻辑。

import osimport jsonfrom flask import Flask, render_template, request, jsonifyfrom flask_bootstrap import Bootstrapfrom pyecharts import options as optsfrom pyecharts.charts import Klinefrom stock_data import StockDataFetcherapp = Flask(__name__)bootstrap = Bootstrap(app)# 通过环境变量配置tushare token(可选),不配置则使用akshareTUSHARE_TOKEN = os.environ.get('TUSHARE_TOKEN', '')stock_fetcher = StockDataFetcher(token=TUSHARE_TOKEN if TUSHARE_TOKEN elseNone)defcreate_kline_chart(dates, data, stock_name):"""生成K线图的pyecharts配置"""ifnot dates ornot data:returnNone    kline = (        Kline(init_opts=opts.InitOpts(width="100%", height="600px"))        .add_xaxis(dates)        .add_yaxis(            series_name=stock_name,            y_axis=data,            itemstyle_opts=opts.ItemStyleOpts(                color="#ef232a",        # 阳线(涨)红色                color0="#14b143",       # 阴线(跌)绿色                border_color="#ef232a",                border_color0="#14b143",            ),            markline_opts=opts.MarkLineOpts(                data=[                    opts.MarkLineItem(type_="max", name="最高价"),                    opts.MarkLineItem(type_="min", name="最低价"),                ]            ),        )        .set_global_opts(            title_opts=opts.TitleOpts(                title=f"{stock_name} K线图",                subtitle="数据来源: akshare / tushare.pro",                pos_left="center",            ),            xaxis_opts=opts.AxisOpts(                type_="category", is_scale=True,                axislabel_opts=opts.LabelOpts(rotate=45, interval=10),            ),            yaxis_opts=opts.AxisOpts(                is_scale=True,                splitarea_opts=opts.SplitAreaOpts(                    is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=0.3)                ),                axislabel_opts=opts.LabelOpts(formatter="{value}元"),            ),            datazoom_opts=[                opts.DataZoomOpts(type_="inside", range_start=0, range_end=100),                opts.DataZoomOpts(type_="slider", range_start=0, range_end=100),            ],            tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"),            legend_opts=opts.LegendOpts(is_show=False),        )    )return kline@app.errorhandler(404)defpage_not_found(e):return render_template("404.html"), 404@app.errorhandler(500)definternal_server_error(e):return render_template("404.html", error="服务器内部错误"), 500@app.route("/")defindex():return render_template("index.html")@app.route("/api/kline", methods=["POST"])defget_kline():"""API:获取K线图数据"""try:        stock_keyword = request.form.get("stockName", "").strip()        query_days = request.form.get("queryTime", "250").strip()ifnot stock_keyword:            stock_keyword = "平安银行"try:            query_days = int(query_days)            query_days = max(10, min(query_days, 1000))except ValueError:            query_days = 250        status, stock_info = stock_fetcher.search_stock(stock_keyword)if status == 0:return jsonify({"error": f"未找到股票: {stock_keyword}"}), 404        stock_code, stock_name = stock_info        dates, ohlc_data = stock_fetcher.get_historical_data(stock_code, query_days)ifnot dates:return jsonify({"error": f"获取 {stock_name}({stock_code}) 数据失败"}), 404        kline = create_kline_chart(dates, ohlc_data, stock_name)if kline isNone:return jsonify({"error": "生成K线图失败"}), 500return jsonify(json.loads(kline.dump_options()))except Exception as e:        print(f"请求处理失败: {e}")return jsonify({"error": str(e)}), 500@app.route("/api/suggest", methods=["GET"])defget_suggestions():"""API:股票名称自动补全"""    keyword = request.args.get("keyword", "").strip()ifnot keyword:return jsonify([])try:        stock_df = stock_fetcher.get_stock_list()        suggestions = []for _, row in stock_df.iterrows():            code = str(row['code'])            name = str(row['name'])if keyword in code or keyword in name:                suggestions.append({"code": code, "name": name, "display": f"{code} - {name}"})if len(suggestions) >= 10:breakreturn jsonify(suggestions)except Exception as e:        print(f"获取建议失败: {e}")return jsonify([])if __name__ == "__main__":    app.run(debug=True, host="0.0.0.0", port=5000)

第三步:创建HTML模板

1. 基础模板(templates/base.html)

基础模板包含导航栏、CDN资源引入和页脚,所有页面继承此模板。

<!DOCTYPE html><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>{% block title %}股票K线图分析系统{% endblock %}</title><!-- Bootstrap 5 CSS --><linkhref="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"rel="stylesheet"><!-- Font Awesome 图标 --><linkhref="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"rel="stylesheet"><!-- jQuery --><scriptsrc="https://code.jquery.com/jquery-3.6.0.min.js"></script><!-- ECharts --><scriptsrc="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><!-- 自定义样式 --><linkhref="{{ url_for('static', filename='css/style.css') }}"rel="stylesheet">    {% block extra_css %}{% endblock %}</head><body><navclass="navbar navbar-expand-lg"><divclass="container"><aclass="navbar-brand"href="/"><iclass="fas fa-chart-line"></i> 股票K线图分析系统</a><buttonclass="navbar-toggler"type="button"data-bs-toggle="collapse"data-bs-target="#navbarNav"><spanclass="navbar-toggler-icon"></span></button><divclass="collapse navbar-collapse"id="navbarNav"><ulclass="navbar-nav ms-auto"><liclass="nav-item"><aclass="nav-link"href="/"><iclass="fas fa-home"></i> 首页</a></li><liclass="nav-item"><aclass="nav-link"href="#"onclick="refreshChart()"><iclass="fas fa-sync"></i> 刷新</a></li></ul></div></div></nav><divclass="container main-container">        {% block content %}{% endblock %}</div><divclass="footer"><p>数据来源: akshare / tushare.pro | 仅供学习参考,不构成投资建议</p></div><scriptsrc="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>    {% block extra_js %}{% endblock %}</body></html>

2. 首页模板(templates/index.html)

首页包含搜索表单和K线图展示区域,集成了自动补全和图表渲染功能。

{% extends "base.html" %}{% block title %}股票K线图查询{% endblock %}{% block content %}<divclass="row"><divclass="col-12"><divclass="card"><divclass="card-header"><h5><iclass="fas fa-search"></i> 查询股票</h5></div><divclass="card-body"><formid="searchForm"onsubmit="return false;"><divclass="row g-3 align-items-end"><divclass="col-md-5"><labelfor="stockName"class="form-label">股票名称或代码</label><divclass="position-relative"><inputtype="text"class="form-control"id="stockName"placeholder="请输入股票名称或代码,如:平安银行、000001"value="平安银行"autocomplete="off"><divid="suggestions"class="list-group"style="display:none; position:absolute; z-index:1000;                                            width:100%; max-height:200px; overflow-y:auto;"></div></div></div><divclass="col-md-4"><labelfor="queryTime"class="form-label">查询天数</label><selectclass="form-select"id="queryTime"><optionvalue="30">近30天</option><optionvalue="60">近60天</option><optionvalue="120">近120天</option><optionvalue="250"selected>近250天</option><optionvalue="500">近500天</option><optionvalue="1000">近1000天</option></select></div><divclass="col-md-3"><buttontype="button"class="btn btn-primary w-100"onclick="loadKline()"><iclass="fas fa-chart-bar"></i> 查看K线图</button></div></div></form><divid="errorMessage"class="error-message"></div></div></div></div></div><divclass="row mt-4"><divclass="col-12"><divclass="card"><divclass="card-header d-flex justify-content-between align-items-center"><h5><iclass="fas fa-candlestick-chart"></i> K线图</h5><spanid="stockInfo"class="badge bg-light text-dark">加载中...</span></div><divclass="card-body"><divid="kline-container"><divclass="loading-text"><iclass="fas fa-spinner fa-spin fa-2x"></i><pclass="mt-2">加载中,请稍候...</p></div></div></div></div></div></div>{% endblock %}{% block extra_js %}<script>var chartInstance = null;$(document).ready(function() {var suggestionTimeout;    $('#stockName').on('input', function() {        clearTimeout(suggestionTimeout);var keyword = $(this).val().trim();if (keyword.length < 1) {            $('#suggestions').hide();return;        }        suggestionTimeout = setTimeout(function() {            $.ajax({url: '/api/suggest',method: 'GET',data: { keyword: keyword },success: function(data) {if (data.length > 0) {var html = '';                        data.forEach(function(item) {                            html += '<a href="#" class="list-group-item list-group-item-action suggestion-item"'                                  + ' data-code="' + item.code + '" data-name="' + item.name + '">'                                  + '<strong>' + item.code + '</strong> - ' + item.name + '</a>';                        });                        $('#suggestions').html(html).show();                    } else {                        $('#suggestions').hide();                    }                }            });        }, 300);    });    $('#suggestions').on('click', '.suggestion-item', function(e) {        e.preventDefault();        $('#stockName').val($(this).data('name'));        $('#suggestions').hide();        loadKline();    });    $(document).click(function(e) {if (!$(e.target).closest('#stockName').length && !$(e.target).closest('#suggestions').length) {            $('#suggestions').hide();        }    });    $('#stockName, #queryTime').on('keypress', function(e) {if (e.which === 13) loadKline();    });    loadKline();});functionloadKline() {var stockName = $('#stockName').val().trim() || '平安银行';var queryTime = $('#queryTime').val();    $('#kline-container').html('<div class="loading-text">'        + '<i class="fas fa-spinner fa-spin fa-2x"></i>'        + '<p class="mt-2">加载中,请稍候...</p></div>');    $('#errorMessage').hide();    $('#stockInfo').text('加载中...');if (chartInstance) { chartInstance.dispose(); chartInstance = null; }    $.ajax({url: '/api/kline',method: 'POST',data: { stockName: stockName, queryTime: queryTime },success: function(data) {if (data.error) { showError(data.error); return; }            chartInstance = echarts.init(document.getElementById('kline-container'));            chartInstance.setOption(data);            $('#stockInfo').text(                (data.title && data.title.text) ? data.title.text : stockName            );window.addEventListener('resize', function() {if (chartInstance) chartInstance.resize();            });        },error: function(xhr) {var msg = (xhr.responseJSON && xhr.responseJSON.error)                ? xhr.responseJSON.error : '查询失败,请稍后重试';            showError(msg);        }    });}functionshowError(message) {    $('#errorMessage').text(message).show();    $('#kline-container').html('<div class="loading-text" style="color:#dc3545;">'        + '<i class="fas fa-exclamation-circle fa-2x"></i>'        + '<p class="mt-2">' + message + '</p></div>');    $('#stockInfo').text('查询失败');}functionrefreshChart() { loadKline(); }</script>{% endblock %}

3. 错误页面(templates/404.html)

{% extends "base.html" %}{% block title %}页面未找到{% endblock %}{% block content %}<divclass="row"><divclass="col-12 text-center"style="padding: 100px 0;"><iclass="fas fa-exclamation-triangle"style="font-size: 80px; color: #f39c12;"></i><h1class="display-1"style="font-weight: 700;">404</h1><h3class="text-muted">抱歉!页面找不到了</h3><pclass="text-muted">您访问的页面不存在或已移动</p><ahref="/"class="btn btn-primary mt-3"><iclass="fas fa-home"></i> 返回首页</a></div></div>{% endblock %}

第四步:样式文件(static/css/style.css)

独立样式文件使代码更清晰,便于维护:

body {background-color: #f5f7fa;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Arial, sans-serif;}.navbar {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);box-shadow: 02px10pxrgba(0, 0, 0, 0.1);}.navbar-brand {font-weight: bold;color: white !important;}.navbar-nav.nav-link {color: rgba(255, 255, 255, 0.9) !important;transition: transform 0.2s;}.navbar-nav.nav-link:hover {color: white !important;transform: translateY(-1px);}.main-container {margin-top: 30px;margin-bottom: 30px;}.card {border: none;border-radius: 15px;box-shadow: 04px20pxrgba(0, 0, 0, 0.08);overflow: hidden;}.card-header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 15px25px;border-bottom: none;}.card-headerh5 {margin: 0;font-weight: 600;}.btn-primary {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);border: none;padding: 8px30px;border-radius: 25px;font-weight: 500;transition: transform 0.2s, box-shadow 0.2s;}.btn-primary:hover {transform: translateY(-2px);box-shadow: 05px15pxrgba(102, 126, 234, 0.4);}.form-control {border-radius: 10px;border: 2px solid #e8ecf1;padding: 10px15px;transition: border-color 0.2s, box-shadow 0.2s;}.form-control:focus {border-color: #667eea;box-shadow: 0000.2remrgba(102, 126, 234, 0.25);}#kline-container {width: 100%;height: 600px;background: white;border-radius: 10px;}.loading-text {text-align: center;padding: 50px;color: #999;}.error-message {color: #dc3545;padding: 10px;margin: 10px0;border-radius: 5px;background: #f8d7da;display: none;}.footer {text-align: center;padding: 20px;color: #999;font-size: 14px;}.suggestion-item {cursor: pointer;}.suggestion-item:hover {background-color: #f0f0ff;}

第五步:依赖文件(requirements.txt)

Flask>=2.2.0flask-bootstrap>=3.3.7.0pyecharts>=2.0.0pandas>=1.5.0tushare>=1.2.60akshare>=1.12.0requests>=2.28.0

运行项目

1. 安装依赖

pip install -r requirements.txt

2. 启动应用

python app.py

3. 访问网站

打开浏览器访问:http://localhost:5000

4. (可选)配置tushare token

# Windows PowerShell$env:TUSHARE_TOKEN="你的token"# Linux / macOSexport TUSHARE_TOKEN="你的token"

如果不配置,系统将自动使用 akshare 作为数据源。


功能演示

功能
说明
股票搜索
支持按名称或代码搜索,覆盖全部A股
自动补全
输入时实时显示匹配的股票(最多10条)
K线图展示
ECharts渲染专业K线图,红涨绿跌
数据缩放
鼠标滚轮缩放 + 底部滑块缩放
交互式分析
鼠标悬停显示OHLC详细数据
最高/最低价标记
自动标注区间最高价和最低价
响应式布局
适配不同屏幕尺寸

常见问题

1. 数据获取失败

  • 确保网络连接正常
  • akshare 无需配置即可使用
  • 如使用 tushare,检查 token 是否正确

2. 端口被占用

# 修改 app.py 最后一行端口号app.run(debug=True, host="0.0.0.0", port=5001)

3. 图表不显示

  • 检查浏览器控制台(F12)是否有错误
  • 确保 CDN 资源可加载(ECharts、Bootstrap、jQuery、Font Awesome)
  • 清除浏览器缓存后重试

4. pip安装失败

# 使用国内镜像加速pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

扩展建议

1. 添加移动平均线(MA)

在 stock_data.py 中添加:

defadd_ma(self, df, days=[5, 10, 20, 60]):for d in days:        df[f'MA{d}'] = df['close'].rolling(d).mean()return df

2. 添加成交量柱状图

在 K线图下方叠加成交量柱状图,使用 ECharts 的 grid 多图布局。

3. 添加更多技术指标

MACD、KDJ、RSI、BOLL等常用技术指标。

4. 添加数据缓存

使用 Redis 或文件缓存减少API调用频率,提升响应速度。


单元测试

本项目包含完整的单元测试,覆盖数据模块和 Flask 应用。

测试结构

测试文件
测试数
覆盖内容
test_stock_data.py
23
StockDataFetcher
 初始化、辅助方法、股票列表、搜索、历史数据
test_app.py
20
Flask 路由、搜索建议 API、K线数据 API、K线图生成

运行测试

# 运行全部测试python -m pytest test_stock_data.py test_app.py -v# 仅运行数据模块测试python -m pytest test_stock_data.py -v# 仅运行 Flask 应用测试python -m pytest test_app.py -v

测试覆盖详情

test_stock_data.py(5 个测试类)

测试类
数量
测试场景
TestStockDataFetcherInit
5
无token、空token、None、成功初始化、失败回退
TestStockDataFetcherHelpers
4
_is_shanghai
 沪深判断、_get_ts_code 代码转换
TestStockDataFetcherStockList
3
正常获取、缓存命中、网络失败回退
TestStockDataFetcherSearch
8
代码精确/模糊搜索、名称搜索、空关键词、未找到
TestStockDataFetcherHistorical
5
成功获取、网络错误、空结果、tail截断

test_app.py(2 个测试类)

测试类
数量
测试场景
TestFlaskApp
15
首页、404页面、suggest 6种场景、kline 8种场景
TestCreateKlineChart
5
空参数、有效数据、单点数据、图表结构验证

测试中发现的 Bug

在编写测试时发现 search_stock 方法中空字符串 "" 会匹配到所有股票(因为 "" in any_string 始终为 True),已在方法开头添加空关键词守卫:

defsearch_stock(self, keyword: str) -> Tuple[int, Optional[list]]:ifnot keyword:return0, None# ... 搜索逻辑

版本控制与开源文件

Git 仓库

项目已初始化 Git 仓库,.gitignore 配置了如下忽略规则:

venv/           # 虚拟环境__pycache__/    # Python 字节码缓存*.pyc           # 编译文件.pytest_cache/  # 测试缓存*.egg-info/     # 打包信息dist/ build/    # 构建产物.env .venv      # 环境变量*.log           # 日志文件

初始化步骤

git initgit add -Agit commit -m "feat: 初始化项目,A股K线图网站"

开源文件说明

文件
用途
LICENSE
MIT 开源许可证,允许自由使用、修改和分发
README.md
项目说明,包含功能特性、快速开始、技术栈
CONTRIBUTING.md
贡献指南,包含提交规范和开发流程
CHANGELOG.md
更新日志,记录版本变更内容

开源许可

本项目代码遵循 MIT 开源协议,依赖的第三方库遵循各自的许可协议:

  • Flask:BSD License
  • pyecharts:MIT License
  • pandas:BSD License
  • akshare:MIT License
  • tushare:MIT License
  • ECharts:Apache 2.0 License
  • Bootstrap:MIT License

总结

本文从零开始构建了一个完整的A股K线图网站,包含:

  • 双数据源支持(akshare + tushare)
  • Web框架(Flask)
  • 图表生成(PyEcharts → ECharts)
  • 前端展示(Bootstrap + jQuery)
  • 交互功能(搜索、自动补全、缩放、标记线)
  • 错误处理(404/500页面、API异常处理)
  • 响应式设计(适配多端屏幕)
  • 完整单元测试(43个测试用例,覆盖数据模块和Web应用)
  • Git版本控制(.gitignore、开源许可证、贡献指南)

所有代码已在本项目中实现,可直接运行。欢迎基于此框架继续扩展更多功能!


作者简介:码上工坊,探索用编程为己赋能,定期分享编程知识和项目实战经验。持续学习、适应变化、记录点滴、复盘反思、成长进步。

重要提示:本文主要是记录自己的学习与实践过程,所提内容或者观点仅代表个人意见,只是我以为的,不代表完全正确,欢迎交流讨论。

最新文章

随机文章