做河南庙会数字化这个项目,前后折腾了快一个月,踩了不少坑,也攒了不少能直接套用的经验。今天就把整个“基于Flask的河南庙会文化艺术展示与定制”项目拆开聊一聊,从技术选型到Vue前端互动,再到PyCharm环境配置和实际部署,把关键环节都过一遍。这套东西适合两类人:一是想拿非遗文化、地方活动做成线上展示产品的朋友,二是刚好在学Flask、Vue、Django这几个技术栈,想找一个综合项目练手的人。
我最早想得很简单,做一个网页,把庙会的时间、地点、节目单放上去就行。真做起来才发现,庙会文化的核心不只是“哪天有会”,而是那些传承了很多年的民间艺术:浚县泥咕咕、淮阳泥泥狗、朱仙镇木版年画、豫剧清唱、高跷、旱船、打铁花……这些内容形态极多,有图片、有视频、有传承人故事,如果只是堆在页面上,用户根本看不下去。所以后来把项目定位改成“展示 + 定制”:一方面把庙会和非遗项目做成结构化的浏览体验,另一方面让用户能挑选喜欢的文化元素,生成一张带自己署名的纪念海报或文创卡片。这个“定制”功能,反而成了整个项目里最有意思的部分。
下面我就按实际开发的顺序,把项目整个拆开讲。
1. 为什么是Flask + Vue:项目整体设计与技术选型心路
1.1 先聊选型:Flask比Django更适合这个项目的原因
项目标题里有“基于flask”,但也有不少人第一反应是:为什么不用Django?毕竟Django在做内容管理、后台数据录入方面确实方便,热搜词里也有一堆和Django相关的内容,比如“django项目实战新手”“django创建app”“django之MTV模式有什么作用”。这问题我在项目初期也认真纠结过。
Django的优势是“全家桶”:自带ORM、Admin后台、认证系统、表单处理,MTV模式更是把“数据模型-模板渲染-视图控制”理得很顺。但如果你的团队不大、项目边界清晰,Django反而会有一种“被框架拖着走”的感觉。尤其是我这个项目的核心是“文化展示接口 + 定制服务”,后台管理只是辅助,不会出现几十个复杂权限角色。用Flask这种微框架,我可以把所有代码都捏在自己手里,每个视图函数都看得懂,遇到需要改的地方直接在app.py或blueprint里改,而不需要翻Django的settings和各种中间件。
另外,Flask和SQLAlchemy的组合足够覆盖这个项目的持久化需求。对于“庙会活动表”“非遗项目表”“定制订单表”这种关系相对简单、查询也不复杂的场景,Flask-SQLAlchemy的表现完全不输Django ORM。所以我的结论是:项目体量不大、追求灵活可控,选Flask;项目一上来就有复杂的权限体系、成熟的内容工作流,才更需要Django这种重型框架。这个项目显然属于前者。
1.2 四条业务线:展示、搜索、定制、管理怎么划分
在动手写代码前,我先把项目功能拆成了四条业务线,这也是Flask蓝图(Blueprint)的划分依据:
- 展示线:庙会日历、非遗项目库、图片库、视频库,核心是让用户“逛得明白”。用户进入首页,能看到近期庙会活动列表,点进详情页能看到庙会对应的非遗艺术、传承人故事、现场图片和视频。
- 搜索线:支持按庙会名称、地区、非遗类型三个维度筛选。比如用户搜索“浚县”,就能把浚县正月庙会、泥咕咕项目全部带出来。
- 定制线:这是项目的亮点。用户在看某个非遗项目时,如果喜欢对应的纹样或图片,可以选择底图、填写祝福文案、选择卡片风格,系统在服务端生成一张定制海报,提供高清PNG下载。
- 管理线:后台只保留最基础的数据维护能力:新增庙会、新增非遗项目、删除下架内容。我直接用Flask-Admin实现,五分钟搭一个后台,后面在“常见问题”里会说清楚为什么不用自写后台。
四条线互不干扰,恰好对应四个Flask蓝图:catalog、search、customize、admin。开发时每一条线都能独立调试,不用在几百行的路由文件里找代码。
2. 庙会文化数据的结构设计:非遗内容如何变成可管理的数据
2.1 数据模型拆分:从“庙会”到“文化要素”的三层结构
文化类项目最容易犯的错,就是把所有信息塞进一个大表格。比如一个“庙会活动表”里既有庙会名称、时间、地点,又把表演项目、非遗图片全用JSON字段塞进去。短时间看是省事,后面做筛选、定制、推荐的时候,查数据会非常痛苦。
我最终拆成了三个核心模型:
- Festival(庙会活动):字段包括id、名称、地区、开始日期、结束日期、简介、主图地址。一个庙会可以关联多个非遗项目,新的庙会信息由后台维护人员录入。
- HeritageItem(非遗项目):字段包括id、名称、类型、所属地区、传承人、介绍、图片地址、视频地址。这里用
festival_id做外键,表示这个项目属于哪一场庙会,同时允许一个项目出现在多个庙会中,用关联表维护多对多关系。 - CustomOrder(定制订单):字段包括id、底图地址、文字内容、风格参数、生成结果地址、创建时间。这个表的用途是记录每一次定制行为,方便后面统计哪个纹样最受欢迎。
选这段结构的关键原因是:用户浏览路径是“庙会 → 非遗项目 → 定制文创”,数据模型必须跟着这个路径走,才能保证前端请求一次就能把详情页需要的所有数据拼出来。如果把图片、视频、项目介绍全堆在同一张表里,后面做定制功能的材料匹配时,就得反复拆JSON,非常难维护。
2.2 中文标签与分类体系:如何避免“类型”字段失控
河南庙会里的非遗内容五花八门,如果分类做得太粗,比如只分“表演类”“手工艺类”,那泥咕咕和朱仙镇年画会被堆在一起,用户搜索时无法精准命中;如果做得太细,比如按每个村的手艺分,后台维护成本又太高。
我的做法是“两级分类”:
- 一级分类(
category):固定几个大项,表演艺术、传统技艺、民俗活动、传统美食。 - 二级分类(
tags):用逗号分隔的标签,比如“泥塑、彩绘”“戏曲、豫剧”“社火、高跷”。一级分类用字典表约束,二级分类允许自定义。
这个方案兼顾了结构化查询和灵活性。写SQLAlchemy查询时,按一级分类过滤非常快;按标签搜索时,直接用LIKE '%泥咕咕%'或者加了简单的倒排缓存,也能满足小规模访问量。如果你一上来就上Elasticsearch,反而有点杀鸡用牛刀。
2.3 图片/视频文件规划:m3u8播放背后的存储约定
庙会现场的视频大多是长视频,如果直接放MP4,用户加载起来很慢。这里就要提到热搜词里反复出现的“vue播放m3u8、vue播放m3u8免安装”。m3u8是HLS流媒体协议里的索引文件,视频被切成很多个.ts小片段,播放器通过索引文件按需加载,它的好处是加载快、支持拖动、对服务器带宽压力小。
我在项目里对文件目录做了统一约定:
media/ festivals/ {festival_id}/ cover.jpg images/ videos/ index.m3u8 ts/视频上传后,用FFmpeg把MP4转成HLS切片:
ffmpeg -i input.mp4 -codec copy -hls_time 6 -hls_list_size 0 -hls_segment_filename ts/output_%03d.ts output.m3u8这条命令的意思是:不重新编码(-codec copy)以节省CPU,每个切片6秒,生成output.m3u8索引文件和按顺序编号的ts切片。-hls_list_size 0表示保留所有切片,不生成滚动播放列表。
文件存储做好约定之后,Flask和Vue只需要按路径规则访问就行。前端播放m3u8的细节我在后面专门讲。
3. 从零搭起项目:PyCharm、Flask后端与Vue前端的完整实操
3.1 PyCharm里创建Flask工程并启动的详细步骤
如果你在热搜里搜过“pycharm安装教程”“pycharm安装flask”,说明你大概率卡在了环境配置这一步。这里我说一套最省心的流程,用PyCharm专业版或社区版都能走通。
第一步,打开PyCharm,选择New Project,左侧选Flask。注意,PyCharm新建Flask项目时会自动创建一个app.py和一个templates目录,这对小项目没问题,但我们后面要拆蓝图,所以只把它当成脚手架。社区版也可以,只是少了Flask模板的快捷入口,手动建目录也一样。
第二步,在PyCharm底部的Terminal里激活虚拟环境并安装依赖。如果有venv目录,Windows下用:
venv\Scripts\activate然后安装核心依赖:
pip install flask flask-sqlalchemy flask-cors flask-admin pillow waitress这里解释一下每个包的用途:
flask:基础Web框架。flask-sqlalchemy:把SQLAlchemy集成到Flask,负责ORM映射。flask-cors:解决前端Vue开发服务器和后端Flask端口不一致时的跨域问题。flask-admin:做成管理后台,不用自己写增删改查页面。pillow:定制海报时做图片合成。waitress:Windows下常用的生产级WSGI服务器,后面部署讲。
第三步,写最简单的启动入口,确认环境没问题:
from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "庙会文化项目启动成功" if __name__ == "__main__": app.run(debug=True, port=5000)在PyCharm里直接右键运行,浏览器访问http://127.0.0.1:5000,看到提示文字就说明环境通了。这一步看起来很简单,但我见过很多新手卡在“为什么pip install之后PyCharm还是找不到flask”——原因基本都是PyCharm当前解释器没指向虚拟环境,需要在Settings -> Project -> Python Interpreter里手动选择venv下的Python。
3.2 Flask后端:REST风格接口和跨域处理
项目到了正式写接口的阶段,我按REST风格定义了最核心的几个API端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/festivals | 庙会列表,支持地区、日期筛选 |
| GET | /api/festivals/ int:id | 庙会详情,附带关联非遗项目 |
| GET | /api/heritage/ int:id | 非遗项目详情 |
| POST | /api/custom/orders | 提交定制订单 |
| GET | /api/custom/orders/ int:id | 查询定制订单状态和下载地址 |
写接口时有个关键点:前端Vue的开发服务器默认跑在5173端口,Flask跑在5000端口,两者端口不同,浏览器默认会拦截跨域请求。解决办法是加flask-cors:
from flask_cors import CORS app = Flask(__name__) CORS(app, resources={r"/api/*": {"origins": "*"}})origins这里开发阶段用通配符*图省事,部署上线后建议改成前端真实域名。我遇到过因为跨域配置太宽松,被临时部署的测试域名白嫖接口的情况,所以生产环境一定不要图方便。
另外,Flask接口返回数据时统一用jsonify,并且给每个接口都套一层code、data、message结构。这样前端拿到响应后,先判断code再取数据,后面写Vue代码会清爽很多。
3.3 Vue前端工程:axios、路由和庙会详情页
前端部分我用Vue 3 + Vite。创建项目之前先确认Node环境装好,然后在PyCharm终端里执行:
npm create vue@latest这里会问你需不需要TypeScript、Vue Router、Pinia等,按需选择。我建议新手第一次做项目可以直接全选默认,先跑通再逐个剥掉。项目创建完进入目录,安装依赖:
npm install npm install axios安装依赖慢是另一个常见痛点,Windows下如果遇到网络问题,把npm镜像切到国内源会明显改善。切源的方法很简单,在项目根目录建一个.npmrc文件,写入:
registry=https://registry.npmmirror.com前端页面结构并不复杂,核心是三个视图:HomeView.vue展示庙会列表,FestivalDetail.vue展示庙会详情和关联的非遗项目,CustomizeView.vue承载定制功能。路由用Vue Router管理,典型配置:
import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import FestivalDetail from '../views/FestivalDetail.vue' import CustomizeView from '../views/CustomizeView.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/festival/:id', name: 'festival-detail', component: FestivalDetail }, { path: '/customize/:heritageId', name: 'customize', component: CustomizeView } ] }) export default router这里有个容易踩的坑:用createWebHistory时,在Vite开发服务器没问题,但打包后部署到Nginx,如果用户直接刷新/festival/1这个地址,Nginx会返回404。原因是没有配置前端路由回退,需要把Nginx的try_files指到index.html。这个我在后面部署章节会详细说。
调用后端接口时,统一封装一个request.js,用axios实例的方式:
import axios from 'axios' const request = axios.create({ baseURL: 'http://127.0.0.1:5000/api', timeout: 10000 }) request.interceptors.response.use( response => response.data, error => { console.error('请求出错', error) return Promise.reject(error) } ) export default request这样每个页面里只需要写:
import request from '@/utils/request' const res = await request.get(`/festivals/${route.params.id}`)把baseURL统一管理之后,后面前端联调或者部署换地址,只改一个文件,不用在几十个组件里搜API链接。
3.4 庙会视频的m3u8在Vue中播放的完整配置
前面说了视频会转成m3u8格式,前端播放是另一个容易卡住很多人的地方。Vue里播放m3u8,我推荐用hls.js,因为流程简单、兼容性好。
先安装:
npm install hls.js然后在组件里写一个播放封装:
<template> <video ref="videoRef" controls class="video-player"></video> </template> <script setup> import { ref, onMounted, watch } from 'vue' import Hls from 'hls.js' const props = defineProps({ src: { type: String, required: true } }) const videoRef = ref(null) function playHls(url) { const video = videoRef.value if (Hls.isSupported()) { if (hlsInstance) { hlsInstance.destroy() } hlsInstance = new Hls() hlsInstance.loadSource(url) hlsInstance.attachMedia(video) hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => { video.play() }) } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari直接支持HLS video.src = url video.play() } } onMounted(() => { if (props.src) { playHls(props.src) } }) watch(() => props.src, (newVal) => { if (newVal) { playHls(newVal) } }) </script>这段代码里有一个细节值得注意:hlsInstance必须存储在组件作用域里,避免音频和视频播放下一个文件时,上一个Hls实例还在后台占用内存和请求。我在调试时遇到过连续切换几个庙会视频后页面卡死的现象,后来发现就是Hls实例没有销毁。
还有一个和m3u8播放强相关的问题:CORS。m3u8索引文件后面跟着几十个ts片段,浏览器会逐个请求这些ts文件,如果服务器没给ts文件设置正确的CORS头,就算m3u8能加载,视频画面也黑屏。Flask里处理办法是给媒体目录单独设置响应头:
@app.after_request def add_media_cors_headers(response): if request.path.startswith('/media/'): response.headers['Access-Control-Allow-Origin'] = '*' return response这个坑非常隐蔽,因为浏览器控制台可能只报一个Failed to load resource,不会明确告诉你是哪个ts文件被CORS拦截了。排查的时候要先打开Network面板,过滤m3u8和ts请求,看响应头里有没有Access-Control-Allow-Origin。
4. 定制功能落地:从“浏览文化”到“带走文化”
4.1 定制表单与订单状态机设计
“定制”听起来很玄,落到功能上就是三步:选底图、填文案、生成图片。
定制页面的表单字段我设计成:
heritageId:当前正在浏览的非遗项目ID,决定有哪些纹样素材可选用。templateId:用户选择的底图模板ID,模板决定了海报的整体色调和构图。senderName:署名,比如“张三 敬献”。message:一句祝福或介绍文字,比如“中原遗韵,匠心永传”。style:配色风格,比如“绛红”“墨青”“宣纸黄”。
订单表里除了这些字段,还要有一个状态字段status,我用字符串表示:
pending:刚提交,服务端还没处理。processing:服务端正在生成图片。done:图片生成完成,提供下载地址。failed:生成失败,记录了失败原因。
状态机看着简单,实际开发时很有价值。因为图片合成是耗时操作,如果直接在请求里同步生成,用户等待时间可能超过5秒,前端axios默认超时时间不够,体验极差。我的做法是:提交订单后立刻返回orderId和pending状态,前端轮询查询接口,等后端把图片合成好后返回done。这样用户界面可以先显示“正在排版,请稍候”,而不是卡在请求里。
4.2 用Pillow生成定制海报的轻量实现
定制功能的核心是服务端用Pillow合成图片。我把整个流程放在一个独立模块poster.py里,关键代码如下:
from PIL import Image, ImageDraw, ImageFont def generate_poster(order): base = Image.open(order.base_image_path).convert("RGB") draw = ImageDraw.Draw(base) # 在底图下方叠加半透明色带,保证文字可读性 overlay = Image.new("RGBA", base.size, (0, 0, 0, 0)) overlay_draw = ImageDraw.Draw(overlay) width, height = base.size overlay_draw.rectangle( [0, int(height * 0.75), width, height], fill=(0, 0, 0, 160) ) base = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB") draw = ImageDraw.Draw(base) # 写文案 font_path = "fonts/SourceHanSerifSC-Regular.otf" title_font = ImageFont.truetype(font_path, 48) text_font = ImageFont.truetype(font_path, 32) draw.text((50, int(height * 0.78)), order.message, fill=(255, 255, 255), font=title_font) draw.text((50, int(height * 0.78) + 80), f"{order.sender_name} 敬献", fill=(230, 220, 200), font=text_font) output_path = f"output/poster_{order.id}.png" base.save(output_path) return output_path这里有两个特别需要注意的坑:
一是字体问题。Pillow默认字体不支持中文,如果直接画中文会得到一堆方框。必须准备中文字体文件,思源宋体、思源黑体都行,而且路径要放到项目里随代码一起管理,不要依赖操作系统的字体目录,否则服务器上一换环境中文就乱码。
二是图片合成时的模式转换。底图可能是JPEG,没有Alpha通道,而半透明遮罩是RGBA,直接粘贴会报错。必须先convert("RGBA")做合成,再转回RGB保存为PNG。我第一次写的时候忘了转回RGB,保存出来的图背景是全黑的,排查了半天才发现是模式问题。
定制结果生成以后,Flask提供一个下载接口,这里需要设置Content-Disposition头,让浏览器把响应当附件下载而不是直接打开:
from flask import send_file @app.route("/api/custom/orders/<int:order_id>/download") def download_custom_order(order_id): order = CustomOrder.query.get_or_404(order_id) if order.status != "done": return jsonify(code=400, message="订单尚未完成"), 400 return send_file( order.result_path, mimetype="image/png", as_attachment=True, download_name=f"poster_{order_id}.png" )如果你在热搜里看到“django streaminghttpresponse 参数content_type和content-disposition”,其实解决的就是同一个问题:文件下载时,响应头里Content-Type告诉浏览器文件类型,Content-Disposition里的attachment告诉浏览器是附件而不是内联展示。Flask的send_file用as_attachment=True和download_name就能完成,底层道理和Django是一模一样的。
4.3 管理后台:Flask-Admin还是自写?
项目后台只需要录入数据,我直接用Flask-Admin,十分钟就能跑起来:
from flask_admin import Admin from flask_admin.contrib.sqla import ModelView admin = Admin(app, name="庙会文化管理后台") admin.add_view(ModelView(Festival, db.session)) admin.add_view(ModelView(HeritageItem, db.session)) admin.add_view(ModelView(CustomOrder, db.session))ModelView会自动根据SQLAlchemy模型生成列表页、新增页、编辑页、删除按钮。对于运营人员来说,能录入数据、能改信息就够了,没必要费劲用Vue写一个独立后台。
但这里要提醒一点:如果把CustomOrder也交给Flask-Admin管理,默认情况下运营人员可以直接看到用户的署名、联系方式(如果有的话),也能编辑订单状态。这在内部使用没问题,一旦项目要对外开放并且涉及用户敏感信息,就必须对后台做权限控制,至少加一个简单的登录认证。Flask-Admin支持用flask-login包装一层,但能查看到什么字段、能不能导出,建议单独配置成只读视图,避免误操作。
5. 常见问题与排查技巧实录
5.1 PyCharm与Vue环境配置的经典报错
这个项目开发环境里最密集的问题,基本都集中在“环境配置”而不是业务逻辑上。
第一个高频报错是pip install flask时报错Microsoft Visual C++ 14.0 is required。这个通常是某些Python包需要编译C扩展,Windows下没有C++构建工具。解决方法是下载“Microsoft C++ Build Tools”安装,或者尽量选有预编译wheel文件的包版本。在安装pillow时如果遇到这个报错,直接去PyPI下载对应Python版本的pillow.whl手动安装,能绕开编译过程。
第二个高频问题是PyCharm里安装Flask后,代码还是飘红。这时要检查右下角解释器状态,确保当前项目使用的是虚拟环境里的Python,而不是PyCharm自带的系统解释器。具体路径是File -> Settings -> Project -> Python Interpreter,选择venv目录下的Python.exe。
第三个高频问题是Vue安装依赖时卡在npm install。除了切换镜像源,还可以试一下npm install --registry=https://registry.npmmirror.com。如果node_modules已经装了一半,先删除node_modules和package-lock.json再重新安装,不要直接在失败现场反复install。
5.2 m3u8播放黑屏与请求拦截图解
我在庙会详情页里嵌了视频播放,自测时发现一个诡异现象:m3u8本身能加载出来,播放器初始化正常,但画面始终黑,进度条可以拖动,就是没有声音没有画面。
排查步骤:
- 打开浏览器Network面板,筛选
m3u8,确认索引请求返回200。 - 筛选
ts,发现大量ts请求返回200,但看Response响应体长度,有些是0。 - 直接复制一个ts链接到新标签页打开,发现能正常播放。
- 查看这个ts请求的响应头,发现没有
Access-Control-Allow-Origin。
问题就出在ts文件的CORS响应头上。Flask里对/media/的请求加了after_request处理,生产环境用Nginx托管媒体文件时,又需要再检查Nginx配置里的add_header是否写对了位置。如果你在Flask里加了CORS头但Nginx又托管了媒体目录,那Nginx很可能把Flask的响应头直接覆盖掉了。正确做法是在Nginx的location /media/块里单独加:
location /media/ { add_header Access-Control-Allow-Origin *; }另外,如果你在开发环境把m3u8索引和ts切片放在不同域名下,情况会更复杂,因为浏览器对每个资源都会单独发CORS预检。最省事的方案就是让m3u8和ts同源。
5.3 部署上线:waitress + Nginx的组合
项目开发完成后的部署,我选了waitress + Nginx。热搜词里也有“python django windows10 waitress+nginx部署”,其实waitress不区分Flask还是Django,它就是一个纯Python的WSGI服务器,Windows下用起来比gunicorn省心得多,因为gunicorn在Windows上支持并不好,经常需要借助WSL才能跑。
生产启动命令很简单:
waitress-serve --host=0.0.0.0 --port=8000 app:appapp:app表示从app.py中导入app这个Flask实例。注意,生产环境一定不要再开Flask的debug模式,那会带来很大的安全风险,我早期有一次忘了关debug,直接把项目搁在一个公网测试服务器上,结果后台暴露了调试器,点开哪个请求都能看到服务端完整报错信息,包括文件路径和部分代码片段。好在只是测试环境,不然后果很麻烦。
Nginx在部署里的职责主要是两个:一是托管dist目录下的Vue静态文件,二是把/api请求反向代理到waitress的8000端口。
前端Vue打包先执行:
npm run build会生成dist目录。Nginx配置核心块:
server { listen 80; server_name your-domain.com; root /path/to/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /media/ { alias /path/to/media/; } }这段配置里最关键的是try_files $uri $uri/ /index.html。没有这一行,用户在前端路由下刷新页面就会Nginx 404,这正是Vue Router使用HTML5 History模式时的经典问题。
5.4 缓存策略与海报下载文件名乱码
还有一个我踩过的小坑:定制海报下载时,如果用户直接点send_file返回的接口,文件名可能是poster_12.png这种格式,浏览器下载时没问题。但如果文件名里有中文,比如我想让用户下载的是“浚县泥咕咕定制海报.png”,必须给download_name传带UTF-8编码的值,同时要确保响应头里的Content-Disposition正确编码。Flask的send_file已经处理了download_name的URL编码,但我遇到过前端拿到下载地址后用window.open打开,某些浏览器会把中文文件名变成一串百分号。
后来我干脆写了一个前端下载辅助函数,先用fetch拿到Blob,再用本地临时URL触发下载,这样文件名由前端决定,不再依赖服务器的Content-Disposition:
async function downloadPoster(orderId) { const res = await fetch(`/api/custom/orders/${orderId}/download`) const blob = await res.blob() const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = '定制海报.png' a.click() URL.revokeObjectURL(url) }这个方法虽然多了一步,但能确保中文文件名在所有浏览器里都正常。如果你既想让浏览器直接下载,又希望文件名是中文,那也可以继续用send_file的download_name,前提是客户端不额外处理响应。
项目做下来的三点体会
如果回头复盘这个项目,我最想强调的不是Flask怎么写、Vue怎么用,而是“文化数据的颗粒度”要先想清楚。庙会文化不是几个字段能装完的,但也不能一开始就把模型设计得无比复杂。先用“庙会-非遗项目-定制订单”这三张表把核心链路跑通,后面加传承人、加展演日历、加数字藏品,都是在稳定骨架上长肉。
另外一点,就是前后端联调时一定要尽早把接口返回结构定下来,不要前端一套、后端一套。我在项目初期吃够了乱改接口的亏,后来把所有API响应统一成{ code, data, message },前后端各写一份接口文档,问题立刻少了大半。
最后说一个可以继续扩展的方向。现在的定制功能只支持静态海报和图片下载,后续可以考虑把定制结果做成动态页面,生成带独立链接的H5分享页,用户在手机上打开就能看到自己定制的庙会艺术卡片,还能转发给朋友。技术上并不复杂,就是在订单完成后多生成一个HTML模板,用Vue做分享页渲染,Flask只需要提供一个公开只读的分享接口。这样整个项目就从“展示 + 定制”进一步升级成了“展示 + 定制 + 分享”,文化传播的链条才算真正闭合。