统一组件使用astro,优化css样式,新增文章,优化文章格式

This commit is contained in:
lsy 2025-04-19 16:19:39 +08:00
parent 57d406c277
commit 0d48cc8591
173 changed files with 10812 additions and 3413 deletions

7107
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +0,0 @@
---
// 面包屑导航组件
// 接收当前页面类型、路径段和标签过滤器作为参数
interface Breadcrumb {
name: string;
path: string;
}
export interface Props {
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
pathSegments?: string[]; // 路径段数组
tagFilter?: string; // 标签过滤器
articleTitle?: string; // 文章标题(仅在文章详情页使用)
}
const { pageType, pathSegments = [], tagFilter = '', articleTitle = '' } = Astro.props;
// 将路径段转换为面包屑对象
const breadcrumbs: Breadcrumb[] = pathSegments
.filter(segment => segment.trim() !== '')
.map((segment, index, array) => {
const path = array.slice(0, index + 1).join('/');
return { name: segment, path };
});
---
<div class="flex items-center text-sm">
<!-- 文章列表链接 -->
<a href="/articles" class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd" />
</svg>
文章
</a>
{/* 标签过滤 */}
{tagFilter && (
<>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span class="text-secondary-600 dark:text-secondary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
{tagFilter}
</span>
</>
)}
{/* 目录路径 */}
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
return (
<span>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
</span>
);
})}
{/* 文章标题 */}
{pageType === 'article' && articleTitle && (
<>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span class="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
</>
)}
</div>

View File

@ -0,0 +1,72 @@
import React from 'react';
interface Breadcrumb {
name: string;
path: string;
}
export interface BreadcrumbProps {
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
pathSegments?: string[]; // 路径段数组
tagFilter?: string; // 标签过滤器
articleTitle?: string; // 文章标题(仅在文章详情页使用)
}
export function Breadcrumb({
pageType,
pathSegments = [],
tagFilter = '',
articleTitle = ''
}: BreadcrumbProps) {
// 将路径段转换为面包屑对象
const breadcrumbs: Breadcrumb[] = pathSegments
.filter(segment => segment.trim() !== '')
.map((segment, index, array) => {
const path = array.slice(0, index + 1).join('/');
return { name: segment, path };
});
return (
<div className="flex items-center text-sm">
{/* 文章列表链接 */}
<a href="/articles" className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clipRule="evenodd" />
</svg>
</a>
{/* 标签过滤 */}
{tagFilter && (
<>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span className="text-secondary-600 dark:text-secondary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
</svg>
{tagFilter}
</span>
</>
)}
{/* 目录路径 */}
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
return (
<span key={`crumb-${index}`}>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
</span>
);
})}
{/* 文章标题 */}
{pageType === 'article' && articleTitle && (
<>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span className="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
</>
)}
</div>
);
}

View File

@ -100,7 +100,7 @@ export const Countdown: React.FC<CountdownProps> = ({ targetDate, className = ''
const TimeBox = ({ value, label }: { value: number; label: string }) => (
<div className="text-center px-4">
<div className="text-4xl font-light transition-all duration-300">
<div className="text-4xl font-light">
{value.toString().padStart(2, '0')}
</div>
<div className="text-sm mt-1 text-gray-500 dark:text-gray-400">{label}</div>

View File

@ -190,7 +190,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
</div>
<button
onClick={() => fetchData()}
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
>
</button>
@ -227,14 +227,14 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
{items.map((item, index) => (
<div
key={`${item.title}-${index}`}
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg"
>
<a href={item.link} target="_blank" rel="noopener noreferrer" className="block">
<div className="relative pb-[140%] overflow-hidden">
<img
src={item.imageUrl}
alt={item.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105"
className="absolute inset-0 w-full h-full object-cover hover:scale-105"
loading="lazy"
onError={(e) => {
const target = e.target as HTMLImageElement;
@ -263,7 +263,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
<button
onClick={() => handlePageChange(pagination.current - 1)}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="上一页"
@ -286,7 +286,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
<button
onClick={() => handlePageChange(pagination.current + 1)}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="下一页"

View File

@ -1,63 +0,0 @@
---
interface Props {
icp?: string;
psbIcp?: string;
psbIcpUrl?: string;
}
const {
icp = "",
psbIcp = "",
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
} = Astro.props;
const currentYear = new Date().getFullYear();
---
<footer class="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
<div class="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
<div class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
{icp && (
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="工信部备案信息"
>
{icp}
</a>
)}
{psbIcp && (
<a
href={psbIcpUrl}
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="公安部备案信息"
>
<img src="/images/national.png" alt="公安备案" class="h-4 mr-1" width="14" height="16" loading="lazy" />
{psbIcp}
</a>
)}
</div>
<div class="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
<a href="https://blog.lsy22.com" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200">
© {currentYear} New Echoes. All rights reserved.
</a>
<span aria-hidden="true" class="hidden sm:inline">·</span>
<a
href="/sitemap-index.xml"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="网站地图"
>
Sitemap
</a>
</div>
</div>
</footer>

65
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,65 @@
import React from 'react';
interface FooterProps {
icp?: string;
psbIcp?: string;
psbIcpUrl?: string;
}
export function Footer({
icp = "",
psbIcp = "",
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
}: FooterProps) {
const currentYear = new Date().getFullYear();
return (
<footer className="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
<div className="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
{icp && (
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-600 dark:hover:text-primary-400"
aria-label="工信部备案信息"
>
{icp}
</a>
)}
{psbIcp && (
<a
href={psbIcpUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center hover:text-primary-600 dark:hover:text-primary-400"
aria-label="公安部备案信息"
>
<img src="/images/national.png" alt="公安备案" className="h-4 mr-1" width="14" height="16" loading="lazy" />
{psbIcp}
</a>
)}
</div>
<div className="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
<a href="https://blog.lsy22.com" className="hover:text-primary-600 dark:hover:text-primary-400">
© {currentYear} New Echoes. All rights reserved.
</a>
<span aria-hidden="true" className="hidden sm:inline">·</span>
<a
href="/sitemap-index.xml"
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-600 dark:hover:text-primary-400"
aria-label="网站地图"
>
Sitemap
</a>
</div>
</div>
</footer>
);
}

View File

@ -284,7 +284,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
</div>
<button
onClick={() => fetchData(pagination.current)}
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
>
</button>
@ -331,10 +331,10 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
columnClassName="pl-4 bg-clip-padding"
>
{projects.map((project, index) => (
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 shadow-lg">
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5">
<div className="flex items-start">
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50 transition-colors">
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50">
{getPlatformIcon(project.platform as GitPlatform)}
</div>
<div className="ml-3 flex-1">
@ -353,7 +353,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
</div>
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-1 mt-2">{project.name}</h3>
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 line-clamp-1 mt-2">{project.name}</h3>
<div className="h-12 mb-3">
{project.description ? (
@ -406,7 +406,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<button
onClick={() => handlePageChange(pagination.current - 1)}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="上一页"
@ -429,7 +429,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<button
onClick={() => handlePageChange(pagination.current + 1)}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="下一页"

View File

@ -1,582 +0,0 @@
---
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
import { ThemeToggle } from './ThemeToggle';
// 获取当前路径
const currentPath = Astro.url.pathname;
// 移除结尾的斜杠以统一路径格式
const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;
// 定义导航链接
---
<header class="fixed w-full top-0 z-50 transition-all duration-300" id="main-header">
<div class="absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 transition-all duration-300" id="header-bg"></div>
<nav class="relative">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<!-- Logo 部分 -->
<div class="flex items-center">
<a href="/" class="text-xl md:text-2xl font-bold tracking-tight transition-all duration-300 ease-in-out bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
{SITE_NAME}
</a>
</div>
<!-- 导航链接 -->
<div class="hidden md:flex md:items-center md:space-x-8">
<!-- 桌面端搜索框 -->
<div class="relative">
<input
type="text"
id="desktop-search"
class="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
placeholder="搜索文章..."
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<!-- 搜索结果容器(默认隐藏) -->
<div id="desktop-search-results" class="absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 hidden">
<!-- 结果将通过JS动态填充 -->
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="desktop-search-message">
<p>输入关键词开始搜索</p>
</div>
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="desktop-search-list"></ul>
</div>
</div>
{NAV_LINKS.map(link => (
<a
href={link.href}
class:list={[
'inline-flex items-center px-1 pt-1 text-sm font-medium transition-colors duration-200',
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
]}
>
{link.text}
</a>
))}
<ThemeToggle client:load />
</div>
<!-- 移动端菜单按钮 -->
<div class="flex items-center md:hidden">
<!-- 移动端搜索按钮 -->
<button
type="button"
id="mobile-search-button"
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2 transition-colors"
aria-expanded="false"
aria-label="搜索"
>
<span class="sr-only">搜索</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 transition-colors"
id="mobile-menu-button"
aria-expanded="false"
aria-label="打开菜单"
>
<span class="sr-only">打开菜单</span>
<svg class="h-6 w-6 block" id="menu-open-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg class="h-6 w-6 hidden" id="menu-close-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- 移动端搜索面板 -->
<div id="mobile-search-panel" class="hidden md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700">
<div class="relative">
<input
type="text"
id="mobile-search"
class="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
placeholder="搜索文章..."
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button
id="mobile-search-close"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label="关闭搜索"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- 移动端搜索结果 -->
<div id="mobile-search-results" class="mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm hidden">
<!-- 结果将通过JS动态填充 -->
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="mobile-search-message">
<p>输入关键词开始搜索</p>
</div>
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="mobile-search-list"></ul>
</div>
</div>
<!-- 移动端菜单 -->
<div class="hidden md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
<div id="mobile-menu-bg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
<div class="grid gap-1">
{NAV_LINKS.map(link => (
<a
href={link.href}
class:list={[
'flex items-center px-3 py-3 rounded-lg text-base font-medium transition-all duration-200 ease-in-out',
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
]}
>
{link.text}
</a>
))}
<div class="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2 transition-colors duration-200" id="theme-toggle-container">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">切换主题</span>
<ThemeToggle client:load />
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<style>
#header-bg {
opacity: 1;
backdrop-filter: blur(0);
transition: all 0.5s ease;
}
#header-bg.scrolled {
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 2px 4px rgba(0, 0, 0, 0.04),
0 4px 8px rgba(0, 0, 0, 0.04);
}
:global([data-theme="dark"]) #header-bg.scrolled {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
/* 移动端菜单样式 */
#mobile-menu {
/* 移除过渡效果,确保菜单内容立即显示 */
opacity: 1;
transform: translateY(0);
max-height: calc(100vh - 4rem);
overflow-y: auto;
/* 确保子元素不受过渡效果影响 */
contain: layout;
}
/* 移动端菜单背景 */
#mobile-menu-bg {
/* 直接应用高斯模糊,不使用过渡效果 */
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
/* 确保高斯模糊立即应用 */
will-change: backdrop-filter;
}
:global([data-theme="dark"]) #mobile-menu-bg {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
/* 搜索面板动画 */
#mobile-search-panel.show {
animation: slide-down 0.2s ease-out forwards;
}
@keyframes slide-down {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
</style>
<script>
// 确保脚本适用于视图转换
function initHeader() {
const header = document.getElementById('header-bg');
const scrollThreshold = 50;
function updateHeaderBackground() {
if (window.scrollY > scrollThreshold) {
header?.classList.add('scrolled');
} else {
header?.classList.remove('scrolled');
}
}
// 初始检查
updateHeaderBackground();
// 添加滚动事件监听
window.addEventListener('scroll', updateHeaderBackground);
// 移动端菜单逻辑
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const menuOpenIcon = document.getElementById('menu-open-icon');
const menuCloseIcon = document.getElementById('menu-close-icon');
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
mobileMenuButton.addEventListener('click', () => {
const expanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
// 切换菜单状态
mobileMenuButton.setAttribute('aria-expanded', (!expanded).toString());
if (expanded) {
// 直接隐藏菜单,不使用过渡效果
mobileMenu.classList.add('hidden');
} else {
// 直接显示菜单,不使用过渡效果
mobileMenu.classList.remove('hidden');
}
// 切换图标
menuOpenIcon.classList.toggle('hidden');
menuCloseIcon.classList.toggle('hidden');
});
}
// 移动端主题切换容器点击处理
const themeToggleContainer = document.getElementById('theme-toggle-container');
if (themeToggleContainer) {
themeToggleContainer.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const themeToggleButton = themeToggleContainer.querySelector('[role="button"]');
// 如果点击的不是主题切换按钮本身,而是容器或文本
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
// 手动触发主题切换按钮的点击
themeToggleButton.click();
}
});
}
// 移动端搜索按钮
const mobileSearchButton = document.getElementById('mobile-search-button');
const mobileSearchPanel = document.getElementById('mobile-search-panel');
const mobileSearch = document.getElementById('mobile-search');
const mobileSearchClose = document.getElementById('mobile-search-close');
if (mobileSearchButton && mobileSearchPanel) {
mobileSearchButton.addEventListener('click', () => {
mobileSearchPanel.classList.remove('hidden');
mobileSearchPanel.classList.add('show');
if (mobileSearch) mobileSearch.focus();
});
if (mobileSearchClose) {
mobileSearchClose.addEventListener('click', () => {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
});
}
}
}
// 搜索功能逻辑
function initSearch() {
// 搜索节流函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | undefined;
return function(this: any, ...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 获取DOM元素
const desktopSearch = document.getElementById('desktop-search');
const desktopResults = document.getElementById('desktop-search-results');
const desktopList = document.getElementById('desktop-search-list');
const desktopMessage = document.getElementById('desktop-search-message');
const mobileSearch = document.getElementById('mobile-search');
const mobileResults = document.getElementById('mobile-search-results');
const mobileList = document.getElementById('mobile-search-list');
const mobileMessage = document.getElementById('mobile-search-message');
// 文章对象的接口定义
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
let articles: Article[] = [];
let isArticlesLoaded = false;
// 获取文章数据
async function fetchArticles() {
if (isArticlesLoaded && articles.length > 0) return;
try {
const response = await fetch('/api/search');
if (!response.ok) {
throw new Error('获取文章数据失败');
}
articles = await response.json();
isArticlesLoaded = true;
} catch (error) {
console.error('获取文章失败:', error);
}
}
// 高亮文本中的匹配部分
function highlightText(text: string, query: string): string {
if (!text || !query.trim()) return text;
// 转义正则表达式中的特殊字符
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedQuery})`, 'gi');
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
}
// 搜索文章
function searchArticles(query: string, resultsList: HTMLElement, resultsMessage: HTMLElement) {
if (!query.trim()) {
resultsList.innerHTML = '';
resultsMessage.textContent = '输入关键词开始搜索';
resultsMessage.style.display = 'block';
return;
}
if (articles.length === 0) {
resultsMessage.textContent = '正在加载数据...';
resultsMessage.style.display = 'block';
return;
}
const lowerQuery = query.toLowerCase();
// 过滤并排序结果
const filteredArticles = articles
.filter((article: Article) => {
const title = article.title.toLowerCase();
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
const summary = article.summary ? article.summary.toLowerCase() : '';
const content = article.content ? article.content.toLowerCase() : '';
return title.includes(lowerQuery) ||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
summary.includes(lowerQuery) ||
content.includes(lowerQuery);
})
.sort((a: Article, b: Article) => {
// 标题匹配优先
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
return -1;
}
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
return 1;
}
// 内容匹配次之
const aContent = a.content ? a.content.toLowerCase() : '';
const bContent = b.content ? b.content.toLowerCase() : '';
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
return -1;
}
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
return 1;
}
// 日期排序
return new Date(b.date).getTime() - new Date(a.date).getTime();
})
.slice(0, 10); // 限制结果数量
if (filteredArticles.length === 0) {
resultsList.innerHTML = '';
resultsMessage.textContent = '没有找到相关内容';
resultsMessage.style.display = 'block';
return;
}
// 显示结果
resultsMessage.style.display = 'none';
resultsList.innerHTML = filteredArticles.map((article: Article) => {
// 生成匹配的内容片段
let contentMatch = '';
if (article.content && article.content.toLowerCase().includes(lowerQuery)) {
// 找到匹配文本在内容中的位置
const matchIndex = article.content.toLowerCase().indexOf(lowerQuery);
// 计算片段的起始和结束位置
const startPos = Math.max(0, matchIndex - 50);
const endPos = Math.min(article.content.length, matchIndex + 100);
// 提取片段
let snippet = article.content.substring(startPos, endPos);
// 如果不是从文章开头开始,添加省略号
if (startPos > 0) {
snippet = '...' + snippet;
}
// 如果不是到文章结尾,添加省略号
if (endPos < article.content.length) {
snippet = snippet + '...';
}
// 高亮匹配的文本
snippet = highlightText(snippet, query);
contentMatch = `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">${snippet}</p>`;
}
// 高亮标题和摘要中的匹配文本
const highlightedTitle = highlightText(article.title, query);
const highlightedSummary = article.summary ? highlightText(article.summary, query) : '';
return `
<li>
<a href="/articles/${article.id}" class="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70 transition-colors duration-200">
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">${highlightedTitle}</h3>
${article.summary ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate">${highlightedSummary}</p>` : ''}
${contentMatch}
${article.tags && article.tags.length > 0 ? `
<div class="flex flex-wrap gap-1 mt-1.5">
${article.tags.slice(0, 3).map((tag: string) => `
<span class="inline-block text-xs bg-primary-50/50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 py-0.5 px-1.5 rounded-full">#${tag}</span>
`).join('')}
${article.tags.length > 3 ? `<span class="text-xs text-gray-400 dark:text-gray-500">+${article.tags.length - 3}</span>` : ''}
</div>
` : ''}
</a>
</li>
`}).join('');
}
// 节流搜索
const debouncedDesktopSearch = debounce((value: string) => {
if (desktopList && desktopMessage) {
searchArticles(value, desktopList as HTMLElement, desktopMessage as HTMLElement);
}
}, 300);
const debouncedMobileSearch = debounce((value: string) => {
if (mobileList && mobileMessage) {
searchArticles(value, mobileList as HTMLElement, mobileMessage as HTMLElement);
}
}, 300);
// 桌面端搜索逻辑
if (desktopSearch && desktopResults) {
desktopSearch.addEventListener('focus', () => {
desktopResults.classList.remove('hidden');
if (!isArticlesLoaded) fetchArticles();
});
desktopSearch.addEventListener('input', (e: Event) => {
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedDesktopSearch(target.value);
}
});
// 点击外部关闭结果
document.addEventListener('click', (e: MouseEvent) => {
const target = e.target as Node;
if (desktopSearch && !desktopSearch.contains(target) && !desktopResults.contains(target)) {
desktopResults.classList.add('hidden');
}
});
// ESC键关闭结果
desktopSearch.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
desktopResults.classList.add('hidden');
}
});
}
// 移动端搜索逻辑
if (mobileSearch && mobileResults) {
mobileSearch.addEventListener('input', (e: Event) => {
mobileResults.classList.remove('hidden');
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedMobileSearch(target.value);
if (!isArticlesLoaded) fetchArticles();
}
});
// ESC键关闭搜索面板
mobileSearch.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
const mobileSearchPanel = document.getElementById('mobile-search-panel');
if (mobileSearchPanel) {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
}
}
});
}
}
// 初始化函数
function setupHeader() {
initHeader();
initSearch();
}
// 在文档加载时初始化一次
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupHeader);
} else {
setupHeader();
}
// 支持 Astro 视图转换
document.addEventListener('astro:swup:page:view', setupHeader);
// 清理
document.addEventListener('astro:before-swap', () => {
// 移除可能的全局事件监听器
window.removeEventListener('scroll', () => {});
});
</script>

421
src/components/Header.tsx Normal file
View File

@ -0,0 +1,421 @@
import { useEffect, useState, useRef } from 'react';
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
import { ThemeToggle } from './ThemeToggle';
import '@/styles/header.css';
// 文章对象类型定义
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
export default function Header() {
// 状态定义
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [articles, setArticles] = useState<Article[]>([]);
const [isArticlesLoaded, setIsArticlesLoaded] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [desktopSearchFocused, setDesktopSearchFocused] = useState(false);
// 获取当前路径 (使用window.location.pathname)
const [pathname, setPathname] = useState('/');
useEffect(() => {
setPathname(window.location.pathname);
}, []);
// 移除结尾的斜杠以统一路径格式
const normalizedPath = pathname?.endsWith('/') ? pathname.slice(0, -1) : pathname;
// 引用
const desktopSearchRef = useRef<HTMLInputElement>(null);
const desktopResultsRef = useRef<HTMLDivElement>(null);
const mobileSearchRef = useRef<HTMLInputElement>(null);
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 处理滚动效果
useEffect(() => {
const scrollThreshold = 50;
function updateHeaderBackground() {
if (window.scrollY > scrollThreshold) {
setScrolled(true);
} else {
setScrolled(false);
}
}
// 初始检查
updateHeaderBackground();
// 添加滚动事件监听
window.addEventListener('scroll', updateHeaderBackground);
// 清理
return () => window.removeEventListener('scroll', updateHeaderBackground);
}, []);
// 搜索节流函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | undefined;
return function(this: any, ...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 获取文章数据
async function fetchArticles() {
if (isArticlesLoaded && articles.length > 0) return;
try {
const response = await fetch('/api/search');
if (!response.ok) {
throw new Error('获取文章数据失败');
}
const data = await response.json();
setArticles(data);
setIsArticlesLoaded(true);
} catch (error) {
console.error('获取文章失败:', error);
}
}
// 高亮文本中的匹配部分
function highlightText(text: string, query: string): string {
if (!text || !query.trim()) return text;
// 转义正则表达式中的特殊字符
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedQuery})`, 'gi');
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
}
// 搜索文章逻辑
const debouncedSearch = debounce((query: string) => {
setSearchQuery(query);
}, 300);
// 点击页面其他区域关闭搜索结果
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
desktopSearchRef.current &&
desktopResultsRef.current &&
!desktopSearchRef.current.contains(event.target as Node) &&
!desktopResultsRef.current.contains(event.target as Node)
) {
setDesktopSearchFocused(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// 处理ESC键
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
setDesktopSearchFocused(false);
setMobileSearchOpen(false);
}
}
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
// 搜索结果处理
const getFilteredArticles = () => {
if (!searchQuery.trim()) return [];
const lowerQuery = searchQuery.toLowerCase();
return articles
.filter((article: Article) => {
const title = article.title.toLowerCase();
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
const summary = article.summary ? article.summary.toLowerCase() : '';
const content = article.content ? article.content.toLowerCase() : '';
return title.includes(lowerQuery) ||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
summary.includes(lowerQuery) ||
content.includes(lowerQuery);
})
.sort((a: Article, b: Article) => {
// 标题匹配优先
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
return -1;
}
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
return 1;
}
// 内容匹配次之
const aContent = a.content ? a.content.toLowerCase() : '';
const bContent = b.content ? b.content.toLowerCase() : '';
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
return -1;
}
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
return 1;
}
// 日期排序
return new Date(b.date).getTime() - new Date(a.date).getTime();
})
.slice(0, 10); // 限制结果数量
};
// 生成搜索结果列表项
const renderSearchResults = () => {
const filteredArticles = getFilteredArticles();
if (filteredArticles.length === 0) {
return (
<div className="py-4 px-4 text-center text-gray-500 dark:text-gray-400">
</div>
);
}
return (
<ul className="py-2">
{filteredArticles.map(article => {
return (
<li key={article.id} className="group">
<a href={`/articles/${article.id}`} className="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70">
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 group-hover:text-primary-600 dark:group-hover:text-primary-400"
dangerouslySetInnerHTML={{ __html: highlightText(article.title, searchQuery) }}
></h4>
{article.summary && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400 line-clamp-1"
dangerouslySetInnerHTML={{ __html: highlightText(article.summary, searchQuery) }}
></p>
)}
{article.tags && article.tags.length > 0 && (
<div className="mt-2 flex items-center flex-wrap gap-1">
{article.tags.slice(0, 3).map(tag => (
<span key={tag} className="inline-block text-2xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700/70 text-gray-600 dark:text-gray-400 rounded">
{tag}
</span>
))}
{article.tags.length > 3 && (
<span className="text-xs text-gray-400 dark:text-gray-500">
+{article.tags.length - 3}
</span>
)}
</div>
)}
</a>
</li>
);
})}
</ul>
);
};
// 准备样式类
const headerBgClasses = `absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 ${
scrolled ? 'scrolled' : ''
}`;
return (
<header className="fixed w-full top-0 z-50" id="main-header">
<div className={headerBgClasses} id="header-bg"></div>
<nav className="relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
{/* Logo 部分 */}
<div className="flex items-center">
<a href="/" className="text-xl md:text-2xl font-bold tracking-tight bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
{SITE_NAME}
</a>
</div>
{/* 导航链接 */}
<div className="hidden md:flex md:items-center md:space-x-8">
{/* 桌面端搜索框 */}
<div className="relative">
<input
type="text"
id="desktop-search"
ref={desktopSearchRef}
className="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
onFocus={() => {
setDesktopSearchFocused(true);
fetchArticles();
}}
onChange={(e) => debouncedSearch(e.target.value)}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
{/* 搜索结果容器 */}
<div
ref={desktopResultsRef}
className={`absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 ${
desktopSearchFocused ? '' : 'hidden'
}`}
>
{renderSearchResults()}
</div>
</div>
{/* 导航链接 */}
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className={`inline-flex items-center px-1 pt-1 text-sm font-medium ${
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
}`}
>
{link.text}
</a>
))}
<ThemeToggle />
</div>
{/* 移动端菜单按钮 */}
<div className="flex items-center md:hidden">
{/* 移动端搜索按钮 */}
<button
type="button"
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2"
aria-expanded={mobileSearchOpen}
aria-label="搜索"
onClick={() => {
setMobileSearchOpen(true);
setTimeout(() => {
mobileSearchRef.current?.focus();
}, 100);
fetchArticles();
}}
>
<span className="sr-only"></span>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
{/* 移动端菜单按钮 */}
<button
type="button"
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
id="mobile-menu-button"
aria-expanded={mobileMenuOpen}
aria-label="打开菜单"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<span className="sr-only"></span>
{mobileMenuOpen ? (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
)}
</button>
</div>
</div>
</div>
{/* 移动端搜索面板 */}
{mobileSearchOpen && (
<div className="md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700 show">
<div className="relative">
<input
type="text"
ref={mobileSearchRef}
className="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
onChange={(e) => debouncedSearch(e.target.value)}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label="关闭搜索"
onClick={() => setMobileSearchOpen(false)}
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 移动端搜索结果 */}
<div className={`mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm ${searchQuery ? '' : 'hidden'}`}>
{renderSearchResults()}
</div>
</div>
)}
{/* 移动端菜单 */}
{mobileMenuOpen && (
<div className="md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
<div className="backdrop-blur-[6px] bg-white/80 dark:bg-gray-800/80 shadow-md">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
<div className="grid gap-1">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className={`flex items-center px-3 py-3 rounded-lg text-base font-medium ${
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
}`}
>
{link.text}
</a>
))}
<div
className="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2"
onClick={(e) => {
const themeToggleButton = e.currentTarget.querySelector('[role="button"]');
const target = e.target as HTMLElement;
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
themeToggleButton.click();
}
}}
>
<span className="text-sm font-medium text-gray-600 dark:text-gray-300"></span>
<ThemeToggle />
</div>
</div>
</div>
</div>
</div>
)}
</nav>
</header>
);
}

View File

@ -1,7 +1,7 @@
---
import "@/styles/global.css";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Header from "@/components/Header.tsx";
import {Footer} from "@/components/Footer.tsx";
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
// 定义Props接口
@ -55,7 +55,13 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i
<meta property="article:tag" content={tag} />
))}
<script is:inline>
// 立即执行主题初始化
// 立即执行主题初始化,并防止变量重复声明
(function() {
// 检查变量是否已存在
if (typeof window.__themeInitDone === 'undefined') {
// 设置标志,表示初始化已完成
window.__themeInitDone = true;
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
@ -66,10 +72,12 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i
return 'light';
})();
document.documentElement.dataset.theme = theme;
}
})();
</script>
</head>
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg transition-colors duration-300 flex flex-col min-h-screen">
<Header />
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
<Header client:load/>
<main class="pt-16 flex-grow">
<slot />
</main>

View File

@ -1,202 +0,0 @@
---
interface Props {
type: 'movie' | 'book';
title: string;
doubanId: string;
}
const { type, title, doubanId } = Astro.props;
// Generate unique IDs for this specific instance of the component
const uniquePrefix = `media-${type}`;
const mediaListId = `${uniquePrefix}-list`;
const loadingId = `${uniquePrefix}-loading`;
const endMessageId = `${uniquePrefix}-end-message`;
---
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl font-bold mb-6">{title}</h1>
<div id={mediaListId} class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- 内容将通过JS动态加载 -->
</div>
<div id={loadingId} class="text-center py-8">
<div class="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
<p class="mt-2 text-gray-600">加载更多...</p>
</div>
<div id={endMessageId} class="text-center py-8 hidden">
<p class="text-gray-600">已加载全部内容</p>
</div>
</div>
<script define:vars={{ type, doubanId, mediaListId, loadingId, endMessageId }}>
// Create a class with scoped methods instead of using global functions and variables
class MediaLoader {
constructor(type, doubanId, mediaListId, loadingId, endMessageId) {
this.type = type;
this.doubanId = doubanId;
this.mediaListId = mediaListId;
this.loadingId = loadingId;
this.endMessageId = endMessageId;
this.currentPage = 1;
this.isLoading = false;
this.hasMoreContent = true;
this.itemsPerPage = 15; // 豆瓣每页显示的数量
this.scrollHandler = this.handleScroll.bind(this);
}
async fetchMedia(page = 1, append = false) {
if (this.isLoading || (!append && !this.hasMoreContent)) {
return;
}
this.isLoading = true;
this.showLoading(true);
const start = (page - 1) * this.itemsPerPage;
try {
const response = await fetch(`/api/douban?type=${this.type}&start=${start}&doubanId=${this.doubanId}`);
if (!response.ok) {
throw new Error(`获取${this.type === 'movie' ? '电影' : '图书'}数据失败`);
}
const data = await response.json();
this.renderMedia(data.items, append);
// 更新分页状态
this.currentPage = data.pagination.current;
this.hasMoreContent = data.pagination.hasNext;
if (!this.hasMoreContent) {
this.showEndMessage(true);
}
} catch (error) {
const mediaList = document.getElementById(this.mediaListId);
if (mediaList && !append) {
mediaList.innerHTML = '<div class="col-span-full text-center text-red-500">获取数据失败,请稍后再试</div>';
}
} finally {
this.isLoading = false;
this.showLoading(false);
}
}
renderMedia(items, append = false) {
const mediaList = document.getElementById(this.mediaListId);
if (!mediaList) return;
if (!items || items.length === 0) {
if (!append) {
mediaList.innerHTML = `<div class="col-span-full text-center">暂无${this.type === 'movie' ? '电影' : '图书'}数据</div>`;
}
return;
}
const mediaHTML = items.map(item => `
<div class="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
<div class="relative pb-[150%] overflow-hidden">
<img src="${item.imageUrl}" alt="${item.title}" class="absolute top-0 left-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105">
<div class="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
<h3 class="font-bold text-white text-sm line-clamp-2">
<a href="${item.link}" target="_blank" class="hover:text-blue-300 transition-colors">${item.title}</a>
</h3>
</div>
</div>
</div>
`).join('');
if (append) {
mediaList.innerHTML += mediaHTML;
} else {
mediaList.innerHTML = mediaHTML;
}
}
showLoading(show) {
const loading = document.getElementById(this.loadingId);
if (loading) {
if (show) {
loading.classList.remove('hidden');
} else {
loading.classList.add('hidden');
}
}
}
showEndMessage(show) {
const endMessage = document.getElementById(this.endMessageId);
if (endMessage) {
endMessage.classList.toggle('hidden', !show);
}
}
setupInfiniteScroll() {
// 添加滚动事件
window.addEventListener('scroll', this.scrollHandler);
// 初始检查一次,以防内容不足一屏
setTimeout(() => this.handleScroll(), 500);
}
handleScroll() {
if (this.isLoading || !this.hasMoreContent) {
return;
}
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// 当滚动到距离底部300px时加载更多
if (scrollY + windowHeight >= documentHeight - 300) {
this.fetchMedia(this.currentPage + 1, true);
}
}
cleanup() {
// 移除滚动事件监听器
window.removeEventListener('scroll', this.scrollHandler);
}
initialize() {
this.fetchMedia(1, false).then(() => {
this.setupInfiniteScroll();
}).catch(err => {
// 错误已在fetchMedia中处理
});
}
}
// 存储每个页面创建的媒体加载器,用于清理
if (!window.mediaLoaders) {
window.mediaLoaders = {};
}
// 创建并初始化媒体加载器
function initMediaLoader() {
// 清理可能存在的旧实例
if (window.mediaLoaders[mediaListId]) {
window.mediaLoaders[mediaListId].cleanup();
}
const loader = new MediaLoader(type, doubanId, mediaListId, loadingId, endMessageId);
window.mediaLoaders[mediaListId] = loader;
loader.initialize();
}
// 页面首次加载
document.addEventListener('astro:swup:page:view', initMediaLoader);
// 页面卸载前清理
document.addEventListener('astro:before-swap', () => {
if (window.mediaLoaders[mediaListId]) {
window.mediaLoaders[mediaListId].cleanup();
}
});
// 如果已经加载了 DOM立即初始化
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initMediaLoader();
}
</script>

View File

@ -0,0 +1,359 @@
import React, { useEffect, useRef, useState } from 'react';
interface MediaGridProps {
type: 'movie' | 'book';
title: string;
doubanId: string;
}
interface MediaItem {
title: string;
imageUrl: string;
link: string;
}
interface PaginationInfo {
current: number;
hasNext: boolean;
}
const MediaGrid: React.FC<MediaGridProps> = ({ type, title, doubanId }) => {
const [items, setItems] = useState<MediaItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMoreContent, setHasMoreContent] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [error, setError] = useState<string | null>(null);
const itemsPerPage = 15;
const mediaListRef = useRef<HTMLDivElement>(null);
const lastScrollTime = useRef(0);
// 使用ref来跟踪关键状态避免闭包问题
const stateRef = useRef({
isLoading: false,
hasMoreContent: true,
currentPage: 1,
error: null as string | null
});
// 封装fetch函数但不使用useCallback以避免依赖循环
const fetchMedia = async (page = 1, append = false) => {
// 使用ref中的最新状态
if (stateRef.current.isLoading ||
(!append && !stateRef.current.hasMoreContent) ||
(append && !stateRef.current.hasMoreContent)) {
return;
}
// 更新状态和ref
setIsLoading(true);
stateRef.current.isLoading = true;
// 只在首次加载时清除错误
if (!append) {
setError(null);
stateRef.current.error = null;
}
const start = (page - 1) * itemsPerPage;
try {
const response = await fetch(`/api/douban?type=${type}&start=${start}&doubanId=${doubanId}`);
if (!response.ok) {
// 解析响应内容,获取详细错误信息
let errorMessage = `获取${type === 'movie' ? '电影' : '图书'}数据失败`;
try {
const errorData = await response.json();
if (errorData && errorData.error) {
errorMessage = errorData.error;
if (errorData.message) {
errorMessage += `: ${errorData.message}`;
}
}
} catch (e) {
// 无法解析JSON使用默认错误信息
}
// 针对不同错误提供更友好的提示
if (response.status === 403) {
errorMessage = "豆瓣接口访问受限,可能是请求过于频繁,请稍后再试";
} else if (response.status === 404) {
// 对于404错误如果是追加模式说明已经到了最后一页设置hasMoreContent为false
if (append) {
setHasMoreContent(false);
stateRef.current.hasMoreContent = false;
setIsLoading(false);
stateRef.current.isLoading = false;
return; // 直接返回,不设置错误,不清空已有数据
} else {
errorMessage = "未找到相关内容请检查豆瓣ID是否正确";
}
}
// 设置错误状态和ref
setError(errorMessage);
stateRef.current.error = errorMessage;
// 只有非追加模式才清空数据
if (!append) {
setItems([]);
}
throw new Error(errorMessage);
}
const data = await response.json();
if (data.items.length === 0) {
// 如果返回的项目为空,则认为已经没有更多内容
setHasMoreContent(false);
stateRef.current.hasMoreContent = false;
if (!append) {
setItems([]);
}
} else {
if (append) {
setItems(prev => {
const newItems = [...prev, ...data.items];
return newItems;
});
} else {
setItems(data.items);
}
// 更新页码状态和ref
setCurrentPage(data.pagination.current);
stateRef.current.currentPage = data.pagination.current;
// 更新是否有更多内容的状态和ref
const newHasMoreContent = data.pagination.hasNext;
setHasMoreContent(newHasMoreContent);
stateRef.current.hasMoreContent = newHasMoreContent;
}
} catch (error) {
// 只有在非追加模式下才清空已加载的内容
if (!append) {
setItems([]);
}
} finally {
// 重置加载状态
setIsLoading(false);
stateRef.current.isLoading = false;
}
};
// 处理滚动事件
const handleScroll = () => {
// 获取关键滚动值
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollPosition = scrollY + windowHeight;
const threshold = documentHeight - 300;
// 限制滚动日志频率,每秒最多输出一次
const now = Date.now();
if (now - lastScrollTime.current < 1000) {
return;
}
lastScrollTime.current = now;
// 使用ref中的最新状态来检查
if (stateRef.current.isLoading || !stateRef.current.hasMoreContent || stateRef.current.error) {
return;
}
// 当滚动到距离底部300px时加载更多
if (scrollPosition >= threshold) {
fetchMedia(stateRef.current.currentPage + 1, true);
}
};
// 更新ref值以跟踪状态变化
useEffect(() => {
stateRef.current.isLoading = isLoading;
}, [isLoading]);
useEffect(() => {
stateRef.current.hasMoreContent = hasMoreContent;
}, [hasMoreContent]);
useEffect(() => {
stateRef.current.currentPage = currentPage;
}, [currentPage]);
useEffect(() => {
stateRef.current.error = error;
}, [error]);
// 组件初始化和依赖变化时重置
useEffect(() => {
// 重置状态
setCurrentPage(1);
stateRef.current.currentPage = 1;
setHasMoreContent(true);
stateRef.current.hasMoreContent = true;
setError(null);
stateRef.current.error = null;
setIsLoading(false);
stateRef.current.isLoading = false;
// 清空列表
setItems([]);
// 加载第一页数据
fetchMedia(1, false);
// 管理滚动事件
const scrollListener = handleScroll;
// 移除任何现有监听器
window.removeEventListener('scroll', scrollListener);
// 添加滚动事件监听器 - 使用passive: true可提高滚动性能
window.addEventListener('scroll', scrollListener, { passive: true });
// 创建一个IntersectionObserver作为备选检测方案
const observerOptions = {
root: null,
rootMargin: '300px',
threshold: 0.1
};
const intersectionObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting &&
!stateRef.current.isLoading &&
stateRef.current.hasMoreContent &&
!stateRef.current.error) {
fetchMedia(stateRef.current.currentPage + 1, true);
}
}, observerOptions);
// 添加检测底部的元素 - 放在grid容器的后面而不是内部
const footer = document.createElement('div');
footer.id = 'scroll-detector';
footer.style.width = '100%';
footer.style.height = '10px';
// 确保mediaListRef有父元素
if (mediaListRef.current && mediaListRef.current.parentElement) {
// 插入到grid后面而不是内部
mediaListRef.current.parentElement.insertBefore(footer, mediaListRef.current.nextSibling);
intersectionObserver.observe(footer);
}
// 初始检查一次,以防内容不足一屏
const timeoutId = setTimeout(() => {
if (stateRef.current.hasMoreContent && !stateRef.current.isLoading) {
scrollListener();
}
}, 500);
// 清理函数
return () => {
clearTimeout(timeoutId);
window.removeEventListener('scroll', scrollListener);
intersectionObserver.disconnect();
document.getElementById('scroll-detector')?.remove();
};
}, [type, doubanId]); // 只在关键属性变化时执行
// 错误提示组件
const ErrorMessage = () => {
if (!error) return null;
return (
<div className="col-span-full text-center bg-red-50 p-4 rounded-md">
<div className="flex flex-col items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-red-500 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<h3 className="text-lg font-medium text-red-800">访</h3>
<p className="mt-1 text-sm text-red-700">{error}</p>
<button
onClick={() => {
// 重置错误和加载状态
setError(null);
stateRef.current.error = null;
// 允许再次加载
setHasMoreContent(true);
stateRef.current.hasMoreContent = true;
// 重新获取当前页
fetchMedia(currentPage, false);
}}
className="mt-4 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
</button>
</div>
</div>
);
};
// 没有更多内容提示
const EndMessage = () => {
if (isLoading || !items.length || error) return null;
return (
<div className="text-center py-8">
<p className="text-gray-600"></p>
</div>
);
};
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 className="text-3xl font-bold mb-6">{title}</h1>
<div ref={mediaListRef} className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
{error && items.length === 0 ? (
<ErrorMessage />
) : items.length > 0 ? (
items.map((item, index) => (
<div key={`${item.title}-${index}`} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl">
<div className="relative pb-[150%] overflow-hidden">
<img
src={item.imageUrl}
alt={item.title}
className="absolute top-0 left-0 w-full h-full object-cover hover:scale-105"
/>
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
<h3 className="font-bold text-white text-sm line-clamp-2">
<a href={item.link} target="_blank" rel="noopener noreferrer" className="hover:text-blue-300">
{item.title}
</a>
</h3>
</div>
</div>
</div>
))
) : !isLoading ? (
<div className="col-span-full text-center">{type === 'movie' ? '电影' : '图书'}</div>
) : null}
</div>
{error && items.length > 0 && (
<div className="mt-4">
<ErrorMessage />
</div>
)}
{isLoading && (
<div className="text-center py-8">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
<p className="mt-2 text-gray-600">...{currentPage}</p>
</div>
)}
{!hasMoreContent && items.length > 0 && !isLoading && (
<EndMessage />
)}
</div>
);
};
export default MediaGrid;

View File

@ -2,10 +2,10 @@ import { useEffect, useState, useCallback, useRef } from 'react';
export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", className = "" }) {
// 使用null作为初始状态表示尚未确定主题
const [theme, setTheme] = useState(null);
const [theme, setTheme] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
const [transitioning, setTransitioning] = useState(false);
const transitionTimeoutRef = useRef(null);
const transitionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 获取系统主题
const getSystemTheme = useCallback(() => {
@ -30,7 +30,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e) => {
const handleMediaChange = (e: MediaQueryListEvent) => {
// 只有当主题设置为跟随系统时才更新主题
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
@ -84,7 +84,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
if (!mounted || theme === null) {
return (
<div
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
>
<span className="sr-only">...</span>
</div>
@ -93,7 +93,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
return (
<div
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
onClick={toggleTheme}
role="button"
tabIndex={0}
@ -110,7 +110,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
className="hover:scale-110"
aria-hidden="true"
>
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
@ -120,7 +120,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
className="hover:scale-110"
aria-hidden="true"
>
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>

View File

@ -844,7 +844,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
/>
{hoveredCountry && (
<div className="absolute bottom-5 left-0 right-0 text-center z-10">
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 transition-all duration-300 hover:scale-105">
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 hover:scale-105">
<p className="text-gray-800 dark:text-white font-medium text-lg flex items-center justify-center gap-2">
{hoveredCountry}
{hoveredCountry && visitedPlaces.includes(hoveredCountry) ? (

View File

@ -3,170 +3,185 @@ title: "常用软件"
date: 2023-04-28T20:56:00Z
tags: []
---
### Windows 应用
---
* **开发软件:**
- **开发软件:**
* [JetBrains全家桶](https://www.jetbrains.com/zh-cn/products/)
* [JetBrains破解软件](https://linux.do/t/topic/115562)
* **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
* **办公软件:**
- [JetBrains 全家桶](https://www.jetbrains.com/zh-cn/products/)
- [JetBrains 破解软件](https://linux.do/t/topic/115562)
* [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office下载)
* [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
* [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
* **下载器:** [IDM](https://www.internetdownloadmanager.com/)
* **卸载工具:** [Geek](https://geekuninstaller.com/)
* **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
* **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
* **浏览器:**
- **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
- **办公软件:**
* [chrome](https://www.google.com/chrome/)
* [Arc](https://arc.net/)
* [百分浏览器](https://www.centbrowser.cn/)
* **ssh工具**
- [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office 下载)
- [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
- [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
* [tabby](https://github.com/Eugeny/tabby/tree/master)
* [FinalShell](http://www.hostbuf.com/)
* **adb工具** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
* **科学上网工具:**
- **下载器:** [IDM](https://www.internetdownloadmanager.com/)
- **卸载工具:** [Geek](https://geekuninstaller.com/)
- **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
- **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
- **浏览器:**
* [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
* [v2rayN](https://github.com/2dust/v2rayN/releases/)
* [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat)V2Ray 路由规则文件加强版
* **驱动管理:** [360驱动大师](http://dm.weishi.360.cn/home.html)
* **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
* **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
* **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
* **防火墙应用:**
- [chrome](https://www.google.com/chrome/)
- [Arc](https://arc.net/)
- [百分浏览器](https://www.centbrowser.cn/)
* [360安全卫士极速版](https://weishi.360.cn/jisu/)
* [火绒](https://www.huorong.cn/)
- **ssh 工具:**
- [tabby](https://github.com/Eugeny/tabby/tree/master)
- [FinalShell](http://www.hostbuf.com/)
- **adb 工具:** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
- **科学上网工具:**
- [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
- [v2rayN](https://github.com/2dust/v2rayN/releases/)
- [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat)V2Ray 路由规则文件加强版
- **驱动管理:** [360 驱动大师](http://dm.weishi.360.cn/home.html)
- **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
- **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
- **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
- **防火墙应用:**
- [360 安全卫士极速版](https://weishi.360.cn/jisu/)
- [火绒](https://www.huorong.cn/)
### Android 应用
---
* **下载器:** 1DM+
* **安装器:** R-安装组件
* **浏览器:**
- **下载器:** 1DM+
- **安装器:** R-安装组件
- **浏览器:**
* [雨见浏览器国际版](https://yjllq.com/)
* [Alook](https://www.alookweb.com/)
* **文件管理器:** [MT管理器](https://mt2.cn/)
* **网络工具:**
- [雨见浏览器国际版](https://yjllq.com/)
- [Alook](https://www.alookweb.com/)
* Cellular Pro
* VPN热点
* **科学上网:**
- **文件管理器:** [MT 管理器](https://mt2.cn/)
- **网络工具:**
* Surfboard
* [v2rayNG](https://github.com/2dust/v2rayNG/releases)
* **终端模拟器**:
- Cellular Pro
- VPN 热点
* [Termux](https://termux.dev/)
* [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
* **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
* **视频播放器:**
- **科学上网:**
* MX播放器专业版
* XPlayer - 万能视频播放器
* **服务器连接:**
- Surfboard
- [v2rayNG](https://github.com/2dust/v2rayNG/releases)
* [JuiceSSH](https://juicessh.com/)
* RD客户端 (连接Windows)
* [Termius](https://termius.com/)
* **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
* **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
* **将操作系统映像写入USB驱动器** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
* **adb工具**
- **终端模拟器**:
* [搞机助手](https://gjzsr.com/)
* 甲壳虫ADB助手
* Bugjaeger Premium
* [scene](https://github.com/kdrag0n/safetynet-fix/releases)
* **Xposed软件**
- [Termux](https://termux.dev/)
- [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
* **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
* **bilibili增强** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
* **微信增强:** [微x模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
* **QQ增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
* **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
* **Magisk模块管理**
- **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
- **视频播放器:**
* [LSPosed](https://github.com/LSPosed/LSPosed):已归档
* 关闭SELinux
* 停用HW叠加层模块
* [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
* [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
- MX 播放器专业版
- XPlayer - 万能视频播放器
- **服务器连接:**
- [JuiceSSH](https://juicessh.com/)
- RD 客户端 (连接 Windows)
- [Termius](https://termius.com/)
- **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
- **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
- **将操作系统映像写入 USB 驱动器:** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
- **adb 工具:**
- [搞机助手](https://gjzsr.com/)
- 甲壳虫 ADB 助手
- Bugjaeger Premium
- [scene](https://github.com/kdrag0n/safetynet-fix/releases)
- **Xposed 软件**
- **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
- **bilibili 增强:** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
- **微信增强:** [微 x 模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
- **QQ 增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
- **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
- **Magisk 模块管理:**
- [LSPosed](https://github.com/LSPosed/LSPosed):已归档
- 关闭 SELinux
- 停用 HW 叠加层模块
- [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
- [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
### 浏览器插件
---
* [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
* [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
* [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
* [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
* [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
* [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
* [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
* [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
* [Floccus](https://floccus.org/download):同步书签和标签
* [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
- [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
- [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
- [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
- [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
- [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
- [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
- [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
- [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
- [Floccus](https://floccus.org/download):同步书签和标签
- [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
### Docker 应用
---
* [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
* [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
* [思源笔记](https://b3log.org/siyuan/)支持web端
* [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
* [Nginx Proxy Manager](https://nginxproxymanager.com/)Nginx 代理管理器
* [雷池](https://waf-ce.chaitin.cn/)基于Nginx开发的Web 应用防火墙
- [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
- [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
- [思源笔记](https://b3log.org/siyuan/):支持 web
- [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
- [Nginx Proxy Manager](https://nginxproxymanager.com/)Nginx 代理管理器
- [雷池](https://waf-ce.chaitin.cn/):基于 Nginx 开发的 Web 应用防火墙
### Linux 应用
---
* [OpenSSH](https://www.openssh.com/)SSH连接工具
* `Wget` `curl`:从网络上获取或发送数据
* [vim](https://www.vim.org/):文本编辑器
* [Git](https://git-scm.com/):分布式版本控制系统
* [Zsh](https://www.zsh.org/)是一款功能强大、灵活可定制的shell
* [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
* [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
* [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
* 镜像仓库:
- [OpenSSH](https://www.openssh.com/)SSH 连接工具
- `Wget` `curl`:从网络上获取或发送数据
- [vim](https://www.vim.org/):文本编辑器
- [Git](https://git-scm.com/):分布式版本控制系统
- [Zsh](https://www.zsh.org/):是一款功能强大、灵活可定制的 shell
- [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
- [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
- [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
- 镜像仓库:
* [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
* [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
* [网易开源镜像站](https://mirrors.163.com/)
* [腾讯软件源](https://mirrors.cloud.tencent.com/)
* [华为开源镜像站](https://mirrors.huaweicloud.com/home)
* [移动云开源镜像站](https://mirrors.cmecloud.cn/)
* [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
- [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
- [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
- [网易开源镜像站](https://mirrors.163.com/)
- [腾讯软件源](https://mirrors.cloud.tencent.com/)
- [华为开源镜像站](https://mirrors.huaweicloud.com/home)
- [移动云开源镜像站](https://mirrors.cmecloud.cn/)
- [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
### 可被托管的应用
---
* [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io)使用CF-workersDocker仓库镜像代理工具
* [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex)使用CF-workers搭建代理
* [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
- [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io):使用 CF-workersDocker 仓库镜像代理工具
- [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex):使用 CF-workers 搭建代理
- [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
### 网站
---
* 盗版软件:
- 盗版软件:
* [CyberMania](https://www.cybermania.ws/)
* [果核剥壳](https://www.ghxi.com/)
* 单价游戏:
- [CyberMania](https://www.cybermania.ws/)
- [果核剥壳](https://www.ghxi.com/)
* [土豆资源库](http://tdtd.chat/index)
* [3DMGAME](https://bbs.3dmgame.com/forum.php)
- 单价游戏:
- [土豆资源库](http://tdtd.chat/index)
- [3DMGAME](https://bbs.3dmgame.com/forum.php)

View File

@ -22,5 +22,5 @@ tags: []
[VPN 热点](https://lsy22.lanzouj.com/iS9hw0hz5rfa?password=lsy22)
1. 打开 *VPN 热点* 给予 root 权限
2. 将 *WLAN 热点* 打开,打开后会多一个 *wlan1*,将 *wlan1* 打开就可以实现 VPN 热点了
1. 打开 _VPN 热点_ 给予 root 权限
2. 将 _WLAN 热点_ 打开,打开后会多一个 _wlan1_,将 _wlan1_ 打开就可以实现 VPN 热点了

View File

@ -16,16 +16,16 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
### 二. 复现环境
* 虚拟环境搭建:`VMware Workstation 17 pro`
- 虚拟环境搭建:`VMware Workstation 17 pro`
* 网络模式:`NAT`
- 网络模式:`NAT`
* 攻击机:`kali Linux WSL`
* 攻击机IP`192.168.97.173`
* 攻击工具:`nmap` `metasploit(MSF)`
- 攻击机:`kali Linux WSL`
- 攻击机 IP`192.168.97.173`
- 攻击工具:`nmap` `metasploit(MSF)`
* 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提win7关闭防火墙**
* 靶机IP`192.168.97.128`
- 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提 win7 关闭防火墙**
- 靶机 IP`192.168.97.128`
### 三. 启动 MSF
@ -49,37 +49,39 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
### 四. 寻找主机
* **ipconfig**
- **ipconfig**
使用`ipconfig`分别查看 win7 和 kali 中的 ip 地址
* **nmap**
- **nmap**
```bash
nmap -T5 -sP 192.168.97.0/24
```
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
* **`-sP`**:执行 Ping 连接扫描。
* **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sP`**:执行 Ping 连接扫描。
- **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
| IP 地址 | 私有 ip 范围 | 子网掩码 | CIDR |
| --------- | :----------------------------- | :-------------- | :--------------- |
| -------- | :----------------------------- | :------------ | :------------- |
| A 类地址 | 10.0.0.0 10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
| B 类地址 | 172.16.0.0 173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
| C 类地址 | 192.168.0.0 192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
### 五. 端口扫描
* **nmap**
- **nmap**
```bash
nmap -T5 -sT 192.168.97.128
```
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
* **`-sT`**:执行 TCP 连接扫描。
* **`192.168.97.128`**:扫描指定的 IP
* **MSF** 端口扫描
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sT`**:执行 TCP 连接扫描。
- **`192.168.97.128`**:扫描指定的 IP
- **MSF** 端口扫描
1. 使用模块
@ -109,27 +111,30 @@ search ms17_010
1. `exploit/windows/smb/ms17_010_eternalblue`
* 这个模块利用了MS17-010漏洞通过EternalBlue攻击载荷远程执行代码。
* EternalBlue利用Windows的Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。
* 攻击成功后通常会在目标机器上生成一个Meterpreter会话从而允许进一步的渗透测试操作。
- 这个模块利用了 MS17-010 漏洞,通过 EternalBlue 攻击载荷,远程执行代码。
- EternalBlue 利用 Windows 的 Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。
- 攻击成功后,通常会在目标机器上生成一个 Meterpreter 会话,从而允许进一步的渗透测试操作。
2. `exploit/windows/smb/ms17_010_psexec`
* 这个模块结合MS17-010漏洞和Psexec技术通过SMB协议在目标系统上执行命令。
* 利用MS17-010漏洞进行初始攻击然后使用Psexec进行进一步的远程命令执行。
* 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
- 这个模块结合 MS17-010 漏洞和 Psexec 技术,通过 SMB 协议在目标系统上执行命令。
- 利用 MS17-010 漏洞进行初始攻击,然后使用 Psexec 进行进一步的远程命令执行。
- 适用于在利用 MS17-010 漏洞后希望使用 Psexec 执行进一步的命令和控制操作时。
3. `auxiliary/admin/smb/ms17_010_command`
* 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
* 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
* 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
- 这个辅助模块用于通过 MS17-010 漏洞在目标系统上执行指定的命令。
- 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
- 适用于希望通过 MS17-010 漏洞在目标系统上执行单个命令的场景。
4. `auxiliary/scanner/smb/smb_ms17_010`
* 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
* 不会进行实际的漏洞利用或攻击而是仅检测目标系统是否易受MS17-010漏洞的影响。
- 这个辅助模块用于扫描目标系统是否存在 MS17-010 漏洞。
- 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受 MS17-010 漏洞的影响。
### 七. 漏洞检测
* 使用探测模块
- 使用探测模块
1. 使用`Auxiliary`辅助探测模块
@ -161,7 +166,7 @@ search ms17_010
run
```
* nmap
- nmap
```bash
nmap --script smb-vuln-ms17-010 192.168.97.128
@ -370,19 +375,19 @@ timestomp 操作文件 MACE 属性
#### 基础使用
* 进入框架
- 进入框架
```bash
msfconsole
```
* 查找漏洞
- 查找漏洞
```bash
search 漏洞编号
```
* 使用模块
- 使用模块
```bash
run
@ -416,40 +421,39 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
##### Metasploit 中的 Payload 模块主要有以下三种类型
* Single
- Single
> 是一种完全独立的 Payload而且使用起来就像运行 calc.exe 一样简单,例如添加一个系统用户或删除一份文件。由于 Single Payload 是完全独立的,因此它们有可能会被类似 netcat 这样的非 metasploit 处理工具所捕捉到。
>
* Stager
> 这种Payload 负责建立目标用户与攻击者之间的网络连接并下载额外的组件或应用程序。一种常见的Stager Payload就是reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是bind_tcp它可以让目标系统开启一个tcp监听器而攻击者随时可以与目标系统进行通信(正向连接)。  
>
* Stage
- Stager
> 这种 Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的 Stager Payload 就是 reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是 bind_tcp它可以让目标系统开启一个 tcp 监听器,而攻击者随时可以与目标系统进行通信(正向连接)。
- Stage
> 是 Stager Payload 下的一种 Payload 组件,这种 Payload 可以提供更加高级的功能,而且没有大小限制。
>
##### 几种常见的 payload
* 正向连接
- 正向连接
```bash
windows/meterpreter/bind_tcp
```
* 反向连接
- 反向连接
```bash
windows/meterpreter/reverse_tcp
```
* 过监听80端口反向连接
- 过监听 80 端口反向连接
```bash
windows/meterpreter/reverse_http
```
* 通过监听443端口反向连接
- 通过监听 443 端口反向连接
```bash
windows/meterpreter/reverse_https
@ -457,15 +461,14 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
##### **使用场景**
* 正向连接使用场景:
- 正向连接使用场景:
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了 80 端口。那么,我们就只能设置正向连接 80 端口了,这里很有可能失败,因为 80 端口上的流量太多了。
>
* 反向连接使用场景:
- 反向连接使用场景:
> 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。
>
* 反向连接80和443端口使用场景:
- 反向连接 80 和 443 端口使用场景:
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的 80 端口或 443 端口与外部通信。
>

View File

@ -98,12 +98,11 @@ tags: ["Docker-compose"]
可自行更改
* nginx 中的端口,默认为`9757`
* MySQL中的 root的密码 和 需要创建的数据库名称,默认都为`typecho`
- nginx 中的端口,默认为`9757`
- MySQL 中的 root 的密码 和 需要创建的数据库名称,默认都为`typecho`
```yaml
services: # 定义多个服务
nginx: # 服务名称
image: nginx # 使用的镜像
ports: # 映射的端口
@ -157,17 +156,17 @@ docker compose up -d
如果修改过`docker-compose.yml`
* 数据库地址: `mysql`
- 数据库地址: `mysql`
```text
因为docker内部网络可以用过容器名访问
```
* 数据库用户名: `root`
* 数据库密码: `typecho`
* 数据库名: `typecho`
* 启用数据库 SSL 服务端证书验证: 关闭
* 其他默认或随意
- 数据库用户名: `root`
- 数据库密码: `typecho`
- 数据库名: `typecho`
- 启用数据库 SSL 服务端证书验证: 关闭
- 其他默认或随意
## 问题

View File

@ -149,3 +149,4 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
}
}
```

View File

@ -8,8 +8,8 @@ tags: ["Docker-compose"]
### 替换说明
*`/var/www/siyuan/` 替换为你的实际物理路径。
*`Password` 替换为你的访问密码。
-`/var/www/siyuan/` 替换为你的实际物理路径。
-`Password` 替换为你的访问密码。
```yaml
version: "3.9"
@ -32,8 +32,8 @@ services:
### Nginx 配置替换说明
*`your_domain.com` 替换为你自己的域名。
* 将 `path` 替换为你的SSL证书的实际路径。
-`your_domain.com` 替换为你自己的域名。
- 将 `path` 替换为你的 SSL 证书的实际路径。
```nginx
upstream siyuan {
@ -78,3 +78,4 @@ server {
proxy_set_header Connection 'Upgrade'; # 支持 WebSocket
}
}
```

View File

@ -2,7 +2,6 @@
title: "密码管理器—Vaultwarden(bitwarden)"
date: 2023-05-18T21:47:00+00:00
tags: ["Docker-compose"]
---
## 1. 安装 Vaultwarden
@ -10,7 +9,7 @@ tags: ["Docker-compose"]
使用以下 `docker-compose.yml` 文件部署 Vaultwarden
```yaml
version: '3.8'
version: "3.8"
services:
bitwarden:
image: vaultwarden/server:latest

View File

@ -19,7 +19,7 @@ tags: ["Docker-compose","WebDAV"]
运行以下 Docker Compose 文件进行 Alist 的安装:
```yaml
version: '3.8'
version: "3.8"
services:
alist:
image: xhofe/alist:latest

View File

@ -42,20 +42,19 @@ sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/ins
### 主题
* [powerlevel10k](https://github.com/romkatv/powerlevel10k)
- [powerlevel10k](https://github.com/romkatv/powerlevel10k)
### 插件
* [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
* [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
- [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
- [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
#### oh-my-zsh 自带插件
直接按照上述方法在 `.zshrc` 配置的 `plugins` 中加入即可:
* [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
* [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
* [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
* [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
* [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持
- [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
- [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
- [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
- [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
- [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持

View File

@ -52,11 +52,11 @@ tags: []
##### bypy 基本操作
* `bypy info`:查看空间使用信息。
* `bypy list`:查看目录信息。
* `bypy upload`:上传根目录所有文件。
* `bypy downdir`:把云盘上的内容同步到本地。
* `bypy compare`:比较本地当前目录和云盘根目录。
- `bypy info`:查看空间使用信息。
- `bypy list`:查看目录信息。
- `bypy upload`:上传根目录所有文件。
- `bypy downdir`:把云盘上的内容同步到本地。
- `bypy compare`:比较本地当前目录和云盘根目录。
## 安装阿里网盘备份工具

View File

@ -0,0 +1,33 @@
---
title: "3x-ui配置"
date: 2025-04-19T10:48:48+08:00
tags: []
---
3x-ui[https://github.com/MHSanaei/3x-ui](https://github.com/MHSanaei/3x-ui)
安全:`Reality`
Dest (Target)
```txt
apple.com:443
```
SNI
```txt
apple.com
```
公钥
```txt
jCK3ORKMwzmJsig7gMWOTGlaF2wvuVAcEr7jbB1bnwU
```
私钥
```txt
oN60djdg_ZRUiChp0Rj-emZ3VhM_x8rL93H4rhdq1Wc
```

View File

@ -1,11 +0,0 @@
---
title: "dns 解锁"
date: 2025-01-18 14:19:00Z
tags: []
---
## 1. 安装dnsmasq
```bash
yum install dnsmasq -y
```

View File

@ -63,8 +63,7 @@ ntpdate time.nist.gov
```json
{
"path": "/随便",
"headers":
{
"headers": {
"Host": "伪装的域名"
}
}
@ -92,8 +91,7 @@ ConnectionConfig:
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
BufferSize: 64 # The internal cache size of each connection, kB
Nodes:
-
PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
- PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
ApiConfig:
ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名
ApiKey: "*****" ## 面板设置的通讯密钥

View File

@ -25,7 +25,9 @@ tags: ["v2board"]
1. 复制一份创造成功的节点
2. 修改复制节点:
- 后台 > 节点管理 > 添加节点
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址CloudFront 分配的域名

View File

@ -4,8 +4,7 @@ date: 2021-08-09T00:07:00+08:00
tags: []
---
1.同步时间
------
## 1.同步时间
CentOS 7
@ -26,8 +25,7 @@ Debian 9 / Ubuntu 16
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ntpdate time.nist.gov
2.一键安装
------
## 2.一键安装
mkdir /home/mtproxy && cd /home/mtproxy
curl -s -o mtproxy.sh https://raw.githubusercontent.com/ellermister/mtproxy/master/mtproxy.sh && chmod +x mtproxy.sh && bash mtproxy.sh

View File

@ -12,7 +12,8 @@ tags: []
## Google 服务框架
下载地址:`https://www.apkmirror.com/apk/google-inc/google-services-framework/`
下载地址:
[https://www.apkmirror.com/apk/google-inc/google-services-framework/](https://www.apkmirror.com/apk/google-inc/google-services-framework/)
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本
@ -23,7 +24,9 @@ tags: []
## Google Play Service
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-services/`
下载地址:
[https://www.apkmirror.com/apk/google-inc/google-play-services/](https://www.apkmirror.com/apk/google-inc/google-play-services/)
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
那么就选择 noDPI 的版本
@ -34,7 +37,9 @@ tags: []
## Google Play Store
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-store/`
下载地址:
[https://www.apkmirror.com/apk/google-inc/google-play-store/](https://www.apkmirror.com/apk/google-inc/google-play-store/)
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本

View File

@ -3,6 +3,7 @@ title: "CDN配置"
date: 2023-12-25T12:07:21+08:00
tags: []
---
## 域名绑定
1. 绑定到需要加速的服务器(源站)

View File

@ -3,12 +3,14 @@ title: "Cloudflare_自选IP"
date: 2024-06-18T16:16:35+08:00
tags: []
---
## Workers
1. DNS 解析
1. 删除现有 Workers 解析(自定义域)
2. 将要用的域名解析到自选 IP 上,注意**不要开启**代理(小云朵)
2. 自定义路由
设置->触发器->路由

View File

@ -82,7 +82,7 @@ jobs:
- name: 安装 Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
python-version: "3.x"
- name: 安装依赖
run: pip install requests
- name: 等待源站部署

View File

@ -22,30 +22,30 @@ tags: []
```typescript
// 网站基本信息
export const SITE_URL = 'https://your-domain.com';
export const SITE_URL = "https://your-domain.com";
export const SITE_NAME = "你的网站名称";
export const SITE_DESCRIPTION = "网站描述";
// 导航链接
export const NAV_LINKS = [
{ href: '/', text: '首页' },
{ href: '/articles', text: '文章' },
{ href: '/movies', text: '观影' },
{ href: '/books', text: '读书' },
{ href: '/projects', text: '项目' },
{ href: '/other', text: '其他' }
{ href: "/", text: "首页" },
{ href: "/articles", text: "文章" },
{ href: "/movies", text: "观影" },
{ href: "/books", text: "读书" },
{ href: "/projects", text: "项目" },
{ href: "/other", text: "其他" },
];
// 备案信息(如果需要)
export const ICP = '你的ICP备案号';
export const PSB_ICP = '你的公安备案号';
export const PSB_ICP_URL = '备案链接';
export const ICP = "你的ICP备案号";
export const PSB_ICP = "你的公安备案号";
export const PSB_ICP_URL = "备案链接";
// 豆瓣配置
export const DOUBAN_ID = '你的豆瓣ID';
export const DOUBAN_ID = "你的豆瓣ID";
// 旅行足迹
export const VISITED_PLACES = ['中国-北京', '中国-上海', '美国-纽约'];
export const VISITED_PLACES = ["中国-北京", "中国-上海", "美国-纽约"];
```
## 文章写作
@ -100,7 +100,7 @@ tags: ["标签1", "标签2"]
export const ARTICLE_EXPIRY_CONFIG = {
enabled: true, // 是否启用文章过期提醒
expiryDays: 365, // 文章过期天数
warningMessage: '这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。' // 提醒消息
warningMessage: "这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。", // 提醒消息
};
```
@ -146,7 +146,7 @@ import { GitPlatform } from '@/components/GitProjectCollection';
`MediaGrid` 组件用于展示豆瓣的观影和读书记录。
#### 基本用法
基本用法
```astro
---
@ -174,7 +174,7 @@ import MediaGrid from '@/components/MediaGrid.astro';
`WorldHeatmap` 组件用于展示你去过的地方,以热力图的形式在世界地图上显示。
#### 基本用法
基本用法
`src/consts.ts` 中配置你去过的地方:
@ -182,13 +182,13 @@ import MediaGrid from '@/components/MediaGrid.astro';
// 配置你去过的地方
export const VISITED_PLACES = [
// 国内地区格式:'中国-省份/城市'
'中国-黑龙江',
'中国-北京',
'中国-上海',
"中国-黑龙江",
"中国-北京",
"中国-上海",
// 国外地区直接使用国家名
'马来西亚',
'泰国',
'美国'
"马来西亚",
"泰国",
"美国",
];
```
@ -214,7 +214,6 @@ import { VISITED_PLACES } from '@/consts';
</Layout>
```
## 主题切换
系统支持三种主题模式:
@ -245,8 +244,6 @@ import { VISITED_PLACES } from '@/consts';
```bash
npm install
# 或者使用 pnpm
pnpm install
```
3. 修改配置
@ -266,6 +263,7 @@ npm run dev
### 部署方式选择
1. **Vercel 部署(推荐)**
- 支持所有功能
- 自动部署和 HTTPS
- 支持 API 路由和动态数据
@ -317,14 +315,17 @@ npm run dev
## 常见问题
1. **图片无法显示**
- 检查图片路径是否正确
- 确保图片已放入 `public` 目录
2. **豆瓣数据无法获取**
- 确认豆瓣 ID 配置正确
- 检查豆瓣记录是否公开
3. **Git 项目无法显示**
- 验证用户名配置
- 确认 API 访问限制

View File

@ -4,7 +4,6 @@ date: 2025-01-15T00:34:11Z
tags: []
---
## 前端
## tailwind

View File

@ -230,3 +230,4 @@ for ((;;)); do
sleep $interval
clear
done
```

View File

@ -6,10 +6,10 @@ tags: []
## 1. 环境准备
* [宝塔面板](https://www.bt.cn/new/index.html)
* PHP 7.4
* MySQL 5.7 / 8 或 MariaDB 10
* Apache HTTP Web Server 或 Nginx
- [宝塔面板](https://www.bt.cn/new/index.html)
- PHP 7.4
- MySQL 5.7 / 8 或 MariaDB 10
- Apache HTTP Web Server 或 Nginx
## 2. 下载

View File

@ -115,9 +115,7 @@ systemctl enable serverTZs.service
```
> #赋权
> #拷贝进系统服务目录
> #重新加载系统服务
> #启动服务端并设置开机自启
> #拷贝进系统服务目录 #重新加载系统服务 #启动服务端并设置开机自启
### 在配置文件中增加服务器主机后重启
@ -198,8 +196,7 @@ systemctl enable serverTZc.service
```
> #赋权
> #拷贝进系统服务目录
> #重新加载系统服务
> #拷贝进系统服务目录 #重新加载系统服务
> #启动服务端并设置开机自启
在配置文件中增加服务器主机后重启

View File

@ -13,6 +13,7 @@ tags: []
### 出现黄色更新弹窗
1. 打开文件目录:
- Mac 修改文件目录:`/Applications/Adobe Photoshop (Beta)/Adobe Photoshop (Beta).app/Contents/Required/UXP/com.adobe.photoshop.inAppMessaging/js/0.js`
- Windows 目录(如果是 C 盘):`C:\Program Files\Adobe\Adobe Photoshop 2023\Required\UXP\com.adobe.photoshop.inAppMessaging\js\0.js`

View File

@ -54,8 +54,8 @@ tags: []
按 Win+R 打开运行,输入 `winver` 检查 Windows 版本要求:
* 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
* 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
- 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
- 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
### 三、启用虚拟机功能
@ -98,7 +98,7 @@ wsl --set-default-version 2
## 常见问题处理
* **关闭 WSL 自动挂载 Windows 分区**
- **关闭 WSL 自动挂载 Windows 分区**
编辑 WSL 配置文件 `/etc/wsl.conf` 并添加内容以禁用自动挂载和 Windows 路径的添加。
```bash
@ -111,19 +111,18 @@ wsl --set-default-version 2
enabled = false
```
* **解决无法定位 package screen 的问题**
- **解决无法定位 package screen 的问题**
在 Linux 分发版中运行 `apt-get update` 来更新软件包列表。
* **WSL 卸载**
- **WSL 卸载**
查看已安装的 WSL 环境并卸载指定的 Linux 分发版。
```bash
wsl --unregister 指定的Linux分发版
```
* **解决 WSLRegisterDistribution 错误**
- **解决 WSLRegisterDistribution 错误**
在 PowerShell 中运行
```bash
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
```

View File

@ -0,0 +1,69 @@
---
title: "vscode配置"
date: 2025-04-19T11:10:57+08:00
tags: []
---
## 自动补全,语法检查
| 语言 | 插件 |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| rust | [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) |
| tailwind | [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) |
| markdown | [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) |
| toml | [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) |
## 格式化
### 格式化插件
| 插件 | 格式化言语 |
| ------------------------------------------------------------------------------------------------ | ------------------- |
| [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | js,md,css,html,yaml |
| [Black Formatter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter) | python |
| [shell-format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format) | shell |
### 保存自动格式化
`settings.json`增加以下代码
```txt
"editor.formatOnSave": true // 保存时自动规范代码
```
## 其他
| 名称 | 作用 |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| [Atom Material Icons](https://marketplace.visualstudio.com/items?itemName=AtomMaterial.a-file-icon-vscode) | 美化图标 |
| [Atom One Dark Theme](https://marketplace.visualstudio.com/items?itemName=akamud.vscode-theme-onedark) | 美化主题 |
| [Live Preview](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) | 提供在线预览web项目 |
| [Markdown Preview Github Styling](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles) | 提供markdown代码块样式 |
| [Chinese (Simplified)](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-zh-hans) | 简体中文语言包 |
## 非html文件中启用tailwind高亮提示
### 安装高亮插件
- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
- [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss)
### 启动高亮配置
>以rust为例
修改`setting.json`文件
```json
// 配置识别的正则表达式
"tailwindCSS.experimental.classRegex": [
"class:\\s?\"(.*?)\"",
"class:\\s?format!\\((\"(.*?)\")\\)"
],
// 配置识别的语言
"tailwindCSS.includeLanguages": {
"rust": "html"
},
```

View File

@ -28,7 +28,7 @@ tags: []
&nbsp;&nbsp;国外的公交车不适合i人,我打算从布城从地铁到市中心需要先坐公交车到布城去差10秒就赶上了但是站台没人所以公交车司机没有停第二次在休息区等了半个小时司机又没停可能是司机没看到我吧第三次我站在公交车站台等车的位置等待可是他还是没停这次可能是没有给司机信号第四次等公交车快到的时候我死死的看着司机与他建立心灵链接但他还是不停浏览器查询原来要招手用打车软件看了一下两公里还是选择打车了
&nbsp;&nbsp;在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,酒店的位置和平台给的地址不同还好遇到一个好心的印度男人,打电话和酒店交流,告诉我酒店在哪里
&nbsp;&nbsp;在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他也找不到,他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走再往哪走到一个塔下快到了问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
&nbsp;&nbsp;晚餐找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒`

View File

@ -12,7 +12,7 @@ tags: []
创建
````
````sql
create funcition 函数名(@参数 类型)
returns 类型
as
@ -28,7 +28,7 @@ end
#### 方案一(处理复杂逻辑,函数体除了sql查询之外还有其他逻辑代码)
````
````sql
create function 函数名(@参数 类型)
returns @表名 table
(
@ -44,7 +44,7 @@ end
#### 方案二(只能return+sql查询结果)
````
````sql
create function 函数名(@参数 类型)
return table
as

View File

@ -35,5 +35,4 @@ select @变量名=值
### go
1.等待go语句之前的代码执行完之后才能执行后面的代码
2.批处理结束的一个标志
1.等待 go 语句之前的代码执行完之后才能执行后面的代码 2.批处理结束的一个标志

View File

@ -3,6 +3,7 @@ title: 触发器
date: 2024-06-06T23:51:43Z
tags: []
---
## instead of(事前触发器)
## After(事后触发器)

View File

@ -4,12 +4,11 @@ date: 2024-06-06T23:51:36Z
tags: []
---
$n
> n 代表数字,$1-$9 代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
$*
$\*
> 代表命令行中的所有参数(所有参数当作一个整体)

View File

@ -9,7 +9,7 @@ tags: []
## 系统变量
| 变量 | 作用 |
| ------| --------------------------------------|
| --------------- | ------------------------------------ |
| `$USER` | 当前用户的用户名 |
| `$HOME` | 当前用户的家目录 |
| `$PATH` | 包含可执行文件的目录列表,用冒号分隔 |

View File

@ -4,11 +4,10 @@ date: 2024-06-06T23:51:45Z
tags: []
---
### 基本和高级条件表达式操作符
| 符号 | 作用 |
| ------| -------------------------------------------------------------------------|
| -------- | ----------------------------------------------------------------------- |
| `{}` | 代码块,不创建新的子 shell用于组织一系列的命令 |
| `()` | 创建一个子 shell并在其中执行命令 |
| `[[]]` | 执行高级条件表达式,支持模式匹配、正则表达式等 |
@ -21,7 +20,7 @@ tags: []
### 特殊
| 符号 | 作用 |
| ------| ----------------------|
| ------ | -------------------- |
| `\|` | 在正则中表示或 |
| `!` | 在引用中表示间距引用 |
| `#` | 在数组中表示长度 |
@ -29,7 +28,7 @@ tags: []
### 条件操作符
| 操作符 | 描述 |
| --------| ------------------------------------------------------------|
| ------- | ----------------------------------------------------------- |
| `=` | 字符串比较(相等) |
| `!=` | 字符串比较(不等) |
| `-lt` | 数值比较(小于) |
@ -46,7 +45,7 @@ tags: []
### 逻辑操作符详解
| 操作符 | 描述 |
| --------| ---------------|
| -------- | ------------- |
| `&&` | 逻辑与AND |
| `\|\|` | 逻辑或OR |
| `!` | 逻辑非NOT |
@ -54,7 +53,7 @@ tags: []
### 算术操作符详解
| 操作符 | 描述 |
| --------| --------|
| ------ | ------ |
| `+` | 加法 |
| `-` | 减法 |
| `*` | 乘法 |
@ -65,7 +64,7 @@ tags: []
### 文件测试操作符
| 操作符 | 描述 | 示例 |
| --------| ----------------------| ------|
| ------ | -------------------- | -------------------------- |
| `-f` | 文件存在且为普通文件 | `if [[ -f $file ]]` |
| `-d` | 目录存在 | `if [[ -d $directory ]]` |
| `-e` | 文件存在 | `if [[ -e $filepath ]]` |

View File

@ -41,7 +41,7 @@ case关键字表示开始一个case语句块。
expression 是需要匹配的表达式或变量。
pattern1, pattern2, pattern3 等是可能的匹配模式。
command1, command2, command3 是与每个模式匹配时要执行的命令。
*)是通配符,用于匹配所有不符合前面模式的情况。
\*)是通配符,用于匹配所有不符合前面模式的情况。
default_command 是当没有匹配模式时执行的命令。
;;用于标识每个模式下命令的结束。

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
tags: []
---
## 将 Shell 变量输出为环境变量
`export 变量名=变量值`

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
tags: []
---
## 方法一:使用 function 关键字
```shell

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:38Z
tags: []
---
## 脚本要求
脚本以`#!/bin/bash`开头

View File

@ -4,25 +4,25 @@ date: 2024-07-02T13:04:06Z
tags: []
---
### 常见命令
* 创建运行容器
- 创建运行容器
```docker
docker run [options] IMAGE [command]
```
* command
- command
* `-d`​:后台运行
* `--name [name]`​:容器名称
* `-p [host port]:[container port]`​:映射端口
* `-v [/host/data]:[/container/data]`​:绑定挂载一个数据卷,如果数据卷不存在会自动创建
* `-e [KEY]=[VALUE]`​:环境变量
* `--network [my-network]`​:指定连接到一个网络
* `[image]:[tag]`​:未指定版本时,默认是latest,代表最新版本
* 拉取镜像
- `-d`​:后台运行
- `--name [name]`​:容器名称
- `-p [host port]:[container port]`​:映射端口
- `-v [/host/data]:[/container/data]`​:绑定挂载一个数据卷,如果数据卷不存在会自动创建
- `-e [KEY]=[VALUE]`​:环境变量
- `--network [my-network]`​:指定连接到一个网络
- `[image]:[tag]`​:未指定版本时,默认是 latest,代表最新版本
- 拉取镜像
未指定版本时,默认是 latest,代表最新版本
@ -30,7 +30,7 @@ tags: []
docker pull [image]:[tag]
```
* 查看指定镜像
- 查看指定镜像
不写镜像名查看所有镜像
@ -38,112 +38,117 @@ tags: []
docker images [image]
```
* 删除指定镜像
- 删除指定镜像
```docker
docker rmi [image]
```
* 保存镜像
- 保存镜像
```docker
docker [options] sava [image:tag]
```
* options
- options
* `-o [name]`​:文件保存文件名
* 加载镜像
- `-o [name]`​:文件保存文件名
- 加载镜像
```docker
docker load [OPTIONS]
```
* options
- options
* `-i [name]`​:镜像文件名
* `-q`​:不输入提示内容
* 列出运行的容器
- `-i [name]`​:镜像文件名
- `-q`​:不输入提示内容
- 列出运行的容器
```docker
docker ps [options]
```
* options
- options
* `-a`​:列出包括未运行的容器
* `-q`仅显示容器ID
* 停止容器
- `-a`​:列出包括未运行的容器
- `-q`​:仅显示容器 ID
- 停止容器
```docker
docker stop [container]
```
* 启动容器
- 启动容器
```docker
docker start [container]
```
* 删除容器
- 删除容器
```docker
docker rm [container]
```
* `-f`​:强制删除
* 查看容器详情
- `-f`​:强制删除
- 查看容器详情
```docker
docker inspect [container]
```
* 查看容器日志
- 查看容器日志
```docker
docker logs [options] [container]
```
* `-f`​:跟随日志输出(实时显示日志)
* 在运行的容器中执行命令
- `-f`​:跟随日志输出(实时显示日志)
- 在运行的容器中执行命令
```docker
docker exec [OPTIONS] [container] [COMMAND] [bash]
```
* `-i`​:保持标准输入打开,即使没有连接。
* `-t`​:分配一个伪终端
* `-u`​:以指定用户的身份运行命令
* `-d`​:在后台运行
* `-e`​:设置环境变量
- `-i`​:保持标准输入打开,即使没有连接。
- `-t`​:分配一个伪终端
- `-u`​:以指定用户的身份运行命令
- `-d`​:在后台运行
- `-e`​:设置环境变量
### 数据卷
* 创建数据卷
- 创建数据卷
```docker
docker volume create
```
* 查看所有数据卷
- 查看所有数据卷
```docker
docker volume ls
```
* 删除指定数据卷
- 删除指定数据卷
```docker
docker volume rm [volume]
```
* 查看某个数据卷的详细
- 查看某个数据卷的详细
```docker
docker volume inspect
```
* 清除数据卷
- 清除数据卷
```docker
docker volume prune
@ -153,25 +158,25 @@ tags: []
包含构建镜像需要执行的指令
* 指定基础镜像
- 指定基础镜像
```docker
FROM [image:tag]
```
* 环境变量
- 环境变量
```docker
ENV [key] [value]
```
* 将本地文件拷贝到容器的指定目录
- 将本地文件拷贝到容器的指定目录
```docker
COPY [/host/file] [/container/file]
```
* 执行容器中的shell命令
- 执行容器中的 shell 命令
一般执行安装过程
@ -179,13 +184,13 @@ tags: []
RUN [command]
```
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
```docker
EXPOSE [port]
```
* 镜像中应用的启动命令
- 镜像中应用的启动命令
```docker
ENTRYPOINT [command]
@ -193,43 +198,43 @@ tags: []
### 网络
* 创建
- 创建
```docker
docker network create [NETWORK]
```
* 查看
- 查看
```docker
docker network ls
```
* 删除指定网络
- 删除指定网络
```docker
docker network rm [NETWORK]
```
* 清楚未使用网络
- 清楚未使用网络
```docker
docker network prune
```
* 使指定容器加入指定网络
- 使指定容器加入指定网络
```docker
docker network connect [NETWORK] [CONTAINER]
```
* 使指定容器离开指定网络
- 使指定容器离开指定网络
```docker
docker network disconnect [NETWORK] [CONTAINER]
```
* 查看网络详细信息
- 查看网络详细信息
```docker
docker network inspect [NETWORK]
@ -239,13 +244,13 @@ tags: []
#### 常见关键字
* 指定 Docker Compose 文件的版本号
- 指定 Docker Compose 文件的版本号
```docker-compose
version: "3.8"
```
* 定义各个服务,每个服务可以有多个配置项。
- 定义各个服务,每个服务可以有多个配置项。
```docker-compose
services:
@ -255,61 +260,61 @@ tags: []
...
```
* 定义 Docker 网络,用于连接各个服务
- 定义 Docker 网络,用于连接各个服务
```docker-compose
networks:
- [NETWORK]:
```
* 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
- 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
```docker-compose
volumes:
- [/host/data]:[/container/data]
```
* 指定使用的镜像名称。
- 指定使用的镜像名称。
```docker-compose
[image]:[tag]
```
* 指定构建 Docker 镜像时的 Dockerfile 路径。
- 指定构建 Docker 镜像时的 Dockerfile 路径。
```docker-compose
build: [path]
```
* 将容器内部端口映射到宿主机,使外部可以访问容器服务。
- 将容器内部端口映射到宿主机,使外部可以访问容器服务。
```docker-compose
ports:
- "[host port]:[container port]"
```
* 定义环境变量
- 定义环境变量
```docker-compose
environment:
- [KEY]=[VALUE]
```
* 指定容器启动时执行的命令
- 指定容器启动时执行的命令
```docker-compose
command:
- "[command]"
```
* 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
- 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
```docker-compose
depends_on:
- service
```
* 定义容器退出时的重启策略
- 定义容器退出时的重启策略
```docker-compose
restart [strategy]
@ -318,13 +323,14 @@ tags: []
`no`​:不重启
`always`​:总是重启
* 指定给容器的名称。它是一个唯一标识符
- 指定给容器的名称。它是一个唯一标识符
```docker-compose
container_name: [my_container]
```
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
```docker-compose
expose:
@ -339,8 +345,9 @@ tags: []
docker-compose up
```
* `-d`​:在后台启动服务。
* `--build`​:构建服务,即使镜像已存在。
- `-d`​:在后台启动服务。
- `--build`​:构建服务,即使镜像已存在。
2. **停止容器应用**
```docker-compose

View File

@ -4,8 +4,6 @@ date: 2024-06-06T23:51:10Z
tags: []
---
## 颜色名
## 光的三原色

View File

@ -110,106 +110,105 @@ git push github master
## 常用的 Git 命令
* 推送
- 推送
```git
git push <origin> <master>
```
* 强制将推送本地分支
- 强制将推送本地分支
```git
git push -f <origin> <master>
```
* 拉取
- 拉取
```git
git pull <origin> <master>
```
* 强制将分支的最新内容拉取到本地的分支
- 强制将分支的最新内容拉取到本地的分支
```git
git pull --force <origin> <master>
```
* 将本地分支重置为远程分支的最新状态
- 将本地分支重置为远程分支的最新状态
```git
git reset --hard <origin>/<master>
```
* 克隆仓库
- 克隆仓库
```git
git clone <url>
```
* 添加所有更改到暂存区
- 添加所有更改到暂存区
```git
git add .
```
* 撤销部分文件的暂存
- 撤销部分文件的暂存
```git
git reset <file1> <file2>
```
* 将文件从缓存区中移除,但物理文件仍然存在
- 将文件从缓存区中移除,但物理文件仍然存在
```git
git rm --cached <path>
```
* 查看暂存区的内容
- 查看暂存区的内容
```git
git ls-files
```
* 提交已暂存的更改
- 提交已暂存的更改
```git
git commit -m "Commit message"
```
* 查看分支
- 查看分支
```git
git branch
```
* 创建并切换到新分支
- 创建并切换到新分支
```git
git checkout -b <new_branch_name>
```
* 删除本地分支
- 删除本地分支
```git
git branch -d <branch_nam>
```
* 添加远程仓库
- 添加远程仓库
```git
git remote add <origin> <remote_repository_url>
```
* 移除与远程仓库的关联
- 移除与远程仓库的关联
```git
git remote remove <origin>
```
* 版本回退
- 版本回退
> HEAD 相当与当前、HEAD~1 退回上一个版本、HEAD~2 退回上两个版本,依次类推。
>
```git
git reset --hard HEAD~1
@ -284,11 +283,16 @@ practice_test/
```markdown
# 忽略 test.c 文件
practice_code/test.c
# 忽略 practice_test/ 目录下的文件
practice_test/
# 忽略 所有 test.c 文件
**/test.c每次提交自动同步到代码托管服务平台
\*\*/test.c 每次提交自动同步到代码托管服务平台
```
#### 2.将已被追踪的文件的更改加入到暂存区

View File

@ -68,13 +68,15 @@ tags: []
## 分组
* 普通分组
- 普通分组
`()`
* 非捕获分组
- 非捕获分组
`(?:<表达式>)`
* 回溯分组
- 回溯分组
`\<number>`
@ -88,30 +90,28 @@ tags: []
* 正向
- 正向
`(?=<表达式>)`
> 右边必须出现某个字符
>
* 反向
- 反向
`(?!<表达式>)`
> 右边不能出现某个字符
>
### 后行断言
* 正向
- 正向
`(?<=<表达式>)`
> 左边必须出现某个字符
>
* 反向
- 反向
`(?<!<表达式>)`
> 左边不能出现某个字符
>

View File

@ -22,9 +22,11 @@ tags: []
1. 源程序(`.c`)
编译
2. 目标文件(`.obj`)
链接
3. 执行文件(`.exe`)
执行
@ -33,14 +35,14 @@ tags: []
#### 整数
* `0`开头的是8进制
* `0x`开头的是16进制
- `0` 开头的是 8 进制
- `0x` 开头的是 16 进制
#### 小数
* `0.7`=`.7`
* `7.0`=`7.`
* 科学计数`6E6`
- `0.7`=`.7`
- `7.0`=`7.`
- 科学计数`6E6`
E 的前后必须有数,后面必须为整数
@ -56,15 +58,15 @@ a=97
##### 转义字符
* 一般转义字符:`\n` `\t`
* 八进制转义字符:`\0`开头,`\0343`
* 十六进制转义字符:`\0x`开头,`\0xaf`
- 一般转义字符:`\n` `\t`
- 八进制转义字符:`\0` 开头,`\0343`
- 十六进制转义字符:`\0x` 开头,`\0xaf`
### 注释
开头:/*
开头:/\*
结尾:*/
结尾:\*/
### 三段论
@ -74,24 +76,24 @@ a=97
#### 关键字
* int
* float
* acse
- int
- float
- acse
不能作为用户标识符
#### 预定义标识符
* printf
* scanf
* define
- printf
- scanf
- define
可以作为用户标识符
#### 用户标识符
* int a
* int _a
- int a
- int \_a
#### 规则

View File

@ -4,27 +4,25 @@ date: 2024-06-06T23:51:12Z
tags: []
---
## stdio.h
* 读写文件
- 读写文件
`FILE *fopen(const char *filename, const char *mode)`
> * filename -- 字符串,表示要打开的文件名称
> * mode -- 字符串,表示文件的访问模式
>
> - filename -- 字符串,表示要打开的文件名称
> - mode -- 字符串,表示文件的访问模式
* 格式化输出字符串
- 格式化输出字符串
`snprintf ( char \* str, size_t size, const char \* format, ... )`
> * **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
> * **size** -- 字符数组的大小。
> * **format** -- 格式化字符串。
> * **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
>
* 清除缓存
> - **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
> - **size** -- 字符数组的大小。
> - **format** -- 格式化字符串。
> - **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
- 清除缓存
```c
fflush(stdin)
@ -32,41 +30,42 @@ tags: []
## stdlib.h
* 清屏命令
- 清屏命令
```c
system("cls"); // windows清屏命令
system("clear"); //Linux和macOS清屏命令
```
* 手动管理内存
- 手动管理内存
* 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
- 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
`void *malloc(size_t size)`
* 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
- 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
`void *calloc(size_t nitems, size_t size)`
* 尝试重新调整之前调用所分配的指针所指向的内存块的大小
- 尝试重新调整之前调用所分配的指针所指向的内存块的大小
`void *realloc(void *ptr, size_t size)`
* 释放之前调用 函数 所分配的内存空间
- 释放之前调用 函数 所分配的内存空间
`void free(void *ptr)`
* > * **ptr** -- 指针指向一个分配内存的内存块的指针。
> * **size** -- 内存块的大小,以字节为单位。
> * **nitems** -- 要被分配的元素个数。
>
* 排序
- > - **ptr** -- 指针指向一个分配内存的内存块的指针。
> - **size** -- 内存块的大小,以字节为单位。
> - **nitems** -- 要被分配的元素个数。
- 排序
```c
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
```
> * **base** -- 指向要排序的数组的第一个元素的指针。
> * **nitems** -- 由 base 指向的数组中元素的个数。
> * **size** -- 数组中每个元素的大小,以字节为单位。
> * **compar** -- 用来比较两个元素的函数。
> * 该函数不返回任何值。
>
* 随机数
> - **base** -- 指向要排序的数组的第一个元素的指针。
> - **nitems** -- 由 base 指向的数组中元素的个数。
> - **size** -- 数组中每个元素的大小,以字节为单位。
> - **compar** -- 用来比较两个元素的函数。
> - 该函数不返回任何值。
- 随机数
1. 设置随机数种子
@ -75,7 +74,7 @@ tags: []
```
> **seed** -- 这是一个整型值,用于伪随机数生成算法播种。
>
2. 获取随机数
```c
@ -83,7 +82,6 @@ tags: []
```
> 该函数返回一个范围在 0 到 RAND_MAX 之间的整数值。
>
## string.h
@ -93,21 +91,20 @@ tags: []
char *strncat(char *dest, const char *src, size_t n)
```
> * **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
> * **src** -- 要追加的字符串。
> * **n** -- 要追加的最大字符数。
> * 该函数返回一个指向最终的目标字符串 dest 的指针。
>
> - **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
> - **src** -- 要追加的字符串。
> - **n** -- 要追加的最大字符数。
> - 该函数返回一个指向最终的目标字符串 dest 的指针。
2. 字符串检索
```c
char *strstr(const char *haystack, const char *needle)
```
> * **haystack** -- 要被检索的 C 字符串。
> * **needle** -- 在 haystack 字符串内要搜索的小字符串。
> * 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
>
> - **haystack** -- 要被检索的 C 字符串。
> - **needle** -- 在 haystack 字符串内要搜索的小字符串。
> - 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
## time.h
@ -117,8 +114,8 @@ sleep(1); // 暂停 1 秒
## conio.h
_kbhit()//如果有按键按下则_kbhit()函数返回真
_getch();//使用_getch()函数获取按下的键值
\_kbhit()//如果有按键按下,则\_kbhit()函数返回真
\_getch();//使用\_getch()函数获取按下的键值
## errno.h

View File

@ -8,14 +8,17 @@ tags: []
- **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。
- **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。
2. **栈区Stack**
- **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出LIFO的数据结构。
- **分配和释放:** 由编译器自动分配和释放,不需要手动管理。
3. **静态区Static**
- **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。
- **分配和释放:** 由编译器分配,程序运行期间一直存在。
4. **代码区Code**
- **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。
@ -36,6 +39,7 @@ tags: []
```
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
2. **不带** **`static`** **关键字的全局静态变量:**
```c
@ -47,6 +51,7 @@ tags: []
```
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
3. **带** **`static`** **关键字的局部静态变量:**
```c
@ -60,6 +65,7 @@ tags: []
```
这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。
4. **不带** **`static`** **关键字的局部静态变量:**
```c

View File

@ -4,7 +4,5 @@ date: 2024-06-06T23:51:47Z
tags: []
---
大端存储:把高位字节放在低字节
小端存储:把低位字节放在高字节

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:39Z
tags: []
---
1. while
```javascript

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:37Z
tags: []
---
`&` 取地址调试符
`%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*)

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:47Z
tags: []
---
main 写 mian
函数后面加;
switch 里面的 case 不加 break

View File

@ -4,10 +4,8 @@ date: 2024-06-06T23:51:44Z
tags: []
---
| 类型 | 符号 | 占位符 | 字节 |
| :------------: | :---------: | :--------------------------------: | :----: |
| :----------: | :-------: | :----: | :--: |
| 字符 | char | %c | 1 |
| 字符串型 | | %s | |
| 整形 | int | %d | 4 |
@ -20,7 +18,7 @@ tags: []
## 储存类
| 存储类 | 描述 |
| :--------: | :----------------------------------------------------: |
| :------: | :--------------------------------------------------: |
| register | 用于定义存储在寄存器中而不是 RAM 中的局部变量 |
| static | 存储类指示编译器在程序的生命周期内保持局部变量的存在 |
| extern | 定义在其他文件中声明的全局变量或函数 |

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:38Z
tags: []
---
`scanf` 是针对标准输入的格式化语句
`printf` 是针对标准输出的格式化语句
@ -39,13 +38,13 @@ tags: []
int fseek(FILE *stream, long int offset, int whence)
```
* 参数
- 参数
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
> offset -- 这是相对 whence 的偏移量,以字节为单位。
> whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
>
* 常量 描述
- 常量 描述
```c
SEEK_SET 文件的开头

View File

@ -35,7 +35,7 @@ tags: []
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
>
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
>
4. 共用体
```c

View File

@ -15,6 +15,7 @@ tags: []
2. 转换为汇编代码
`-S`
3. 将汇编代码转化为机器指令
`-c`

View File

@ -18,7 +18,7 @@ tags: []
2. 三元运算
```javascript
条件?true:false
条件 ? true : false;
```
3. switch
@ -26,14 +26,14 @@ tags: []
```javascript
switch (data) {
case value1:
code1
break
code1;
break;
case value2:
code2
break
code2;
break;
default:
coden
break
coden;
break;
}
```

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:44Z
tags: []
---
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证 with 语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
语法结构

View File

@ -24,8 +24,7 @@ tags: []
## 遍历
```html
for i in enumerate(数组):
print(i)
for i in enumerate(数组): print(i)
```
## 操作

View File

@ -27,9 +27,7 @@ tags: []
```html
"""
需要注释的代码
"""
""" 需要注释的代码 """
```
## 导入模块

View File

@ -4,13 +4,14 @@ date: 2024-06-06T23:51:30Z
tags: []
---
## 创建方式
### 第一种使用{}直接创建字典
`d={key1:value1,key2:value2....}`
语法结构如下:
`dict(key1=value1,key2=value2....)`
### 第二种使用内置函数 dict()创建字典
@ -43,12 +44,21 @@ d[key]或d.get(key)
## 相关的操作方法
获取所有的 key 数据
`d.keys()`
获取所有的 value 的数据
`d.values()`
key 存在获取相应的 value,同时删除 key-value 对,否则获取默认值
`d.pop(key,default)`
随机从字典种取出一个 key-value 对,结果为元组类型,同时将该 key-value 从字典种删除
`d.popitem()`
清空字典中所有的 key-value 对
`d.clear()`

View File

@ -7,18 +7,31 @@ tags: []
## 常用方法
`str.lower()`:将 str 字符串全部转换成小写字母,结果为一个新的字符串
`str.upper()`:将字符串全部转为大写字母,结果为一个新的字符串
`str.split(sep=None)`:把 str 按照指定的分隔符 sep 进行分隔,结果为列表类型
`str.count(sub)`:结果为 sub 这个字符串在 str 中出现的次数
`find(sub)`:结果为 sub 这个字符串在 str 中是否存在,如果不存在结果为-1,如果存在,如果出现结果为 sub 首次出现的索引
`str.index(sub)`:功能与 find()相同,区别在于要查询的子串 sub 不存在时,程序报错
`str.startswith(s)`:查询字符串 str 是否以子字符串 s 开头
`str.endswith(s)`:查询字符串 str 是否以子字符串 s 结尾
`str.replace(old,news,count)`:使用 news 替换字符串 s 中所有 old 字符串,结果是一个新的字符串
`str.center(width,filllchar)`:字符串 str 在指定的宽度范围内居中,可以使用 fillchar 进行填充
`str.join(iter)`:在 iter 中的每个元素后面都增加一个新的字符串 str
`str.strip(chars)`:从字符串中去掉左侧和右侧 chars 中列出的字符串
`str.lstrip(chars)`:从字符串中去掉左侧 chars、中列出的字符串
`str.rstrip(chars)`:从字符串中去掉右侧 chars、中列出的字符串
## 格式化字符串的方法
@ -84,14 +97,10 @@ python3.6引入的格式化字符串的方式,比{}表明被替换的字符
1.使用`str.join()`方法进行拼接字符串
例如
`print("|".join([x,y,"ww"]))`
2.直接拼接
3.使用格式化字符串进行拼接
`print("|".join([x,y,"ww"]))` 2.直接拼接 3.使用格式化字符串进行拼接
### 字符串的去重
1.字符串的拼接以及 not in
`for i in x: if i not in x_new: x_new += i print(x_new)`
2.索引+not in
3.通过列表集合+列表排序
`for i in x: if i not in x_new: x_new += i print(x_new)` 2.索引+not in 3.通过列表集合+列表排序
`x_new = set(x) lst = list(x_new) lst.sort(key=x.index) print(''.join(lst))`

View File

@ -6,23 +6,34 @@ tags: []
## 导入
`import 模块名称 [as 别名]`
`from 模块名称 import 变量/函数/类/*`
```python
import 模块名称 [as 别名]
```
```python
from 模块名称 import 变量/函数/类/*
```
## os模块
> 与操作系统和文件相关操作有关的模块
`getcwd()`:获取当前工作路径
`listdir(path)`:获取path路径下的文件,如果没有指定path,则获取当前路径下的文件和目录信息
`mkdir(path)`:在指定路径下创建目录(文件夹)
`makedirs(path)`:创建多级目录
### os.path模块
`abspath(path)`:获取目录或文件的绝对路径
`exists(path)`:判断目录或文件在磁盘上是否存在,结果为bool类型,如果目录或文件在磁盘上存在,结果为True,否则为False
`join(path.name)`:将目录与文件名进行拼接,相当于字符串`+`​的操作
`splitext()`:分别获取文件名和后缀
## re模块
@ -34,11 +45,17 @@ tags: []
> 用于产生随机数的模块
`seed(x)`:初始化给定的随机数种子,默认为当前系统时间
`random()`:产生一个[0.0,1.0]之间的随机小数
`randint(a,b)`生成一个[a,b]之间的整数
`randrange(m,n,k)`​生成一个[m,n)之间步长为k的随机整数
`uniform(a,b)`:生成一个[a,b]之间的随机小数
`choice(seq)`:从序列中随机选择一个数
`shuffle(seq)`:将序列seq中元素随机排列,然后打乱后的序列
## json模块
@ -46,8 +63,11 @@ tags: []
> 用于对高维数据进行编码和解码的模块
`json.dumps(obj)`:将python数据类型转为json格式过程,编码过程
`json.loads(s)`:将JSON格式字符串转为python数据类型,解码过程
`json.dump(obj,file)`:与dumps功能相同,将转换结果储存到文件file中
`json.load(file)`:与loads功能相同,从文件a中读入数据
## time模块
@ -55,10 +75,15 @@ tags: []
> 与时间相关的模块
`time()`:获取当前时间戳
`localtime(sec)`:获取指定时间戳对应的本地时间的structural_time对象
`ctime()`:获取当前时间戳对应的易读字符串
`strftime()`:格式化时间结果为字符串
`strptime()`:提取字符串的时间,结果为字符串
`sleep(sec)`:休眠sec秒
## datetime模块
@ -66,7 +91,11 @@ tags: []
> 与日期相关的模块,可以方便的显示日期并对日期进行运算
`datetime`:表示日期时间类
`timedelta`:表示时间间隔的类
`date`:表示日期的类
`time`:表示时间的类
`tzinfo`:时区相关的类

View File

@ -22,7 +22,7 @@ raise [exception 类型(异常描述信息)]
```
| 异常名称 | 描述 |
| ---------------------------| ----------------------------------------------------|
| ------------------------- | -------------------------------------------------- |
| BaseException | 所有异常的基类 |
| SystemExit | 解释器请求退出 |
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |

View File

@ -4,11 +4,10 @@ date: 2024-06-06T23:51:27Z
tags: []
---
## 整数类型
| 进制类型 | 引导符号 |
| ----------| ----------|
| -------- | -------- |
| 十进制 | 无 |
| 二进制 | 0b 或 0B |
| 八进制 | 0o 或 0O |
@ -47,7 +46,7 @@ False表示整数0
- False 或者是 None
- 数字中的 0,包含 0,0.0,虚数 0
- 空序列,包含空字符串,空元组,空列表,空字典,空合集
- 自定义对象的实例,该对象__bool__()方法返回False或__len__()方法返回False
- 自定义对象的实例,该对象**bool**()方法返回 False 或**len**()方法返回 False
## 数据类型的转换

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:22Z
tags: []
---
`import numpy`
数组转 numpy
`对象名 = numpy.array(数组)`
@ -12,7 +11,7 @@ tags: []
1. 随机生成列表
| 作用 | 语句 |
| :--------------------------------------------------------------------: | ------|
| :---------------------------------------------------------------------: | ------------------------------------------ |
| | `numpy.arange(起始,结束,步长)` |
| 全 0 | `numpy.zeros(shape)` |
| 全 1 | `numpy.ones(shape)` |
@ -27,6 +26,7 @@ tags: []
2. 设置数组
通过设置不同个数的元组,生成不同的
`numpy.reshape(shape)`
1. 读取数据
`numpy.loadtxt(frame,dtype=numpy.float,delimiter=None,skiprows=0,usecols=None,unpack=False)`
@ -40,7 +40,7 @@ tags: []
3. 取不同的行和列
| 作用 | 语句 |
| ----------------| ------|
| -------------- | ----------------------------------------------- |
| 取两个元素 | `元素名[[number1,number2],[number1,number2]]` |
| 取单独的列 | `元素名[:,number]` |
| 取单独的行 | `元素名[number,:]` |
@ -67,7 +67,7 @@ tags: []
8. 常用统计函数
| 作用 | 语句 | 描述 |
| ----------| ------| --------------------|
| -------- | ---------------------------------- | ------------------ |
| 求和 | `元素名.sum(axis=None)` | |
| 均值 | `元素名.mean(axis=None)` | 受离群点影响比较大 |
| 中值 | `numpy.median(元素名.axis=None)` | |

View File

@ -15,10 +15,15 @@ tags: []
## 操作文件
`file.read(size)`:从文件中读写 size 个字符或字节,如果没有给定参数,则读取文件中的全部内容
`file.readline(size)`:读写文件中的一行数据,如果给定参数,则读取这一行中的 size 个字符或字节
`file.readlines()`从文件中读取所有内容,结果为列表类型
`file.write(s)`:将字符串 s 写入文件
`file.writelines(lst)`:将内容全部为字符串的列表写入文件
`file.seek(offst)`:改变当前文件操作指针的位置,英文占一个字节,中文 GBK 占两个字节,utf-8 编码占三个字节
## 关闭文件

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:30Z
tags: []
---
## 请求
1 请求行 -> 请求方式 请求 url 地址 协议

View File

@ -17,6 +17,7 @@ x = requests.get('https://www.runoob.com/')
print(x.text)
```
```txt
apparent_encoding:编码方式
close():关闭与服务器的连接
content:返回响应的内容,以字节为单位
@ -39,9 +40,11 @@ request:返回请求此响应的请求对象
status_code:返回 http 的状态码,比如 404 和 200200 是 OK404 是 Not Found
text:返回响应的内容unicode 类型数据
url:返回响应的 URL
````
## requests 方法
```txt
delete(url, args):发送 DELETE 请求到指定 url
get(url, params, args):发送 GET 请求到指定 url
head(url, args):发送 HEAD 请求到指定 url
@ -49,6 +52,7 @@ patch(url, data, args):发送 PATCH 请求到指定 url
post(url, data, json, args):发送 POST 请求到指定 url
put(url, data, args):发送 PUT 请求到指定 url
request(method, url, args):向指定的 url 发送指定的请求方法
```
- url 请求 url
- data 参数为要发送到指定 url 的字典、元组列表、字节或文件对象

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:36Z
tags: []
---
## 创建工程
scrapy startproject (projectname)
@ -17,7 +16,7 @@ scrapy startproject (projectname)
## 编写对应的代码在爬虫文件中
1.将parse中的response解析
1. parse 中的 response 解析
想要使用数据,必须使用 extract()提取数据
extract():返回列表
extract_first():返回一个数据

View File

@ -6,10 +6,12 @@ tags: []
## 爬虫源文件
```txt
name:当前源文件的唯一标识
allowed_domains:允许请求的域名
start_urls:起始的 url 列表,作用:列表中存储的 url 会被 get 发送
parse 方法:解析服务器返回的响应对象的解析方法
```
## settings

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:47Z
tags: []
---
```python
from selenium import webdriver # 驱动
from selenium.webdriver.common.by import By # 解析方式

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:26Z
tags: []
---
## 安装
`pip install 模块名称`
@ -56,7 +55,7 @@ Workbook():创建新的工作簿对象
1. 随机生成列表
| 作用 | 语句 |
| :--------------------------------------------------------------------: | ------|
| :---------------------------------------------------------------------: | ------------------------------------------ |
| | `numpy.arange(起始,结束,步长)` |
| 全 0 | `numpy.zeros(shape)` |
| 全 1 | `numpy.ones(shape)` |
@ -84,7 +83,7 @@ Workbook():创建新的工作簿对象
4. 取不同的行和列
| 作用 | 语句 |
| ----------------| ------|
| -------------- | ----------------------------------------------- |
| 取两个元素 | `元素名[[number1,number2],[number1,number2]]` |
| 取单独的列 | `元素名[:,number]` |
| 取单独的行 | `元素名[number,:]` |
@ -110,7 +109,7 @@ Workbook():创建新的工作簿对象
9. 常用统计函数
| 作用 | 语句 | 描述 |
| ----------| ------| --------------------|
| -------- | ---------------------------------- | ------------------ |
| 求和 | `元素名.sum(axis=None)` | |
| 均值 | `元素名.mean(axis=None)` | 受离群点影响比较大 |
| 中值 | `numpy.median(元素名.axis=None)` | |
@ -170,7 +169,6 @@ Workbook():创建新的工作簿对象
>
> transparent:透明
> bbox_inches:是否紧凑布局
>
## 图形表述
@ -184,4 +182,3 @@ Workbook():创建新的工作簿对象
> alpha:区间
> linestyle:样式
>

View File

@ -7,4 +7,4 @@ tags: []
1. 使用 socket 类创建一个套接字对象
2. 使用 connect((host,port))设置连接的主机 IP 和主机设置的端口
3. 使用 recv()/send()方法接收和发送数据
3.使用close()关闭套接字
4. 使用 close()关闭套接字

View File

@ -5,12 +5,21 @@ tags: []
---
`bind(ip,port)`:绑定 ip 地址和端口
`listen(N)`:开始 TCP 监听,N 表示操作系统挂起的最大连接数量,取值范围 1-6 之间,一般设置为 5
`accept()`:被动接收 TCP 客户端连接,阻塞式
`connect((ip,port))`:主动初始化 TCP 服务器连接
`recv(size)`:接收 TCP 数据,返回值为字符串类型,size 表示要接收的最大数据量
`send(str)`:发送 TCP 数据,返回值是要发送的字节数量
`sebdall(str)`:完整发送 TCP 数据,将 str 中的数据发送到连接的套接字,返回之前尝试发送所有数据,如果成功为 None,失败抛出异常
`recvfrom()`:接收 UDP 数据,返回值为一个元组(data,address),data 表示接收的数据,address 表示发送数据的套接字地址
`sendto(data,(ip,port))`:发送 UDP 数据,返回值是发送的字节数
`close()`:关闭套接字

View File

@ -5,7 +5,11 @@ tags: []
---
`/`表示层级关系,第一个是根目录
`text()`拿文本
`//`后代
`*`:通配符
`@属性`:属性值

View File

@ -4,14 +4,13 @@ date: 2024-06-06T23:51:47Z
tags: []
---
| 描述 | 属性 |
| ---------------------------------- | -------- |
| ----------------------------------- | -------- |
| 当前进程的实例别名,默认为 Process-N | `name` |
| 当前进程对象的 PID 值 | `pid` |
| 描述 | 方法 |
| --------------------------------------------------------- | ----------------- |
| ------------------------------------------------------------- | ----------------- |
| 进程是否执行完,没执行完结果为 True,否则为 False | `is_alive()` |
| 等待结束或等待 timeout 秒 | `join(timeout)` |
| 启动进程 | `start()` |

View File

@ -10,7 +10,11 @@ tags: []
参数说明
> group:表示分组,实际上不使用,值默认为 None 即可
>
> target:表示子进程要执行的任务,支持函数名
>
> name:表示子进程的名称
>
> args:表示调用函数的位置参数,以元组的形式进行传递
>
>kwargs:表示调用函数的关键字参数,以字典的形式进行传参

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
tags: []
---
> 队列是一种先进先出的数据结构
## 语法
@ -14,7 +13,7 @@ tags: []
## 常用方法
| 功能 | 方法 |
| ------------------------------------------------------- | ------------------------ |
| --------------------------------------------------------- | ------------------------ |
| 获取当前队列包含的消息数量 | `qsize()` |
| 判断队列是否为空,为空结果为 True,否则为 False | `empty()` |
| 判断队列是否满了,满结果为 True,否则为 False | `full()` |

View File

@ -5,7 +5,7 @@ tags: []
---
| 名称 | 单词 | 作用 |
| ------| ------| ----------|
| ---- | ---- | -------- |
| and | 与 | 从左到右 |
| or | 或 | 从左到右 |
| not | 非 | 从右到左 |

View File

@ -43,10 +43,15 @@ A^B
## 相关操作
如果 x 不在集合 s 中,则将 x 添加到集合 s
`s.add(x)`
如果 x 在集合中,将其删除,如何步骤集合中,程序报错
`s.remove(x)`
清楚集合所有元素
`s.clear()`

View File

@ -0,0 +1,378 @@
---
title: rust
date: 2024-10-09T18:49:45Z
tags: []
---
### 变量
`let`
1. 支持类型推导,也可以显示指定变量类型
2. 变量名用蛇命名法`a_b`​,而枚举和结构体使用帕斯卡命名法`Ab`
3. 如果变量没有使用可以前置下划线,消除警告
4. 强制类型转换
let a = 3.1;let b = a as i32
5. 打印变量
```rust
printIn!("val{}",x);
printIn!("val{x}");
```
6. 变量默认不可变
7. 使用`mut`​关键字声明可变
```rust
let mut x=10;
```
8. 变量可以解构
### 常量
`const`
1. 必须指定类型和值
2. 常量值被直接嵌入生成的底层机器代码中
3. 常量名与静态变量名必须全部大写,单词之间加入下划线
4. 常量的作用域块级作用域,他们只再声明它们的作用域内可见
### 静态变量
`static`
1. static 变量是在运行时分配内存
2. 并不是不可修改,可以使用`unsafe`​修改
3. 静态变量的生命周期为整个程序的运行时间
### 解构
### 函数
1. 需要使用蛇形命名法
2. 当用`!`​做返回值,代表永不返回
### 所有权
1. 每一个值都被一个变量所拥有,该变量被称为值的所有权
2. 一个值只能被一个变量拥有
3. 当所有者(变量)离开作用域范围时会被丢弃
4. 有一个基本特征`copy`​,任何基本类型都具有该特征,在给其他值赋值后依旧可用
### 引用与解引用
* 引用
* 默认不可变引用
* 用`&`​引用,得到需要引用的地址
* 可变引用
1. 先声明可引用
2. 传参用`mut`​修饰,变成可引用类型
同一作用域,特定数据只能有一个可变引用
* 可变引用和不可变引用不能同时存在
* 引用作用域在最后一次引用的地方
* 解引用
* 用`*`​解开引用,得到需要引用的数据
### 复合类型
`unimplemented!()`​在复合类型中为了快速构建框架告诉编译器该函数未实现
### 切片
* 创建方法`[起始位置..结束位置]`
### 字符
* 字符Unicode编码
* 字符串UTF-8编码
* 追加字符`push`
* 追加字符串`push_str`
* 修改`insert`
* 替换
* 对原来进行修改`replace`
* 返回新字符串,可对自变量使用`replacen`
* 删除
* 删除最后一个字符`pop`
* 删除指定位置的字符`remove`
* 删除指定位置到结尾的字符`truncate`
* 清空`clear`
* 链接
* `+` `+=`
* `format!("{}{}",x,y)`
* 注意区分字符串和字变量
* 字符转义
`\`
* 保持字符串原样
```rust
r"<str>"
r#""<str>""# //如果包含双引号
r###""<str>""###//如果还有歧义可以一直加#
```
### 元组
* 长度固定
* 可以多种元素混合
* 可以使用解构或`.`
### 结构体
`struct`
* 可以通过关键词创建结构体
* 可以通过函数返回创建结构体
* 在使用`..<name>`​可以当前结构体没有显示声明的字段,全部从`<name>`​结构体中获得
* 元组结构体,结构体字段没有名称的结构体
* 单元结构体,只定义
* 打印结构体信息
```rust
#[derive(Debug)]
println!("{:?}", <struct variable>);
```
### 枚举
`enum` `enumeration`
* 也可以用结构体使用
* 枚举用`::`​访问
* 枚举能直接关联数据信息
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
```
* option枚举值用于处理空值
可以让编译器推断出整个变量是什么类型
Some(T):表示成员含有值
None:成员没有值
### 数组
元素必须相同类型
* 不可变数组
* 声明可以直接赋值
* 声明时可以定义类型和长度`let a:[i32;5]`
* 可变数组
`let arr:&[u16] = &[114,112];`
### 循环
loop是一个表达式
### 模式
特殊语法,它用于匹配类型中的结构和数据
* match
* if let
* while let
* let
* 忽略模式中
* 值`_`
* 使用下划线本身不会绑定值
* 省略模式`..`
* 匹配守卫
位于math分支后的额外if条件他能为分支提供一个额外的条件,因为他不是模式所以可以寻找外部的值,来使用
### 方法
1. 对象的属性和方法定义是分离的
2. 属性和方法名称相同即可
3. 生成对象的方法一般用`new`
4. `impl`​生成方法
5. `struct`​生成属性
6. self
* `self`​:表示该所有权
* `&self``self:&Self`​的简写,对该方法的不可变借用
* `&mut self`​:表示可借用
7. 允许方法名和结构体字段名相同
8. 可以多个`impl`​定义
9. 可以为枚举生成方法
### 闭包
本质上是一个匿名函数
```rust
|param1, param2,...| {
语句1;
语句2;
返回表达式
}
|param1| 返回表达式
```
1. 如果类型推断出一个类型,就不可改变了
2. 闭包可以捕获作用域终端值
3. 闭包特征:`T: Fn(u32) -> u32`
4. 三种fn特征
* `fnOnce`
所有的包自动实现这个特征
如果没有copy特征会拿走被捕获变量的所有权
如果想强制获取捕获变量的所有权,可以在参数列表前添加`move`​关键词
* `fnMut`
如果没有移出捕获所有权自动实现这个特征
可以以借用的方式捕获环境中的值,因此修改该值
如果想在闭包里面修改可变借用变量,需要把闭包声明为可变类型
* `fn`
不需要对捕获变量进行改变的闭包自动实现该特征
以不可变借用的方法捕获环境中的变量
### 迭代器
特征`IntoIterator`
* 惰性初始化
如果不适用不会产生任何的性能损耗
* IntoIterator特征
显示的转为迭代器
* `into_iter`​:夺走所有权
* `iter`​:借用
* `iter_mut`​:可变借用
* next方法
会改变迭代器当前的数据
对迭代器的遍历是消耗性的
for循环就是通过不断调用next方法
* 适配器
* 消费型适配器
该迭代器在内部调用了next方法会不断的消耗迭代器上的原素
* 迭代器适配器
会返回一个新的迭代器
迭代器适配器是惰性的所以需要消费者适配器来收尾
可以将闭包作为参数不仅实现了迭代器环境的处理,还可以捕获环境值
* collect
消费者迭代器
使用它可以将一个迭代器的元素收进指定元素中
### 类型转换
* as
* RryInto
需要导入`use std::convert::TryInto;`
* 点操作符
点操作符会发生很多类型转换,最外层的类型不匹配时,会自动引用,自动解引用,强类型转换直到找到需要的特征
* 强类型转换
### type别名
`type`
### Sized
每个可定大小都实现了该特征
### 动态大小类型DST
储存在堆上不能直接使用,只能通过引用或`Box`​来间接使用
* 切片
* str
是String和&str的底层数据
### 智能指针
使用智能指针可以将动态大小类型变成固定大小
* `Box<T>`
可以将数据存在堆上
Box::leak可以消费掉box并内存泄漏
* Derf特征
可以实现智能指针的自动解引用
三种Deref转换
* `T: Deref<Target=U>`​可以将&T转换为&U
* `T: DerefMut<Target=U>`​可以将&mut T转换为&mut U
* `T: Deref<Target=U>`​可以将&mut T转换为&U
* Drop特征
可以实现资源的回收
执行一些收尾工作
和Copy特征互斥
可以手动收回`Drop()`
* Rc与Arc
通过引用器计数
clone:可以实现浅拷贝,会拿走拷贝指针的所有权
Arc原子化的智能指针能够保证数据安全会增加数据损耗
* Cell和RefCell
提供了内部数据可修改性
cell只使用于copy类型没有性能损耗
RefCell 只是将借用规则从编译期推迟到程序运行期
* Weak
类似于Rc但是Weak不持有所有权仅仅保存一份指向数据引用
不计入所有权,不会阻止释放
存在返回Some不存在返回None
### 多线程

View File

@ -14,7 +14,4 @@ mov AX,BX
机器码是单片工作且能识别和运行的一类代码,常见的格式有二进制 BIN 格式、十进制 HEX 格式等。
汇编语言三类组成
1.汇编指令(机器码的助记符(能够直接翻译为机器码))
2.伪指令(由编译器执行(计算机不能直接识别,编译器可以识别,由编译器转化为想表达的意思)
3.其他符号(由编译器识别,如:+ - * /
汇编语言三类组成 1.汇编指令(机器码的助记符(能够直接翻译为机器码)) 2.伪指令(由编译器执行(计算机不能直接识别,编译器可以识别,由编译器转化为想表达的意思) 3.其他符号(由编译器识别,如:+ - \* /

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
tags: []
---
预处理Preprocessing
任务:预处理阶段对源代码进行预处理,展开宏、处理条件编译等。
工具:预处理器,如 C 语言中的 cpp。

View File

@ -23,5 +23,4 @@ A --> B[系统调用的过程]
B --> 陷入指令/Trap/访管
B --> 有系统内核程序处理系统调用请求
B --> 返回系统程序
```

View File

@ -7,7 +7,7 @@ tags: []
两种虚拟机管理程序(VMM)的对比
| | 第一类 | 第二类 |
| ------------------| ------------------------------------------------------------------------------------------------------| ---------------------------------------------------------------------------------------------------------|
| ------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| 对物理资源的控制权 | 直接运行在硬件之上,能直接控制和分配物理资源 | 运行在 Host OS 字上,依赖 Host OS 为其分配物理资源 |
| 资源的分配方式 | 在安装 Guest OS 是,VMM 要在原本的硬盘上自行进行分配储存空间,类似于"外核"的分配方式,分配未经抽象的物理硬件 | CuestOS 拥有自己的虚拟磁盘,该盘实际上是 Host OS 文件系统中的一个大文件.GuestOS 分配的内存是虚拟内存 |
| 虚拟 | 虚拟更好 | 虚拟更差,需要 HostOS 作为"中介" |

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