19 changed files with 499 additions and 192 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,6 @@ |
|||||
|
|
||||
|
# 开发文档 |
||||
|
|
||||
|
## 记录说明 |
||||
|
|
||||
|
本系统存在token和session两种机制,因此,ctx.session表明session,会影响到客户端的cookie, 由于此机制,ctx.session.user和ctx.state.user都存放了用户信息,注意区别使用。 |
||||
@ -0,0 +1,123 @@ |
|||||
|
import { ArticleModel } from "../../db/models/ArticleModel.js" |
||||
|
import Router from "utils/router.js" |
||||
|
import { marked } from "marked" |
||||
|
|
||||
|
class ArticleController { |
||||
|
async index(ctx) { |
||||
|
const { page = 1, view = 'grid' } = ctx.query |
||||
|
const limit = 12 // 每页显示的文章数量
|
||||
|
const offset = (page - 1) * limit |
||||
|
|
||||
|
// 获取文章总数
|
||||
|
const total = await ArticleModel.getPublishedArticleCount() |
||||
|
const totalPages = Math.ceil(total / limit) |
||||
|
|
||||
|
// 获取分页文章
|
||||
|
const articles = await ArticleModel.findPublished(offset, limit) |
||||
|
|
||||
|
// 获取所有分类和标签
|
||||
|
const categories = await ArticleModel.getArticleCountByCategory() |
||||
|
const allArticles = await ArticleModel.findPublished() |
||||
|
const tags = new Set() |
||||
|
allArticles.forEach(article => { |
||||
|
if (article.tags) { |
||||
|
article.tags.split(',').forEach(tag => { |
||||
|
tags.add(tag.trim()) |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
return ctx.render("page/articles/index", { |
||||
|
articles, |
||||
|
categories: categories.map(c => c.category), |
||||
|
tags: Array.from(tags), |
||||
|
currentPage: parseInt(page), |
||||
|
totalPages, |
||||
|
view, |
||||
|
title: "文章列表", |
||||
|
}, { |
||||
|
includeUser: true, |
||||
|
includeSite: true, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async show(ctx) { |
||||
|
const { slug } = ctx.params |
||||
|
console.log(slug); |
||||
|
|
||||
|
const article = await ArticleModel.findBySlug(slug) |
||||
|
|
||||
|
if (!article) { |
||||
|
ctx.throw(404, "文章不存在") |
||||
|
} |
||||
|
|
||||
|
// 增加阅读次数
|
||||
|
await ArticleModel.incrementViewCount(article.id) |
||||
|
|
||||
|
// 将文章内容解析为HTML
|
||||
|
article.content = marked(article.content || '') |
||||
|
|
||||
|
// 获取相关文章
|
||||
|
const relatedArticles = await ArticleModel.getRelatedArticles(article.id) |
||||
|
|
||||
|
return ctx.render("page/articles/article", { |
||||
|
article, |
||||
|
relatedArticles, |
||||
|
title: article.title, |
||||
|
}, { |
||||
|
includeUser: true, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async byCategory(ctx) { |
||||
|
const { category } = ctx.params |
||||
|
const articles = await ArticleModel.findByCategory(category) |
||||
|
|
||||
|
return ctx.render("page/articles/category", { |
||||
|
articles, |
||||
|
category, |
||||
|
title: `${category} - 分类文章`, |
||||
|
}, { |
||||
|
includeUser: true, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async byTag(ctx) { |
||||
|
const { tag } = ctx.params |
||||
|
const articles = await ArticleModel.findByTags(tag) |
||||
|
|
||||
|
return ctx.render("page/articles/tag", { |
||||
|
articles, |
||||
|
tag, |
||||
|
title: `${tag} - 标签文章`, |
||||
|
}, { |
||||
|
includeUser: true, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async search(ctx) { |
||||
|
const { q } = ctx.query |
||||
|
const articles = await ArticleModel.searchByKeyword(q) |
||||
|
|
||||
|
return ctx.render("page/articles/search", { |
||||
|
articles, |
||||
|
keyword: q, |
||||
|
title: `搜索:${q}`, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
static createRoutes() { |
||||
|
const controller = new ArticleController() |
||||
|
const router = new Router({ auth: true, prefix: "/articles" }) |
||||
|
router.get("", controller.index, { auth: false }) // 允许未登录访问
|
||||
|
router.get("/", controller.index, { auth: false }) // 允许未登录访问
|
||||
|
router.get("/search", controller.search) |
||||
|
router.get("/category/:category", controller.byCategory) |
||||
|
router.get("/tag/:tag", controller.byTag) |
||||
|
router.get("/:slug", controller.show) |
||||
|
return router |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default ArticleController |
||||
|
export { ArticleController } |
||||
@ -0,0 +1,70 @@ |
|||||
|
extends /layouts/empty.pug |
||||
|
|
||||
|
block pageContent |
||||
|
.container.mx-auto.px-4.py-8 |
||||
|
article.max-w-4xl.mx-auto |
||||
|
header.mb-8 |
||||
|
h1.text-4xl.font-bold.mb-4= article.title |
||||
|
.flex.flex-wrap.items-center.text-gray-600.mb-4 |
||||
|
span.mr-4 |
||||
|
i.fas.fa-calendar-alt.mr-1 |
||||
|
= new Date(article.published_at).toLocaleDateString() |
||||
|
span.mr-4 |
||||
|
i.fas.fa-eye.mr-1 |
||||
|
= article.view_count + " 阅读" |
||||
|
if article.reading_time |
||||
|
span.mr-4 |
||||
|
i.fas.fa-clock.mr-1 |
||||
|
= article.reading_time + " 分钟阅读" |
||||
|
if article.category |
||||
|
a.text-blue-600.mr-4(href=`/articles/category/${article.category}` class="hover:text-blue-800") |
||||
|
i.fas.fa-folder.mr-1 |
||||
|
= article.category |
||||
|
if article.status === "draft" |
||||
|
span(class="ml-2 px-2 py-0.5 text-xs bg-yellow-200 text-yellow-800 rounded align-middle") 未发布 |
||||
|
|
||||
|
if article.tags |
||||
|
.flex.flex-wrap.gap-2.mb-4 |
||||
|
each tag in article.tags.split(',') |
||||
|
a.bg-gray-100.text-gray-700.px-3.py-1.rounded-full.text-sm(href=`/articles/tag/${tag.trim()}` class="hover:bg-gray-200") |
||||
|
i.fas.fa-tag.mr-1 |
||||
|
= tag.trim() |
||||
|
|
||||
|
if article.featured_image |
||||
|
.mb-8 |
||||
|
img.w-full.rounded-lg.shadow-lg(src=article.featured_image alt=article.title) |
||||
|
|
||||
|
.prose.prose-lg.max-w-none.mb-8.markdown-content(class="prose-pre:bg-gray-100 prose-pre:p-4 prose-pre:rounded-lg prose-code:text-blue-600 prose-blockquote:border-l-4 prose-blockquote:border-gray-300 prose-blockquote:pl-4 prose-blockquote:italic prose-img:rounded-lg prose-img:shadow-md") |
||||
|
!= article.content |
||||
|
|
||||
|
if article.keywords || article.description |
||||
|
.bg-gray-50.rounded-lg.p-6.mb-8 |
||||
|
if article.keywords |
||||
|
.mb-4 |
||||
|
h3.text-lg.font-semibold.mb-2 关键词 |
||||
|
.flex.flex-wrap.gap-2 |
||||
|
each keyword in article.keywords.split(',') |
||||
|
span.bg-white.px-3.py-1.rounded-full.text-sm= keyword.trim() |
||||
|
if article.description |
||||
|
h3.text-lg.font-semibold.mb-2 描述 |
||||
|
p.text-gray-600= article.description |
||||
|
|
||||
|
if relatedArticles && relatedArticles.length |
||||
|
section.border-t.pt-8.mt-8 |
||||
|
h2.text-2xl.font-bold.mb-6 相关文章 |
||||
|
.grid.grid-cols-1.gap-6(class="md:grid-cols-2") |
||||
|
each related in relatedArticles |
||||
|
.bg-white.shadow-md.rounded-lg.overflow-hidden |
||||
|
if related.featured_image |
||||
|
img.w-full.h-48.object-cover(src=related.featured_image alt=related.title) |
||||
|
.p-6 |
||||
|
h3.text-xl.font-semibold.mb-2 |
||||
|
a(href=`/articles/${related.slug}` class="hover:text-blue-600")= related.title |
||||
|
if related.excerpt |
||||
|
p.text-gray-600.text-sm.mb-4= related.excerpt |
||||
|
.flex.justify-between.items-center.text-sm.text-gray-500 |
||||
|
span |
||||
|
i.fas.fa-calendar-alt.mr-1 |
||||
|
= new Date(related.published_at).toLocaleDateString() |
||||
|
if related.category |
||||
|
a.text-blue-600(href=`/articles/category/${related.category}` class="hover:text-blue-800")= related.category |
||||
@ -0,0 +1,29 @@ |
|||||
|
extends /layouts/empty.pug |
||||
|
|
||||
|
block pageContent |
||||
|
.container.mx-auto.px-4.py-8 |
||||
|
h1.text-3xl.font-bold.mb-8 |
||||
|
span.text-gray-600 分类: |
||||
|
= category |
||||
|
|
||||
|
.grid.grid-cols-1.gap-6(class="md:grid-cols-2 lg:grid-cols-3") |
||||
|
each article in articles |
||||
|
.bg-white.shadow-md.rounded-lg.overflow-hidden |
||||
|
if article.featured_image |
||||
|
img.w-full.h-48.object-cover(src=article.featured_image alt=article.title) |
||||
|
.p-6 |
||||
|
h2.text-xl.font-semibold.mb-2 |
||||
|
a(href=`/articles/${article.slug}` class="hover:text-blue-600")= article.title |
||||
|
if article.excerpt |
||||
|
p.text-gray-600.mb-4= article.excerpt |
||||
|
.flex.justify-between.items-center.text-sm.text-gray-500 |
||||
|
span |
||||
|
i.fas.fa-calendar-alt.mr-1 |
||||
|
= new Date(article.published_at).toLocaleDateString() |
||||
|
span |
||||
|
i.fas.fa-eye.mr-1 |
||||
|
= article.view_count + " 阅读" |
||||
|
|
||||
|
if !articles.length |
||||
|
.text-center.py-8 |
||||
|
p.text-gray-500 该分类下暂无文章 |
||||
@ -1,113 +1,133 @@ |
|||||
extends /layouts/page.pug |
extends /layouts/empty.pug |
||||
|
|
||||
block pageContent |
block pageContent |
||||
.article-list-container-full |
.flex.flex-col |
||||
- const articles = [] |
.flex-1.p-8.bg-gray-50 |
||||
- articles.push({ id: 1, title: '文章标题1', author: '作者1', created_at: '2023-08-01', summary: '这是文章摘要...' }) |
.container.mx-auto |
||||
- articles.push({ id: 2, title: '文章标题2', author: '作者2', created_at: '2023-08-02', summary: '这是另一篇文章摘要...' }) |
// 页头 |
||||
//- 文章列表 |
.flex.justify-between.items-center.mb-8 |
||||
if articles && articles.length |
h1.text-2xl.font-bold 文章列表 |
||||
|
.flex.gap-4 |
||||
|
// 搜索框 |
||||
|
.relative |
||||
|
input#searchInput.w-64.pl-10.pr-4.py-2.border.rounded-lg( |
||||
|
type="text" |
||||
|
placeholder="搜索文章..." |
||||
|
hx-get="/articles/search" |
||||
|
hx-trigger="keyup changed delay:500ms" |
||||
|
hx-target="#articleList" |
||||
|
name="q" |
||||
|
class="focus:outline-none focus:ring-blue-500 focus:ring-2" |
||||
|
) |
||||
|
i.fas.fa-search.absolute.left-3.top-3.text-gray-400 |
||||
|
|
||||
|
// 视图切换按钮 |
||||
|
.flex.items-center.gap-2.bg-white.p-1.rounded-lg.border |
||||
|
button.p-2.rounded( |
||||
|
class="hover:bg-gray-100" |
||||
|
hx-get="/articles?view=grid" |
||||
|
hx-target="#articleList" |
||||
|
) |
||||
|
i.fas.fa-th-large |
||||
|
button.p-2.rounded( |
||||
|
class="hover:bg-gray-100" |
||||
|
hx-get="/articles?view=list" |
||||
|
hx-target="#articleList" |
||||
|
) |
||||
|
i.fas.fa-list |
||||
|
|
||||
|
// 筛选栏 |
||||
|
.bg-white.rounded-lg.shadow-sm.p-4.mb-6 |
||||
|
.flex.flex-wrap.gap-4 |
||||
|
if categories && categories.length |
||||
|
.flex.items-center.gap-2 |
||||
|
span.text-gray-600 分类: |
||||
|
each cat in categories |
||||
|
a.px-3.py-1.rounded-full( |
||||
|
class="hover:bg-blue-50 hover:text-blue-600" + (cat === currentCategory ? " bg-blue-100 text-blue-600" : "") |
||||
|
href=`/articles/category/${cat}` |
||||
|
)= cat |
||||
|
|
||||
|
if tags && tags.length |
||||
|
.flex.items-center.gap-2 |
||||
|
span.text-gray-600 标签: |
||||
|
each tag in tags |
||||
|
a.px-3.py-1.rounded-full( |
||||
|
class="hover:bg-blue-50 hover:text-blue-600" + (tag === currentTag ? " bg-blue-100 text-blue-600" : "") |
||||
|
href=`/articles/tag/${tag}` |
||||
|
)= tag |
||||
|
|
||||
|
// 文章列表 |
||||
|
#articleList.grid.grid-cols-1.gap-6(class="md:grid-cols-2 lg:grid-cols-3") |
||||
each article in articles |
each article in articles |
||||
.article-item-full |
.bg-white.rounded-lg.shadow-sm.overflow-hidden.transition.duration-300.transform(class="hover:-translate-y-1 hover:shadow-md") |
||||
h2.article-title-full |
if article.featured_image |
||||
a(href=`/articles/${article.id}`) #{article.title} |
.relative.h-48 |
||||
.article-meta-full |
img.w-full.h-full.object-cover(src=article.featured_image alt=article.title) |
||||
span 作者:#{article.author} | 发布时间:#{article.created_at} |
if article.category |
||||
p.article-summary-full #{article.summary} |
a.absolute.top-3.right-3.px-3.py-1.bg-blue-600.text-white.text-sm.rounded-full.opacity-90( |
||||
else |
href=`/articles/category/${article.category}` |
||||
p.no-articles 暂无文章 |
class="hover:opacity-100" |
||||
|
)= article.category |
||||
|
.p-6 |
||||
|
h2.text-xl.font-bold.mb-3 |
||||
|
a(href=`/articles/${article.slug}` class="hover:text-blue-600")= article.title |
||||
|
if article.excerpt |
||||
|
p.text-gray-600.text-sm.mb-4.line-clamp-2= article.excerpt |
||||
|
|
||||
|
.flex.flex-wrap.gap-2.mb-4 |
||||
|
if article.tags |
||||
|
each tag in article.tags.split(',') |
||||
|
a.text-sm.text-gray-500( |
||||
|
href=`/articles/tag/${tag.trim()}` |
||||
|
class="hover:text-blue-600" |
||||
|
) |
||||
|
i.fas.fa-tag.mr-1 |
||||
|
= tag.trim() |
||||
|
|
||||
//- 分页控件 |
.flex.justify-between.items-center.text-sm.text-gray-500 |
||||
|
.flex.items-center.gap-4 |
||||
|
span |
||||
|
i.far.fa-calendar.mr-1 |
||||
|
= new Date(article.published_at).toLocaleDateString() |
||||
|
if article.reading_time |
||||
|
span |
||||
|
i.far.fa-clock.mr-1 |
||||
|
= article.reading_time + "分钟" |
||||
|
span |
||||
|
i.far.fa-eye.mr-1 |
||||
|
= article.view_count + " 阅读" |
||||
|
|
||||
|
if !articles.length |
||||
|
.col-span-full.py-16.text-center |
||||
|
.text-gray-400.mb-4 |
||||
|
i.fas.fa-inbox.text-6xl |
||||
|
p.text-gray-500 暂无文章 |
||||
|
|
||||
|
// 分页 |
||||
if totalPages > 1 |
if totalPages > 1 |
||||
.pagination-full |
.flex.justify-center.mt-8 |
||||
if page > 1 |
nav.flex.items-center.gap-1(aria-label="Pagination") |
||||
a.page-btn-full(href=`?page=${page-1}`) 上一页 |
// 上一页 |
||||
else |
if currentPage > 1 |
||||
span.page-btn-full.disabled 上一页 |
a.px-3.py-1.rounded-md.bg-white.border( |
||||
span.page-info-full 第 #{page} / #{totalPages} 页 |
href=`/articles?page=${currentPage - 1}` |
||||
if page < totalPages |
class="text-gray-500 hover:text-gray-700 hover:bg-gray-50" |
||||
a.page-btn-full(href=`?page=${page+1}`) 下一页 |
) 上一页 |
||||
|
|
||||
|
// 页码 |
||||
|
each page in Array.from({length: totalPages}, (_, i) => i + 1) |
||||
|
if page === currentPage |
||||
|
span.px-3.py-1.rounded-md.bg-blue-50.text-blue-600.border.border-blue-200= page |
||||
else |
else |
||||
span.page-btn-full.disabled 下一页 |
a.px-3.py-1.rounded-md.bg-white.border( |
||||
style. |
href=`/articles?page=${page}` |
||||
.article-list-container-full { |
class="text-gray-500 hover:text-gray-700 hover:bg-gray-50" |
||||
width: 100%; |
)= page |
||||
max-width: 100%; |
|
||||
margin: 40px 0 0 0; |
// 下一页 |
||||
background: transparent; |
if currentPage < totalPages |
||||
border-radius: 0; |
a.px-3.py-1.rounded-md.bg-white.border( |
||||
box-shadow: none; |
href=`/articles?page=${currentPage + 1}` |
||||
padding: 0; |
class="text-gray-500 hover:text-gray-700 hover:bg-gray-50" |
||||
display: flex; |
) 下一页 |
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
} |
|
||||
.article-item-full { |
|
||||
width: 90vw; |
|
||||
max-width: 1200px; |
|
||||
background: #fff; |
|
||||
border-radius: 14px; |
|
||||
box-shadow: 0 2px 16px #e0e7ef; |
|
||||
margin-bottom: 28px; |
|
||||
padding: 28px 36px 18px 36px; |
|
||||
border-left: 6px solid #6dd5fa; |
|
||||
transition: box-shadow 0.2s, border-color 0.2s; |
|
||||
} |
|
||||
.article-item-full:hover { |
|
||||
box-shadow: 0 4px 32px #cbe7ff; |
|
||||
border-left: 6px solid #ff6a88; |
|
||||
} |
|
||||
.article-title-full { |
|
||||
margin: 0 0 8px 0; |
|
||||
font-size: 1.6em; |
|
||||
} |
|
||||
.article-title-full a { |
|
||||
color: #2b7cff; |
|
||||
text-decoration: none; |
|
||||
transition: color 0.2s; |
|
||||
} |
|
||||
.article-title-full a:hover { |
|
||||
color: #ff6a88; |
|
||||
} |
|
||||
.article-meta-full { |
|
||||
color: #888; |
|
||||
font-size: 1em; |
|
||||
margin-bottom: 8px; |
|
||||
} |
|
||||
.article-summary-full { |
|
||||
color: #444; |
|
||||
font-size: 1.13em; |
|
||||
} |
|
||||
.no-articles { |
|
||||
text-align: center; |
|
||||
color: #aaa; |
|
||||
margin: 40px 0; |
|
||||
} |
|
||||
.pagination-full { |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
margin: 32px 0 0 0; |
|
||||
gap: 18px; |
|
||||
} |
|
||||
.page-btn-full { |
|
||||
padding: 7px 22px; |
|
||||
border-radius: 22px; |
|
||||
background: linear-gradient(90deg, #6dd5fa, #ff6a88); |
|
||||
color: #fff; |
|
||||
text-decoration: none; |
|
||||
font-weight: bold; |
|
||||
transition: background 0.2s; |
|
||||
cursor: pointer; |
|
||||
font-size: 1.08em; |
|
||||
} |
|
||||
.page-btn-full.disabled { |
|
||||
background: #eee; |
|
||||
color: #bbb; |
|
||||
cursor: not-allowed; |
|
||||
pointer-events: none; |
|
||||
} |
|
||||
.page-info-full { |
|
||||
color: #666; |
|
||||
font-size: 1.12em; |
|
||||
} |
|
||||
|
|||||
@ -0,0 +1,34 @@ |
|||||
|
extends /layouts/empty.pug |
||||
|
|
||||
|
block pageContent |
||||
|
.container.mx-auto.px-4.py-8 |
||||
|
.mb-8 |
||||
|
h1.text-3xl.font-bold.mb-4 |
||||
|
span.text-gray-600 搜索结果: |
||||
|
= keyword |
||||
|
p.text-gray-500 找到 #{articles.length} 篇相关文章 |
||||
|
|
||||
|
.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.gap-6 |
||||
|
each article in articles |
||||
|
.bg-white.shadow-md.rounded-lg.overflow-hidden |
||||
|
if article.featured_image |
||||
|
img.w-full.h-48.object-cover(src=article.featured_image alt=article.title) |
||||
|
.p-6 |
||||
|
h2.text-xl.font-semibold.mb-2 |
||||
|
a.hover:text-blue-600(href=`/articles/${article.slug}`)= article.title |
||||
|
if article.excerpt |
||||
|
p.text-gray-600.mb-4= article.excerpt |
||||
|
.flex.justify-between.items-center |
||||
|
.text-sm.text-gray-500 |
||||
|
span.mr-4 |
||||
|
i.fas.fa-calendar-alt.mr-1 |
||||
|
= new Date(article.published_at).toLocaleDateString() |
||||
|
span |
||||
|
i.fas.fa-eye.mr-1 |
||||
|
= article.view_count + " 阅读" |
||||
|
if article.category |
||||
|
a.text-sm.bg-blue-100.text-blue-600.px-3.py-1.rounded-full.hover:bg-blue-200(href=`/articles/category/${article.category}`)= article.category |
||||
|
|
||||
|
if !articles.length |
||||
|
.text-center.py-8 |
||||
|
p.text-gray-500 未找到相关文章 |
||||
@ -0,0 +1,32 @@ |
|||||
|
extends /layouts/empty.pug |
||||
|
|
||||
|
block pageContent |
||||
|
.container.mx-auto.px-4.py-8 |
||||
|
h1.text-3xl.font-bold.mb-8 |
||||
|
span.text-gray-600 标签: |
||||
|
= tag |
||||
|
|
||||
|
.grid.grid-cols-1.gap-6(class="md:grid-cols-2 lg:grid-cols-3") |
||||
|
each article in articles |
||||
|
.bg-white.shadow-md.rounded-lg.overflow-hidden |
||||
|
if article.featured_image |
||||
|
img.w-full.h-48.object-cover(src=article.featured_image alt=article.title) |
||||
|
.p-6 |
||||
|
h2.text-xl.font-semibold.mb-2 |
||||
|
a(href=`/articles/${article.slug}` class="hover:text-blue-600")= article.title |
||||
|
if article.excerpt |
||||
|
p.text-gray-600.mb-4= article.excerpt |
||||
|
.flex.justify-between.items-center |
||||
|
.text-sm.text-gray-500 |
||||
|
span.mr-4 |
||||
|
i.fas.fa-calendar-alt.mr-1 |
||||
|
= new Date(article.published_at).toLocaleDateString() |
||||
|
span |
||||
|
i.fas.fa-eye.mr-1 |
||||
|
= article.view_count + " 阅读" |
||||
|
if article.category |
||||
|
a.text-sm.bg-blue-100.text-blue-600.px-3.py-1.rounded-full(href=`/articles/category/${article.category}` class="hover:bg-blue-200")= article.category |
||||
|
|
||||
|
if !articles.length |
||||
|
.text-center.py-8 |
||||
|
p.text-gray-500 该标签下暂无文章 |
||||
Loading…
Reference in new issue