目录
一、程序总体介绍
- 系统名为“慧教云端”,目的是为了为更多不会使用prompt工程的普通学生更好的使用以星火讯飞为核心的AI应用提高自己的学习能力。
- 系统提供了四种不同风格对应四种不同科目的老师,也配备了飞花令、成语接龙、古代文化常识、创意写作等四种娱乐模式,希望寓教于乐。未来,程序将退出语音测评、语音互动、语音娱乐等模式,希望同学们可以很好的利用人工智能进行口语练习。
- 系统采取前后端分离的框架,前端基于vue3-cli,后端基于Django,数据库使用了MySQL。
二、程序功能
- 用户可以通过注册功能创建个人账户,提供必要的个人信息,如用户名、密码、邮箱等。
- 登录功能允许用户输入注册时的用户名和密码,验证后进入系统。
- 登出功能允许用户安全退出系统,保护用户隐私和数据安全。
- 才华横溢的国文老师欧阳老师:今日之学,当以经典为本,以白话为辅。咱们将在古典诗词和现代文之间穿梭,让每一篇文章都能深入浅出,妙趣横生。
- 温柔甜美的英语老师小惠老师:根据英语水平灵活应用中英文,并且选取合适的单词和语法来帮助学习。
- 风趣幽默的科学老师老杨:不只要探索科学的奥秘,还要看看哪些科学现象能让你捧腹大笑!准备好了吗?让我们开启这段既搞笑又有趣的科学之旅吧!
- 严谨认真的计算机老师史老师:在这里,我们将深入探讨计算机科学的原理和技术。请准备好你的笔记本和编程工具,我们将以严谨和系统的方式学习每一个知识点并进行适当的扩展。让我们开始吧。
- 每个页面都有不同风格的老师头像,以及不同风格的背景音乐,不同风格的背景图片,为用户提供不同风格的服务。
- 飞花令:设置一个关键字,如“花”、“春”、“月”、“云”、“夜”、“风”、“雨”、“雪”等诗词中的高频字,机器人小飞与用户轮流说出含有关键字的诗句。小飞会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关介绍作者、诗词意思、写作背景、作者趣事。
- 成语接龙:由一个人先说出一个成语,比如“半途而废”;另一个人要说出一个与上一个成语的最后一个字相同的成语,比如“废话连篇”,机器人小龙与用户轮流成语接龙。小龙会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关成语典故、成语意思。
- 古代文化常识:机器人小文将向用户提问中国高中生必备的中国文化常识并进行相关拓展。小龙会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关典故和文化常识。
- 创意写错:机器人小猹将随时模仿鲁迅先生的独特的讽刺、夸张、反讽的写作风格,语言简练凝练,字里行间透露出对现实的愤怒和悲悯。为用户提供很好的写作思路与写作技巧。
- 开发语音识别系统,能够准确识别学生的语音输入,并根据语音的发音、语调、语速等标准进行评分。
- 利用语音识别和自然语言处理技术,实现与用户的语音交互。用户可以通过语音指令来控制系统,获取信息或服务。
- 结合以上两点进行创新拓展,如配音练习、对抗游戏、演讲效果评估等。
三、程序总体架构
访问层
- PC端:系统在个人电脑端的访问界面。即用户通过浏览器访问系统的用户界面,例如网页应用或管理后台。
前端UI
- Element UI:一种基于Vue.js的UI组件库,提供了丰富的UI组件,如按钮、表单、对话框等,方便开发者快速构建美观且一致的用户界面。
- HTML (HyperText Markup Language): 用于构建网页的基础结构。例如,定义标题、段落、链接、图像等。
- CSS (Cascading Style Sheets): 用于控制网页的样式和布局。例如,定义颜色、字体、边距、对齐方式等。
- Vue.js:一种用于构建用户界面的渐进式JavaScript框架,支持组件化开发。它可以帮助开发者构建单页面应用(SPA),通过组件化的方式提高代码的可维护性和复用性。
- Node.js:一个基于Chrome V8引擎的JavaScript运行环境。Node.js不仅能用于服务器端开发,还能在前端开发中通过其工具链(如Webpack、Babel等)进行编译、打包等操作。
- Vue CLI:Vue的命令行工具,提供了一套标准化的项目架构和开发工具链,帮助开发者快速生成和管理Vue项目。可以通过一系列命令进行项目创建、开发、构建和发布。
交互层
- Vue Router:Vue官方的路由管理器,允许开发者通过路由配置将不同的URL映射到不同的组件,构建单页面应用(SPA),实现页面的无刷新跳转。
- Axios:一个基于Promise的HTTP客户端,允许在浏览器和Node.js中发送异步HTTP请求。常用于与后端服务器进行数据交互,支持GET、POST、PUT、DELETE等多种请求方式。
- GET请求: HTTP协议中用于请求数据的一种方法,通常用于从服务器获取数据。
- POST请求: HTTP协议中用于发送数据的一种方法,通常用于向服务器提交数据。
- npm (Node Package Manager):Node.js的包管理工具,用于管理项目中的依赖库和工具。通过npm,开发者可以方便地安装、更新和删除项目中的各种依赖包。
后端部分
- controller层:处理前端发送的请求,并调用service层进行业务逻辑处理。它是前端与后端交互的中间层,负责接受请求、校验参数、调用服务层处理逻辑以及返回结果。
- view层:负责将数据渲染成用户可以浏览的视图。在传统MVC架构中,这一层通常用于生成HTML页面并返回给客户端。
- service层:包含业务逻辑,处理具体的业务需求。它从controller层接收请求,处理业务逻辑,然后与DAO层进行数据交互,最终返回处理结果。
- Model层:定义了数据模型,通常包含数据验证、转换等逻辑。在ORM(对象关系映射)框架中,这一层负责将数据库中的数据映射到代码中的对象,并定义数据的结构和关系。
- Mapper.xml:在MyBatis等框架中,Mapper.xml文件用于将SQL语句和实体对象进行映射。通过配置文件将SQL查询与Java对象进行绑定,实现数据持久化操作。
- Mapper层(Dao层):数据访问对象层,负责与数据库进行交互,执行增删改查操作。它是服务层与数据库之间的桥梁,通过SQL语句对数据库进行操作,并将结果返回给服务层。
数据库部分
- MySQL:一种关系型数据库管理系统,用于存储和管理数据。MySQL以其高性能、可靠性和易用性广泛应用于各种应用场景。
- 数据库的建立: 包括创建数据库实例、配置数据库参数等。
- 表的建立: 包括定义表的结构(字段、数据类型、主键、索引等)和关系(外键等)。
- 数据的存储: 包括插入、更新、删除和查询数据,以及数据的备份和恢复等操作。
Django 是一个高级的 Python 网络框架,可以快速开发安全和可维护的网站。Django负责处理网站开发中麻烦的部分,因此可以专注于编写应用程序,而无需重新开发。
Django 可以使你的应用具有以下优点:
完备性:Django 遵循“功能完备”的理念,提供开发人员可能想要“开箱即用”的几乎所有功能。
通用性:Django 可以用于构建几乎任何类型的网站。它可以与任何客户端框架一起工作,并且可以提供几乎任何格式(包括 HTML,Rss 源,JSON,XML 等)的内容。
安全性:Django可以自动保护网站的框架来避免许多常见的安全错误。例如,Django 提供了一种安全的方式来管理用户账户和密码,避免了常见的错误,比如将 session 放在 cookie 中这种易受攻击的做法(取而代之的是 cookies 只包含一个密钥,实际数据存储在数据库中)或直接存储密码而不是密码哈希。默认情况下,Django 可以防范许多漏洞,包括 SQL 注入,跨站点脚本,跨站点请求伪造和点击劫持 。
可扩展:Django 使用基于组件的“无共享”架构 (架构的每一部分独立于其他架构,因此可以根据需要进行替换或更改)。在不用部分之间有明确的分隔意味着它可以通过在任何级别添加硬件来扩展服务:缓存服务器,数据库服务器或应用程序服务器。
可维护性:Django 代码编写是遵照设计原则和模式,鼓励创建可维护和可重复使用的代码。特别是它使用了不要重复自己(DRY)原则,所以没有不必要的重复,减少了代码的数量。
灵活性:Django 是用 Python 编写的,它在许多平台上运行。这意味着你不受任务特定的服务器平台的限制,并且可以在许多种类的 Linux,Windows 和 Mac上运行应用程序。
Django采取了MVC模式,即将应用程序分解成三个组成部分:model(模型),view(视图),和 controller(控制器)。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为 MTV模式。其中:
M——管理应用程序的状态(通常存储到数据库中),并约束改变状态的行为(或者叫做“业务规则”)。
C——接受外部用户的操作,根据操作访问模型获取数据,并调用“视图”显示这些数据。控制器是将“模型”和“视图”隔离,并成为二者之间的联系纽带。
V——负责把数据格式化后呈现给用户。
前端开发环境
- 浏览器:用户与前端应用交互的平台。浏览器解析HTML和CSS文件,执行JavaScript代码,渲染用户界面,并处理用户输入与事件。
- HTML (HyperText Markup Language): 构建网页的基础技术,负责定义网页的结构和内容。例如,通过HTML标签来定义标题、段落、图像、链接等。
- CSS (Cascading Style Sheets): 用于控制网页的样式和布局。通过CSS,可以定义颜色、字体、边距、对齐方式、动画等,使网页内容呈现出美观且一致的外观。
单页面应用(SPA)
- 单页面应用 (SPA):SPA允许用户在不重新加载整个页面的情况下与Web应用进行交互。所有的资源(HTML、CSS、JavaScript)通常在首次加载时获取,随后通过JavaScript动态更新页面内容,从而提供更快的响应和更流畅的用户体验。
- 通过按需加载不同的模块来展示不同的内容,可以减少页面重新加载的时间,提高应用的性能和用户体验。
前端构建工具
- Webpack:一个现代JavaScript应用程序的静态模块打包器。Webpack能够递归地构建应用的依赖关系图,将项目中的各种资源(JavaScript、CSS、图片等)打包成一个或多个静态文件。
- 开发模式 (dev): 通常用于开发过程中,提供热重载、模块热替换等特性,以提高开发效率。
- 生产模式 (build): 用于生产环境,启用各种优化,例如压缩文件、代码分割、移除未使用的代码等,以减少文件大小和提高加载速度。
前端核心库和框架
- Vue.js:一个用于构建用户界面的渐进式框架。Vue的核心是一个响应式的数据绑定系统,可以将数据变化自动映射到DOM。
- Vue Router: Vue官方的路由管理器,用于构建SPA。它允许开发者定义路由和视图之间的映射,实现页面的无刷新跳转。
- Vuex: Vue的状态管理库,用于在多个组件之间共享状态。Vuex通过一个全局的状态树来管理应用的所有状态,使状态管理更加集中和可预测。
- Vue Component: Vue组件是Vue应用的基本构建块。每个组件通常包含自己的模板、样式和逻辑,可以复用和嵌套,从而提高代码的可维护性和复用性。
前端代码组织
- app.js:是应用的入口文件,负责初始化Vue实例和配置路由等。在这个文件中,通常会导入Vue、Vue Router、Vuex等,并配置和挂载根组件。
- 页面容器:包含SPA中的不同页面,如首页、登录页等。页面容器通常是Vue组件,代表应用的不同视图。
前端组件化
- 页面组件:SPA中的各个页面,如Home、Login等。页面组件是较高层级的组件,通常用于表示应用的不同视图和功能模块。
- UI组件:可复用的UI元素,如按钮、输入框等。UI组件是较小且通用的组件,可以在多个页面和组件中使用。
- 公共组件:在多个页面或组件中共享的组件。例如,导航栏、页脚等常见的布局元素,通常会作为公共组件进行复用。
状态管理和路由配置
- State:Vuex中的状态对象,存储应用的状态。state是Vuex store中的数据源,组件可以通过访问store来读取或更新state。
- Actions:Vuex中的行为,用于执行异步操作或复杂的同步操作。actions可以调用mutations来更新state,并且可以包含业务逻辑,例如向API发送请求。
- Vue Router的配置,定义了应用的路由地址和对应的页面组件。通过配置路由表,可以将不同的URL映射到不同的组件,实现页面的无刷新跳转。
第三方库和工具类
- 第三方库:包括各种插件和库,用于扩展Vue应用的功能。例如,图表库(如Chart.js)、表单验证库(如VeeValidate)等。
- 工具类:用于处理数据或在应用中实现某些功能的辅助类。例如,数据过滤器、中间件、日期处理库等。
网络请求
- Axios:一个基于Promise的HTTP客户端,用于浏览器和Node.js中发送异步HTTP请求。Axios支持请求和响应拦截器、请求取消、自动转换JSON数据等功能。
- Network:网络请求的抽象层,可能包括请求拦截器、响应拦截器等。通过封装网络请求逻辑,可以更方便地处理请求和响应的统一处理,例如添加认证token、处理错误等。
- API:应用程序接口,用于前端向后端发送请求并接收响应。API通常由后端提供,定义了前后端交互的数据格式和操作方式。
四、后端数据库设计
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, username, password=None, **extra_fields):
if not username:
raise ValueError('The Username field must be set')
user = self.model(username=username, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(username, password, **extra_fields)
class Users(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=150, unique=True, primary_key=True)
password = models.CharField(max_length=128)
date_joined = models.DateTimeField(auto_now_add=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
age = models.PositiveIntegerField(null=True, blank=True)
groups = models.ManyToManyField(
'auth.Group',
verbose_name='groups',
blank=True,
related_name='custom_user_set' # 自定义 related_name 避免冲突
)
user_permissions = models.ManyToManyField(
'auth.Permission',
verbose_name='user permissions',
blank=True,
related_name='custom_user_permissions' # 自定义 related_name 避免冲突
)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['first_name', 'last_name', 'age']
objects = UserManager()
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
def get_short_name(self):
return self.first_name
这是一个自定义用户模型,用于替代Django内置的用户模型。这个模型继承了AbstractBaseUser和PermissionsMixin类,并定义了用户的各种属性和管理方法。
UserManager类
这个类是用户管理器,用于管理用户对象的创建和操作。它继承自BaseUserManager,并定义了两个方法:create_user和create_superuser。
create_user(self, username, password=None, extra_fields):用于创建一个普通用户。首先检查是否提供了用户名,如果没有则抛出一个值错误。创建用户对象,并通过set_password方法为用户设置密码(密码会进行加密处理)。保存用户对象到数据库。返回创建的用户对象。
create_superuser(self, username, password, extra_fields):用于创建一个超级用户。在创建超级用户时,默认设置is_staff和is_superuser为True。调用create_user方法来创建用户。
Users类
这个类是自定义的用户模型,继承了AbstractBaseUser和PermissionsMixin。
属性:
username:用户名,字符字段,最大长度为150,必须唯一,并作为主键。
password:密码,字符字段,最大长度为128。
date_joined:用户注册日期,自动设置为当前日期和时间。
first_name:用户的名,字符字段,最大长度为30。
last_name:用户的姓,字符字段,最大长度为30。
is_active:布尔字段,表示用户是否处于激活状态,默认值为True。
is_staff:布尔字段,表示用户是否是工作人员,默认值为False。
age:用户年龄,正整数字段,可以为空或为空白。
groups:用户所属的权限组,多对多关系,使用自定义的related_name以避免与默认用户模型的冲突。
user_permissions:用户的权限,多对多关系,使用自定义的related_name以避免与默认用户模型的冲突。
配置:
USERNAME_FIELD:指定用于标识用户的字段,这里设置为username。
REQUIRED_FIELDS:创建用户时必须提供的字段列表,这里设置为first_name、last_name和age。
objects:
设置默认的用户管理器为UserManager。
方法:
get_full_name(self):返回用户的全名,格式为“名 姓”。
get_short_name(self):返回用户的名。
这个模型通过继承AbstractBaseUser和PermissionsMixin提供了对用户密码和权限的管理功能,并且可以通过定义的用户管理器来创建普通用户和超级用户。通过自定义的字段和方法,这个模型能够满足具体的业务需求。
1. 成绩单模型 (Transcript)
描述: 记录用户在某一学期或某一阶段的成绩单。
关系: 一个用户可以有多份成绩单(用户与成绩单是一对多关系)。
主要字段: 成绩单编号、创建日期、用户外键等。
2. 课程模型 (Course)
描述: 记录学校或培训机构提供的课程信息。
关系: 一个成绩单可以包含多门课程(成绩单与课程是多对多关系),一个用户也可以选修多门课程。
主要字段: 课程编号、课程名称、学分、授课教师等。
3. 成绩模型 (Grade)
描述: 记录用户在某门课程中的具体成绩。
关系: 一个成绩单包含多门课程的成绩(成绩单与成绩是一对多关系),每个成绩记录关联到一个用户和一门课程。
主要字段: 成绩编号、课程外键、用户外键、成绩数值、学期等。
4. 教师模型 (Teacher)
描述: 记录教师的信息,他们可能是课程的授课教师。
关系: 一个教师可以教授多门课程(教师与课程是一对多关系),课程中有多个学生(教师与用户通过课程间接关联)。
主要字段: 教师编号、姓名、职称、所授课程等。
5. 班级模型 (Class)
描述: 记录班级的信息,每个班级有多个学生。
关系: 一个班级可以包含多个用户(班级与用户是一对多关系),可以选修多门课程(班级与课程是多对多关系)。
主要字段: 班级编号、班级名称、班主任等。
五、后端核心程序设计
A5.
│ manage.py
│
├─A5
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ └─ __init__.py
│
├─GPT
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ urls.py
│ │ views.py
│ │ __init__.py
│ │
│ ├─functions
│ │ │ SparkApi.py
│ │ │ teacherModel.py
│ │ └─ __init__.py
│ │
│ └─migrations
│ └─ __init__.py
│
├─templates
└─Users
│ admin.py
│ apps.py
│ forms.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
└─migrations
0001_initial.py
__init__.py
register函数:
装饰器 @csrf_exempt 禁用了CSRF令牌的检查。
接受POST请求,用于处理用户注册。
解析请求体中的JSON数据,获取用户名、密码和确认密码。
使用 UserCreationForm 表单验证数据的有效性。
如果表单有效,创建用户并自动登录。
如果表单无效,返回表单的错误信息。
如果请求方法不是POST,返回欢迎信息。
@csrf_exempt
def register(request):
if request.method == 'POST':
# 从 request.POST 中获取前端发送的数据
data = json.loads(request.body.decode('utf-8'))
username = data.get('username')
password = data.get('password')
confirm_password = data.get('confirmPassword')
# 创建用户注册表单并验证数据
form = UserCreationForm({'username': username, 'password1': password, 'password2': confirm_password})
if form.is_valid():
# 保存用户
user = form.save()
# 登录用户
login(request, user)
return JsonResponse({'success': True, 'message': 'Registration successful'})
else:
# 返回表单验证错误信息
errors = dict(form.errors.items())
print(errors)
return JsonResponse({'success': False, 'errors': errors})
else:
# 如果请求方法不是 POST,返回一个 JSON 欢迎信息
return JsonResponse({'message': 'Welcome to the registration page!'})
login函数:
同样使用 @csrf_exempt 装饰器。
接受POST请求,用于处理用户登录。
解析请求体中的JSON数据,获取用户名和密码。
使用 authenticate 函数验证用户凭据。
如果认证成功,登录用户并返回成功消息。
如果认证失败,返回认证失败的消息。
如果请求方法不是POST,返回欢迎信息
@csrf_exempt
def user_login(request):
if request.method == 'POST':
data = json.loads(request.body.decode('utf-8'))
# 从 request.POST 中获取前端发送的数据
username = data.get('username')
password = data.get('password')
# 使用 Django 提供的 authenticate() 函数验证用户凭据
user = authenticate(request, username=username, password=password)
if user is not None:
# 如果认证成功,登录用户
login(request, user)
return JsonResponse({'success': True, 'message': 'Login successful'})
else:
# 如果认证失败,返回错误消息
return JsonResponse({'success': False, 'message': 'Invalid credentials'})
else:
# 如果请求方法不是 POST,返回一个 JSON 欢迎信息
return JsonResponse({'message': 'Welcome to the login page!'})
logut函数:
使用 @csrf_exempt 装饰器。
接受POST请求,用于处理用户登出。
调用 logout 函数使当前用户登出。
如果请求成功,返回登出成功的消息。
如果请求方法不是POST,返回只允许POST方法的消息,并给出405状态码。
@csrf_exempt
def user_logout(request):
if request.method == 'POST':
logout(request)
return JsonResponse({'success': True, 'message': 'Logout successful'})
return JsonResponse({'message': 'Only POST method is allowed'}, status=405)
SparkApi.py:
实现了一个基于websocket的客户端,用于与一个提供API服务的服务器进行通信。以下是代码的主要组成部分及其功能:
导入模块:代码首先导入了所需的Python模块,包括多线程、日期时间处理、加密算法、JSON处理、URL编码、websocket通信等。
Ws_Param类:这个类用于初始化与API服务通信所需的参数,包括APPID、APIKey、APISecret和Spark_url。它还包含了创建请求URL的方法。
创建URL方法:create_url方法用于生成符合API服务鉴权要求的URL。它首先生成当前时间的RFC1123格式的时间戳,然后构造一个待签名的字符串,使用HMAC-SHA256算法进行签名,并将其转换为Base64编码的字符串。最后,将签名和其他鉴权信息拼接成URL。
WebSocket事件处理函数:定义了四个WebSocket事件处理函数,分别用于处理WebSocket连接建立、消息接收、错误和关闭事件。
run函数:这是一个在新线程中运行的函数,用于发送数据到服务器。它将生成的参数序列化为JSON格式并发送。
on_message函数:当从服务器接收到消息时,此函数会被调用。它解析JSON格式的消息,并根据消息中的代码判断是否有错误发生。如果没有错误,它会将接收到的内容累加到全局变量answer中,并在特定条件下关闭WebSocket连接。
gen_params函数:此函数用于生成发送给服务器的参数,包括app_id、uid、domain、temperature、max_tokens、top_k和用户的问题。
main函数:这是程序的入口点,它接收必要的参数并初始化WebSocketApp对象。然后,它设置SSL选项以忽略证书验证,并启动WebSocket客户端。
WebSocketApp对象:使用websocket.WebSocketApp创建WebSocket客户端对象,设置事件处理函数,并启动客户端。
整体来看,这段代码实现了一个客户端,它通过websocket与服务器进行通信,发送请求并接收响应。它使用了加密和鉴权机制来确保通信的安全性。代码中的多线程和事件驱动机制使得客户端能够在接收到消息时及时响应,同时处理其他任务。
TeacherModel.py:
导入模块:代码开始处导入了多个Python标准库模块,以及一个名为`GPT.functions.SparkApi`的自定义模块,这个模块可能包含了与Spark API进行交互的函数和逻辑。
初始对话内容:定义了一个名为`text`的列表,其中包含了一些JSON对象,每个对象都有`role`和`content`两个键,用于设置对话的背景或角色。
getText函数:`getText`函数用于创建一个新的对话内容条目,并将该条目添加到`text`列表中。这个函数接受`role`和`content`作为参数,并返回更新后的`text`列表。
getlength函数:`getlength`函数计算`text`列表中所有对话内容的总长度。
checklen函数:`checklen`函数确保`text`列表的长度不超过8000字节。如果超过,它会删除最早的对话内容条目,直到总长度满足要求。
role_of_teacher函数:`role_of_teacher`函数根据不同的教师角色(如中文、英文、科学、计算机等),设置不同的系统提示和助手的初始回答。
teacher_model函数:`teacher_model`函数是程序的核心,它负责处理用户的输入问题,通过Spark API获取回答,并将其添加到对话历史中。
A5/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("GPT/", include("GPT.urls")),
path("api/", include("Users.urls")),
]
GPT/urls.py:
from django.urls import path
from GPT.views import teacher_model,set_role
urlpatterns = [
path("teacher_model/", teacher_model, ),
path("set_role/", set_role, ),
]
GPT/urls.py:
from django.urls import path
from Users.views import register,user_login,user_logout
urlpatterns = [
path('register/', register),
path('login/', user_login),
path('logout/', user_logout),
]
六、前端程序设计
front-end-of-a5:.
│ .gitignore
│ env.d.ts
│ index.html
│ package-lock.json
│ package.json
│ pnpm-lock.yaml
│ README.md
│ tsconfig.app.json
│ tsconfig.json
│ tsconfig.node.json
│ vite.config.ts
│
├─dist
│ │ favicon.ico
│ │ index.html
│ │
│ └─assets
│ Cases-DBLH5obt.js
│ Cases-J-F-X9JV.css
│ Check-BRjEUFMn.js
│ Check-dGjJwNjH.css
│ Idea-B3rUwng2.css
│ Idea-CTRV8uD0.js
│ index-BhkMPI2j.js
│ index-CoiphWpS.css
│ index-yW-6OX0l.js
│ User-BceklG04.js
│ User-DMYpVj-2.css
│
├─public
│ favicon.ico
│ logo.png
│
└─src
│ App.vue
│ axios.js
│ main.ts
│
├─api
│ api.js
│ GPT.js
│
├─assets
│ │ logo.svg
│ │ main.css
│ │
│ ├─Entertainment
│ │ Culture-bgm.mp3
│ │ Culture.png
│ │ Culture_Backgrond.png
│ │ Luxun-bgm.mp3
│ │ LuXun.jpg
│ │ Luxun_Background.png
│ │ Luxun_Head.png
│ │ Poem-bgm.mp3
│ │ Poem.png
│ │ Poem_Background.png
│ │ Word-bgm.mp3
│ │ Word.png
│ │ Word_Background.png
│ │
│ ├─GPT
│ │ Chinese-background-music.mp3
│ │ Chinese.jpg
│ │ ChineseStudentHead.png
│ │ ChineseTeacherHead.png
│ │ Chinese_background.jpg
│ │ English.jpg
│ │ EnglishStudentHead.jpg
│ │ EnglishTeacherHead.jpg
│ │ English_background.jpeg
│ │ English_Background_Music.mp3
│ │ InformationStudentHead.jpg
│ │ InformationTeacherHead.jpg
│ │ Information_background.png
│ │ IT.jpg
│ │ IT_Background_Music.mp3
│ │ Science-Background_Music.mp3
│ │ Science.jpg
│ │ ScienceStudentHead.jpg
│ │ ScienceTeacherHead.jpg
│ │ Science_background.png
│ │
│ └─Login
│ │ Christmas_Trees.png
│ │ Cloud.png
│ │ Cloud2.png
│ │
│ └─font
│ iconfont.eot
│ iconfont.svg
│ iconfont.ttf
│ iconfont.woff
│ iconfont.woff2
│
├─components
│ EntertainmentChoosen.vue
│ Login.vue
│ MyStart.vue
│ Register.vue
│ SubjectChoosen.vue
│ TeacherChinese.vue
│ TeacherEnglish.vue
│ TeacherInformation.vue
│ TeacherScience.vue
│
└─router
index.ts
axios.js:
这个文件配置了用于API请求的axios实例。
导入axios库。
导出一个名为baseURL的常量,这是API请求的基础URL。
创建axios的实例instance,设置baseURL和timeout(请求超时时间,这里设置为5分钟)。
导出axios实例供其他组件或模块使用。
import axios from "axios";
export const baseURL = "http://localhost:8000"
const instance = axios.create({
baseURL: baseURL, // 你的 API 基础 URL
timeout: 300000, // 请求超时时间, 设定为5min
})
export default instance;
main.ts:
这是Vue应用的入口文件。
导入项目的主要CSS文件。
导入Arco Design的Vue组件库及其样式。
导入Vue的createApp函数,App组件,以及路由配置。
引入Element Plus组件库及其样式,Element Plus是一个基于Vue 3的组件库。
导入并注册Element Plus的图标组件。
创建Vue应用实例,并使用ArcoVue、路由和Element Plus。
挂载应用到DOM元素#app。
import './assets/main.css'
import ArcoVue from '@arco-design/web-vue';
import '@arco-design/web-vue/dist/arco.css';
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// Element Plus
import 'element-plus/theme-chalk/index.css' // 引入 ElementPlus 组件样式
// 图标和组件需要分开引入
import ElementPlus from 'element-plus'; // 引入 ElementPlus 组件
import { Edit } from '@element-plus/icons-vue' // 按需引入 Icon 图标
const app = createApp(App)
app.use(ArcoVue);
app.use(router)
// 全局注册 Icon 图标
app.component('Edit', Edit)
app.use(ElementPlus) // 全局挂载 ElementPlus
app.mount('#app')
vite.config.ts:
这是Vite构建工具的配置文件。
导入Node.js的fileURLToPath和URL模块。
导入Vite的defineConfig函数。
导入Vite的Vue插件。
定义Vite配置对象,包括插件、解析规则、服务器设置和代理配置。
使用vue()插件来支持Vue单文件组件。
设置路径别名@指向项目的src目录。
配置开发服务器,允许从任意主机访问。
设置代理规则,将/GPT路径代理到本地服务器http://127.0.0.1:8000,并开启日志记录。
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: "0.0.0.0"
},
proxy: {
'/GPT': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
//rewrite: (path) => path.replace(/^\/GPT/, '/GPT'),
pathRewrite: {
'^/GPT': '/GPT'//重写,
},
logLevel: 'debug'
}
},
})
- import { createRouter, createWebHistory } from 'vue-router'
- const router = createRouter({
- history: createWebHistory(import.meta.env.BASE_URL),
- routes: [
- {
- path: '',
- name: 'login',
- component: () => import('../components/Login.vue'),
- meta: { noLayout: true }, // 标记不使用布局的路由
- },
- {
- path: '/Register',
- name: 'register',
- component: () => import('../components/Register.vue'),
- meta: { noLayout: true }, // 标记不使用布局的路由
- },
- {
- path: '/Start',
- name: 'start',
- component: () => import('../components/MyStart.vue'),
- //meta: { noLayout: true }, // 标记不使用布局的路由
- },
- {
- path: '/TeacherModel/Chinese',
- name: 'Chinese',
- component: () => import('../components/TeacherChinese.vue')
- },
- {
- path: '/TeacherModel/English',
- name: 'English',
- component: () => import('../components/TeacherEnglish.vue')
- },
- {
- path: '/TeacherModel/Science',
- name: 'Science',
- component: () => import('../components/TeacherScience.vue')
- },
- {
- path: '/TeacherModel/Information_technology',
- name: 'Information',
- component: () => import('../components/TeacherInformation.vue')
- },
- ]
- })
- export default router
api.js:
import axios from "../axios.js";
export function registerUser(userData) {
return axios.post('/api/register/', userData);
}
export function loginUser(username, password) {
return axios.post('/api/login/', { username, password });
}
export function logoutUser() {
return axios.post('/api/logout/');
}
GPT.js:
import axios from "../axios.js";
import {baseURL} from "../axios.js";
import {ElMessage} from "element-plus";
export function getAnswer(data,type){
return axios.post("GPT/teacher_model/",
{question: data,
type: type
})
}
export function setRole(role){
return axios.post("GPT/set_role/", {role: role})
}
因为组件太多,代码繁杂,并且有很多重复内容,故只展示一部分代码,如下:
App.vue:
<template>
<a-config-provider :locale="zhCN">
<!-- 使用 v-if 条件渲染,当路由的 meta.noLayout 为 true 时不显示布局 -->
<div class="layout-demo" v-if="!route.meta.noLayout">
<a-layout style="height: 100vh">
<a-layout-header>
<div class="logo"></div>
<a-tag class="user-service" color="green" size="large" bordered>服务正常</a-tag>
<div class="user-info">
<icon-user size="30px" />
</div>
</a-layout-header>
<a-layout>
<a-layout-sider class="layout-sider" breakpoint="xxl" :width="220" collapsible
:collapsed="collapsed" @collapse="collapsed = $event">
<a-menu :defaultOpenKeys="['1']" :defaultSelectedKeys="['0_2']"
@menuItemClick="handleMenuClick">
<a-menu-item class="menu-item" key="/Start">
<icon-home />
我的主页
</a-menu-item>
<a-menu-item class="menu-item" key="/TeacherModel">
<icon-bulb />
老师模式
</a-menu-item>
<a-menu-item class="menu-item" key="/cases">
<icon-trophy />
娱乐模式
</a-menu-item>
<a-menu-item class="menu-item" key="/check">
<icon-voice />
语音打分
</a-menu-item>
<a-menu-item class="menu-item" key="/b">
<icon-book />
学习参考
</a-menu-item>
<a-menu-item class="menu-item" key="/c">
<icon-question-circle />
使用帮助
</a-menu-item>
</a-menu>
<a-button class="menu-exit" type="outline" status="danger" @click="logout">退出系统</a-button>
</a-layout-sider>
<a-layout-content>
<!-- 使用 RouterView 进行路由展示 -->
<RouterView v-slot="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
</RouterView>
</a-layout-content>
</a-layout>
</a-layout>
</div>
<!-- 当 meta.noLayout 为 true 时,直接显示路由组件 -->
<router-view v-else v-slot="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
</router-view>
</a-config-provider>
<ModeSelectionModal :show="isModalVisible" @update:show="isModalVisible = $event" @modeSelected="handleModeSelected" />
<ModeSelectionModalForEntertain :show="isModalForEntertainVisible" @update:show="isModalForEntertainVisible = $event" @modeSelected="handleModeSelectedForEntertain" />
</template>
<script setup lang="ts">
import { setRole } from "/src/api/GPT.js";
import { RouterView, useRoute } from 'vue-router'
import { computed, ref } from 'vue';
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
import {
IconHome,
IconUser,
IconTrophy,
IconBook,
IconQuestionCircle,
IconBulb,
IconVoice
} from '@arco-design/web-vue/es/icon';
import router from './router';
import ModeSelectionModal from './components/SubjectChoosen.vue';
import ModeSelectionModalForEntertain from './components/EntertainmentChoosen.vue';
import { logoutUser } from './api/api.js'; // 假设您有一个API方法来处理登出
const collapsed = ref(false);
const isModalVisible = ref(false); // 控制弹窗显示的状态变量
const isModalForEntertainVisible = ref(false); //娱乐模式弹窗
const showExit = computed(() => {
return collapsed.value ? 'none' : 'block'
});
const route = useRoute(); // 获取当前路由对象
function logout(){
logoutUser().then(() => {
alert('登出成功');
router.push('/').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
}).catch((err) => {
console.error('Navigation error:', err);
});
}
function handleMenuClick(key: string) {
if (key === '/TeacherModel') {
isModalVisible.value = true;
} else if (key == '/cases') {
isModalForEntertainVisible.value = true;
}
else {
router.push(key);
}
}
function handleModeSelected(mode: string) {
if (mode === 'Chinese') {
router.push('/TeacherModel/Chinese').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("chinese");
} else if (mode === 'English') {
router.push('/TeacherModel/English').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("english");
} else if (mode === 'Science') {
router.push('/TeacherModel/Science').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("science");
} else if (mode === 'Information_technology') {
router.push('/TeacherModel/Information_technology').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("information");
}
}
function handleModeSelectedForEntertain(mode: string) {
if (mode === 'Flower') {
router.push('/TeacherModel/Chinese').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("flower");
} else if (mode === 'Idiom') {
router.push('/TeacherModel/English').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("idiom");
} else if (mode === 'Culture') {
router.push('/TeacherModel/Science').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("culture");
} else if (mode === 'Luxun') {
router.push('/TeacherModel/Information_technology').then(() => {
}).catch((err) => {
console.error('Navigation error:', err);
});
setRole("luxun");
}
} //TODO 修改对应逻辑 目前仅显示页面
</script>
<style scoped>
.logo {
background-image: url(/logo.png);
background-position: center;
background-size: contain;
background-repeat: no-repeat;
height: 80px;
width: 100px;
margin-left: 20px;
}
.layout-demo :deep(.arco-layout-header),
.layout-demo :deep(.arco-layout-sider-children),
.layout-demo :deep(.arco-layout-content) {
color: black;
font-size: 16px;
font-stretch: condensed;
text-align: center;
}
.layout-demo :deep(.arco-layout-header) {
display: flex;
align-items: center;
}
.layout-demo :deep(.arco-layout-header) {
height: 64px;
background-color: white;
border: 1px solid rgb(236, 236, 236);
}
.layout-demo :deep(.arco-layout-sider) {
width: 206px;
background-color: white;
}
.layout-demo :deep(.arco-layout-sider-children) {
display: flex;
flex-direction: column;
justify-content: start;
padding-top: 20px;
width: 206px;
}
.layout-demo :deep(.arco-layout-content) {
background-color: rgb(250, 250, 250);
}
.layout-sider {
display: flex;
flex-direction: column;
align-items: center;
}
.menu-item {
font-size: 20px;
}
.user-service {
margin: 0 30px 0 auto;
}
.user-info {
height: 40px;
width: 40px;
border-radius: 9999rem;
border: 2px solid black;
display: flex;
align-items: center;
justify-content: center;
margin-right: 30px;
}
.menu-exit {
margin-top: auto;
margin-bottom: 20px;
border-radius: 5px;
width: 100%;
display: v-bind(showExit);
}
</style>
TeacherChinese.vue:
<template>
<div id="app">
<div class="music-controls">
<button @click="toggleMusic" class="music-button">
<IconPause v-if="isPlaying" style="font-size: 1.5em;"/>
<IconPlayArrow v-else style="font-size: 1.5em;"/>
</button>
<input type="range" min="0" max="1" step="0.01" v-model="volume" @input="changeVolume" class="volume-slider">
</div>
<div class="chat-container">
<div class="chat-messages" ref="chatMessages">
<div v-for="message in messages" :key="message.id"
:class="['message', {'user-message': message.type === 'user', 'bot-message': message.type === 'bot'}]">
<img :src="message.type === 'user' ? userAvatar : botAvatar" class="avatar">
<span class="message-content" v-html="renderMarkdown(message.content)"></span>
</div>
</div>
<div class="chat-input">
<textarea v-model="inputMessage" @input="autoExpand" @keydown.enter="handleKeyDown" placeholder="请输入你的问题" </textarea>
ref="textarea">
<button @click="sendMessage">
<icon-send style="rotate: -45deg; font-size:1.8em; "/>
</button>
</div>
<div class="help-container">
<div class="help">内容由AI生成,仅供参考,请遵循《用户协议》</div>
</div>
</div>
</div>
</template>
<script>
import { getAnswer } from "../api/GPT.js";
import { marked } from 'marked'; // 或者其他你选择的Markdown渲染库
import userAvatar from '../assets/GPT/ChineseStudentHead.png';
import botAvatar from '../assets/GPT/ChineseTeacherHead.png';
import musicFile from '../assets/GPT/Chinese-background-music.mp3'; // 你的背景音乐文件路径
import {
IconPause,
IconPlayArrow,
IconSend,
} from '@arco-design/web-vue/es/icon';
export default {
name: "ChatInterface",
components: {
IconPause,
IconPlayArrow,
IconSend
},
data() {
return {
messages: [],
inputMessage: "",
userAvatar: userAvatar,
botAvatar: botAvatar,
music: new Audio(musicFile), // 创建一个音频对象
isPlaying: false, // 追踪音乐播放状态
volume: 0.1 // 初始音量设置为10%
};
},
beforeRouteLeave(){ /*离开当前界面,暂停音乐*/
if(this.isPlaying){
this.music.pause();
}
},
methods: {
renderMarkdown(content) {
return marked(content); // 使用marked或者你选择的Markdown库来渲染Markdown
},
async sendMessage() {
if (this.inputMessage.trim()) {
// 将用户输入的消息添加到消息列表中
const userMessage = { id: Date.now(), content: this.inputMessage.trim(), type: 'user' };
this.messages.push(userMessage);
let message = this.inputMessage.trim();
// 设置发送消息为空
this.inputMessage = "";
this.autoExpand({ target: this.$refs.textarea }); // 重置textarea高度
this.scrollToBottom(); // 确保滚动到最新消息
// 临时显示机器人正在思考的消息
const botMessage = { id: Date.now() + 1, content: "正在思考中", type: 'bot' };
this.messages.push(botMessage);
try {
// 发送用户输入的文字作为 POST 请求,并设置超时时间为60秒
const response = await getAnswer(message, "chinese", 60000);
// 将获取到的响应内容更新到机器人消息
botMessage.content = response.data.trim();
} catch (error) {
console.error('请求超时或出错:', error);
// 处理超时或错误情况
botMessage.content = "抱歉,出错了,请稍后再试。";
}
// 更新界面,确保最新消息可见
this.$forceUpdate(); // 强制刷新组件
this.scrollToBottom(); // 确保滚动到最新消息
}
},
autoExpand(event) {
const textarea = event.target;
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.chatMessages;
containercontainer.scrollTop = container.scrollHeight; // 调整滚动位置
});
},
handleKeyDown(event) {
if (event.key === 'Enter') {
if (event.shiftKey) {
// Shift + Enter 换行
this.inputMessage += '\n';
thisthis.inputMessage = this.inputMessage.trim(); // 删除末尾的换行符
} else {
// Enter 发送消息
event.preventDefault(); // 阻止默认的换行行为
thisthis.inputMessage = this.inputMessage.trim(); // 删除末尾的换行符
this.sendMessage();
}
}
},
toggleMusic() {
if (this.isPlaying) {
this.music.pause();
} else {
this.music.play();
}
this.isPlaying = !this.isPlaying;
},
changeVolume(event) {
this.music.volume = event.target.value;
}
},
mounted() {
this.messages.push({
id: Date.now(), content: " 同学们好,我是你们才华横溢的国文老师欧阳老师!今日之学,当以经典为本,以白话为辅。咱们将在古典诗词和现代文之间穿梭,让每一篇文章都能深入浅出,妙趣横生。请告诉我你的学习需求和基础,我将以最适合你的方式带你领略国文之美。", type: 'bot'
});
this.music.loop = true; // 设置音乐循环播放
thisthis.music.volume = this.volume; // 初始化音量
this.music.play(); // 页面加载时自动播放音乐
this.isPlaying = true; // 设置播放状态为true
}
};
</script>
<style scoped>
html, body, #app {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#app {
display: flex;
justify-content: center;
align-items: center;
background-image: url('../assets/GPT/Chinese_background.jpg');
background-size: cover;
background-position: center;
}
.chat-container {
display: flex;
flex-direction: column;
height: 775px;
width: 80%;
padding: 35px;
border: none;
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.chat-messages {
flex: 1;
overflow-y: auto;
margin-bottom: 20px;
}
.help-container {
display: flex;
justify-content: center;
margin-top: 10px;
}
.help {
color: gray;
font-size: 14px;
text-align: center;
}
.message {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
.user-message {
flex-direction: row-reverse;
}
.user-message .message-content {
background-color: rgba(115, 0, 255, 0.4);
color: #ffffff;
padding: 15px 20px;
border-radius: 10px;
margin-left: 80px;/*保证长文本时对齐*/
text-align: left;
white-space: pre-wrap; /* 确保换行符被正确处理 */
/* 允许文本在需要时换行,包括在单词内部 */
overflow-wrap: break-word; /* 现代的属性,优先使用 */
word-wrap: break-word; /* 旧的属性,但大多数浏览器仍支持 */
/* 防止单词溢出(通常 word-wrap 或 overflow-wrap 已经足够)*/
word-break: break-all; /* 这是一个更激进的选项,它会在任意字符处换行 */
line-height: 1.5; /* 调整行高以去除额外行间距 */
}
.bot-message .message-content {
background-color: #f0f0f0;
color: #000000;
padding: 15px 20px;
border-radius: 10px;
text-align: left; /* 确保机器人消息内容靠左对齐 */
margin-right: 80px;/*保证长文本时对齐*/
white-space: pre-wrap; /* 确保换行符被正确处理 */
word-wrap: break-word; /* 防止单词溢出 */
line-height: 1.5; /* 调整行高以去除额外行间距 */
}
.avatar {
width: 60px; /* 控制头像宽度 */
height: 60px; /* 控制头像高度 */
border-radius: 50%;
margin: 0 10px;
object-fit: cover; /* 确保图片适应框架 */
}
.chat-input {
display: flex;
align-items: center;
}
.chat-input textarea {
flex: 1;
padding: 15px;
border-radius: 25px;
border: 1px solid rgba(115, 0, 255, 0.4);
resize: none; /* 禁用手动调整大小 */
overflow: auto; /* 启用滚动条 */
min-height: 50px; /* 设置最小高度 */
max-height: 200px; /* 设置最大高度,防止无限扩展 */
white-space: pre-wrap; /* 确保换行符被正确处理 */
word-wrap: break-word; /* 防止单词溢出 */
line-height: 1.5; /* 调整行高以去除额外行间距 */
}
button {
background-color: rgba(115, 0, 255, 0.4);
color: white;
padding: 15px;
border: none;
border-radius: 25px;
margin-left: 10px;
cursor: pointer;
}
.music-button {
background-color: transparent;
color: rgba(42, 38, 38, 0.83);
padding: 1px;
border: none;
cursor: pointer;
font-size: 24px;
}
.music-controls {
position: fixed;
top: 80px;
right: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.volume-slider {
width: 100px;
margin-top: 10px;
}
</style>
SubjectChoosen.vue:
<template>
<div v-if="show" class="modal-overlay">
<div class="modal">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span class="title-chosen">少年,请选择你的英雄</span>
</div>
</template>
<div class="button-container">
<div class="mode-option" @click="selectMode('Chinese')">
<img src="@/assets/GPT/Chinese.jpg" alt="Mode 1" class="mode-image" />
<span class="mode-text">才华横溢的国学老师</span>
</div>
<div class="mode-option" @click="selectMode('English')">
<img src="@/assets/GPT/English.jpg" alt="Mode 2" class="mode-image" />
<span class="mode-text">温柔摩登的英语老师</span>
</div>
<div class="mode-option" @click="selectMode('Science')">
<img src="@/assets/GPT/Science.jpg" alt="Mode 3" class="mode-image" />
<span class="mode-text">风趣幽默的科学老师</span>
</div>
<div class="mode-option" @click="selectMode('Information_technology')">
<img src="@/assets/GPT/IT.jpg" alt="Mode 4" class="mode-image" />
<span class="mode-text">严谨认真的计算机老师</span>
</div>
</div>
<div class="button-container">
<button @click="closeModal" class="close-button">Close</button>
</div>
</el-card>
</div>
</div>
</template>
<script>
import { ElCard, ElButton } from 'element-plus'
export default {
components: {
ElCard,
ElButton
},
props: ['show'],
methods: {
selectMode(mode) {
this.$emit('modeSelected', mode);
this.closeModal();
},
closeModal() {
this.$emit('update:show', false);
}
}
}
</script>
<style>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
width: 90%;
}
.box-card {
width: 100%;
}
.card-header {
display: flex;
justify-content: center;
align-items: center;
}
.title-chosen {
font-size: 60px;
color: #326044;
font-weight: bold;
font-family: "KaiTi", "STKaiti", serif;
transition: color,font-size 0.5s ease;
}
.title-chosen:hover{
color: #326044; /* 设置文字颜色 */
font-size: 72px;
transition: color,font-size 0.5s ease;
}
.button-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.mode-option {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
margin: 20px;
}
.mode-image {
width: 150px;
height: 150px;
object-fit: contain;
border-radius: 40px; /* 设置圆角大小 */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); /* 添加阴影,偏移量为0,模糊半径为10像素,颜色为黑色,透明度为0.5 */
transition: width 0.8s ease, height 0.8s ease;
}
.mode-image:hover {
width: 200px;
height: 200px;
transition: width 0.8s ease, height 0.8s ease;
}
.mode-text {
margin-top: 10px;
font-size: 32px;
font-weight: bold;
color: #5581e4; /* 设置文字颜色 */
cursor: pointer; /* 鼠标悬停时显示为指针 */
display: inline-block; /* 保持块级元素性质 */
font-family: "Times New Roman", serif;
transition: color,font-size 0.5s ease;
}
.mode-text:hover {
color: #0647e2; /* 设置文字颜色 */
font-size: 38px;
transition: color,font-size 0.5s ease; /* 设置背景颜色渐变效果 */
}
.close-button {
width: 180px; /* 设置宽度 */
height: 45px; /* 设置高度 */
background-color: #f56c6c; /* 设置背景颜色 */
color: #fff; /* 设置文字颜色 */
border: none; /* 移除边框 */
border-radius: 5px; /* 设置圆角 */
font-size: 25px; /* 设置字体大小 */
cursor: pointer; /* 鼠标悬停时显示为指针 */
display: inline-block; /* 保持块级元素性质 */
text-align: center; /* 文字居中 */
line-height: 40px; /* 设置行高,使文字垂直居中 */
font-family: "Times New Roman", serif;
transition: background-color 0.3s ease;
}
.close-button:hover {
background-color: #b30000; /* 鼠标悬停时的背景颜色 */
transition: background-color 0.3s ease; /* 设置背景颜色渐变效果 */
}
</style>
Login.vue:
<template>
<div class="container">
<div class="main">
<div class="loginbox">
<div class="loginbox-in">
<div class="userbox">
<span class="iconfont"></span>
<input id="user" v-model="name" class="user" placeholder="用户名">
</div>
<br>
<div class="pwdbox">
<span class="iconfont"></span>
<input id="password" v-model="pwd" class="pwd" placeholder="密码" type="password">
</div>
<br>
<div class="log-box">
<button class="login_btn" @click="login">Login</button>
</div>
<br>
<div class="reg-box">
<div class="log-box-text"> 忘记密码</div>
<br>
<button class="register_btn" @click="register">若无账号请点击注册</button>
</div>
</div>
<div class="background">
<div class="title">欢迎来到 慧教云端</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { loginUser} from '../api/api.js';
import router from '../router';
export default {
name: "Login",
watch: {
'$route.query.key'() {
// 当查询参数key变化时重新渲染
this.$forceUpdate();
}
},
data() {
return {
name: '',
pwd: '',
};
},
methods: {
register() {
router.push("/Register");
},
login() {
if (!this.name || !this.pwd) {
alert("用户名和密码不能为空");
return;
}
loginUser(this.name, this.pwd)
.then(response => {
if (response.data.success) {
router.push("/Start");
} else {
alert("用户名或密码错误,请重新输入");
}
})
.catch(error => {
console.error('登录失败:', error);
alert("登录失败,请稍后再试");
});
},
}
};
</script>
<style>
body {
background-color: #E3F2FD; /* 你想要的背景颜色 */
}
.container {
/* 其他样式保持不变 */
}
.loginbox {
display: flex;
position: absolute;
width: 800px;
height: 400px;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.5); /* 添加阴影,偏移量为0,模糊半径为10像素,颜色为黑色,透明度为0.5 */
}
.loginbox-in {
background-color: #BBDEFB;
width: 240px;
}
.userbox {
margin-top: 120px;
height: 30px;
width: 230px;
display: flex;
margin-left: 25px;
}
.pwdbox {
height: 30px;
width: 225px;
display: flex;
margin-left: 25px;
}
.background {
width: 570px;
display: flex;
align-items: center;
justify-content: center;
background-image: url('../assets/Login/Cloud2.png');
background-size: cover;
font-family: sans-serif;
}
.title {
margin-top: 280px;
font-weight: bold;
font-size: 32px;
color: #3f72af;
font-family: "KaiTi", "STKaiti", serif;
transition: all 0.4s ease-in;
}
.title:hover {
font-size: 40px;
transition: all 0.4s ease-in-out;
cursor: pointer;
}
input {
outline-style: none;
border: 0;
border-bottom: 1px solid #E9E9E9;
background-color: transparent;
height: 20px;
font-family: sans-serif;
font-size: 15px;
color: #1976D2;
font-weight: bold;
}
input:focus {
border-bottom: 2px solid #90CAF9;
background-color: transparent;
transition: all 0.2s ease-in;
font-family: sans-serif;
font-size: 15px;
color: #90CAF9;
font-weight: bold;
}
input:hover {
border-bottom: 2px solid #90CAF9;
background-color: transparent;
transition: all 0.2s ease-in;
font-family: sans-serif;
font-size: 15px;
color: #90CAF9;
font-weight: bold;
}
input:-webkit-autofill {
/* 修改默认背景框的颜色 */
box-shadow: 0 0 0 1000px #B3E5FC inset !important;
/* 修改默认字体的颜色 */
-webkit-text-fill-color: #01579B;
}
input:-webkit-autofill::first-line {
/* 修改默认字体的大小 */
font-size: 15px;
/* 修改默认字体的样式 */
font-weight: bold;
}
.log-box {
font-size: 12px;
display: flex;
justify-content: space-between;
width: 190px;
margin-left: 30px;
color: #0277BD;
margin-top: -5px;
align-items: center;
}
.reg-box {
font-size: 12px;
display: flex;
justify-content: space-between;
width: 190px;
margin-left: 30px;
color: #0277BD;
margin-top: -5px;
align-items: center;
}
.log-box-text {
color: #0277BD;
font-size: 11px;
text-decoration: underline;
}
.login_btn {
background-color: #0277BD;
border: none;
color: #FAFAFA;
padding: 5px 22px;
text-align: center;
text-decoration: none;
font-size: 13px;
border-radius: 20px;
outline: none;
}
.login_btn:hover {
box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19);
cursor: pointer;
background-color: #0277BD;
transition: all 0.2s ease-in;
}
.register_btn {
background-color: transparent;
border: none;
font-size: 11px;
color: #0277BD;
text-decoration: underline;
display: flex;
margin-left: 25px;
outline: none;
}
.register_btn:hover {
font-weight: bold;
cursor: pointer;
}
@font-face {
font-family: "iconfont";
src: url('../assets/Login/font/iconfont.eot');
src: url('../assets/Login/font/iconfont.eot?#iefix') format('embedded-opentype'),
url('../assets/Login/font/iconfont.woff2') format('woff2'),
url('../assets/Login/font/iconfont.woff') format('woff'),
url('../assets/Login/font/iconfont.ttf') format('truetype'),
url('../assets/Login/font/iconfont.svg#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 20px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 22px;
color: #71A5F4;
margin-right: 10px;
margin-top: 3px;
}
七、运行结果展示
- 用户可以通过注册功能创建个人账户,提供必要的个人信息,如用户名、密码、邮箱等。
- 登录功能允许用户输入注册时的用户名和密码,验证后进入系统。
- 登出功能允许用户安全退出系统,保护用户隐私和数据安全。
图4 登录界面图
图5 注册界面图
图6 注册成功界面图
- 才华横溢的国文老师欧阳老师:今日之学,当以经典为本,以白话为辅。咱们将在古典诗词和现代文之间穿梭,让每一篇文章都能深入浅出,妙趣横生。
- 温柔甜美的英语老师小惠老师:根据英语水平灵活应用中英文,并且选取合适的单词和语法来帮助学习。
- 风趣幽默的科学老师老杨:不只要探索科学的奥秘,还要看看哪些科学现象能让你捧腹大笑!准备好了吗?让我们开启这段既搞笑又有趣的科学之旅吧!
- 严谨认真的计算机老师史老师:在这里,我们将深入探讨计算机科学的原理和技术。请准备好你的笔记本和编程工具,我们将以严谨和系统的方式学习每一个知识点并进行适当的扩展。让我们开始吧。
- 每个页面都有不同风格的老师头像,以及不同风格的背景音乐,不同风格的背景图片,为用户提供不同风格的服务。
图7 选择老师界面图
图8 语文老师界面图
图9 英语老师界面图
图10 科学老师界面图
图11 计算机老师界面图
- 飞花令:设置一个关键字,如“花”、“春”、“月”、“云”、“夜”、“风”、“雨”、“雪”等诗词中的高频字,机器人小飞与用户轮流说出含有关键字的诗句。小飞会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关介绍作者、诗词意思、写作背景、作者趣事。
- 成语接龙:由一个人先说出一个成语,比如“半途而废”;另一个人要说出一个与上一个成语的最后一个字相同的成语,比如“废话连篇”,机器人小龙与用户轮流成语接龙。小龙会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关成语典故、成语意思。
- 古代文化常识:机器人小文将向用户提问中国高中生必备的中国文化常识并进行相关拓展。小龙会充分考虑用户的知识储备,判断用户是初学者、有些经验还是领域大师,并使用恰当的语言与用户交流, 适当使用生动形象的描述。并且小龙会对用户所讲进行纠错,补充相关典故和文化常识。
- 创意写错:机器人小猹将随时模仿鲁迅先生的独特的讽刺、夸张、反讽的写作风格,语言简练凝练,字里行间透露出对现实的愤怒和悲悯。为用户提供很好的写作思路与写作技巧。
图12 选择游戏界面图
图12 飞花令游戏界面图