fix: 修复博客静态资源兜底与头部结构
@ -8,7 +8,7 @@ KT 博客前台 demo,基于 Argon WordPress 主题的视觉资产重新实现
|
|||||||
- 组件库:antdv-next。
|
- 组件库:antdv-next。
|
||||||
- 样式:SCSS,类名按 BEM 组织。
|
- 样式:SCSS,类名按 BEM 组织。
|
||||||
- 路由:hash 模式,便于静态部署。
|
- 路由:hash 模式,便于静态部署。
|
||||||
- 静态资产:只从 Argon 主题包抽取 demo 需要的图片,不引入 WordPress PHP 与主题脚本。
|
- 静态资产:默认使用旧线上博客真实资源,`public/blog-assets/` 保留本地备份兜底;不得把 Argon demo 图片作为默认背景、头像或 fallback 封面。
|
||||||
|
|
||||||
## 本地运行
|
## 本地运行
|
||||||
|
|
||||||
@ -28,14 +28,15 @@ pnpm exec playwright test e2e/argon-parity/interactions.spec.ts --project=chromi
|
|||||||
pnpm exec playwright test e2e/argon-parity/baseline.spec.ts --project=chromium
|
pnpm exec playwright test e2e/argon-parity/baseline.spec.ts --project=chromium
|
||||||
```
|
```
|
||||||
|
|
||||||
`baseline.spec.ts` 只在需要重新抓线上 WordPress Argon 基准时运行;常规本地回归跑 `pages.spec.ts` 和 `interactions.spec.ts`。线上 `https://blog.kwitsukasa.top/` 是只读视觉/交互基准,不是 Admin iframe 预览目标。
|
`baseline.spec.ts` 只在需要重新抓旧 WordPress Argon 基准时运行;常规本地回归跑 `pages.spec.ts` 和 `interactions.spec.ts`。公开域名 `https://blog.kwitsukasa.top/` 当前是 KT Blog Web 静态站入口,不是只读基准站;旧 WordPress 端口只作为视觉/交互基准和回滚入口。
|
||||||
|
|
||||||
## Argon 还原范围
|
## Argon 还原范围
|
||||||
|
|
||||||
- `e2e/argon-parity` 保存与线上 `https://blog.kwitsukasa.top/` 对齐的页面、视口和交互矩阵。
|
- `e2e/argon-parity` 保存与旧 WordPress Argon 基准对齐的页面、视口和交互矩阵;公开域名切到 KT Blog Web 后,基准抓取必须显式使用旧 WordPress 入口。
|
||||||
- 本地 hash 路由按语义映射 WordPress query 路由:文章、分类、标签、搜索和月份归档都用同一套矩阵验证。
|
- 本地 hash 路由按语义映射 WordPress query 路由:文章、分类、标签、搜索和月份归档都用同一套矩阵验证。
|
||||||
- 页面根节点通过 `kt-blog--home/search/category/tag/archive/post` 暴露 Argon 页面语义,方便样式、测试和 Admin iframe 预览复用。
|
- 页面根节点通过 `kt-blog--home/search/category/tag/archive/post` 暴露 Argon 页面语义,方便样式、测试和 Admin iframe 预览复用。
|
||||||
- 公开 Blog API 返回非空文章列表时优先使用 API;API 不可用或返回空列表时保留内置 WordPress 抓取文章种子,避免数据未迁移期间线上静态站变成空站。
|
- 公开 Blog API 返回非空文章列表时优先使用 API;API 不可用或返回空列表时保留内置 WordPress 抓取文章种子,避免数据未迁移期间线上静态站变成空站。
|
||||||
|
- 主题接口若仍返回 `/argon/theme/*` 历史 demo 占位图,前端必须映射回旧线上博客资源;本地备份只用于兜底,避免静态站重新露出模板图。
|
||||||
- 文章正文以线上 WordPress `#post_content` 渲染结果和 Argon `style.css` / `argontheme.js` 为准;代码块控制条、复制 toast、Fancybox 图片预览、正文链接 hover、分隔线和图片 lazyload 都必须有 Playwright 断言。
|
- 文章正文以线上 WordPress `#post_content` 渲染结果和 Argon `style.css` / `argontheme.js` 为准;代码块控制条、复制 toast、Fancybox 图片预览、正文链接 hover、分隔线和图片 lazyload 都必须有 Playwright 断言。
|
||||||
- `hljs-codeblock` 静态快照需要恢复 Argon `highlightjs-line-numbers` 运行时生成的 `data-line-number` 与 `.hljs-ln-n::before`,否则行号列会坍塌成 0px。
|
- `hljs-codeblock` 静态快照需要恢复 Argon `highlightjs-line-numbers` 运行时生成的 `data-line-number` 与 `.hljs-ln-n::before`,否则行号列会坍塌成 0px。
|
||||||
- 左栏 overview sticky/relative 切换用 no-headroom 回归用例固定,解除 fixed 后不得重放卡片入场动画或产生缩放闪烁。
|
- 左栏 overview sticky/relative 切换用 no-headroom 回归用例固定,解除 fixed 后不得重放卡片入场动画或产生缩放闪烁。
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 44 KiB |
BIN
public/blog-assets/avatar-tsukasa-1.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/blog-assets/bg-donggungun.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
public/blog-assets/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/blog-assets/icons/mstile-270x270.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/blog-assets/icons/site-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/blog-assets/icons/site-icon-32x32.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
@ -126,6 +126,20 @@ describe('App', () => {
|
|||||||
expect(wrapper.text()).toContain('KwiTsukasa的小站');
|
expect(wrapper.text()).toContain('KwiTsukasa的小站');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders Argon header without the old collapse container', async () => {
|
||||||
|
await router.push('/');
|
||||||
|
await router.isReady();
|
||||||
|
|
||||||
|
const wrapper = mount(App, {
|
||||||
|
global: {
|
||||||
|
plugins: [router],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('.kt-blog__header-collapse').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('.kt-blog__header-nav').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('loads local blog theme config by default', async () => {
|
it('loads local blog theme config by default', async () => {
|
||||||
const fetchMock = vi.fn((url: string | URL | Request) => {
|
const fetchMock = vi.fn((url: string | URL | Request) => {
|
||||||
const target = `${url}`;
|
const target = `${url}`;
|
||||||
@ -225,10 +239,10 @@ describe('App', () => {
|
|||||||
expect(styleText).toContain('--themecolor: #C3A1ED;');
|
expect(styleText).toContain('--themecolor: #C3A1ED;');
|
||||||
expect(styleText).toContain('--radius: 6px;');
|
expect(styleText).toContain('--radius: 6px;');
|
||||||
expect(styleText).toContain(
|
expect(styleText).toContain(
|
||||||
"--argon-background-dark-image: url('https://s3.kwitsukasa.top/images/bg-冬滚滚.png');",
|
"--argon-background-dark-image: url('https://s3.kwitsukasa.top/images/bg-冬滚滚.png'), url('/blog-assets/bg-donggungun.png');",
|
||||||
);
|
);
|
||||||
expect(styleText).toContain(
|
expect(styleText).toContain(
|
||||||
"--argon-author-avatar: url('http://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg');",
|
"--argon-author-avatar: url('http://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg'), url('/blog-assets/avatar-tsukasa-1.jpg');",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -359,10 +373,10 @@ describe('App', () => {
|
|||||||
expect(styleText).toContain('--radius: 10px;');
|
expect(styleText).toContain('--radius: 10px;');
|
||||||
expect(styleText).toContain('--argon-background-opacity: 0.8;');
|
expect(styleText).toContain('--argon-background-opacity: 0.8;');
|
||||||
expect(styleText).toContain(
|
expect(styleText).toContain(
|
||||||
"--argon-background-dark-image: url('https://s3.kwitsukasa.top/images/wp-dark.png');",
|
"--argon-background-dark-image: url('https://s3.kwitsukasa.top/images/wp-dark.png'), url('/blog-assets/bg-donggungun.png');",
|
||||||
);
|
);
|
||||||
expect(styleText).toContain(
|
expect(styleText).toContain(
|
||||||
"--argon-author-avatar: url('http://s3.kwitsukasa.top/images/wp-avatar.jpg');",
|
"--argon-author-avatar: url('http://s3.kwitsukasa.top/images/wp-avatar.jpg'), url('/blog-assets/avatar-tsukasa-1.jpg');",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -145,6 +145,40 @@ describe('useBlogArticles', () => {
|
|||||||
expect(blogArticles.articles.value).toEqual(fallbackArticles);
|
expect(blogArticles.articles.value).toEqual(fallbackArticles);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps fallback article covers on previous blog assets instead of Argon demo images', async () => {
|
||||||
|
expect(fallbackArticles.map((article) => article.cover)).not.toContain('/argon/theme/landing.jpg');
|
||||||
|
expect(fallbackArticles.map((article) => article.cover)).not.toContain('/argon/theme/img-2-1200x1000.jpg');
|
||||||
|
expect(fallbackArticles.map((article) => article.cover)).not.toContain('/argon/theme/img-1-1200x1000.jpg');
|
||||||
|
expect(fallbackArticles.map((article) => article.cover)).not.toContain('/argon/theme/promo-1.png');
|
||||||
|
expect(fallbackArticles.every((article) => article.cover.startsWith('https://s3.kwitsukasa.top/images/'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the previous blog cover when public API articles have no cover', async () => {
|
||||||
|
mockFetch([
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
...publicArticle,
|
||||||
|
cover: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const { useBlogArticles } = await import('@/hooks/useBlogArticles');
|
||||||
|
const blogArticles = useBlogArticles();
|
||||||
|
|
||||||
|
await blogArticles.loadArticles();
|
||||||
|
|
||||||
|
expect(blogArticles.articles.value[0]?.cover).toBe('https://s3.kwitsukasa.top/images/bg-冬滚滚.png');
|
||||||
|
});
|
||||||
|
|
||||||
it('fetches public article detail when cached list data does not include html content', async () => {
|
it('fetches public article detail when cached list data does not include html content', async () => {
|
||||||
const fetchMock = mockFetch([
|
const fetchMock = mockFetch([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -140,13 +140,34 @@ describe('useBlogTheme', () => {
|
|||||||
expect(themeRootClass.value).toContain('kt-blog--shadow-big');
|
expect(themeRootClass.value).toContain('kt-blog--shadow-big');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--themecolor: #C3A1ED;');
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--themecolor: #C3A1ED;');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain("url('https://s3.kwitsukasa.top/images/bg-冬滚滚.png')");
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain("url('https://s3.kwitsukasa.top/images/bg-冬滚滚.png')");
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-dark-image: url(\'https://s3.kwitsukasa.top/images/bg-冬滚滚.png\');');
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-dark-image: url(\'https://s3.kwitsukasa.top/images/bg-冬滚滚.png\'), url(\'/blog-assets/bg-donggungun.png\');');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-filter: brightness(0.65);');
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-filter: brightness(0.65);');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-opacity: 1;');
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-opacity: 1;');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-dark-opacity: 1;');
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain('--argon-background-dark-opacity: 1;');
|
||||||
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain("url('http://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg')");
|
expect(document.querySelector('#kt-blog-theme-style')?.textContent).toContain("url('http://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg')");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps legacy Argon placeholder image config back to previous blog static assets', async () => {
|
||||||
|
const { applyWordpressThemeConfig, siteConfig } = useBlogTheme();
|
||||||
|
|
||||||
|
applyWordpressThemeConfig({
|
||||||
|
backgroundDarkImage: '/argon/theme/img-2-1200x1000.jpg',
|
||||||
|
backgroundImage: '/argon/theme/img-2-1200x1000.jpg',
|
||||||
|
site: {
|
||||||
|
authorAvatar: '/argon/theme/profile.jpg',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const styleText = document.querySelector('#kt-blog-theme-style')?.textContent || '';
|
||||||
|
expect(styleText).toContain("url('https://s3.kwitsukasa.top/images/bg-冬滚滚.png')");
|
||||||
|
expect(styleText).toContain("url('/blog-assets/bg-donggungun.png')");
|
||||||
|
expect(styleText).toContain("url('https://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg')");
|
||||||
|
expect(styleText).toContain("url('/blog-assets/avatar-tsukasa-1.jpg')");
|
||||||
|
expect(styleText).not.toContain('/argon/theme/');
|
||||||
|
expect(siteConfig.value.authorAvatar).toBe('https://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
it('mirrors remote layout class switches instead of keeping stale defaults', async () => {
|
it('mirrors remote layout class switches instead of keeping stale defaults', async () => {
|
||||||
const { applyWordpressThemeConfig, themeRootClass, wordpressThemeConfig } = useBlogTheme();
|
const { applyWordpressThemeConfig, themeRootClass, wordpressThemeConfig } = useBlogTheme();
|
||||||
|
|
||||||
|
|||||||
@ -88,24 +88,6 @@ export default defineComponent({
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kt-blog__header-collapse">
|
|
||||||
<div class="kt-blog__header-collapse-head">
|
|
||||||
<div class="kt-blog__header-mobile-search">
|
|
||||||
<div class="kt-blog__input-group">
|
|
||||||
<div class="kt-blog__input-addon-wrap">
|
|
||||||
<span class="kt-blog__input-addon">
|
|
||||||
<SearchOutlined />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<BlogInput
|
|
||||||
class="kt-blog__header-mobile-search-input kt-blog__input"
|
|
||||||
placeholder="搜索什么..."
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="kt-blog__header-nav kt-blog__header-nav--hover">
|
<ul class="kt-blog__header-nav kt-blog__header-nav--hover">
|
||||||
{siteConfig.value.headerMenu.map((item) => (
|
{siteConfig.value.headerMenu.map((item) => (
|
||||||
<li key={item.label} class="kt-blog__header-nav-item">
|
<li key={item.label} class="kt-blog__header-nav-item">
|
||||||
@ -163,7 +145,6 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="kt-blog__header-menu-mask" />
|
<div class="kt-blog__header-menu-mask" />
|
||||||
<BlogButton
|
<BlogButton
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { articleContentHtml } from './blogArticleContent';
|
import { articleContentHtml } from './blogArticleContent';
|
||||||
|
import { PREVIOUS_BLOG_BACKGROUND_IMAGE } from './blogStaticAssets';
|
||||||
|
|
||||||
export interface BlogArticle {
|
export interface BlogArticle {
|
||||||
id: number;
|
id: number;
|
||||||
@ -252,7 +253,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'public',
|
category: 'public',
|
||||||
categorySlug: 'public',
|
categorySlug: 'public',
|
||||||
tags: [],
|
tags: [],
|
||||||
cover: '/argon/theme/landing.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2026-06-18 11:11',
|
date: '2026-06-18 11:11',
|
||||||
readTime: '18 分钟',
|
readTime: '18 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -274,7 +275,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'NAS',
|
category: 'NAS',
|
||||||
categorySlug: 'nas',
|
categorySlug: 'nas',
|
||||||
tags: ['NAS'],
|
tags: ['NAS'],
|
||||||
cover: '/argon/theme/img-2-1200x1000.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2026-05-16 16:43',
|
date: '2026-05-16 16:43',
|
||||||
readTime: '14 分钟',
|
readTime: '14 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -298,7 +299,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'NAS',
|
category: 'NAS',
|
||||||
categorySlug: 'nas',
|
categorySlug: 'nas',
|
||||||
tags: ['NAS'],
|
tags: ['NAS'],
|
||||||
cover: '/argon/theme/img-1-1200x1000.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2026-05-12 12:00',
|
date: '2026-05-12 12:00',
|
||||||
readTime: '16 分钟',
|
readTime: '16 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -321,7 +322,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'NAS',
|
category: 'NAS',
|
||||||
categorySlug: 'nas',
|
categorySlug: 'nas',
|
||||||
tags: ['NAS'],
|
tags: ['NAS'],
|
||||||
cover: '/argon/theme/landing.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2026-05-12 11:58',
|
date: '2026-05-12 11:58',
|
||||||
readTime: '14 分钟',
|
readTime: '14 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -344,7 +345,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'Vue',
|
category: 'Vue',
|
||||||
categorySlug: 'vue',
|
categorySlug: 'vue',
|
||||||
tags: ['Vue'],
|
tags: ['Vue'],
|
||||||
cover: '/argon/theme/promo-1.png',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2025-10-31 16:16',
|
date: '2025-10-31 16:16',
|
||||||
readTime: '11 分钟',
|
readTime: '11 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -369,7 +370,7 @@ export const articles: BlogArticle[] = [
|
|||||||
{ slug: 'vue', label: 'Vue' },
|
{ slug: 'vue', label: 'Vue' },
|
||||||
],
|
],
|
||||||
tags: ['MQTT', 'Vue'],
|
tags: ['MQTT', 'Vue'],
|
||||||
cover: '/argon/theme/img-2-1200x1000.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2025-10-29 16:50',
|
date: '2025-10-29 16:50',
|
||||||
readTime: '17 分钟',
|
readTime: '17 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
@ -392,7 +393,7 @@ export const articles: BlogArticle[] = [
|
|||||||
category: 'Node',
|
category: 'Node',
|
||||||
categorySlug: 'node',
|
categorySlug: 'node',
|
||||||
tags: ['Node'],
|
tags: ['Node'],
|
||||||
cover: '/argon/theme/img-1-1200x1000.jpg',
|
cover: PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
date: '2025-10-29 15:38',
|
date: '2025-10-29 15:38',
|
||||||
readTime: '13 分钟',
|
readTime: '13 分钟',
|
||||||
author: 'KwiTsukasa',
|
author: 'KwiTsukasa',
|
||||||
|
|||||||
59
src/data/blogStaticAssets.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
export const PREVIOUS_BLOG_BACKGROUND_IMAGE = 'https://s3.kwitsukasa.top/images/bg-冬滚滚.png';
|
||||||
|
export const PREVIOUS_BLOG_AUTHOR_AVATAR = 'https://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg';
|
||||||
|
export const LOCAL_BLOG_BACKGROUND_IMAGE = '/blog-assets/bg-donggungun.png';
|
||||||
|
export const LOCAL_BLOG_AUTHOR_AVATAR = '/blog-assets/avatar-tsukasa-1.jpg';
|
||||||
|
|
||||||
|
const LEGACY_ARGON_ASSET_REPLACEMENTS: Record<string, string> = {
|
||||||
|
'/argon/theme/img-1-1200x1000.jpg': PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
|
'/argon/theme/img-2-1200x1000.jpg': PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
|
'/argon/theme/landing.jpg': PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
|
'/argon/theme/profile.jpg': PREVIOUS_BLOG_AUTHOR_AVATAR,
|
||||||
|
'/argon/theme/promo-1.png': PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves theme/article image values while keeping the old real online resources as the primary source.
|
||||||
|
*
|
||||||
|
* @param value Image URL from API data, static captured article data, or a CSS `url(...)` value.
|
||||||
|
* @param fallback Primary fallback to use when the value is empty; callers pass the matching online old asset.
|
||||||
|
* @returns A usable online/local image URL with legacy Argon demo placeholders mapped back to previous blog assets.
|
||||||
|
*/
|
||||||
|
export function resolveBlogStaticAsset(value?: null | string, fallback = PREVIOUS_BLOG_BACKGROUND_IMAGE) {
|
||||||
|
const asset = unwrapBlogCssImage(value);
|
||||||
|
if (!asset) return fallback;
|
||||||
|
|
||||||
|
const replacementKey = getAssetPath(asset);
|
||||||
|
|
||||||
|
return LEGACY_ARGON_ASSET_REPLACEMENTS[replacementKey] || asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the raw asset URL from plain URLs and CSS `url(...)` tokens.
|
||||||
|
*
|
||||||
|
* @param value Candidate image token from the theme API or generated CSS.
|
||||||
|
* @returns Trimmed URL/path without surrounding `url(...)` syntax, or an empty string for unusable values.
|
||||||
|
*/
|
||||||
|
export function unwrapBlogCssImage(value?: null | string) {
|
||||||
|
const normalized = `${value || ''}`.trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
const cssImage = /^url\((.*)\)$/i.exec(normalized)?.[1]?.trim();
|
||||||
|
|
||||||
|
return (cssImage ? cssImage.replace(/^['"]|['"]$/g, '') : normalized).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes absolute same-path assets to their pathname so legacy placeholder matching is stable.
|
||||||
|
*
|
||||||
|
* @param asset URL or root-relative path to compare against known Argon demo placeholder files.
|
||||||
|
* @returns Pathname for absolute URLs and the original value for root-relative paths.
|
||||||
|
*/
|
||||||
|
function getAssetPath(asset: string) {
|
||||||
|
if (asset.startsWith('/')) return asset;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(asset).pathname;
|
||||||
|
} catch {
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,14 +16,9 @@ import {
|
|||||||
type BlogCategory,
|
type BlogCategory,
|
||||||
type BlogTag,
|
type BlogTag,
|
||||||
} from '@/data/blog';
|
} from '@/data/blog';
|
||||||
|
import { PREVIOUS_BLOG_BACKGROUND_IMAGE, resolveBlogStaticAsset } from '@/data/blogStaticAssets';
|
||||||
|
|
||||||
const coverPool = [
|
const defaultCover = PREVIOUS_BLOG_BACKGROUND_IMAGE;
|
||||||
'/argon/theme/img-2-1200x1000.jpg',
|
|
||||||
'/argon/theme/img-1-1200x1000.jpg',
|
|
||||||
'/argon/theme/landing.jpg',
|
|
||||||
'/argon/theme/promo-1.png',
|
|
||||||
];
|
|
||||||
const defaultCover = coverPool[0] || '/argon/theme/img-2-1200x1000.jpg';
|
|
||||||
const colorPool = ['blue', 'purple', 'green', 'orange', 'geekblue', 'cyan', 'volcano', 'magenta'];
|
const colorPool = ['blue', 'purple', 'green', 'orange', 'geekblue', 'cyan', 'volcano', 'magenta'];
|
||||||
const blogArticles = ref<BlogArticle[]>(fallbackArticles);
|
const blogArticles = ref<BlogArticle[]>(fallbackArticles);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -222,7 +217,7 @@ function normalizeWordpressArticle(article: WordpressPublicArticle): BlogArticle
|
|||||||
comments: 0,
|
comments: 0,
|
||||||
content: contentText ? [contentText] : [],
|
content: contentText ? [contentText] : [],
|
||||||
contentHtml,
|
contentHtml,
|
||||||
cover: article.cover || coverPool[article.id % coverPool.length] || defaultCover,
|
cover: resolveBlogStaticAsset(article.cover, defaultCover),
|
||||||
date: formatDate(article.date || article.modified),
|
date: formatDate(article.date || article.modified),
|
||||||
excerpt,
|
excerpt,
|
||||||
id: article.id,
|
id: article.id,
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
import { theme } from 'antdv-next';
|
import { theme } from 'antdv-next';
|
||||||
import { computed, reactive, watch } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PREVIOUS_BLOG_AUTHOR_AVATAR,
|
||||||
|
PREVIOUS_BLOG_BACKGROUND_IMAGE,
|
||||||
|
LOCAL_BLOG_AUTHOR_AVATAR,
|
||||||
|
LOCAL_BLOG_BACKGROUND_IMAGE,
|
||||||
|
resolveBlogStaticAsset,
|
||||||
|
unwrapBlogCssImage,
|
||||||
|
} from '@/data/blogStaticAssets';
|
||||||
import { createBlogMotionCssVariables } from '@/factories/blogAnimationFactory';
|
import { createBlogMotionCssVariables } from '@/factories/blogAnimationFactory';
|
||||||
import { BLOG_META_NAMES, blogDomId, blogDomSelector, blogMetaSelector } from '@/factories/blogDomFactory';
|
import { BLOG_META_NAMES, blogDomId, blogDomSelector, blogMetaSelector } from '@/factories/blogDomFactory';
|
||||||
|
|
||||||
@ -107,8 +115,8 @@ const ARGON_SERIF_FONT_FAMILY = 'Georgia, "Times New Roman", "Noto Serif SC", se
|
|||||||
const ARGON_DEFAULT_COLOR_PRIMARY = '#c3a1ed';
|
const ARGON_DEFAULT_COLOR_PRIMARY = '#c3a1ed';
|
||||||
const ARGON_PRIMARY_SOFT = '#4a4058';
|
const ARGON_PRIMARY_SOFT = '#4a4058';
|
||||||
const ARGON_CARD_SHADOW = '0 2px 4px rgba(0, 0, 0, 0.075)';
|
const ARGON_CARD_SHADOW = '0 2px 4px rgba(0, 0, 0, 0.075)';
|
||||||
const ARGON_DEFAULT_BACKGROUND = 'https://s3.kwitsukasa.top/images/bg-冬滚滚.png';
|
const ARGON_DEFAULT_BACKGROUND = PREVIOUS_BLOG_BACKGROUND_IMAGE;
|
||||||
const ARGON_DEFAULT_AUTHOR_AVATAR = 'https://s3.kwitsukasa.top/images/avatar-tsukasa-1.jpg';
|
const ARGON_DEFAULT_AUTHOR_AVATAR = PREVIOUS_BLOG_AUTHOR_AVATAR;
|
||||||
const defaultHeaderMenu: BlogThemeMenuItem[] = [];
|
const defaultHeaderMenu: BlogThemeMenuItem[] = [];
|
||||||
const defaultSidebarMenu: BlogThemeMenuItem[] = [
|
const defaultSidebarMenu: BlogThemeMenuItem[] = [
|
||||||
{ href: '/', icon: 'fa-home', label: '首页' },
|
{ href: '/', icon: 'fa-home', label: '首页' },
|
||||||
@ -304,7 +312,8 @@ function applyWordpressThemeConfig(config: WordpressArgonThemeConfig) {
|
|||||||
typeof nextBackgroundDarkBrightness === 'number'
|
typeof nextBackgroundDarkBrightness === 'number'
|
||||||
? nextBackgroundDarkBrightness
|
? nextBackgroundDarkBrightness
|
||||||
: runtimeConfig.backgroundDarkBrightness;
|
: runtimeConfig.backgroundDarkBrightness;
|
||||||
runtimeConfig.backgroundDarkImage = normalizeCssImage(config.backgroundDarkImage) || runtimeConfig.backgroundDarkImage;
|
runtimeConfig.backgroundDarkImage =
|
||||||
|
normalizeCssImage(config.backgroundDarkImage) || runtimeConfig.backgroundDarkImage;
|
||||||
runtimeConfig.backgroundDarkOpacity =
|
runtimeConfig.backgroundDarkOpacity =
|
||||||
typeof nextBackgroundDarkOpacity === 'number'
|
typeof nextBackgroundDarkOpacity === 'number'
|
||||||
? nextBackgroundDarkOpacity
|
? nextBackgroundDarkOpacity
|
||||||
@ -432,12 +441,14 @@ function applyCssVariables(currentPreferences: BlogThemePreferences) {
|
|||||||
const palette = createThemePalette(currentPreferences.colorPrimary, currentPreferences.mode);
|
const palette = createThemePalette(currentPreferences.colorPrimary, currentPreferences.mode);
|
||||||
const fontFamily = getThemeFontFamily(currentPreferences.font);
|
const fontFamily = getThemeFontFamily(currentPreferences.font);
|
||||||
const argonColorVariables = createArgonColorVariables(primaryRgb, currentPreferences.mode);
|
const argonColorVariables = createArgonColorVariables(primaryRgb, currentPreferences.mode);
|
||||||
const backgroundImage =
|
const backgroundImage = appendCssImageFallback(
|
||||||
normalizeCssImage(
|
normalizeCssImage(
|
||||||
currentPreferences.mode === 'dark'
|
currentPreferences.mode === 'dark'
|
||||||
? runtimeConfig.backgroundDarkImage || runtimeConfig.backgroundImage
|
? runtimeConfig.backgroundDarkImage || runtimeConfig.backgroundImage
|
||||||
: runtimeConfig.backgroundImage,
|
: runtimeConfig.backgroundImage,
|
||||||
) || `url('${ARGON_DEFAULT_BACKGROUND}')`;
|
) || `url('${ARGON_DEFAULT_BACKGROUND}')`,
|
||||||
|
LOCAL_BLOG_BACKGROUND_IMAGE,
|
||||||
|
);
|
||||||
const backgroundOpacity =
|
const backgroundOpacity =
|
||||||
currentPreferences.mode === 'dark'
|
currentPreferences.mode === 'dark'
|
||||||
? runtimeConfig.backgroundDarkOpacity
|
? runtimeConfig.backgroundDarkOpacity
|
||||||
@ -505,13 +516,13 @@ ${serializeCssVariables(argonColorVariables.current)}
|
|||||||
--radius: ${currentPreferences.radius}px;
|
--radius: ${currentPreferences.radius}px;
|
||||||
--card-radius: ${currentPreferences.radius}px;
|
--card-radius: ${currentPreferences.radius}px;
|
||||||
--argon-background-image: ${backgroundImage};
|
--argon-background-image: ${backgroundImage};
|
||||||
--argon-background-light-image: ${normalizeCssImage(runtimeConfig.backgroundImage) || `url('${ARGON_DEFAULT_BACKGROUND}')`};
|
--argon-background-light-image: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.backgroundImage) || `url('${ARGON_DEFAULT_BACKGROUND}')`, LOCAL_BLOG_BACKGROUND_IMAGE)};
|
||||||
--argon-background-dark-image: ${normalizeCssImage(runtimeConfig.backgroundDarkImage) || backgroundImage};
|
--argon-background-dark-image: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.backgroundDarkImage) || backgroundImage, LOCAL_BLOG_BACKGROUND_IMAGE)};
|
||||||
--argon-background-filter: ${currentPreferences.mode === 'dark' ? `brightness(${runtimeConfig.backgroundDarkBrightness})` : 'none'};
|
--argon-background-filter: ${currentPreferences.mode === 'dark' ? `brightness(${runtimeConfig.backgroundDarkBrightness})` : 'none'};
|
||||||
--argon-background-opacity: ${backgroundOpacity};
|
--argon-background-opacity: ${backgroundOpacity};
|
||||||
--argon-background-light-opacity: ${runtimeConfig.backgroundOpacity};
|
--argon-background-light-opacity: ${runtimeConfig.backgroundOpacity};
|
||||||
--argon-background-dark-opacity: ${runtimeConfig.backgroundDarkOpacity};
|
--argon-background-dark-opacity: ${runtimeConfig.backgroundDarkOpacity};
|
||||||
--argon-author-avatar: ${normalizeCssImage(runtimeConfig.siteAuthorAvatar) || `url('${ARGON_DEFAULT_AUTHOR_AVATAR}')`};
|
--argon-author-avatar: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.siteAuthorAvatar) || `url('${ARGON_DEFAULT_AUTHOR_AVATAR}')`, LOCAL_BLOG_AUTHOR_AVATAR)};
|
||||||
--argon-font-family: ${fontFamily};
|
--argon-font-family: ${fontFamily};
|
||||||
--argon-shadow: ${ARGON_CARD_SHADOW};
|
--argon-shadow: ${ARGON_CARD_SHADOW};
|
||||||
--argon-primary-soft: ${ARGON_PRIMARY_SOFT};
|
--argon-primary-soft: ${ARGON_PRIMARY_SOFT};
|
||||||
@ -904,9 +915,12 @@ function normalizePositiveNumber(value?: number | string) {
|
|||||||
return Number.isFinite(numericValue) ? Math.max(0, numericValue) : null;
|
return Number.isFinite(numericValue) ? Math.max(0, numericValue) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value Theme image URL returned by the API or static defaults.
|
||||||
|
* @returns CSS `url(...)` token after replacing legacy Argon demo placeholders with previous blog assets.
|
||||||
|
*/
|
||||||
function normalizeCssImage(value?: string) {
|
function normalizeCssImage(value?: string) {
|
||||||
if (!value) return '';
|
const normalized = resolveBlogStaticAsset(value, '');
|
||||||
const normalized = value.trim();
|
|
||||||
|
|
||||||
if (normalized.startsWith('url(')) return normalized;
|
if (normalized.startsWith('url(')) return normalized;
|
||||||
if (/^https?:\/\//i.test(normalized) || normalized.startsWith('/')) {
|
if (/^https?:\/\//i.test(normalized) || normalized.startsWith('/')) {
|
||||||
@ -916,13 +930,27 @@ function normalizeCssImage(value?: string) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param primaryCssImage CSS `url(...)` token for the preferred remote image.
|
||||||
|
* @param localFallback Local packaged image path used when the remote image cannot load.
|
||||||
|
* @returns A CSS background-image layer list with the remote image first and local backup second.
|
||||||
|
*/
|
||||||
|
function appendCssImageFallback(primaryCssImage: string, localFallback: string) {
|
||||||
|
const fallbackCssImage = normalizeCssImage(localFallback);
|
||||||
|
|
||||||
|
if (!fallbackCssImage || primaryCssImage.includes(fallbackCssImage)) {
|
||||||
|
return primaryCssImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${primaryCssImage}, ${fallbackCssImage}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value Theme asset URL returned by WordPress-compatible config.
|
||||||
|
* @returns Plain URL/path after replacing legacy Argon demo placeholders, suitable for component state.
|
||||||
|
*/
|
||||||
function normalizeThemeAsset(value?: string) {
|
function normalizeThemeAsset(value?: string) {
|
||||||
if (!value) return '';
|
const asset = resolveBlogStaticAsset(unwrapBlogCssImage(value), '');
|
||||||
const normalized = value.trim();
|
|
||||||
const cssImage = /^url\((.*)\)$/i.exec(normalized)?.[1]?.trim();
|
|
||||||
const asset = cssImage
|
|
||||||
? cssImage.replace(/^['"]|['"]$/g, '')
|
|
||||||
: normalized;
|
|
||||||
|
|
||||||
return /^https?:\/\//i.test(asset) || asset.startsWith('/') ? asset : '';
|
return /^https?:\/\//i.test(asset) || asset.startsWith('/') ? asset : '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -240,8 +240,11 @@
|
|||||||
height: 100px;
|
height: 100px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--argon-author-avatar, url('/argon/theme/profile.jpg')) center / cover no-repeat;
|
|
||||||
background-color: rgba(127, 127, 127, 0.1);
|
background-color: rgba(127, 127, 127, 0.1);
|
||||||
|
background-image: var(--argon-author-avatar, url('/blog-assets/avatar-tsukasa-1.jpg'));
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
box-shadow: var(--argon-shadow);
|
box-shadow: var(--argon-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,11 +46,10 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header-collapse {
|
&__header-nav--hover {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header-nav {
|
&__header-nav {
|
||||||
|
|||||||