简介:一个基于Python与微信小程序搭建的看图猜成语游戏项目,面向Python初学者和小程序开发者,完整还原了从后端接口到前端交互的开发链路。资源共40个文件,压缩包仅559KB,其中6个py脚本实现Flask后端逻辑,4个wxml与5个js构成小程序页面交互,7个wxss负责视觉样式,6个json承担数据配置,另有sql数据库脚本、png示例图片以及doc/docx使用与配置说明;目录分为Idiom、Flask、weapp-idiom等模块,方便按需学习。已有116人学习下载。项目以图片展示、用户输入与结果判断为主线,覆盖变量、条件分支、函数封装、文件读写和异常捕获等Python核心知识点,可进一步掌握接口编写、小程序事件绑定、数据库设计,以及成语与图片的关联存储;随包文档还给出配置、启动与排错要点,适合课程设计或入门练手。
1. 用 Python 拆解看图猜成语小程序:从题库设计到成品交付的思路
如果把“看图猜成语小程序”当作一个纯前端问题,你很快会撞上一堵墙:图片从哪来、题目怎么组织、交互逻辑和题库怎么解耦、后续怎么加题而不动代码。用 Python 写这个项目,不是为了在手机里跑 Python,而是把 Python 当作内容生产、题包生成和逻辑验证的“后方工厂”,前端小程序只负责渲染和接收用户输入。这个分工,才是这类项目最常见的可靠做法。
如果你刚接触小程序开发,这个项目是极好的练手对象——它不涉及支付、登录、复杂组件,核心只有“出图、猜词、判对错、跳下一题”。对五年以上工程师来说,这个标题真正值得关注的是两个容易被忽视的点:一是 Python 生成题目数据包时的编码与资源管理,二是小程序端拿到中文答案后的比对策略,这两处恰恰是大多数开源版“看图猜成语”做得最粗糙的地方。下文会先立住技术选型和数据设计,再给出可跑的代码、可调的参数和会踩的坑。
2. 把技术选型说透:为什么用 Python 做小程序的后端与题库生成
2.1 小程序端不能跑 Python,那 Python 在这个项目里到底干什么
微信小程序的前端运行环境是 JavaScript(WebView 与逻辑层分离),它既不支持 Python 代码直接执行,也无法直接 import 一个.py文件。因此,“基于 Python 所写”的看图猜成语小程序,通常不是指小程序本身用 Python 开发,而是指整个项目的完整链路中有 Python 参与,而且参与得很深。最常见的分工有两类:
- 题库与资源生成:Python 脚本从原始图片、文字描述等素材出发,生成小程序可用的 JSON 题库、图片资源配置表、甚至裁剪压缩后的图片。
- 后端服务:小程序通过
wx.request请求 Python 编写的后端接口(Flask / FastAPI),动态拉取题目。数据不在前端写死,便于更新题库。
实际开发中,我个人更推荐“题库生成 + 静态资源托管”的做法。原因很简单:看图猜成语这类小程序的核心玩法对实时性要求不高,题目数量通常在几十到几百之间,完全可以把题库打成 JSON 文件随小程序包发布,省去服务器成本和接口维护。只有当你的题目量上千、需要每日更新或做用户答题记录时,再引入 Python 后端。
2.2 题目 JSON 结构设计:编码、图片 URL 与答案的存储方式
无论选哪种模式,Python 脚本输出的题目数据结构都要提前定好。它决定了前端编写的复杂度,也决定了你将来加题时是否需要改动小程序代码。一个合理的最小结构如下:
{ "version": "20240601", "total": 3, "questions": [ { "id": 1, "image": "/images/questions/001.png", "answer": "鸡飞蛋打", "hint": "形容两头落空,一无所得", "difficulty": 1, "options": ["鸡飞蛋打", "鸡犬不宁", "鸡鸣狗盗", "鸡毛蒜皮"] }, { "id": 2, "image": "https://cdn.example.com/images/002.png", "answer": "画蛇添足", "hint": "比喻做了多余的事", "difficulty": 2, "options": ["画蛇添足", "画龙点睛", "守株待兔", "掩耳盗铃"] } ] }image字段既支持本地路径也支持网络 URL,本地路径用于小程序包内图片,网络 URL 用于远程资源。answer是标准答案,options是四个候选选项;注意options数组里必须包含answer,否则用户永远选不对。difficulty字段用于后续扩展难度筛选,虽然没有它也能跑,但加上后不用改结构,前端就能做“简单/困难”模式。
有一个细节值得专门强调:JSON 文件必须使用 UTF-8 编码。Python 的json.dump默认会保证这一点,但如果你的图片资源或题目文本来自 Windows 环境,要格外小心编码转换问题,否则小程序端解析时会出现中文乱码,表现就是“页面能打开,题目文字全是问号”。
2.3 Flask 与 FastAPI 的选择:小程序后端接口用哪个框架更稳
如果题目量不大,但你就是想引入 Python 后端,那么框架选择会直接影响开发效率和部署复杂度。目前社区里针对小程序后端用得最多的两个 Python 框架是 Flask 和 FastAPI,它们各有明确的适用边界。
| 对比维度 | Flask | FastAPI |
|---|---|---|
| 上手难度 | 极低,源码精简 | 较低,但需要理解类型注解 |
| 性能 | 同步框架,适合低并发 | 异步原生,适合高并发 IO 场景 |
| 接口文档 | 需手动集成 Swagger | 自动生成 OpenAPI 文档 |
| 数据校验 | 手动写或另加 marshmallow | Pydantic,声明即校验 |
| 适合场景 | 几十到几百题量、内部使用 | 面向大量用户、需要接口文档 |
对这个项目而言,只要你的预期用户量不是“上线即爆”的量级,Flask 是更稳妥的选择。原因在于它的代码更少、依赖更少、部署时不容易出现异步环境导致的怪问题。FastAPI 虽然性能好、文档自动生成很诱人,但它引入了 Pydantic 和 Uvicorn,对初学者排查环境问题的成本更高。
如果你决定用 Flask 编写小程序的后端接口,下面是最小可用的代码框架:
from flask import Flask, jsonify, request import json import random app = Flask(__name__) def load_questions(): with open('questions.json', 'r', encoding='utf-8') as f: data = json.load(f) return data['questions'] @app.route('/api/question', methods=['GET']) def get_question(): questions = load_questions() difficulty = request.args.get('difficulty', type=int) if difficulty: pool = [q for q in questions if q['difficulty'] == difficulty] else: pool = questions question = random.choice(pool) return jsonify(question) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)这段代码的逻辑不复杂,但有三个点需要说明:
load_questions每次请求都重新读文件,开发时方便,但上线后频繁磁盘 IO 会拖慢响应;改进方案是全局加载一次,只在文件变动时重新读取。difficulty参数通过request.args.get获取并转为 int,如果用户传了非数字值,type=int会使其变为None,不会抛异常——这是 Flask 的隐藏容错机制,值得记住。random.choice是直接从池子里随机选一题,没有做“已答题目排除”,用户可能连续抽到同一题。要避免这个体验问题,需要为每个用户维护一个已答列表,这又引出了会话状态管理,复杂度会上升。
3. 用 Python 脚本批量生成题库与图片素材:从零到一的最小可跑方案
3.1 图片名规范化:为什么不要用中文文件名
看图猜成语的核心素材是图片。很多人最初会直接把图片命名为“鸡飞蛋打.png”放在目录里,Python 脚本读取时确实没问题,但一旦交给小程序前端使用,就会踩坑:微信小程序开发者工具对中文路径的支持尚可,但在部分 Android 真机或某些 CDN 环境下,中文文件名会出现 URL 编码不一致的情况,表现为“图片加载不出来”。排查到最后,问题往往不在代码,而在文件名。
常见的做法是,用图片对应的题目 ID 作为文件名,答案信息只存进 JSON。Python 脚本可以自动完成从“中文名”到“ID 名”的映射和重命名,这样既保留了人工整理的便利,又避免了前端资源引用的隐患。下面这段脚本演示了如何批量处理一个目录下的图片文件:
import os import re source_dir = './raw_images' target_dir = './processed_images' os.makedirs(target_dir, exist_ok=True) for filename in os.listdir(source_dir): if not filename.endswith('.png'): continue # 去掉扩展名,取中文名部分 name = os.path.splitext(filename)[0] # 假设原有的中文名就是答案,例如 "鸡飞蛋打.png" # 用一个字典为每道题分配固定 ID answer_to_id = { '鸡飞蛋打': 1, '画蛇添足': 2, '守株待兔': 3, } if name in answer_to_id: new_name = f"{answer_to_id[name]:03d}.png" src_path = os.path.join(source_dir, filename) dst_path = os.path.join(target_dir, new_name) os.rename(src_path, dst_path) print(f"{filename} -> {new_name}")这段代码有几个值得注意的要点:
answer_to_id字典是硬编码的,实际项目中建议从一个 CSV 或 Excel 题库表读取,脚本负责映射,不负责维护数据。:03d将 ID 格式化为三位数,保证文件名长度一致,方便前端按模式匹配。- 如果图片本身是 JPG 或 WebP 格式,判断条件需要同步扩展;更稳妥的做法是不以扩展名过滤,而是用
PIL.Image.open验证文件是否为有效图片。
3.2 图片压缩策略:小程序包体限制与加载速度的平衡
微信小程序主包大小限制是 2MB,超过后将无法上传。假设一道题的图片平均 100KB,20 张图就接近极限,这还不包括代码和其它资源。因此,图片压缩不是可选项,而是上线前的必经步骤。Python 配合 Pillow 库可以批量完成这项工作,以下代码实现了“等比缩放 + 质量压缩”:
from PIL import Image import os def compress_image(src_path, dst_path, max_width=600, quality=70): img = Image.open(src_path) if img.width > max_width: ratio = max_width / img.width new_height = int(img.height * ratio) img = img.resize((max_width, new_height), Image.LANCZOS) img.save(dst_path, 'JPEG', quality=quality, optimize=True) source_dir = './processed_images' target_dir = './compressed_images' os.makedirs(target_dir, exist_ok=True) for filename in os.listdir(source_dir): if filename.endswith('.png'): src = os.path.join(source_dir, filename) dst = os.path.join(target_dir, os.path.splitext(filename)[0] + '.jpg') compress_image(src, dst) original_size = os.path.getsize(src) new_size = os.path.getsize(dst) print(f"{filename}: {original_size // 1024}KB -> {new_size // 1024}KB")参数的选取直接关系到用户在小程序里看到的图片清晰度:
max_width=600适合绝大多数手机屏幕的显示密度。微信小程序的image组件默认宽度是 300px,在 2 倍屏下实际需要 600px 宽的资源;设置太大会浪费包体,设置太小会模糊。quality=70是 JPEG 压缩的甜点值,低于 60 时肉眼可见的压缩痕迹会增多,高于 80 时体积下降不明显。- 转换成 JPEG 而不是保留 PNG,是因为成语图片通常是照片、插画或复杂场景,JPEG 的压缩率远高于 PNG;如果图片包含文字截图或透明背景,才需要保持 PNG 格式。
3.3 选项干扰项生成:让 Python 自动构造“像样”的错误答案
看图猜成语的选项设计直接影响游戏质量。如果四个选项毫无相似度,用户不用看图片都能猜到答案。常见的错误选项设计思路有三种:同字干扰、近义干扰、结构干扰。用 Python 自动生成这些干扰项,比纯靠手工录入效率高得多,但需要一份候选成语库作为原料。
import random idioms = ['鸡飞蛋打', '鸡犬不宁', '鸡鸣狗盗', '鸡毛蒜皮', '画蛇添足', '画龙点睛', '守株待兔', '掩耳盗铃', '狐假虎威', '亡羊补牢', '自相矛盾', '刻舟求剑'] def generate_options(correct_answer, pool, num_options=4): options = [correct_answer] candidates = [w for w in pool if w != correct_answer] # 优先选与正确答案有相同字的成语作为干扰项 same_char = [w for w in candidates if set(w) & set(correct_answer)] rest = [w for w in candidates if w not in same_char] random.shuffle(same_char) random.shuffle(rest) options.extend(same_char[:num_options - 1]) if len(options) < num_options: # 干扰项不够时再从剩余池补齐 options.extend(rest[:num_options - len(options)]) random.shuffle(options) return options for answer in idioms: opts = generate_options(answer, idioms) print(f"{answer} -> {opts}")输出的选项列表每次运行都不同,因为random.shuffle会打乱顺序。这里的重点是same_char的筛选逻辑:用set(w) & set(correct_answer)求交集,存在共同汉字就认为它是“同字干扰”。对于“鸡飞蛋打”,鸡犬不宁和鸡毛蒜皮会被优先选为干扰项,用户不能只看单个字就排除所有错误选项。
需要说明的是,这个干扰项生成策略有一个已知盲区:如果候选池里的成语和正确答案之间没有共享汉字但意思相近,就不会被选中。例如“画蛇添足”和“多此一举”是近义关系,但两者没有共同汉字。要覆盖这种情况,需要额外引入一个近义词映射表,Python 脚本负责把近义词也纳入候选集。投入产出比是否划算,取决于你的题目总量——题少时手工整理反而更快。
4. 微信小程序端的实现:页面结构、数据绑定与核心交互
4.1 目录结构与静态题库的引用方式
小程序端的代码结构与普通微信小程序无异,但针对“看图猜成语”这个特定场景,目录组织有一个推荐实践:
miniprogram/ ├── pages/ │ └── game/ │ ├── game.wxml │ ├── game.wxss │ ├── game.js │ └── game.json ├── images/ │ └── questions/ │ ├── 001.jpg │ ├── 002.jpg │ └── 003.jpg ├── data/ │ └── questions.json └── app.json关键点是data/questions.json这个目录划分。很多入门教程会把题库直接写进game.js的data对象里,这样做在题目只有三五道时没有问题,但题量上来后,game.js会变得极度臃肿,而且setData和刷新逻辑缠绕在一起。把题库独立成 JSON 文件,然后通过require引入,是更干净的分离方式:
// game.js const questionBank = require('../../data/questions.json'); Page({ data: { currentIndex: 0, currentQuestion: null, selectedOption: '', showResult: false, isCorrect: false }, onLoad() { this.initGame(); }, initGame() { const first = questionBank.questions[0]; this.setData({ currentQuestion: first, currentIndex: 0 }); } });这里有一个微信小程序的隐藏知识:require一个 JSON 文件时,路径是相对于当前 JS 文件的,且不需要写扩展名。另外,由于小程序打包机制的限制,JSON 文件里不能包含注释,所以你在 Python 生成题库时不要把带有//注释的 JSON 直接放进小程序目录,要先做一次压缩或去注释处理。
4.2 猜题页面的 WXML 结构与选项点击判定
页面模板是整个小程序最直观的部分。图片展示、选项列表、反馈区域三层结构清晰分离,便于后期调整样式:
<view class="container"> <image class="question-image" src="{{currentQuestion.image}}" mode="aspectFit"></image> <view class="options-grid"> <view wx:for="{{currentQuestion.options}}" wx:key="index" class="option-item {{selectedOption === item ? 'selected' : ''}}" bindtap="onOptionTap" >onOptionTap(e) { const selected = e.currentTarget.dataset.option; const correct = this.data.currentQuestion.answer; // 统一去掉首尾空格后比较 const normalizedSelected = selected.replace(/\s+/g, '').trim(); const normalizedCorrect = correct.replace(/\s+/g, '').trim(); const isCorrect = normalizedSelected === normalizedCorrect; this.setData({ selectedOption: selected, showResult: true, isCorrect: isCorrect }); }处理逻辑不复杂,但replace(/\s+/g, '')这一步很多人会忽略。它把字符串里所有空白字符(包括全角空格、制表符)都删除再比较,虽然代价是“对答案内部的空格完全不敏感”,但对于成语这种固定四字结构而言,这个取舍是值得的——你不会希望用户因为不小心打了个空格被判错。
4.4 修改导航栏标题:动态设置当前关卡或难度标识
看图猜成语通常会分关卡或难度,很多人会把“第 X 关”“难度:简单”这类信息放在导航栏标题上。微信小程序的原生导航栏支持动态修改标题,只改game.json里的静态配置是不够的,需要在 JS 中调用 API 或者使用页面配置:
{ "navigationBarTitleText": "看图猜成语", "navigationBarBackgroundColor": "#4A90D9", "navigationBarTextStyle": "white" }这是在game.json中设置的静态标题。如果每关的标题不同,需要在 JS 里调用wx.setNavigationBarTitle:
changeLevel(levelName) { wx.setNavigationBarTitle({ title: `看图猜成语 - ${levelName}` }); }如果题目按难度分文件存储(例如easy.json、hard.json),在切换题库时同步调用这个 API 即可。注意wx.setNavigationBarTitle的调用时机要在setData之后或之前都没有问题,它和页面数据不在同一条渲染链路上,但建议在页面onLoad或onShow里做一次设置,避免用户从别的页面返回时标题还停留在上一关的状态。
5. 题库更新与版本迭代:Python 脚本在前、小程序发版在后的协作流程
5.1 题库去重与完整性校验脚本
当题库规模增长到几十甚至上百题后,人为录入错误的概率显著上升。常见的错误包括:选项里没有正确答案、答案重复、图片文件缺失、JSON 格式错误。与其上线后由用户发现,不如在 Python 侧做一轮自动化校验。下面的脚本可以帮你提前抓住大部分问题:
import json def validate_questions(file_path, image_dir=None): with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) questions = data.get('questions', []) errors = [] seen_answers = set() for idx, q in enumerate(questions): qid = q.get('id', idx + 1) if not q.get('answer'): errors.append(f"第 {qid} 题缺少答案") if q.get('answer') in seen_answers: errors.append(f"第 {qid} 题答案重复: {q.get('answer')}") seen_answers.add(q.get('answer')) options = q.get('options', []) if len(options) != 4: errors.append(f"第 {qid} 题选项数量为 {len(options)},应为 4") if q.get('answer') not in options: errors.append(f"第 {qid} 题选项中不包含正确答案") if image_dir: import os img_path = os.path.join(image_dir, os.path.basename(q.get('image', ''))) if not os.path.exists(img_path): errors.append(f"第 {qid} 题图片不存在: {q.get('image')}") if errors: print("校验未通过,发现以下问题:") for err in errors: print(f" - {err}") else: print(f"校验通过,共 {len(questions)} 题,无异常。") return len(errors) == 0 if __name__ == '__main__': validate_questions('questions.json', image_dir='./compressed_images')这个脚本最适合放在“更新题库后、提交小程序代码前”这个时间点执行。它会即时反馈出题时的低级错误,比如忘记把正确答案塞进选项、或图片文件名对不上。
值得说明的是,这个脚本没有校验“语义正确性”——它无法判断一张图片是否真的表达了对应成语。这类主观校验仍需要人工看一遍。实践中更高效的做法是让脚本生成一份“题目概览表”:
for idx, q in enumerate(questions): print(f"{idx + 1:03d}. {q.get('answer')} -> {q.get('image')}")然后人工快速扫一遍列表,确认图片与答案的对应关系没有错位。这比一张张打开图片校验快得多,尤其是在图片本身已按 ID 重命名的前提下。
5.2 版本号管理与用户端缓存刷新策略
小程序发版的经典痛点是:用户手机上运行的是旧版本,你已经更新了题库,但他们看不到新题。微信小程序的更新机制是,冷启动时会向微信服务器检查版本,发现新版本后会异步下载,但用户本次启动仍然运行旧代码,等下次冷启动才切换新版本。
针对这个问题,在看图猜成语这类内容型小程序中,一个有效的做法是给题库 JSON 加上version字段,并在前端启动时向后端或静态服务器请求一次版本信息:
checkVersionUpdate() { wx.request({ url: 'https://api.example.com/question/version', success: (res) => { const remoteVersion = res.data.version; const localVersion = questionBank.version; if (remoteVersion !== localVersion) { wx.showModal({ title: '发现新题库', content: '是否立即更新?', success: (r) => { if (r.confirm) { // 跳转到更新页面或重新拉取题库 this.downloadNewQuestions(remoteVersion); } } }); } } }); }这个方案需要 API 存在,如果你的架构里没有后端,替代方案是把版本号写进小程序app.json的config自定义字段,每次加题后手动修改。由于微信的版本机制,你改了app.json并发布新版本,用户侧更新后自然会拿到新题库——这个方案更省事,但用户感知到新题的时间会更滞后。
两种方式的取舍很简单:题目更新后希望“立刻生效”,就要上远程版本检查;能接受“等用户自然升级小程序”,就只改本地题库。大多数项目从后者起步,做大了再迁移到前者。
5.3 微信开发者工具里的踩坑记录:地铁路径与资源加载
最后整理几条在微信开发者工具里经常遇到、且看图猜成语项目里特别容易被触发的坑,它们都不是代码逻辑错误,但排查起来非常浪费生命。
第一,项目目录直接打开与导入的差异。如果直接用微信开发者工具打开整个项目文件夹(包含 Python 脚本、原始素材、压缩脚本),开发者工具可能会尝试编译所有文件,导致报错或警告。正确做法是工具里选择“导入项目”,目录指向miniprogram这一层。Python 脚本放在项目外层,不进小程序编译链路。
your-project/ ├── scripts/ # Python 脚本,不进小程序 │ ├── generate_questions.py │ └── compress_images.py ├── raw_images/ # 原始图片素材,不进小程序 └── miniprogram/ # 微信开发者工具导入这个目录 ├── app.js ├── app.json └── pages/这种目录分离还有一个附带好处:Python 脚本生成的questions.json和压缩后的图片会直接输出到miniprogram/data和miniprogram/images下,后续重新生成时不会把乱七八糟的中间文件带进小程序包。
第二,清缓存调试的正确姿势。当你改了图片或题库但小程序里没变化,多半是缓存问题。微信开发者工具顶部菜单的“工具 -> 清除缓存 -> 清除全部缓存”,然后再编译。如果还不行,检查project.config.json里是否有"setting": { "urlCheck": false },当你的后端接口是 HTTP 而非 HTTPS 时,这个配置缺失会导致请求被拦截,页面拿到不到数据,表现形式和缓存一模一样。
第三,真机预览与开发者工具的差异。开发者工具里图片能显示,真机上图片空白,十有八九是图片路径问题。开发者工具对路径解析更宽容,真机则严格按照app.json注册的页面路径和资源路径查找。调试时优先在真机上验证图片加载,不要只看模拟器效果就发版。
6. 把 Python 脚本沉淀成自动化工作流:一键生成题库、压缩图片并自检
发展到这个阶段,你应该把之前写的零散脚本整合为一个可重复执行的流水线。这样无论是新增 10 题还是更换全部题库,都只需要一条命令,而不是手动跑三个脚本再人工检查。这里给出一个用 Python 标准库argparse写的一键脚本框架,用最少的代码组合前面的功能:
import argparse import subprocess import sys from pathlib import Path def main(): parser = argparse.ArgumentParser(description='看图猜成语题库生成流水线') parser.add_argument('--source-dir', default='./raw_images', help='原始图片目录') parser.add_argument('--output-dir', default='./miniprogram', help='小程序项目根目录') parser.add_argument('--no-compress', action='store_true', help='跳过图片压缩步骤') parser.add_argument('--no-validate', action='store_true', help='跳过题库校验步骤') args = parser.parse_args() project_root = Path(args.output_dir) # 步骤一:重命名并映射图片 if not args.no_compress: print('>>> 步骤 1/3:压缩图片') subprocess.run([sys.executable, 'compress_images.py', '--src', args.source_dir, '--dst', str(project_root / 'images' / 'questions')], check=True) # 步骤二:生成题库 JSON print('>>> 步骤 2/3:生成题库 JSON') subprocess.run([sys.executable, 'generate_questions.py', '--src', args.source_dir, '--dst', str(project_root / 'data' / 'questions.json')], check=True) # 步骤三:完整性校验 if not args.no_validate: print('>>> 步骤 3/3:校验题库') from validate_questions import validate_questions ok = validate_questions( str(project_root / 'data' / 'questions.json'), image_dir=str(project_root / 'images' / 'questions') ) if not ok: sys.exit(1) print('>>> 全部完成,可以打开微信开发者工具预览了') if __name__ == '__main__': main()这个脚本本质上是在编排三个独立模块,而不是把所有逻辑塞进一个文件。每个子脚本仍然可以单独运行,便于调试和复用。--no-validate这类开关适合在快速迭代时跳过校验,但提交代码前必须完整跑一遍带校验的流程。
使用方式是:
python build_pipeline.py --source-dir ./new_content --output-dir ./miniprogram在new_content里放好新题目的原始图片和一份题目描述 CSV,脚本执行完,小程序目录下的questions.json和图片就已经更新完毕,开发者工具里直接重新编译就能看到效果。这套自动化流程的最大收益不是省掉那几分钟执行时间,而是让“加新题”变成一个不会因为人为遗漏而出错的标准动作,每一次生成结果都是可预期的。等到团队协作时,你甚至可以让非技术人员只准备素材、跑一条命令,不需要碰任何代码。
本文还有配套的精品资源,点击获取