Compare commits

..

20 Commits

Author SHA1 Message Date
pm2spy6xt e650795d53 更新最新版小组自评
3 months ago
pm2spy6xt 3843c7b77f Delete '实践考评-开源软件大作业项目的自评报告.xlsx'
3 months ago
pm2spy6xt 9b49043bab 更新演示视频
3 months ago
ni-jie404 227be5b3ef 更新演示视频
3 months ago
pm2spy6xt 942228c971 更新自评报告
3 months ago
pm2spy6xt 1d346e6d99 Delete 'doc/实践考评-开源软件大作业项目的自评报告.xlsx'
3 months ago
pm2spy6xt 7fdc6e55c5 上传自评报告
3 months ago
pm2spy6xt 365c7b67b8 合并系统演示视频
3 months ago
ni-jie404 27004b48f1 提交系统演示视频
3 months ago
pm2spy6xt 584f15767f 添加开源软件泛读,标注和维护报告文档
3 months ago
pm2spy6xt b2b3e58497 添加开源软件泛读报告
3 months ago
ni-jie404 1e5ef79634 新增相关功能,修复相关缺陷
3 months ago
ni-jie404 95f2dfd7e3 完成质量分析报告和编码规范
4 months ago
ni-jie404 344fcbccb2 Merge branch 'wwc_branch'
4 months ago
ni-jie404 6a585ee2ab Merge branch 'wjl_branch'
4 months ago
ni-jie404 21b7d8a17e Merge branch 'ssj_branch'
4 months ago
ni-jie404 f790368d44 Merge branch 'gjl_branch'
4 months ago
顾佳乐 f39c7773db 完善owntracks包下的注释
4 months ago
吴俊龙 6bb30aa54b 完善servermanager包下的注释
4 months ago
商世浚 ce347eff5e 完善comment包下的注释
4 months ago

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.12 (DjangoBlog)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="djangoBlog" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -9,3 +9,4 @@ settings_production.py
docs/ docs/
logs/ logs/
static/ static/
.github/

1
src/.gitignore vendored

@ -62,7 +62,6 @@ target/
# http://www.jetbrains.com/pycharm/webhelp/project.html # http://www.jetbrains.com/pycharm/webhelp/project.html
.idea .idea
.iml .iml
static/
# virtualenv # virtualenv
venv/ venv/

@ -7,34 +7,33 @@ from django.utils.translation import gettext_lazy as _
# Register your models here. # Register your models here.
from .models import BlogUser from .models import BlogUser
# 用户创建表单 - 处理新用户创建时的密码验证和保存逻辑
class BlogUserCreationForm(forms.ModelForm): class BlogUserCreationForm(forms.ModelForm):
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)# 密码字段 - 用户设置的密码输入 password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)# 确认密码字段 - 再次输入密码用于验证一致性 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta: class Meta:
model = BlogUser model = BlogUser
fields = ('email',) fields = ('email',)
def clean_password2(self):# 密码验证方法 - 检查两次输入的密码是否一致 def clean_password2(self):
# Check that the two password entries match # Check that the two password entries match
password1 = self.cleaned_data.get("password1") password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")# 获取两次输入的密码值 password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2: if password1 and password2 and password1 != password2:
raise forms.ValidationError(_("passwords do not match"))# 密码一致性检查 - 如果两次输入不一致则抛出验证错误 raise forms.ValidationError(_("passwords do not match"))
return password2 return password2
def save(self, commit=True):# 用户保存方法 - 处理密码哈希化和用户来源记录 def save(self, commit=True):
# Save the provided password in hashed format # Save the provided password in hashed format
user = super().save(commit=False) user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"]) user.set_password(self.cleaned_data["password1"])
# 提交保存 - 设置用户来源为管理后台并保存到数据库
if commit: if commit:
user.source = 'adminsite' user.source = 'adminsite'
user.save() user.save()
return user return user
# 用户修改表单 - 继承Django默认用户修改表单
class BlogUserChangeForm(UserChangeForm): class BlogUserChangeForm(UserChangeForm):
class Meta: class Meta:
model = BlogUser model = BlogUser
@ -44,7 +43,7 @@ class BlogUserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# 用户管理类 - 自定义用户模型在Django后台的显示和管理方式
class BlogUserAdmin(UserAdmin): class BlogUserAdmin(UserAdmin):
form = BlogUserChangeForm form = BlogUserChangeForm
add_form = BlogUserCreationForm add_form = BlogUserCreationForm
@ -58,3 +57,4 @@ class BlogUserAdmin(UserAdmin):
'source') 'source')
list_display_links = ('id', 'username') list_display_links = ('id', 'username')
ordering = ('-id',) ordering = ('-id',)
search_fields = ('username', 'nickname', 'email')

@ -1,5 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
# 账户应用配置类
class AccountsConfig(AppConfig): class AccountsConfig(AppConfig):
name = 'accounts' name = 'accounts'

@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
from . import utils from . import utils
from .models import BlogUser from .models import BlogUser
# 用户登录表单 - 处理用户登录认证
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs) super(LoginForm, self).__init__(*args, **kwargs)
@ -16,7 +16,7 @@ class LoginForm(AuthenticationForm):
self.fields['password'].widget = widgets.PasswordInput( self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"}) attrs={'placeholder': "password", "class": "form-control"})
# 用户注册表单 - 处理新用户注册流程
class RegisterForm(UserCreationForm): class RegisterForm(UserCreationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs) super(RegisterForm, self).__init__(*args, **kwargs)
@ -40,9 +40,8 @@ class RegisterForm(UserCreationForm):
model = get_user_model() model = get_user_model()
fields = ("username", "email") fields = ("username", "email")
# 密码重置表单 - 处理用户忘记密码时的重置流程
class ForgetPasswordForm(forms.Form): class ForgetPasswordForm(forms.Form):
# 新密码字段 - 用户设置的新密码
new_password1 = forms.CharField( new_password1 = forms.CharField(
label=_("New password"), label=_("New password"),
widget=forms.PasswordInput( widget=forms.PasswordInput(
@ -52,7 +51,7 @@ class ForgetPasswordForm(forms.Form):
} }
), ),
) )
# 确认密码字段 - 再次输入新密码用于验证
new_password2 = forms.CharField( new_password2 = forms.CharField(
label="确认密码", label="确认密码",
widget=forms.PasswordInput( widget=forms.PasswordInput(
@ -62,7 +61,7 @@ class ForgetPasswordForm(forms.Form):
} }
), ),
) )
# 邮箱字段 - 用户注册时使用的邮箱地址
email = forms.EmailField( email = forms.EmailField(
label='邮箱', label='邮箱',
widget=forms.TextInput( widget=forms.TextInput(
@ -72,7 +71,7 @@ class ForgetPasswordForm(forms.Form):
} }
), ),
) )
# 验证码字段 - 邮箱接收的验证码
code = forms.CharField( code = forms.CharField(
label=_('Code'), label=_('Code'),
widget=forms.TextInput( widget=forms.TextInput(
@ -83,7 +82,6 @@ class ForgetPasswordForm(forms.Form):
), ),
) )
# 密码确认验证 - 检查两次输入的密码是否一致
def clean_new_password2(self): def clean_new_password2(self):
password1 = self.data.get("new_password1") password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2") password2 = self.data.get("new_password2")
@ -93,7 +91,6 @@ class ForgetPasswordForm(forms.Form):
return password2 return password2
# 邮箱验证 - 检查邮箱是否在系统中注册
def clean_email(self): def clean_email(self):
user_email = self.cleaned_data.get("email") user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter( if not BlogUser.objects.filter(
@ -103,7 +100,6 @@ class ForgetPasswordForm(forms.Form):
raise ValidationError(_("email does not exist")) raise ValidationError(_("email does not exist"))
return user_email return user_email
# 验证码验证 - 检查邮箱验证码是否正确
def clean_code(self): def clean_code(self):
code = self.cleaned_data.get("code") code = self.cleaned_data.get("code")
error = utils.verify( error = utils.verify(
@ -114,7 +110,7 @@ class ForgetPasswordForm(forms.Form):
raise ValidationError(error) raise ValidationError(error)
return code return code
# 验证码请求表单 - 用于请求发送密码重置验证码
class ForgetPasswordCodeForm(forms.Form): class ForgetPasswordCodeForm(forms.Form):
email = forms.EmailField( email = forms.EmailField(
label=_('Email'), label=_('Email'),

@ -18,28 +18,28 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='BlogUser', name='BlogUser',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),# 主键字段,唯一标识用户 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),# 认证字段,用户密码哈希值 ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),# 登录信息字段 ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), # 权限字段(超级用户状态标识) ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),# 个人信息字段:用户姓氏 ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),# 个人信息字段:用户名字 ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),# 联系字段 - 用户邮箱地址 ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),# 权限字段 - 后台管理权限标识 ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),# 状态字段 - 用户账户激活状态 ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),# 时间字段 - 用户注册时间 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),# 权限关联字段 - 用户所属权限组 ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), # 权限关联字段 - 用户特定权限 ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
], ],
options={ options={
'verbose_name': '用户', 'verbose_name': '用户',
'verbose_name_plural': '用户', 'verbose_name_plural': '用户',
'ordering': ['-id'],#按ID降序排列 'ordering': ['-id'],
'get_latest_by': 'id', 'get_latest_by': 'id',
}, },
managers=[ managers=[

@ -11,40 +11,33 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
# 模型选项调整 - 更新用户模型的元数据配置
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='bloguser', name='bloguser',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
), ),
# 字段删除操作 - 移除旧的创建时间字段
migrations.RemoveField( migrations.RemoveField(
model_name='bloguser', model_name='bloguser',
name='created_time', name='created_time',
), ),
# 字段删除操作 - 移除旧的最后修改时间字段
migrations.RemoveField( migrations.RemoveField(
model_name='bloguser', model_name='bloguser',
name='last_mod_time', name='last_mod_time',
), ),
# 字段添加操作 - 新增标准化的创建时间字段
migrations.AddField( migrations.AddField(
model_name='bloguser', model_name='bloguser',
name='creation_time', name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
), ),
# 字段添加操作 - 新增标准化的最后修改时间字段
migrations.AddField( migrations.AddField(
model_name='bloguser', model_name='bloguser',
name='last_modify_time', name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
), ),
# 字段调整操作 - 更新昵称字段的显示名称
migrations.AlterField( migrations.AlterField(
model_name='bloguser', model_name='bloguser',
name='nickname', name='nickname',
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
), ),
# 字段调整操作 - 更新来源字段的显示名称
migrations.AlterField( migrations.AlterField(
model_name='bloguser', model_name='bloguser',
name='source', name='source',

@ -7,24 +7,21 @@ from djangoblog.utils import get_current_site
# Create your models here. # Create your models here.
# 博客用户模型类 - 继承Django抽象用户基类
class BlogUser(AbstractUser): class BlogUser(AbstractUser):
nickname = models.CharField(_('nick name'), max_length=100, blank=True) nickname = models.CharField(_('nick name'), max_length=100, blank=True)
creation_time = models.DateTimeField(_('creation time'), default=now) creation_time = models.DateTimeField(_('creation time'), default=now)
last_modify_time = models.DateTimeField(_('last modify time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now)
source = models.CharField(_('create source'), max_length=100, blank=True) source = models.CharField(_('create source'), max_length=100, blank=True)
# URL方法 - 获取用户详情页的相对URL路径
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
'blog:author_detail', kwargs={ 'blog:author_detail', kwargs={
'author_name': self.username}) 'author_name': self.username})
# 字符串表示 - 定义对象的字符串显示格式
def __str__(self): def __str__(self):
return self.email return self.email
# 完整URL方法 - 生成包含域名的用户完整详情页URL
def get_full_url(self): def get_full_url(self):
site = get_current_site().domain site = get_current_site().domain
url = "https://{site}{path}".format(site=site, url = "https://{site}{path}".format(site=site,

@ -10,7 +10,7 @@ from . import utils
# Create your tests here. # Create your tests here.
# 账户功能测试类 - 继承Django测试基类
class AccountTest(TestCase): class AccountTest(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
@ -22,7 +22,6 @@ class AccountTest(TestCase):
) )
self.new_test = "xxx123--=" self.new_test = "xxx123--="
# 账户验证测试 - 测试超级用户登录和管理权限
def test_validate_account(self): def test_validate_account(self):
site = get_current_site().domain site = get_current_site().domain
user = BlogUser.objects.create_superuser( user = BlogUser.objects.create_superuser(
@ -37,13 +36,13 @@ class AccountTest(TestCase):
self.assertEqual(loginresult, True) self.assertEqual(loginresult, True)
response = self.client.get('/admin/') response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# 分类创建 - 为文章测试创建分类数据
category = Category() category = Category()
category.name = "categoryaaa" category.name = "categoryaaa"
category.creation_time = timezone.now() category.creation_time = timezone.now()
category.last_modify_time = timezone.now() category.last_modify_time = timezone.now()
category.save() category.save()
# 文章创建 - 创建测试文章数据
article = Article() article = Article()
article.title = "nicetitleaaa" article.title = "nicetitleaaa"
article.body = "nicecontentaaa" article.body = "nicecontentaaa"
@ -56,7 +55,6 @@ class AccountTest(TestCase):
response = self.client.get(article.get_admin_url()) response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# 注册流程测试 - 测试用户完整注册流程
def test_validate_register(self): def test_validate_register(self):
self.assertEquals( self.assertEquals(
0, len( 0, len(
@ -120,7 +118,6 @@ class AccountTest(TestCase):
response = self.client.get(article.get_admin_url()) response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200]) self.assertIn(response.status_code, [301, 302, 200])
# 邮箱验证码测试 - 测试邮箱验证码的生成和验证
def test_verify_email_code(self): def test_verify_email_code(self):
to_email = "admin@admin.com" to_email = "admin@admin.com"
code = generate_code() code = generate_code()
@ -133,7 +130,6 @@ class AccountTest(TestCase):
err = utils.verify("admin@123.com", code) err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str) self.assertEqual(type(err), str)
# 密码重置邮件发送成功测试
def test_forget_password_email_code_success(self): def test_forget_password_email_code_success(self):
resp = self.client.post( resp = self.client.post(
path=reverse("account:forget_password_code"), path=reverse("account:forget_password_code"),
@ -143,7 +139,6 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok") self.assertEqual(resp.content.decode("utf-8"), "ok")
# 密码重置邮件发送失败测试
def test_forget_password_email_code_fail(self): def test_forget_password_email_code_fail(self):
resp = self.client.post( resp = self.client.post(
path=reverse("account:forget_password_code"), path=reverse("account:forget_password_code"),
@ -157,7 +152,6 @@ class AccountTest(TestCase):
) )
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 密码重置成功流程测试
def test_forget_password_email_success(self): def test_forget_password_email_success(self):
code = generate_code() code = generate_code()
utils.set_code(self.blog_user.email, code) utils.set_code(self.blog_user.email, code)
@ -180,7 +174,6 @@ class AccountTest(TestCase):
self.assertNotEqual(blog_user, None) self.assertNotEqual(blog_user, None)
self.assertEqual(blog_user.check_password(data["new_password1"]), True) self.assertEqual(blog_user.check_password(data["new_password1"]), True)
# 不存在的用户密码重置测试
def test_forget_password_email_not_user(self): def test_forget_password_email_not_user(self):
data = dict( data = dict(
new_password1=self.new_test, new_password1=self.new_test,
@ -195,7 +188,7 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
# 验证码错误测试
def test_forget_password_email_code_error(self): def test_forget_password_email_code_error(self):
code = generate_code() code = generate_code()
utils.set_code(self.blog_user.email, code) utils.set_code(self.blog_user.email, code)

@ -5,24 +5,24 @@ from . import views
from .forms import LoginForm from .forms import LoginForm
app_name = "accounts" app_name = "accounts"
# URL路由配置 - 定义URL路径与视图函数的映射关系
urlpatterns = [re_path(r'^login/$', # 用户登录路由 - 处理用户登录认证 urlpatterns = [re_path(r'^login/$',
views.LoginView.as_view(success_url='/'), views.LoginView.as_view(success_url='/'),
name='login', name='login',
kwargs={'authentication_form': LoginForm}), kwargs={'authentication_form': LoginForm}),
re_path(r'^register/$', # 用户注册路由 - 处理新用户注册 re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"), views.RegisterView.as_view(success_url="/"),
name='register'), name='register'),
re_path(r'^logout/$',# 用户退出路由 - 处理用户登出操作 re_path(r'^logout/$',
views.LogoutView.as_view(), views.LogoutView.as_view(),
name='logout'), name='logout'),
path(r'account/result.html',# 账户结果页面路由 - 显示操作结果信息(如验证结果) path(r'account/result.html',
views.account_result, views.account_result,
name='result'), name='result'),
re_path(r'^forget_password/$',# 密码重置路由 - 处理忘记密码重置请求 re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(), views.ForgetPasswordView.as_view(),
name='forget_password'), name='forget_password'),
re_path(r'^forget_password_code/$',# 密码重置验证码路由 - 处理密码重置验证码发送 re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(), views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'), name='forget_password_code'),
] ]

@ -2,13 +2,11 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
# 用户认证方法 - 验证用户凭据并返回用户对象
class EmailOrUsernameModelBackend(ModelBackend): class EmailOrUsernameModelBackend(ModelBackend):
""" """
允许使用用户名或邮箱登录 允许使用用户名或邮箱登录
""" """
# 用户认证方法 - 验证用户凭据并返回用户对象
def authenticate(self, request, username=None, password=None, **kwargs): def authenticate(self, request, username=None, password=None, **kwargs):
if '@' in username: if '@' in username:
kwargs = {'email': username} kwargs = {'email': username}
@ -21,7 +19,6 @@ class EmailOrUsernameModelBackend(ModelBackend):
except get_user_model().DoesNotExist: except get_user_model().DoesNotExist:
return None return None
# 用户获取方法 - 根据用户ID获取用户对象
def get_user(self, username): def get_user(self, username):
try: try:
return get_user_model().objects.get(pk=username) return get_user_model().objects.get(pk=username)

@ -9,7 +9,7 @@ from djangoblog.utils import send_email
_code_ttl = timedelta(minutes=5) _code_ttl = timedelta(minutes=5)
# 验证邮件发送函数 - 向指定邮箱发送验证码邮件
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""发送重设密码验证码 """发送重设密码验证码
Args: Args:
@ -22,7 +22,7 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
"properly") % {'code': code} "properly") % {'code': code}
send_email([to_mail], subject, html_content) send_email([to_mail], subject, html_content)
# 验证码验证函数 - 检查用户输入的验证码是否正确
def verify(email: str, code: str) -> typing.Optional[str]: def verify(email: str, code: str) -> typing.Optional[str]:
"""验证code是否有效 """验证code是否有效
Args: Args:
@ -38,12 +38,12 @@ def verify(email: str, code: str) -> typing.Optional[str]:
if cache_code != code: if cache_code != code:
return gettext("Verification code error") return gettext("Verification code error")
# 验证码存储函数 - 将验证码保存到缓存系统
def set_code(email: str, code: str): def set_code(email: str, code: str):
"""设置code""" """设置code"""
cache.set(email, code, _code_ttl.seconds) cache.set(email, code, _code_ttl.seconds)
# 验证码获取函数 - 从缓存系统中获取验证码
def get_code(email: str) -> typing.Optional[str]: def get_code(email: str) -> typing.Optional[str]:
"""获取code""" """获取code"""
return cache.get(email) return cache.get(email)

@ -30,33 +30,30 @@ logger = logging.getLogger(__name__)
# Create your views here. # Create your views here.
# 用户注册视图 - 处理新用户注册流程
class RegisterView(FormView): class RegisterView(FormView):
form_class = RegisterForm form_class = RegisterForm
template_name = 'account/registration_form.html' template_name = 'account/registration_form.html'
# 请求分发 - 添加CSRF保护装饰器
@method_decorator(csrf_protect) @method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs) return super(RegisterView, self).dispatch(*args, **kwargs)
# 表单验证成功处理 - 保存用户并发送验证邮件
def form_valid(self, form): def form_valid(self, form):
if form.is_valid(): if form.is_valid():
# 用户创建 - 保存表单数据但不立即提交到数据库
user = form.save(False) user = form.save(False)
user.is_active = False user.is_active = False
user.source = 'Register' user.source = 'Register'
user.save(True) user.save(True)
site = get_current_site().domain site = get_current_site().domain
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 开发环境调整 - 在调试模式下使用本地地址
if settings.DEBUG: if settings.DEBUG:
site = '127.0.0.1:8000' site = '127.0.0.1:8000'
path = reverse('account:result') path = reverse('account:result')
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign) site=site, path=path, id=user.id, sign=sign)
# 邮件内容构建 - 创建验证邮件的HTML内容
content = """ content = """
<p>请点击下面链接验证您的邮箱</p> <p>请点击下面链接验证您的邮箱</p>
@ -67,7 +64,6 @@ class RegisterView(FormView):
如果上面链接无法打开请将此链接复制至浏览器 如果上面链接无法打开请将此链接复制至浏览器
{url} {url}
""".format(url=url) """.format(url=url)
# 邮件发送 - 发送验证邮件到用户邮箱
send_email( send_email(
emailto=[ emailto=[
user.email, user.email,
@ -83,7 +79,7 @@ class RegisterView(FormView):
'form': form 'form': form
}) })
# 用户退出视图 - 处理用户登出操作
class LogoutView(RedirectView): class LogoutView(RedirectView):
url = '/login/' url = '/login/'
@ -96,7 +92,7 @@ class LogoutView(RedirectView):
delete_sidebar_cache() delete_sidebar_cache()
return super(LogoutView, self).get(request, *args, **kwargs) return super(LogoutView, self).get(request, *args, **kwargs)
# 用户登录视图 - 处理用户登录认证
class LoginView(FormView): class LoginView(FormView):
form_class = LoginForm form_class = LoginForm
template_name = 'account/login.html' template_name = 'account/login.html'
@ -111,7 +107,6 @@ class LoginView(FormView):
return super(LoginView, self).dispatch(request, *args, **kwargs) return super(LoginView, self).dispatch(request, *args, **kwargs)
# 上下文数据 - 获取重定向目标并添加到上下文
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
redirect_to = self.request.GET.get(self.redirect_field_name) redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None: if redirect_to is None:
@ -120,7 +115,6 @@ class LoginView(FormView):
return super(LoginView, self).get_context_data(**kwargs) return super(LoginView, self).get_context_data(**kwargs)
# 表单验证成功处理 - 执行用户登录操作
def form_valid(self, form): def form_valid(self, form):
form = AuthenticationForm(data=self.request.POST, request=self.request) form = AuthenticationForm(data=self.request.POST, request=self.request)
@ -138,7 +132,6 @@ class LoginView(FormView):
'form': form 'form': form
}) })
# 成功URL获取 - 处理登录后的重定向逻辑
def get_success_url(self): def get_success_url(self):
redirect_to = self.request.POST.get(self.redirect_field_name) redirect_to = self.request.POST.get(self.redirect_field_name)
@ -148,7 +141,7 @@ class LoginView(FormView):
redirect_to = self.success_url redirect_to = self.success_url
return redirect_to return redirect_to
# 账户结果页面视图 - 显示注册或验证结果
def account_result(request): def account_result(request):
type = request.GET.get('type') type = request.GET.get('type')
id = request.GET.get('id') id = request.GET.get('id')
@ -181,7 +174,7 @@ def account_result(request):
else: else:
return HttpResponseRedirect('/') return HttpResponseRedirect('/')
# 密码重置视图 - 处理用户忘记密码重置
class ForgetPasswordView(FormView): class ForgetPasswordView(FormView):
form_class = ForgetPasswordForm form_class = ForgetPasswordForm
template_name = 'account/forget_password.html' template_name = 'account/forget_password.html'
@ -195,7 +188,7 @@ class ForgetPasswordView(FormView):
else: else:
return self.render_to_response({'form': form}) return self.render_to_response({'form': form})
# 密码重置验证码发送视图 - 处理验证码邮件发送
class ForgetPasswordEmailCode(View): class ForgetPasswordEmailCode(View):
def post(self, request: HttpRequest): def post(self, request: HttpRequest):

@ -6,7 +6,7 @@ from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# Register your models here. # Register your models here.
from .models import Article from .models import Article, Category, Tag, Links, SideBar, BlogSettings
class ArticleForm(forms.ModelForm): class ArticleForm(forms.ModelForm):
@ -55,6 +55,7 @@ class ArticlelAdmin(admin.ModelAdmin):
'article_order') 'article_order')
list_display_links = ('id', 'title') list_display_links = ('id', 'title')
list_filter = ('status', 'type', 'category') list_filter = ('status', 'type', 'category')
date_hierarchy = 'creation_time'
filter_horizontal = ('tags',) filter_horizontal = ('tags',)
exclude = ('creation_time', 'last_modify_time') exclude = ('creation_time', 'last_modify_time')
view_on_site = True view_on_site = True
@ -63,6 +64,7 @@ class ArticlelAdmin(admin.ModelAdmin):
draft_article, draft_article,
close_article_commentstatus, close_article_commentstatus,
open_article_commentstatus] open_article_commentstatus]
raw_id_fields = ('author', 'category',)
def link_to_category(self, obj): def link_to_category(self, obj):
info = (obj.category._meta.app_label, obj.category._meta.model_name) info = (obj.category._meta.app_label, obj.category._meta.model_name)
@ -89,6 +91,11 @@ class ArticlelAdmin(admin.ModelAdmin):
site = get_current_site().domain site = get_current_site().domain
return site return site
class Media:
js = (
'blog/js/ai_article_admin.js', # 加载我们刚才写的 JS
)
class TagAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin):
exclude = ('slug', 'last_mod_time', 'creation_time') exclude = ('slug', 'last_mod_time', 'creation_time')

@ -0,0 +1,9 @@
.button {
border: none;
padding: 4px 80px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
}

@ -0,0 +1,47 @@
let wait = 60;
function time(o) {
if (wait == 0) {
o.removeAttribute("disabled");
o.value = "获取验证码";
wait = 60
return false
} else {
o.setAttribute("disabled", true);
o.value = "重新发送(" + wait + ")";
wait--;
setTimeout(function () {
time(o)
},
1000)
}
}
document.getElementById("btn").onclick = function () {
let id_email = $("#id_email")
let token = $("*[name='csrfmiddlewaretoken']").val()
let ts = this
let myErr = $("#myErr")
$.ajax(
{
url: "/forget_password_code/",
type: "POST",
data: {
"email": id_email.val(),
"csrfmiddlewaretoken": token
},
success: function (result) {
if (result != "ok") {
myErr.remove()
id_email.after("<ul className='errorlist' id='myErr'><li>" + result + "</li></ul>")
return
}
myErr.remove()
time(ts)
},
error: function (e) {
alert("发送失败,请重试")
}
}
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,13 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*
* See the Getting Started docs for more information:
* http://getbootstrap.com/getting-started/#support-ie10-width
*/
@-ms-viewport { width: device-width; }
@-o-viewport { width: device-width; }
@viewport { width: device-width; }

@ -0,0 +1,58 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #fff;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin-heading {
margin: 0 0 15px;
font-size: 18px;
font-weight: 400;
color: #555;
}
.form-signin .checkbox {
margin-bottom: 10px;
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: 10px;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
}
.card {
width: 304px;
padding: 20px 25px 30px;
margin: 0 auto 25px;
background-color: #f7f7f7;
border-radius: 2px;
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
}
.card-signin {
width: 354px;
padding: 40px;
}
.card-signin .profile-img {
display: block;
width: 96px;
height: 96px;
margin: 0 auto 10px;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

@ -0,0 +1,51 @@
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
/*!
* Copyright 2014-2015 Twitter, Inc.
*
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see https://creativecommons.org/licenses/by/3.0/.
*/
// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes.
(function () {
'use strict';
function emulatedIEMajorVersion() {
var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
if (groups === null) {
return null
}
var ieVersionNum = parseInt(groups[1], 10)
var ieMajorVersion = Math.floor(ieVersionNum)
return ieMajorVersion
}
function actualNonEmulatedIEMajorVersion() {
// Detects the actual version of IE in use, even if it's in an older-IE emulation mode.
// IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx
// @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx
var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line
if (jscriptVersion === undefined) {
return 11 // IE11+ not in emulation mode
}
if (jscriptVersion < 9) {
return 8 // IE8 (or lower; haven't tested on IE<8)
}
return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode
}
var ua = window.navigator.userAgent
if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) {
return // Opera, which might pretend to be IE
}
var emulated = emulatedIEMajorVersion()
if (emulated === null) {
return // Not IE
}
var nonEmulated = actualNonEmulatedIEMajorVersion()
if (emulated !== nonEmulated) {
window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
}
})();

@ -0,0 +1,23 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
// See the Getting Started docs for more information:
// http://getbootstrap.com/getting-started/#support-ie10-width
(function () {
'use strict';
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
var msViewportStyle = document.createElement('style')
msViewportStyle.appendChild(
document.createTextNode(
'@-ms-viewport{width:auto!important}'
)
)
document.querySelector('head').appendChild(msViewportStyle)
}
})();

@ -0,0 +1,154 @@
/* 悬浮按钮 */
.ai-chat-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background: linear-gradient(135deg, #0A76F7, #00c6ff);
border-radius: 50%;
box-shadow: 0 4px 15px rgba(10, 118, 247, 0.4);
cursor: pointer;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
}
.ai-chat-btn:hover {
transform: scale(1.1);
}
.ai-chat-btn svg {
width: 30px;
height: 30px;
fill: #fff;
}
/* 聊天窗口容器 */
.ai-chat-window {
position: fixed;
bottom: 100px;
right: 30px;
width: 380px;
height: 550px;
background: #fff;
border-radius: 16px;
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15);
display: none; /* 默认隐藏 */
flex-direction: column;
z-index: 9998;
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
border: 1px solid #eee;
}
.ai-chat-window.active {
display: flex;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 头部 */
.ai-header {
padding: 15px 20px;
background: #0A76F7;
color: #fff;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.ai-header .close-btn {
cursor: pointer;
font-size: 20px;
}
/* 消息区域 */
.ai-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #f9f9f9;
}
.message {
margin-bottom: 15px;
max-width: 85%;
line-height: 1.5;
font-size: 14px;
word-wrap: break-word;
}
.message.user {
margin-left: auto;
background: #0A76F7;
color: #fff;
padding: 10px 15px;
border-radius: 12px 12px 0 12px;
}
.message.ai {
margin-right: auto;
background: #fff;
color: #333;
padding: 10px 15px;
border-radius: 12px 12px 12px 0;
border: 1px solid #e0e0e0;
}
/* 思考过程样式 */
.message.thinking {
font-size: 12px;
color: #888;
font-style: italic;
border-left: 2px solid #ccc;
padding-left: 10px;
margin-bottom: 5px;
background: transparent;
border: none;
border-left: 3px solid #ddd;
}
/* 输入区域 */
.ai-input-area {
padding: 15px;
background: #fff;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
.ai-input-area input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
outline: none;
transition: border-color 0.2s;
}
.ai-input-area input:focus {
border-color: #0A76F7;
}
.ai-input-area button {
background: #0A76F7;
color: #fff;
border: none;
padding: 0 20px;
border-radius: 20px;
cursor: pointer;
font-weight: 600;
}
.ai-input-area button:disabled {
background: #ccc;
cursor: not-allowed;
}

@ -0,0 +1,273 @@
/*
Styles for older IE versions (previous to IE9).
*/
body {
background-color: #e6e6e6;
}
body.custom-background-empty {
background-color: #fff;
}
body.custom-background-empty .site,
body.custom-background-white .site {
box-shadow: none;
margin-bottom: 0;
margin-top: 0;
padding: 0;
}
.assistive-text,
.site .screen-reader-text {
clip: rect(1px 1px 1px 1px);
}
.full-width .site-content {
float: none;
width: 100%;
}
img.size-full,
img.size-large,
img.header-image,
img.wp-post-image,
img[class*="align"],
img[class*="wp-image-"],
img[class*="attachment-"] {
width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */
}
.author-avatar {
float: left;
margin-top: 8px;
margin-top: 0.571428571rem;
}
.author-description {
float: right;
width: 80%;
}
.site {
box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3);
margin: 48px auto;
max-width: 960px;
overflow: hidden;
padding: 0 40px;
}
.site-content {
float: left;
width: 65.104166667%;
}
body.template-front-page .site-content,
body.attachment .site-content,
body.full-width .site-content {
width: 100%;
}
.widget-area {
float: right;
width: 26.041666667%;
}
.site-header h1,
.site-header h2 {
text-align: left;
}
.site-header h1 {
font-size: 26px;
line-height: 1.846153846;
}
.main-navigation ul.nav-menu,
.main-navigation div.nav-menu > ul {
border-bottom: 1px solid #ededed;
border-top: 1px solid #ededed;
display: inline-block !important;
text-align: left;
width: 100%;
}
.main-navigation ul {
margin: 0;
text-indent: 0;
}
.main-navigation li a,
.main-navigation li {
display: inline-block;
text-decoration: none;
}
.ie7 .main-navigation li a,
.ie7 .main-navigation li {
display: inline;
}
.main-navigation li a {
border-bottom: 0;
color: #6a6a6a;
line-height: 3.692307692;
text-transform: uppercase;
}
.main-navigation li a:hover {
color: #000;
}
.main-navigation li {
margin: 0 40px 0 0;
position: relative;
}
.main-navigation li ul {
margin: 0;
padding: 0;
position: absolute;
top: 100%;
z-index: 1;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
.ie7 .main-navigation li ul {
clip: inherit;
display: none;
left: 0;
overflow: visible;
}
.main-navigation li ul ul,
.ie7 .main-navigation li ul ul {
top: 0;
left: 100%;
}
.main-navigation ul li:hover > ul,
.main-navigation ul li:focus > ul,
.main-navigation .focus > ul {
border-left: 0;
clip: inherit;
overflow: inherit;
height: inherit;
width: inherit;
}
.ie7 .main-navigation ul li:hover > ul,
.ie7 .main-navigation ul li:focus > ul {
display: block;
}
.main-navigation li ul li a {
background: #efefef;
border-bottom: 1px solid #ededed;
display: block;
font-size: 11px;
line-height: 2.181818182;
padding: 8px 10px;
width: 180px;
}
.main-navigation li ul li a:hover {
background: #e3e3e3;
color: #444;
}
.main-navigation .current-menu-item > a,
.main-navigation .current-menu-ancestor > a,
.main-navigation .current_page_item > a,
.main-navigation .current_page_ancestor > a {
color: #636363;
font-weight: bold;
}
.main-navigation .menu-toggle {
display: none;
}
.entry-header .entry-title {
font-size: 22px;
}
#respond form input[type="text"] {
width: 46.333333333%;
}
#respond form textarea.blog-textarea {
width: 79.666666667%;
}
.template-front-page .site-content,
.template-front-page article {
overflow: hidden;
}
.template-front-page.has-post-thumbnail article {
float: left;
width: 47.916666667%;
}
.entry-page-image {
float: right;
margin-bottom: 0;
width: 47.916666667%;
}
/* IE Front Page Template Widget fix */
.template-front-page .widget-area {
clear: both;
}
.template-front-page .widget {
width: 100% !important;
border: none;
}
.template-front-page .widget-area .widget,
.template-front-page .first.front-widgets,
.template-front-page.two-sidebars .widget-area .front-widgets {
float: left;
margin-bottom: 24px;
width: 51.875%;
}
.template-front-page .second.front-widgets,
.template-front-page .widget-area .widget:nth-child(odd) {
clear: right;
}
.template-front-page .first.front-widgets,
.template-front-page .second.front-widgets,
.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets {
float: right;
margin: 0 0 24px;
width: 39.0625%;
}
.template-front-page.two-sidebars .widget,
.template-front-page.two-sidebars .widget:nth-child(even) {
float: none;
width: auto;
}
/* add input font for <IE9 Password Box to make the bullets show up */
input[type="password"] {
font-family: Helvetica, Arial, sans-serif;
}
/* RTL overrides for IE7 and IE8
-------------------------------------------------------------- */
.rtl .site-header h1,
.rtl .site-header h2 {
text-align: right;
}
.rtl .widget-area,
.rtl .author-description {
float: left;
}
.rtl .author-avatar,
.rtl .site-content {
float: right;
}
.rtl .main-navigation ul.nav-menu,
.rtl .main-navigation div.nav-menu > ul {
text-align: right;
}
.rtl .main-navigation ul li ul li,
.rtl .main-navigation ul li ul li ul li {
margin-left: 40px;
margin-right: auto;
}
.rtl .main-navigation li ul ul {
position: absolute;
bottom: 0;
right: 100%;
z-index: 1;
}
.ie7 .rtl .main-navigation li ul ul {
position: absolute;
bottom: 0;
right: 100%;
z-index: 1;
}
.ie7 .rtl .main-navigation ul li {
z-index: 99;
}
.ie7 .rtl .main-navigation li ul {
position: absolute;
bottom: 100%;
right: 0;
z-index: 1;
}
.ie7 .rtl .main-navigation li {
margin-right: auto;
margin-left: 40px;
}
.ie7 .rtl .main-navigation li ul ul ul {
position: relative;
z-index: 1;
}

@ -0,0 +1,74 @@
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: red;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: red;
border-left-color: red;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

@ -0,0 +1,305 @@
.icon-sn-google {
background-position: 0 -28px;
}
.icon-sn-bg-google {
background-color: #4285f4;
background-position: 0 0;
}
.fa-sn-google {
color: #4285f4;
}
.icon-sn-github {
background-position: -28px -28px;
}
.icon-sn-bg-github {
background-color: #333;
background-position: -28px 0;
}
.fa-sn-github {
color: #333;
}
.icon-sn-weibo {
background-position: -56px -28px;
}
.icon-sn-bg-weibo {
background-color: #e90d24;
background-position: -56px 0;
}
.fa-sn-weibo {
color: #e90d24;
}
.icon-sn-qq {
background-position: -84px -28px;
}
.icon-sn-bg-qq {
background-color: #0098e6;
background-position: -84px 0;
}
.fa-sn-qq {
color: #0098e6;
}
.icon-sn-twitter {
background-position: -112px -28px;
}
.icon-sn-bg-twitter {
background-color: #50abf1;
background-position: -112px 0;
}
.fa-sn-twitter {
color: #50abf1;
}
.icon-sn-facebook {
background-position: -140px -28px;
}
.icon-sn-bg-facebook {
background-color: #4862a3;
background-position: -140px 0;
}
.fa-sn-facebook {
color: #4862a3;
}
.icon-sn-renren {
background-position: -168px -28px;
}
.icon-sn-bg-renren {
background-color: #197bc8;
background-position: -168px 0;
}
.fa-sn-renren {
color: #197bc8;
}
.icon-sn-tqq {
background-position: -196px -28px;
}
.icon-sn-bg-tqq {
background-color: #1f9ed2;
background-position: -196px 0;
}
.fa-sn-tqq {
color: #1f9ed2;
}
.icon-sn-douban {
background-position: -224px -28px;
}
.icon-sn-bg-douban {
background-color: #279738;
background-position: -224px 0;
}
.fa-sn-douban {
color: #279738;
}
.icon-sn-weixin {
background-position: -252px -28px;
}
.icon-sn-bg-weixin {
background-color: #00b500;
background-position: -252px 0;
}
.fa-sn-weixin {
color: #00b500;
}
.icon-sn-dotted {
background-position: -280px -28px;
}
.icon-sn-bg-dotted {
background-color: #eee;
background-position: -280px 0;
}
.fa-sn-dotted {
color: #eee;
}
.icon-sn-site {
background-position: -308px -28px;
}
.icon-sn-bg-site {
background-color: #00b500;
background-position: -308px 0;
}
.fa-sn-site {
color: #00b500;
}
.icon-sn-linkedin {
background-position: -336px -28px;
}
.icon-sn-bg-linkedin {
background-color: #0077b9;
background-position: -336px 0;
}
.fa-sn-linkedin {
color: #0077b9;
}
[class*=icon-sn-] {
display: inline-block;
background-image: url('../img/icon-sn.svg');
background-repeat: no-repeat;
width: 28px;
height: 28px;
vertical-align: middle;
background-size: auto 56px;
}
[class*=icon-sn-]:hover {
opacity: .8;
filter: alpha(opacity=80);
}
.btn-sn-google {
background: #4285f4;
}
.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover {
background: #2a75f3;
}
.btn-sn-github {
background: #333;
}
.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover {
background: #262626;
}
.btn-sn-weibo {
background: #e90d24;
}
.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover {
background: #d10c20;
}
.btn-sn-qq {
background: #0098e6;
}
.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover {
background: #0087cd;
}
.btn-sn-twitter {
background: #50abf1;
}
.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover {
background: #38a0ef;
}
.btn-sn-facebook {
background: #4862a3;
}
.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover {
background: #405791;
}
.btn-sn-renren {
background: #197bc8;
}
.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover {
background: #166db1;
}
.btn-sn-tqq {
background: #1f9ed2;
}
.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover {
background: #1c8dbc;
}
.btn-sn-douban {
background: #279738;
}
.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover {
background: #228330;
}
.btn-sn-weixin {
background: #00b500;
}
.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover {
background: #009c00;
}
.btn-sn-dotted {
background: #eee;
}
.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover {
background: #e1e1e1;
}
.btn-sn-site {
background: #00b500;
}
.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover {
background: #009c00;
}
.btn-sn-linkedin {
background: #0077b9;
}
.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover {
background: #0067a0;
}
[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover {
border: none;
color: #fff;
}
.btn-sn-more {
padding: 0;
}
.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover {
box-shadow: none;
}
[class*=btn-sn-] [class*=icon-sn-] {
background-color: transparent;
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,597 @@
/*
:
- (): #0A76F7
- : #F4F7FC
- /: #FFFFFF
- : #333333
- : #888888
- /线: #EAECEF
*/
/* 1. 全局与基础样式 */
:root {
--theme-blue: #0A76F7;
--bg-color: #F4F7FC;
--card-bg: #FFFFFF;
--text-primary: #333333;
--text-secondary: #888888;
--border-color: #EAECEF;
--font-family-base: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
font-family: var(--font-family-base);
background-color: var(--bg-color);
color: var(--text-primary);
line-height: 1.7;
font-size: 16px;
margin: 0;
}
a {
color: var(--theme-blue);
text-decoration: none;
transition: color 0.2s ease-in-out;
}
a:hover {
color: #085db8;
text-decoration: underline;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px; /* 给图片统一的圆角 */
}
/* 移除旧布局的边框和阴影 */
#page.site {
padding: 0;
margin: 0;
max-width: 100%;
box-shadow: none;
background-color: transparent;
}
.wrapper {
max-width: 1200px; /* 定义内容区域最大宽度 */
margin: 0 auto;
padding: 20px;
display: flex;
gap: 24px; /* 主内容区和侧边栏的间距 */
}
/* === 2. 头部 (Header) 与导航栏 (Navigation) === */
#masthead.site-header {
background-color: var(--card-bg);
padding: 0 20px;
border-bottom: 1px solid var(--border-color);
position: sticky; /* 导航栏吸顶 */
top: 0;
z-index: 1000;
width: 100%;
box-sizing: border-box;
}
.site-header .hgroup {
display: none; /* 隐藏旧的标题和描述,我们将用更现代的方式呈现 */
}
#site-navigation.main-navigation {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between; /* Logo和菜单项两端对齐 */
align-items: center;
height: 64px;
}
/* 导航栏左侧的Logo */
.main-navigation .nav-logo {
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
}
.main-navigation .nav-logo a {
color: inherit;
text-decoration: none;
}
.main-navigation .nav-logo a:hover {
color: var(--theme-blue);
}
/* 导航菜单项 */
.main-navigation ul.nav-menu {
display: flex !important; /* 强制显示菜单 */
list-style: none;
margin: 0;
padding: 0;
gap: 20px;
}
.main-navigation li a {
color: var(--text-secondary);
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
text-transform: none; /* 移除大写 */
line-height: 1.5;
}
.main-navigation li a:hover {
background-color: var(--bg-color);
color: var(--text-primary);
text-decoration: none;
}
/* 当前激活的菜单项 */
.main-navigation .current-menu-item > a,
.main-navigation .current_page_item > a {
background-color: var(--theme-blue);
color: #fff;
}
/* 隐藏子菜单和旧的菜单切换按钮 */
.main-navigation .sub-menu,
.menu-toggle {
display: none !important;
}
/* === 3. 主内容区 (Main Content) === */
#primary.site-content {
flex: 1; /* 占据剩余空间 */
width: 100%;
margin: 0;
}
/* 文章列表项的卡片样式 */
.site-content article {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.site-content article:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
}
.entry-header .entry-title {
font-size: 28px;
font-weight: 700;
margin-bottom: 12px;
}
.entry-header .entry-title a {
color: var(--text-primary);
text-decoration: none;
}
.entry-header .entry-title a:hover {
color: var(--theme-blue);
}
/* 文章摘要/内容 */
.entry-summary, .entry-content {
color: #555;
margin-bottom: 20px;
}
.entry-content p {
margin-bottom: 1.5em;
}
/* "Read more" 链接 */
.entry-summary a.more-link, .read-more a {
display: inline-block;
font-weight: 600;
margin-top: 10px;
}
/* 文章元信息 (作者、日期、分类、标签) */
footer.entry-meta {
font-size: 14px;
color: var(--text-secondary);
border-top: 1px solid var(--border-color);
padding-top: 16px;
margin-top: 16px;
}
footer.entry-meta a {
color: var(--text-secondary);
text-decoration: underline;
text-decoration-color: transparent;
transition: all 0.2s;
}
footer.entry-meta a:hover {
color: var(--theme-blue);
text-decoration-color: var(--theme-blue);
}
footer.entry-meta span {
margin-right: 15px;
}
/* === 4. 侧边栏 (Sidebar) === */
.widget-area {
width: 300px; /* 固定宽度 */
flex-shrink: 0;
margin: 0;
}
.widget-area .widget {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.widget-area .widget-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid var(--theme-blue);
}
.widget-area .widget ul {
list-style: none;
padding: 0;
margin: 0;
}
.widget-area .widget li {
margin-bottom: 10px;
}
.widget-area .widget li a {
color: #555;
text-decoration: none;
display: flex;
justify-content: space-between;
}
.widget-area .widget li a:hover {
color: var(--theme-blue);
}
/* 搜索框样式 */
#searchform #s {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
box-sizing: border-box;
}
#searchform #searchsubmit {
display: none;
}
/* === 5. 页脚 (Footer) === */
footer[role="contentinfo"] {
background-color: #2c3e50; /* 深蓝灰色背景 */
color: #bdc3c7; /* 浅灰色文字 */
padding: 40px 20px;
text-align: center;
font-size: 14px;
border-top: none;
max-width: 100%;
}
footer[role="contentinfo"] a {
color: #ecf0f1; /* 白色链接 */
}
footer[role="contentinfo"] a:hover {
color: var(--theme-blue);
}
.site-info {
margin-bottom: 10px;
}
/* === 6. 文章详情页特定样式 === */
.entry-content h1, .entry-content h2, .entry-content h3 {
font-weight: 700;
margin-top: 2em;
margin-bottom: 1em;
}
.entry-content h1 { font-size: 2em; }
.entry-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--border-color); padding-bottom: .3em;}
.entry-content h3 { font-size: 1.25em; }
.entry-content blockquote {
border-left: 4px solid var(--theme-blue);
background-color: var(--bg-color);
padding: 15px 20px;
margin: 20px 0;
font-style: italic;
color: #666;
}
/* 代码块样式 */
.entry-content pre {
background-color: #2d2d2d;
color: #f8f8f2;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
border: none;
}
.entry-content code {
background-color: #e8e8e8;
padding: .2em .4em;
margin: 0;
font-size: 85%;
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.entry-content pre code {
background: none;
padding: 0;
}
/* === 7. 分页导航 === */
.pagination {
display: flex;
justify-content: center;
gap: 10px;
margin: 40px 0;
list-style: none;
}
.pagination .page-item a, .pagination .page-item span {
display: block;
padding: 10px 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--card-bg);
color: var(--text-secondary);
transition: all 0.2s;
}
.pagination .page-item a:hover {
border-color: var(--theme-blue);
color: var(--theme-blue);
text-decoration: none;
}
.pagination .page-item.active span {
background-color: var(--theme-blue);
border-color: var(--theme-blue);
color: #fff;
}
/* 响应式设计 */
@media screen and (max-width: 768px) {
.wrapper {
flex-direction: column;
}
.widget-area {
width: 100%;
}
.main-navigation .nav-menu {
display: none !important;
}
}
/* === 8. 文章导航 (上一篇/下一篇) 卡片化 === */
.nav-single {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 40px 0;
border-top: none;
}
.nav-single .nav-previous,
.nav-single .nav-next {
width: 100%;
text-align: left;
}
.nav-single a {
display: block;
padding: 20px;
border: 1px solid var(--border-color);
border-radius: 12px;
background-color: var(--card-bg);
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.3s ease;
height: 100%; /* 保证两张卡片等高 */
box-sizing: border-box;
}
.nav-single a:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
border-color: var(--theme-blue);
text-decoration: none;
}
/* 导航卡片内的标题和提示文字 */
.nav-single .meta-nav {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 8px;
}
.nav-single .nav-next {
text-align: right; /* 下一篇卡片内容右对齐 */
}
.nav-single .nav-next a {
text-align: right;
}
/* === 9. 评论区 UI 优化 === */
/* 评论区整体容器 */
.comments-area {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 30px;
margin-top: 40px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
/* 评论区标题,如“发表评论” */
.comments-area .comments-title,
.comments-area #reply-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 24px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
/* 评论列表 */
.commentlist {
list-style: none;
padding: 0;
}
.commentlist .comment {
border-bottom: 1px solid var(--border-color);
padding: 20px 0;
}
.commentlist .comment:last-child {
border-bottom: none;
}
/* 评论头部:头像、作者、时间 */
.commentlist .comment-author .avatar {
float: left;
margin-right: 15px;
border-radius: 50%; /* 圆形头像 */
box-shadow: none;
}
.commentlist .fn { /* 评论作者 */
font-weight: 600;
color: var(--text-primary);
}
.commentlist .comment-meta a { /* 评论时间 */
font-size: 14px;
color: var(--text-secondary);
}
/* 评论内容 */
.comment-content {
padding-top: 10px;
clear: both;
}
/* 评论回复按钮 */
.reply a {
font-size: 14px;
font-weight: 600;
}
/* 评论表单 */
#respond form {
margin-top: 20px;
}
#respond textarea {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
min-height: 120px;
font-family: var(--font-family-base);
font-size: 16px;
box-sizing: border-box;
transition: border-color 0.2s;
}
#respond textarea:focus {
outline: none;
border-color: var(--theme-blue);
box-shadow: 0 0 0 3px rgba(10, 118, 247, 0.2);
}
/* 发表评论按钮 */
#respond .form-submit input[type="submit"] {
background-color: var(--theme-blue);
color: #fff;
border: none;
padding: 10px 24px;
font-size: 16px;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
box-shadow: none;
}
#respond .form-submit input[type="submit"]:hover {
background-color: #085db8;
}
/* “支持markdown”提示文字 */
#respond .comment-notes {
font-size: 14px;
color: var(--text-secondary);
margin-top: 10px;
}
/* 登录后才能评论的提示 */
.comments-area .comment-meta {
font-size: 16px;
font-weight: 500;
}
/* =====================
(Dark Mode)
===================== */
[data-theme="dark"] {
--theme-blue: #3d8bfd; /* 稍微亮一点的蓝 */
--bg-color: #121212;
--card-bg: #1e1e1e;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--border-color: #333333;
}
[data-theme="dark"] img {
filter: brightness(0.9); /* 图片稍微压暗一点,护眼 */
}
/* 评论区代码块在深色模式下的微调 */
[data-theme="dark"] .entry-content pre {
background-color: #111;
border: 1px solid #333;
}
[data-theme="dark"] .ai-chat-window {
background: #1e1e1e;
border-color: #333;
}
[data-theme="dark"] .ai-input-area {
background: #1e1e1e;
border-color: #333;
}
[data-theme="dark"] .ai-input-area input {
background: #2d2d2d;
color: #fff;
border-color: #444;
}
[data-theme="dark"] .message.ai {
background: #2d2d2d;
color: #eee;
border-color: #444;
}

@ -0,0 +1,378 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Zdc0.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWJ0bbck.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFUZ0bbck.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWZ0bbck.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWp0bbck.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFW50bbck.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save