资讯动态

Python四六级单词助手APP设计与实现:从爬虫到记忆算法全解析

发布时间:2026/9/29 3:53:41 来源:尧图企业网站定制
身边有好几个人问我毕业设计选了“四六级英语单词助手”这种题目到底该怎么做还有零基础想做个背单词工具给自己用的朋友也在纠结用什么语言、怎么起步。我个人的看法是四六级单词助手这类项目用Python做是最合适的。它不是那种大而全的商业产品正好需要Python这种开发效率高、生态丰富、代码量小的技术栈把爬虫、数据库、算法、界面几个模块串起来一个完完整整的项目就能跑起来。这篇内容不是我贴一段源码注释就完事而是把一个真实项目的需求分析、技术选型、功能落地、踩坑记录完整过一遍代码可以抄思路也可以直接用。今天就把这个“Python小程序风格的APP设计与实现”从头到尾拆一遍。我会先说整体架构怎么搭然后讲单词数据怎么来、怎么存再讲记忆算法和界面交互是怎么实现的最后还聊一下怎么把它扩展成手机上的小程序。整个过程没有高大上的东西但每一步你都能落地下来的。1. 整体设计这个单词助手APP到底要做什么1.1 先拆需求别一上来就写代码很多人拿到这种题目第一反应是打开IDE直接撸界面。这其实是大忌。四六级单词助手看着功能简单你要真做起来至少涉及五个模块单词数据获取、数据存储、学习计划、记忆算法、用户界面。如果做得细一点还要有发音、例句、拼写测验、学习统计。我从实际需求出发把这款APP的功能切成三类基础功能单词列表展示、中文释义、音标、例句、收藏。学习功能每日学习一组单词、卡片式背诵、英译中自测、中译英拼写测验、错词记录。复习功能基于艾宾浩斯遗忘曲线安排复习计划、统计学习进度、标记薄弱词。一个背单词工具看起来核心是“单词量”其实用户真正在意的是“能否坚持下来”。所以你设计的版本必须要有复习提醒和进度反馈不然用户学两天就忘工具再好也没用。这也是为什么很多人用Excel背单词根本坚持不下去因为Excel没有记忆调度而单词助手APP的核心竞争力恰恰在“什么时候该复习哪些词”。1.2 技术选型为什么是Python不少同学会纠结一个问题做APP是不是一定要用Java、Kotlin或者Swift不一定。题目里写的是“Python小程序”和“APP”其实可以理解为一个桌面端的轻量应用或者用一个Python后端来支撑前端小程序。我最后是这么定的方案模块选型理由语言Python 3.10生态全、上手快、爬虫和算法都好写数据存储SQLite单文件数据库零配置适合个人项目和毕设数据获取requests lxml爬四六级词表够用且稳定桌面界面TkinterPython自带不需要装额外GUI库打包体积小API支撑Flask后续想接小程序、网页版同一套代码直接复用打包PyInstaller一键打包成exeWindows上直接跑你可能会问为什么不直接用PyQt或者PySide这要分情况。PyQt好看是好看但学习成本高而且申请开源授权要注意GPL协议的问题。Tkinter虽然丑但它是标准库零依赖做课程设计和毕设完全够用。我自己的项目就是Tkinter写桌面版再加一个Flask接口层为以后扩展小程序留好后路。这套组合的好处是一个项目能同时产出桌面APP和移动端小程序两种形态写两份简历素材。1.3 模块划分与文件结构实际项目我采用了经典的三层结构让代码不挤在一起word_helper/ │ ├── app.py # 程序入口 ├── ui/ │ ├── main_window.py # 主窗口 │ ├── study_view.py # 学习页面 │ ├── review_view.py # 复习页面 │ └── stats_view.py # 统计页面 ├── core/ │ ├── database.py # 数据库初始化与操作 │ ├── memory.py # 遗忘曲线算法 │ ├── word_service.py # 业务逻辑 │ └── crawler.py # 词库爬虫 ├── resources/ │ └── words.db # SQLite数据库 └── requirements.txt这样的分层逻辑很清楚ui层只负责展示和收集用户动作core层只处理业务谁都别越界。后面扩展小程序端的时候只需要调core层的方法ui层直接换成微信小程序的wxml和js就行。这个架构说实话不算创新但它是经过真实项目验证的稳妥方案尤其适合比较完整的“设计与实现”类题目。2. 词库获取与存储从0到8000个单词2.1 单词数据从哪来做单词助手最头疼的一件事就是“没词库”。有人直接从网上下现成的Word文档但格式乱七八糟释义、音标可能对不齐处理起来特别痛苦。我试过几种方案最后发现两条路比较靠谱第一用爬虫从公开的在线词典或考试类网站抓取四六级词表解析后入库。第二用GitHub上开源的四六级词库比如各种txt格式的词汇表直接导入。我建议你优先用开源词库因为在课程设计和毕业设计里重点不是爬虫多厉害而是整个系统能不能稳定运行。词库的准确性和完整性远比你怎么爬来的更重要。如果你确实想写爬虫展示一下技术能力也有办法。四六级的大纲词表是公开资料找到一个稳定的页面结构后用requests拉取HTML再用lxml解析出单词、音标、释义。这里给你一个参考代码我做的是通用处理import requests from lxml import html headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } def fetch_words(url): resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 tree html.fromstring(resp.text) words [] # 这里按页面实际的XPath写不同页面结构不一样 for item in tree.xpath(//div[contains(class,word-item)]): word item.xpath(.//span[contains(class,word)]/text())[0].strip() phonetic item.xpath(.//span[contains(class,phonetic)]/text()) meaning item.xpath(.//div[contains(class,mean)]/text())[0].strip() words.append({ word: word, phonetic: phonetic[0].strip() if phonetic else , meaning: meaning }) return words这里有三个点要提醒你。第一请求头一定要带上User-Agent不然很多网站直接拒绝请求。第二解析页面之前先把resp.encoding设置好不然中文容易出现乱码我一开始就用gbk解UTF-8的页面查了半天才发现是编码问题。第三每个网站结构不一样XPath不要照抄先用开发者工具看看节点路径。爬虫的代码本身不难难的是你对页面结构足够了解。2.2 数据清洗比爬数据更花时间把词抓下来只是第一步真正费时间的是清洗。你会发现原始数据里有很多脏东西词条带空格、释义里有多余的换行、词性和释义挤在一起、同一单词重复出现。我写了个清洗函数来统一处理import re def clean_word(raw): word raw[word].strip().lower() # 去掉数字和下划线只保留字母 word re.sub(r[^a-zA-Z], , word) meaning re.sub(r\s, , raw[meaning]).strip() # 把n. 苹果这种拆成词性和释义 match re.match(r^([a-z]\.|vt\.|vi\.|adj\.|adv\.|prep\.)?\s*(.*), meaning) pos match.group(1) or gloss match.group(2) or meaning return { word: word, phonetic: raw.get(phonetic, ), pos: pos.replace(., ), meaning: gloss }清洗的规则每个词库不一样但核心思路是一致的统一大小写、去掉非法字符、去除重复项、拆分词性和释义。这一步做好了后面学习模块的显示才会整齐。你想想一个背单词软件如果释义里带一堆换行和空格用户一眼就觉得不专业。我自己在清洗这块花的时间比写整个界面都多。2.3 数据库表结构设计数据清洗完就要入库了。我用的是SQLite它就是一个单文件数据库不需要安装服务Python标准库sqlite3直接就能操作。后续如果你要部署成一个真正的服务器SQLite也可以无缝换成MySQLDao层的sql改动不大。这个项目我设计了三个表足够应付学习过程中的所有需求-- 单词表 CREATE TABLE IF NOT EXISTS words ( id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT UNIQUE NOT NULL, phonetic TEXT, pos TEXT, meaning TEXT NOT NULL, example TEXT, freq INTEGER DEFAULT 0 ); -- 学习记录表 CREATE TABLE IF NOT EXISTS study_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, word_id INTEGER NOT NULL, stage INTEGER DEFAULT 0, last_review TEXT, next_review TEXT, mistake_count INTEGER DEFAULT 0, FOREIGN KEY(word_id) REFERENCES words(id) ); -- 学习日志表 CREATE TABLE IF NOT EXISTS study_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, learned_count INTEGER DEFAULT 0, reviewed_count INTEGER DEFAULT 0 );单词表存词本身study_records表存储每个单词的学习进度比如当前记忆阶段、下次复习时间、错误次数。study_log表记录每天的整体学习量方便统计页展示曲线。这里有个设计细节值得说为什么要把学习记录单独拆一张表而不是直接在words表里加字段因为单词数据是静态的学习进度是动态的。静态数据和动态数据混在一张表里后面做统计、做重置、做同步都会非常麻烦。拆开之后你甚至可以把words表替换成任何一本其他词库学习记录完全不受影响这叫“数据的关注点分离”。3. 记忆引擎把艾宾浩斯遗忘曲线变成代码3.1 记忆算法的核心思路市面上所有背单词软件都会提到艾宾浩斯遗忘曲线但真正做起来很少有人去严格复现那条曲线。因为不同的人遗忘速度不一样机械地按第1天、第2天、第4天、第7天、第15天去安排复习效果未必好。我在项目里采用的是一种工程化的简化方案把每个单词的记忆状态划分为多个阶段每个阶段对应一个复习间隔。间隔序列我设置成这样的阶段含义复习间隔0新学单词当天重复1已学过一次1天后2基本认识2天后3比较熟悉4天后4熟悉7天后5很熟15天后6掌握30天后这样设计有一个天然的好处不需要给每个单词单独存一串复习日期只需要存“当前阶段”和“下次复习日期”两个字段就够了。到时候扫描数据库把next_review小于当前时间的单词捞出来这些就是要复习的。3.2 记忆算法的实现用代码实现上面的逻辑核心就是两个函数一个是学完一个单词后更新状态一个是计算下一次复习时间。import datetime # 各阶段对应的复习间隔天 INTERVALS [0, 1, 2, 4, 7, 15, 30] def update_stage(record, is_correct): 根据是否答对更新记忆阶段 is_correct: True表示答对False表示答错 if is_correct: # 答对了阶段前进一格最多到最后一格 record[stage] min(record[stage] 1, len(INTERVALS) - 1) else: # 答错了阶段回退这里回退两格让生词尽快再次出现 record[stage] max(record[stage] - 2, 0) # 重新计算下次复习时间 days INTERVALS[record[stage]] now datetime.datetime.now() record[next_review] (now datetime.timedelta(daysdays)).strftime(%Y-%m-%d %H:%M:%S) record[last_review] now.strftime(%Y-%m-%d %H:%M:%S) if not is_correct: record[mistake_count] 1 return record注意我在这里做的一个关键决定是答错的时候“回退两格”而不是“回退到零”。回退到零的惩罚太狠了用户稍微失误一次这个词就永远在重学学习体验特别差。回退两格意味着这个词会在一两天内重新出现但不会让人挫败。这种“温和惩罚”机制在真实使用中明显比严格重置更让人愿意坚持。还有个细节stage达到最大值6以后就不再增加了因为这个词已经进入长期记忆通道30天后再复习一次基本就不会忘。未来如果你还想更进一步可以把stage6的间隔从30天换成60天变成“熟悉但不完全掌握”的长期复习科目。3.3 获取今天需要学习的单词记忆逻辑做完了还有个问题是“每天学多少新词、复习多少旧词”。我采用的办法是比例控制新词每批15个复习词数不设上限但优先展示已经到期的单词。def get_today_words(db, new_count15): now datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) # 获取到期待复习的词 review_words db.query( SELECT w.*, s.stage, s.mistake_count FROM words w JOIN study_records s ON w.id s.word_id WHERE s.next_review ? ORDER BY s.next_review LIMIT 50, (now,) ) # 如果复习的词不足 new_count用新词补齐 if len(review_words) new_count: need new_count - len(review_words) new_words db.query( SELECT * FROM words WHERE id NOT IN (SELECT word_id FROM study_records) LIMIT ?, (need,) ) review_words.extend(new_words) return review_words这里我把复习优先于新词学习放进了SQL里。你可以想象一个用户某天特别忙只打开了软件三分钟那他应该先看该复习的词而不是先学新词。否则旧词越积越多系统就失衡了。这算是项目管理中的一个经典概念——先还旧债再借新债。4. 界面与交互用Tkinter做出能每天使用的工具4.1 界面设计思路Tkinter做出来的界面比较简单朴素但这不代表它不好用。关键是交互逻辑要顺畅。我设计了三个页面学习页、复习页、统计页。三个页面通过左侧的导航按钮切换。学习页显示一个单词卡片左侧是单词和音标右侧是释义。用户心里默念“认识”或“不认识”然后点击按钮进入下一个单词。复习页显示一个单词让用户在输入框里拼写或选择正确的释义系统自动判断对错并调用记忆引擎更新状态。统计页显示今天学了多少词、复习了多少词、累计掌握多少词、连续打卡多少天。我觉得最关键的界面设计是“卡片流”。不是一次性把所有单词列在屏幕上而是每次只给一个单词配合“认识”“模糊”“不认识”三个按钮。这种单焦点交互模式能迫使用户集中注意力而不是一边滚动列表一边走神。这几个按钮的逻辑很简单点击后调用记忆引擎的update_stage然后加载下一个单词。为了让界面不显得太单调我把单词本体的字体故意调大放在界面中央偏上的位置音标用灰色小字排在下方释义默认隐藏。用户必须做出“认识/不认识”的判断之后才显示释义。这个小设计模拟了真实考试环境——先回忆再看答案。4.2 核心界面代码界面代码没什么高科技给你看一个学习页核心切换逻辑的参考import tkinter as tk from tkinter import messagebox class StudyView(tk.Frame): def __init__(self, master, word_service): super().__init__(master) self.word_service word_service self.word_list word_service.get_today_words() self.index 0 self.word_label tk.Label(self, font(Helvetica, 28, bold)) self.word_label.pack(pady40) self.phonetic_label tk.Label(self, font(Helvetica, 12), fggray) self.phonetic_label.pack() self.meaning_label tk.Label(self, font(Microsoft YaHei, 14), fg#333) self.meaning_label.pack(pady30) btn_frame tk.Frame(self) btn_frame.pack(pady20) tk.Button(btn_frame, text认识, width10, commandlambda: self.answer(True)).grid(row0, column0, padx10) tk.Button(btn_frame, text不认识, width10, commandlambda: self.answer(False)).grid(row0, column1, padx10) self.show_word() def show_word(self): if self.index len(self.word_list): messagebox.showinfo(完成, 今天的单词已经全部学完啦) return word self.word_list[self.index] self.word_label.config(textword[word]) self.phonetic_label.config(textword[phonetic]) self.meaning_label.config(text) # 初始不显示释义 def answer(self, known): word self.word_list[self.index] self.word_service.update_record(word[id], is_correctknown) self.index 1 self.show_word()有些同学会觉得这个界面也太简单了吧。但实际做下来这个界面已经够用了而且是每天都能顺畅使用的程度。你想想背单词工具最重要的使用场景是碎片时间——地铁上、排队时。如果你的界面花里胡哨反而干扰注意力。真正让用户留下来的是那个“今日挑战完成”的成就感。我遇到很多人的毕业设计界面做得特别复杂数据可视化、动画特效都堆上去结果核心逻辑一塌糊涂程序跑起来卡顿。做工具类应用先保证能用、好用再追求好看。4.3 连接数据库的操作封装界面代码里用到的word_service是我封装的一层业务对象。它负责协调数据库和界面避免界面直接访问sqlite3的cursor对象。import sqlite3 class WordService: def __init__(self, db_path): self.conn sqlite3.connect(db_path) self.conn.row_factory sqlite3.Row def get_today_words(self): # 具体SQL逻辑略参考前面 pass def update_record(self, word_id, is_correct): row self.conn.execute( SELECT * FROM study_records WHERE word_id?, (word_id,) ).fetchone() record dict(row) record update_stage(record, is_correct) self.conn.execute( UPDATE study_records SET stage?, last_review?, next_review?, mistake_count? WHERE word_id?, (record[stage], record[last_review], record[next_review], record[mistake_count], word_id) ) self.conn.commit() def close(self): self.conn.close()这里有个实践技巧conn.row_factory sqlite3.Row这个设置非常有用它让查询结果可以像字典一样通过列名访问而不是只能用下标。代码读起来清晰很多也避免你把字段顺序搞错。数据操作全部走参数化查询一定不要用字符串拼接SQL这是安全底线也能避免因引号、特殊字符导致的语法错误。5. 扩展成小程序端让手机也能用5.1 为什么要做一层API既然标题里提到了“APP”“小程序”那项目就不能只停留在桌面。我的做法是给core层加一个Flask接口层把单词查询、学习记录、复习计划这些能力通过HTTP接口暴露出去微信小程序只要请求这个接口就能完成数据交互。好处很明显里面的记忆算法、数据库、业务逻辑全部不用重写小程序端只做展示和输入。Flask是我个人觉得最轻的Python Web框架几十行代码就能架起一个可用API服务。一个人维护的项目不需要Django那种重型框架。5.2 Flask接口实现这里做一个基础的API设计from flask import Flask, request, jsonify import sqlite3 app Flask(__name__) DB_PATH words.db def db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn app.route(/api/daily_words, methods[GET]) def daily_words(): 获取今日学习和复习的单词 conn db() words conn.execute( SELECT w.id, w.word, w.phonetic, w.meaning FROM words w LEFT JOIN study_records s ON w.id s.word_id WHERE s.next_review IS NULL OR s.next_review datetime(now) LIMIT 15 ).fetchall() conn.close() return jsonify([dict(row) for row in words]) app.route(/api/answer, methods[POST]) def answer(): 提交答题结果 data request.get_json() word_id data[word_id] is_correct data[is_correct] # 这里调core层update_stage逻辑 return jsonify({code: 0, msg: ok}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)如果你是在本地调试小程序开发工具里可以直接请求http://127.0.0.1:5000。如果要上线让手机访问就需要一台云服务器把Flask服务部署上去。这里提醒一下app.run(host0.0.0.0)是让服务监听所有网卡部署到公网之前一定要加访问控制和HTTPS否则任何人都能调用你的接口。课程设计阶段本地跑没问题真正上线发布时安全性要重视。我说一句经验之谈小程序端的“技术含量”不在界面而在接口设计。只要接口设计得足够清晰小程序前端哪怕换个新手来做也能很快接上。你把/api/daily_words返回的JSON结构设计成前端最需要的字段前端就不需要做二次数据处理。5.3 小程序端的页面结构小程序端的页面可以很简单首页显示今日单词卡片点击“认识”或“不认识”后请求/api/answer然后切换到下一个词复习页展示历史错词我的页面显示学习统计。小程序和桌面端共用同一个后端数据就是同步的。在电脑上学过的单词手机上能继续复习这体验已经很完整了。实际开发时我建议小程序端只做学习和复习两个核心页面就够了统计页可以后续再加避免一开始铺太大导致做不完。6. 实操中遇到的问题与排查经验6.1 中文乱码问题这个坑百分之九十九写爬虫的人都会踩。我用requests拉取页面时直接resp.text拿到内容发现有部分中文乱码查了半天才发现网站用的是UTF-8但requests默认按ISO-8859-1去猜编码了。解决办法很简单获取后主动指定编码resp.encoding resp.apparent_encodingapparent_encoding会根据页面内容自动判断编码方式准确率很高。在解析任何外部网页之前都先做这一步可以少踩很多坑。另外SQLite里读写中文本身没问题前提是连接数据库时不要乱设置编码Python3自带的sqlite3模块默认就是UTF-8保持默认就好。6.2 数据库重复数据问题词库清洗时如果没做去重后面学习页面会出现两个一模一样的单词看起来很尴尬。我的方案是在建表时给word字段加上UNIQUE约束同时在插入时使用INSERT OR IGNOREdef import_words(db, words): conn db conn.execute(BEGIN) for item in words: conn.execute( INSERT OR IGNORE INTO words (word, phonetic, pos, meaning) VALUES (?, ?, ?, ?), (item[word], item[phonetic], item[pos], item[meaning]) ) conn.commit()加上INSERT OR IGNORE之后重复词自动跳过。如果你的源词库质量实在不行建议先跑一遍去重脚本再入库。数据库的“唯一约束”是最后的防线“清洗脚本”才是主力。6.3 打包成exe之后打不开桌面端做好以后用PyInstaller打包成exe给室友测试结果一运行就闪退。这个问题几乎每个人都会遇到。排查方法是在打包时不要加--noconsole参数先让错误信息显示在控制台里要么打一个带日志的版本pyinstaller -F -w app.py --hidden-importsqlite3闪退最常见的原因是资源文件路径不对。比如你的代码里用了相对路径resources/words.db脚本运行时是在项目根目录但打包后exe的工作目录可能变了。稳妥的写法是import os, sys def resource_path(relative_path): 获取资源文件的绝对路径兼容打包后的场景 base_path getattr(sys, _MEIPASS, os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path) DB_PATH resource_path(resources/words.db)用这个函数包装所有资源路径打包后就不会因为找不到数据库而闪退。这个技巧我也是踩了好几次坑才记住的。还有一个注意点如果你用了--onefile模式打包程序启动时会把所有资源解压到临时目录第一次启动会稍慢属于正常现象。6.4 学习记录清零问题有用户反馈说第二天打开软件昨天的学习记录都不见了。排查后发现是学习记录表的next_review字段格式问题。我在插入时用了datetime.now()默认格式到了第二天SQL里的datetime(now)返回的是一个带时区的字符串和存储的格式不一致导致比较出错。解决办法是统一用同一种时间格式。我在代码里所有地方都用strftime(%Y-%m-%d %H:%M:%S)格式化SQL里的datetime(now)也通过参数传递进去不在SQL里直接做转换。时间处理的“单一数据源原则”在这里体现得特别明显你的代码里只能有一个地方产生当前时间字符串所有模块都从那里拿。问题原因解决中文乱码网页编码判断错误设置resp.encodingresp.apparent_encoding重复单词源数据存在重复建表加UNIQUE约束插入用INSERT OR IGNORE打包闪退相对路径失效用sys._MEIPASS转绝对路径记录消失时间格式不统一统一用strftime格式化时间复习顺序不对排序字段为NULL用ORDER BY s.next_review IS NULL, s.next_review6.5 体验优化怎么做“顺滑”代码都跑通以后我花了很多时间打磨一些“手感”细节。比如答题反馈点击“认识”之后释义刷地出来并跳下一个单词中间加一个极短的延迟让用户看到自己的判断有没有答对比如界面上方显示一个进度条“今日已完成 12/15”让人有种“快结束了”的感觉再比如错词率超过百分之三十的时候就不再塞新词而是复习旧词。这些优化没有一项是算法层面的突破但合在一起整体的使用体验就上来了。我给自己的原则是每个交互反馈都要在300毫秒内响应否则用户会觉得卡。Tkinter本地应用理论上没问题但如果数据库查询很慢比如词库十万词没建索引就要在word和next_review字段上建索引CREATE INDEX idx_words_word ON words(word); CREATE INDEX idx_records_review ON study_records(next_review);加了索引之后哪怕数据库有几万条数据查询也是毫秒级的。课程设计的数据库规模通常不会太大但养成建索引的习惯不会吃亏。最后聊几句个人体会这个项目从头到尾做完我最大的感受是工具类应用的难点从来不是哪个单独的算法而是“把所有细节整合在一起的系统思维”。从爬虫获取词库、清洗数据、设计数据库、写记忆算法、搭Tkinter界面、再到提供Flask接口每一步都不难难的是你在做爬虫的时候就想到后面数据库怎么存做数据库的时候就想到记忆算法怎么读做算法的时候就想到用户界面需要展示什么。这其实就是一个工程化思维的过程远比背下来某个框架的API更有价值。如果你打算在这个项目基础上扩展我个人建议的优先级是先给词库加上真人发音用pygame或者调用系统TTS这会让学习的真实感强很多然后把“每日打卡”“学习统计”做成可以分享的卡片这个小功能对产品留存帮助很大最后如果你想要挑战一点还可以加一个“词根词缀拆解”模块把单词和词根库关联起来这算是一个很亮眼的卖点。我自己就是在这个地方停下来的没继续做因为再往下就是产品迭代的事情了不是课程设计能覆盖的范围。到这一步这个项目已经可以作为一份完整的“设计与实现”作品交出去了。

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

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

免费获取报价 →
↑