资讯动态

Python Flask + 微信小程序实战:从零搭建水果百科全栈项目

发布时间:2026/9/7 20:15:56 来源:尧图企业网站定制
最近这几周我一直在折腾一个算得上“麻雀虽小五脏俱全”的完整项目Python后端 微信小程序名字叫“鲜而廉水果百科”。核心功能是水果百科查询、时令水果价格展示、在线购买外加用户登录和订单支付。这个项目是我今年做过的最能串起全链路知识的练手项目从Python环境搭建、后端接口设计、数据库建模到小程序前端页面、微信登录、支付联调、真机测试再到审核上线几乎每个环节都有值得记录的坑。这篇文章就完整还原一下我当时的实现思路和落地过程给正在做类似“Python 微信小程序”项目的朋友一份可以直接参考的实战手册。1. 项目拆解鲜而廉水果百科到底要做什么很多人拿到这种项目名第一反应是“做个小程序商城”但“鲜而廉水果百科”这个组合其实包含了两条业务线一条是内容线也就是“百科”给用户提供水果的产地、营养、挑选技巧、时令月份这些知识另一条是商业线也就是“鲜而廉”把实时价格和购买入口接进来让用户看完百科顺手就能下单。两条线不是割裂的好的设计是让百科内容为购买决策服务比如用户搜“草莓”百科里告诉你怎么挑、几月最便宜旁边就带当前价格和购买按钮这样转化路径最短。我最后确定的边界是水果百科水果基本信息、时令表、挑选技巧、营养功效、存储方式。价格行情展示当下水果的市场参考价按“鲜而廉”的逻辑突出性价比。购买链路首页推荐、商品列表、详情、购物车、下单、微信支付。用户体系微信登录、昵称头像展示、收藏、历史记录。从技术选型上我后端用了Python 3.9 Flask MySQL前端是微信小程序原生框架WXML/WXSS/JS没有用 uni-app。原因后面细说。1.1 核心需求先从名字说起“鲜”字决定了你在做百科和价格时必须有时效性概念不能像普通CMS那样把文章往库里一扔就完事。我专门做了一个season字段标记每个水果的上市月份首页轮播图按当前月份自动切水果主题价格表也加了price_date每天更新超过两天的价格前端会标记成“历史参考价”后台会提示运营去刷新。这个设计看起来很不起眼但真正上线以后用户反馈最多的就是“今天价格准不准”所以时效性是这类内容型电商小程序的命根子。“廉”字则体现在价格对比功能上。我加了一个简单的“性价比指数”字段算法不复杂用某个水果的当日价除以过去30天该水果的均价再乘以100指数越低说明当前越便宜。页面里用红色标签展示“低于近30天均价xx%”这个功能用户很买账因为它真正把“廉”变成了可感知的信息。1.2 为什么选Python 微信小程序这套组合Python主要扛两件事接口服务和数据采集。百科数据不是天上掉下来的我用 requests BeautifulSoup 从几个公开的水果信息站抓了基础数据再人工校对补充这个活儿用Python做最顺手价格数据同理每天定时抓取清洗后落库再用 Flask 提供 JSON 接口。如果用Node或Java做后端爬虫这块也能做但Python生态里解析HTML、清洗数据的库实在太成熟了没必要绕路。小程序这边我之所以不选 uni-app是因为这个项目最核心的交互集中在微信生态内原生框架的调试体验、API完整度、真机预览都比跨端框架更省心。当然如果你有“以后要出支付宝小程序”的明确规划uni-app会更有优势。但我个人建议第一个完整项目尽量用原生框架因为遇到问题时你搜索到的原生解决思路最多坑最少。2. 整体架构与数据库设计思路这个项目的架构不算复杂但如果一开始不想清楚后面会陷入不断改接口的泥潭。我画的是“小程序 → Flask API → MySQL”三层结构文件上传暂时没用对象存储直接存本地目录再配一个静态路由访问等需要上云再迁OSS。对练手项目来说这套方案成本最低跑通全链路后才需要考虑上不上云。后端工程结构我按功能模块分不要把所有接口堆在一个app.py里fruit_project/ ├── app.py # Flask入口注册蓝图 ├── config.py # 配置数据库、小程序appid/secret ├── models/ # SQLAlchemy模型 │ ├── __init__.py │ ├── fruit.py # 水果百科表 │ ├── price.py # 价格表 │ └── user.py # 用户、订单、收藏 ├── api/ # 蓝图路由 │ ├── __init__.py │ ├── home.py # 首页 │ ├── fruit.py # 百科列表/详情 │ ├── cart.py # 购物车 │ ├── order.py # 订单/支付 │ └── user.py # 登录/收藏 ├── utils/ # 工具函数 │ ├── wx_auth.py # 微信登录解密 │ ├── response.py # 统一返回结构 │ └── spider/ # 爬虫模块 └── requirements.txt2.1 后端的模块划分与接口约定Flask 项目最容易犯的错是蓝图划得太粗比如“所有接口都写在 user.py 里面”。我这次分了6个蓝图每个蓝图只负责自己那一块接口路径也有统一前缀比如/api/fruit/list、/api/fruit/detail、/api/order/create这样后面调参、排查都方便。接口返回格式我从一开始就统一成{ code: 0, msg: success, data: {} }code非零表示业务错误。为什么不用 HTTP 状态码来区分业务异常因为小程序端的wx.request只要 HTTP 200 就会走 success 回调如果你返回 500小程序里还要写一堆 fail 处理不方便。统一用业务码前端只需要判断code 0即可逻辑就简单很多。2.2 水果百科和价格表的结构设计水果百科表fruit_info是最关键的表字段设计时我特意加了season和tags这两个字段决定了首页聚合和百科筛选的灵活性CREATE TABLE fruit_info ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, alias_name VARCHAR(50), origin TEXT, nutrition TEXT, pick_skill TEXT, storage TEXT, season VARCHAR(20), tags VARCHAR(100), cover_url VARCHAR(255), detail_url VARCHAR(255), sale_status TINYINT DEFAULT 1, create_time DATETIME ); CREATE TABLE fruit_price ( id INT PRIMARY KEY AUTO_INCREMENT, fruit_name VARCHAR(50), price DECIMAL(10,2), unit VARCHAR(20), price_date DATE, avg_price_30d DECIMAL(10,2), cheap_index DECIMAL(10,2), source VARCHAR(100) );这两个表的联合查询就是我首页“今日鲜果推荐”的数据基础。fruit_price.cheap_index的算法我写成了一句话当日价 / 近30天均价 * 100用户可以直观看到“当前价格相比近一个月便宜/贵了多少”。注意price_date一定要加索引因为价格查询最频繁的就是“最近一天、最近30天”这种范围查询没索引的话数据量一大接口就钻数据库死角。3. 小程序端页面搭建与核心功能实现小程序端我规划了5个主页面首页、百科、购物车、订单、我的。底部TabBar用4个就够了把“订单”合并进“我的”里面减少页面切换成本。百科列表用scroll-view 分页加载首页用swiper做时令主题轮播。很多新手一上来就关注UI炫不炫但我觉得小程序项目里页面结构设计比样式重要。比如首页从上到下我固定为轮播区当前月时令水果主题比如3月推草莓、菠萝价格行情横向滚动条显示当日低价水果百科精选列表带封面图的水果卡片“性价比TOP5”榜单按 cheap_index 从低到高排列每个模块的数据都来自不同接口我在小程序端做了一个简单的request.js封装统一处理 loading、错误提示和登录态失效跳转避免每个页面重复写 wx.request 长串代码。3.1 原生小程序还是uni-app我为什么选原生虽然热搜词里有人在搜“uniapp开发微信小程序”但我在这个项目里坚定选了原生。原因特别实在项目涉及wx.login、wx.getUserProfile、wx.requestPayment这类微信强依赖API原生框架API第一手同步报错信息也更准确。uni-app确实能一套代码多端复用但它的编译链路会多一层遇到报错你还要判断是原生报错还是框架报错对新手排查不友好。原生小程序还有一个好处组件生态的坑更少。我在做购物车数字增减时原生input 自定义stepper可以直接控制不用依赖第三方组件库避免引入多余依赖导致包体积膨胀。小程序有2MB主包限制虽然现在支持分包但主包越小加载越快体验越好。3.2 首页、百科详情、购物车、个人中心的实现要点每个页面我都有几句经验可以分享首页的swiper高度是个坑不能直接写死height: 300rpx因为不同机型下rpx换算成物理像素后轮播图会被裁切。更稳妥的做法是用aspect-ratio或者监听图片加载后动态设置高度。我最终用的方案是给swiper-item内的图片加上modeaspectFill然后容器用padding-bottom: 75%撑开高度适配大多数机型。百科详情页是这个项目的灵魂页面。除了展示nutrition、pick_skill外我还把该水果的“当前价格”“近30天价格趋势”“购买按钮”做成一个悬浮卡片固定底部用户在读完百科后可以直接操作这个设计让百科内容真正成了购买的“种草环节”。购物车要注意的是“选中状态”必须用数组保存不能只靠checkbox组件自带的点击。因为购物车结算时要批量计算选中商品的总价如果你不维护选中数组就没法高效计算。我用selected_ids数组存储所有选中的商品 id每次点击checkbox就更新数组并在onShow里重新拉取购物车列表。个人中心看起来简单但收藏列表和历史浏览记录建议做成两个 Tab不要都堆在一个页面里。历史记录用本地wx.setStorageSync存储即可收藏必须走后端接口因为用户换设备后收藏不能丢。4. Python后端开发接口、鉴权与数据来源后端这部分是这个项目的重头戏我从环境搭建、数据库初始化、登录鉴权、数据采集四个维度展开说。如果你也是从零开始建议先照着下面的步骤把环境捋顺再开始写业务代码。4.1 环境准备Python安装与依赖清单先有一台能跑的 Python 环境。Windows 用户去官网下载 Python 3.9 安装包时最关键的一步是勾选“Add Python to PATH”这一步漏了后面python命令全都失效。macOS 或 Linux 用户如果自带 Python 3建议用python3 -m venv创建独立虚拟环境避免污染系统环境。然后准备requirements.txtFlask2.2.5 Flask-CORS4.0.0 Flask-SQLAlchemy3.0.5 PyMySQL1.1.0 requests2.31.0 beautifulsoup44.12.2 APScheduler3.10.4安装命令是pip install -r requirements.txt注意Flask 版本不要一上来就装 3.x很多教程和扩展还停留在 2.x版本不一致会导致Flask-SQLAlchemy初始化报错。我自己踩过用 Flask 3.0 SQLAlchemy 2.0 时db.init_app(app)的写法没有任何问题但部分老教程里的app.config[SQLALCHEMY_...]前缀不生效会出现“Table already exists”之类的诡异问题。稳妥起见跟着这个版本列表来。4.2 Flask接口怎么写从目录结构到统一响应类后端写接口时我建议先封装一个统一的响应工具避免每个接口里手动拼jsonify# utils/response.py from flask import jsonify def ok(dataNone, msgsuccess): return jsonify({code: 0, msg: msg, data: data}) def fail(msgerror, code1): return jsonify({code: code, msg: msg, data: None})然后在蓝图里返回时只写return ok(lists)即可。这个封装很基础但它能让接口代码短一半而且前端拿到的是一个结构完全一致的数据对象。在app.py里注册蓝图from flask import Flask from flask_cors import CORS from api.home import home_bp from api.fruit import fruit_bp from api.order import order_bp from api.user import user_bp app Flask(__name__) app.config.from_object(config) CORS(app) app.register_blueprint(home_bp, url_prefix/api/home) app.register_blueprint(fruit_bp, url_prefix/api/fruit) app.register_blueprint(order_bp, url_prefix/api/order) app.register_blueprint(user_bp, url_prefix/api/user)4.3 登录鉴权wx.login换openid这步不能省小程序的用户身份最终靠openid识别。登录流程是小程序调用wx.login拿到临时code再把code发给后端后端用code appid secret去微信接口换openid和session_key然后记录用户并返回自定义登录态。我后端实现的验证代码大致如下# api/user.py import requests from flask import request APPID 你的小程序appid SECRET 你的小程序secret def code2session(code): url ( https://api.weixin.qq.com/sns/jscode2session? fappid{APPID}secret{SECRET}js_code{code}grant_typeauthorization_code ) resp requests.get(url, timeout3) data resp.json() if openid not in data: raise Exception(f登录失败: {data}) return data[openid], data[session_key] user_bp.route(/login, methods[POST]) def login(): code request.json.get(code) try: openid, session_key code2session(code) except Exception as e: return fail(msgstr(e)) # 查库或创建用户 user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid, nickname水果用户, avatar) db.session.add(user) db.session.commit() return ok({user_id: user.id, nickname: user.nickname})这里不建议自己发明一套复杂的Token机制对于练手项目返回user_id前端用wx.setStorageSync保存并在后续请求头里带上X-User-Id后端用装饰器解析即可。等做到真实商用再考虑JWT或Redis session。4.4 水果百科数据从哪来爬虫人工校对百科数据我用爬虫从公开的水果资料站点抓取脚本如下# utils/spider/fruit_spider.py import requests from bs4 import BeautifulSoup def fetch_fruit_info(fruit_name): url fhttps://example.com/search?q{fruit_name} resp requests.get(url, headers{User-Agent: Mozilla/5.0}, timeout5) soup BeautifulSoup(resp.text, html.parser) # 解析页面里的营养、挑选技巧字段 ...抓完之后要做两件事去重和人工校对。因为网上很多资料是互相抄的可能出现“陕西”和“陕西/山西”这种产地描述不一致的情况。我写了一个简单脚本把抓到的数据导入CSV再人工过一遍汇总到MySQL。对于价格数据我用了APScheduler定时任务每天早上8点抓取一次当日价格并写入fruit_price表。这里要注意抓取频率不要太频繁每天一次足够频率过高只会给你的IP带来访问限制风险。5. 实操过程记录从零把这个项目跑起来这一节我按自己实际操作时的顺序把从环境到联调的过程完整走一遍。你可以照着往下做基本能复现出一模一样的项目。5.1 第一步配置Python后端并初始化数据库创建虚拟环境并激活python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate安装依赖pip install -r requirements.txt在config.py里填上你的 MySQL 配置和微信小程序 appid/secretclass Config: SQLALCHEMY_DATABASE_URI mysqlpymysql://root:你的密码localhost/fruit_db?charsetutf8mb4 SQLALCHEMY_TRACK_MODIFICATIONS False APPID wx1234567890abcdef SECRET 你的secret创建数据库并初始化表mysql -u root -p -e CREATE DATABASE fruit_db DEFAULT CHARACTER SET utf8mb4; python init_db.py5.2 第二步运行Flask服务python app.py默认监听 5000 端口。本地调试时小程序开发者工具里需要勾选“不校验合法域名”不然请求http://127.0.0.1:5000会被拦截。真机调试的话需要让手机和电脑处于同一局域网然后在request.js里把 baseURL 改成电脑的局域网IP比如http://192.168.1.101:5000。我在请求封装里是这样写的// utils/request.js const BASE_URL http://192.168.1.101:5000; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method, data, header: { content-type: application/json, X-User-Id: wx.getStorageSync(user_id) || }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail: (err) reject(err) }); }); } module.exports { request, BASE_URL };5.3 第三步小程序开发者工具配置与AppID在微信公众平台注册小程序后你会拿到一个 AppID类似wx1cb4398e1413dce7。打开微信开发者工具新建项目时填入这个 AppID不要选“测试号”因为测试号不支持wx.requestPayment等很多真实接口。如果你是从别人那边拷贝代码或者用 HBuilderX 打开项目一定要检查project.config.json以及开发者工具右上角的 AppID 是否一致。热搜里有人问“为什么运行到微信小程序模拟器中小程序ID还是原来的”十有八九是 manifest.json 或 project.config.json 里的 appid 忘了改工具配置里显示的是项目配置文件里的旧值你需要手动改成自己的 AppID。项目导入后在开发者工具“详情-域名信息”里把http://192.168.1.101:5000加入 request 合法域名本地调试时可以勾选“不校验合法域名”跳过。真机预览时同样需要在“项目设置”里开启“不校验合法域名”或配置真实HTTPS域名。5.4 第四步前后端联调我习惯先在开发者工具里跑通“首页列表”接口再逐个页面联调。联调时尽量先用静态假数据把页面画完再切到真实接口这样能区分是前端布局问题还是后端接口问题。联调时最常用的调试工具是小程序端 Console 面板看报错Network 面板看请求状态码和返回 body后端 Flask 控制台看 SQLAlchemy 查询日志全套联调通了以后可以跑一次wx.preview真机预览检查一下接口请求是否正常。6. 常见问题与排查技巧实录做这个项目时我在热搜里看到大家反馈最多的几个报错几乎全踩了一遍。下面这张表是我整理的排查思路很实用报错/场景可能原因排查方法小程序获取登录后的微信用户失败: wx1cb4398e1413dce7AppID与后台不一致或AppID无效到微信公众平台核对AppID检查开发者工具右上角配置真机测试出现 net::ERR_CONNECTION_RESET后端服务没跑或手机与电脑不在同一局域网又或者未勾选不校验合法域名用电脑浏览器先访问后端接口确认服务正常手机Ping一下电脑IP勾选“不校验合法域名”运行到模拟器时小程序ID还是原来的项目配置文件里的appid没有替换检查 project.config.json 和 manifest.json全局搜索原appid并替换真机测试时无法获取头像昵称微信基础库调整静默获取接口被废弃改用button open-typechooseAvatar和input typenickname顶部导航栏高度在不同机型上错位未适配刘海屏/安全区使用wx.getMenuButtonBoundingClientRect()动态计算胶囊高度再设置自定义导航栏高度小程序开发者工具报 maximum setlocal recursion level reachedWindows 下工具文件路径过长或版本bug升级微信开发者工具清理缓存把项目放到短路径下比如D:/project/fruit微信支付拉起失败商户号未关联AppID或调用参数少字段检查支付参数签名确认package、nonceStr、timeStamp等字段完整6.1 登录态失效和用户信息获取最近微信对用户信息接口收敛得很严格具体表现是以前wx.getUserInfo弹窗授权的方案已经不推荐现在官方推荐用头像昵称填写能力。我在个人中心页面里是这样做用户信息收集的button classavatar-wrapper open-typechooseAvatar bind:chooseavataronChooseAvatar image src{{avatarUrl}} modeaspectFill / /button input typenickname placeholder请输入昵称 bind:bluronNicknameChange /用户头像和昵称拿到后通过/api/user/update同步到后端。登录态失效的判断我统一在后端加了个异常捕获当X-User-Id对应的用户在数据库中不存在时返回code: 1001前端请求封装里监听这个 code 就跳转个人中心引导重新登录。6.2 真机调试网络请求失败的排查真机预览和模拟器的网络环境不一样。模拟器里localhost代表电脑本身真机里localhost代表手机所以请求电脑IP的192.168.1.101一定不能用localhost。如果报net::ERR_CONNECTION_RESET我的排查步骤是电脑浏览器直接访问http://192.168.1.101:5000/api/home能通说明后端没问题。手机浏览器访问同样的地址能通说明同一局域网OK。手机浏览器不能通检查防火墙是否放行5000端口。都通了但小程序不行检查小程序后台request合法域名本地调试直接勾选“不校验合法域名”。Windows防火墙放行端口的命令是netsh advfirewall firewall add rule nameFlask 5000 dirin actionallow protocolTCP localport50006.3 自定义导航栏和顶部安全区小程序默认导航栏标题是白底黑字能做定制的东西很少。我想在首页顶部放一个搜索框所以开启了navigationStyle: custom。自定义后顶部状态栏高度和胶囊按钮位置在不同机型上都不一样我用了一份公共代码处理// utils/menu.js function getMenuButton() { const menu wx.getMenuButtonBoundingClientRect(); const statusBarHeight wx.getSystemInfoSync().statusBarHeight; return { top: menu.top, height: menu.height, statusBarHeight }; }拿到menu.top menu.height之后把这个值作为自定义导航栏的高度就可以让搜索框在iPhone 14 Pro Max和iPhone SE上都不顶头。这个适配一定要做尤其现在全面屏手机这么多不能只调模拟器里的显示效果。6.4 支付功能怎么接支付这步是小程序电商项目里最绕不开的。我接的是微信支付V3流程是用户点击支付小程序调后端/api/order/pay。后端生成订单号调用微信支付统一下单接口拿到prepay_id。后端用prepay_id生成前端拉起支付所需的paySign参数。小程序端拿到参数后调wx.requestPayment。后端核心代码简化如下# utils/wx_pay.py def create_pay_params(order_no, openid, amount, desc): params { appid: APPID, mch_id: MCH_ID, description: desc, out_trade_no: order_no, notify_url: NOTIFY_URL, amount: {total: amount}, # 单位: 分 payer: {openid: openid} } # 使用requests请求微信支付API v3地址具体签名逻辑参考官方API ...这里最容易踩的坑是amount单位是分不是元。我一开始直接把19.9传进去结果支付金额变成了0.19元对账时闹了大笑话。另外支付回调notify_url必须是可以被公网访问的HTTPS地址本地开发时可以先用内网穿透工具临时把服务暴露到公网测试但生产环境一定要申请正式HTTPS域名。6.5 消息推送和优惠券通知搜索热词里有人问“小程序推送消息方案”和“公众号优惠券通知”。小程序里现在比较可靠的通知方式是订阅消息用户下一次订单后可以申请一次订阅后续用于给他推送“订单发货”“优惠券到期”这类模板消息。后端调用subscribeMessage.send接口注意用户订阅一次只能推一条所以别把多条通知塞在一个授权里用户会反感验证时也容易失败。优惠券那部分如果你只想在“鲜而廉水果百科”里做优惠券展示和抵扣就不需要碰公众号能力直接在小程序内做一个优惠券表下单时校验coupon_code是否有效即可。这种方式更适合小程序闭环也比对接公众号开放平台省很多事。7. 上线前准备与踩坑避雷指南项目跑通不是终点上线才是。我这里分享几个我经验里最有价值的“避雷”点省得你上线时被卡得措手不及。7.1 域名和HTTPS配置小程序生产环境要求所有请求域名必须是HTTPS且已在后台配置合法。我上线前先去域名服务商买了一个域名再用Nginx配置了SSL证书把5000端口的Flask服务反向代理到https://api.example.com。这一步的核心原因是微信官方对request合法域名的强制要求本地开发可以绕过但审核版和正式环境绕不过。Nginx反代的配置大致如下server { listen 443 ssl; server_name api.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }之后去微信公众平台“开发管理-开发设置-服务器域名”里把https://api.example.com加到 request 合法域名。只要你的域名没备案这一步基本做不了所以提前把域名备案这件事排上日程。7.2 审核发布的小细节小程序审核员会重点看几个地方支付流程是否顺畅、用户隐私协议是否明确、有没有测试数据残留。我在提交审核前做了三件事把所有乱码数据、测试订单清掉让首页看起来像真实运营状态。在“我的”页面放一个“用户协议”和“隐私政策”入口虽然麻烦但审核遇到“收集用户信息未声明”的驳回概率会小很多。确保“购买”按钮没有诱导分享、诱导关注等违规文案价格数据不要夸大宣传。审核通过以后正式版就出现在用户微信里了。后续迭代一定要记住每次改完代码先用体验版测试再提交审核不要直接拿开发版当正式版发布。7.3 我做完这个项目后最想说的几句话整个“鲜而廉水果百科”项目从零到上线我大概花了两周多时间。最大的体会是这类项目真正的难点不在于某个单一技术点而在于把零散的技术串成一个完整可用的产品。Python环境、Flask接口、数据库建模、小程序页面、微信登录、支付联调、审核上线每一环单独拎出来都有教程但把它们串到同一条流水线上你就必须自己去解决“为什么接口在模拟器能通真机不通”“为什么支付金额少了100倍”这类只有联调才能暴露的问题。最后再分享一个小技巧开发过程中养成每次接口改完就用 postman 跑一遍的习惯保存成集合。这样后期小程序端出问题时你第一件事不是在小程序里找bug而是先确认接口本身是否正常能省下一大堆无意义的排查时间。这个项目后续如果你想继续扩展可以加一个后台管理系统用来维护百科内容和价格数据或者用定时任务把价格数据接入可视化图表让用户看到30天价格趋势曲线那会是另一个很有意思的迭代方向。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价