统一组件使用astro,优化css样式,新增文章,优化文章格式
This commit is contained in:
parent
57d406c277
commit
0d48cc8591
7107
package-lock.json
generated
7107
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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>
|
72
src/components/Breadcrumb.tsx
Normal file
72
src/components/Breadcrumb.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
|
@ -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="下一页"
|
||||
|
@ -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
65
src/components/Footer.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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="下一页"
|
||||
|
@ -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
421
src/components/Header.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
359
src/components/MediaGrid.tsx
Normal file
359
src/components/MediaGrid.tsx
Normal 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;
|
@ -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"/>
|
@ -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) ? (
|
||||
|
@ -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)
|
||||
|
@ -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 热点了
|
||||
|
@ -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 Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
||||
* 攻击成功后,通常会在目标机器上生成一个Meterpreter会话,从而允许进一步的渗透测试操作。
|
||||
- 这个模块利用了 MS17-010 漏洞,通过 EternalBlue 攻击载荷,远程执行代码。
|
||||
- EternalBlue 利用 Windows 的 Server Message Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
||||
- 攻击成功后,通常会在目标机器上生成一个 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 端口与外部通信。
|
||||
>
|
@ -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 服务端证书验证: 关闭
|
||||
- 其他默认或随意
|
||||
|
||||
## 问题
|
||||
|
||||
|
@ -149,3 +149,4 @@ server {
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -19,7 +19,7 @@ tags: ["Docker-compose","WebDAV"]
|
||||
运行以下 Docker Compose 文件进行 Alist 的安装:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
version: "3.8"
|
||||
services:
|
||||
alist:
|
||||
image: xhofe/alist:latest
|
||||
|
@ -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` 命令添加自动补全支持
|
||||
|
@ -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`:比较本地当前目录和云盘根目录。
|
||||
|
||||
## 安装阿里网盘备份工具
|
||||
|
||||
|
33
src/content/技术日志/vpn/3x-ui配置.md
Normal file
33
src/content/技术日志/vpn/3x-ui配置.md
Normal 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
|
||||
```
|
@ -1,11 +0,0 @@
|
||||
---
|
||||
title: "dns 解锁"
|
||||
date: 2025-01-18 14:19:00Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 1. 安装dnsmasq
|
||||
|
||||
```bash
|
||||
yum install dnsmasq -y
|
||||
```
|
@ -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: "*****" ## 面板设置的通讯密钥
|
||||
|
@ -25,7 +25,9 @@ tags: ["v2board"]
|
||||
|
||||
1. 复制一份创造成功的节点
|
||||
2. 修改复制节点:
|
||||
|
||||
- 后台 > 节点管理 > 添加节点
|
||||
|
||||
- 节点名称:随便填写
|
||||
- 权限组:随便填写
|
||||
- 节点地址:CloudFront 分配的域名
|
||||
|
@ -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
|
||||
|
@ -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 的版本
|
||||
|
@ -3,6 +3,7 @@ title: "CDN配置"
|
||||
date: 2023-12-25T12:07:21+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 域名绑定
|
||||
|
||||
1. 绑定到需要加速的服务器(源站)
|
||||
|
@ -3,12 +3,14 @@ title: "Cloudflare_自选IP"
|
||||
date: 2024-06-18T16:16:35+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Workers
|
||||
|
||||
1. DNS 解析
|
||||
|
||||
1. 删除现有 Workers 解析(自定义域)
|
||||
2. 将要用的域名解析到自选 IP 上,注意**不要开启**代理(小云朵)
|
||||
|
||||
2. 自定义路由
|
||||
|
||||
设置->触发器->路由
|
||||
|
@ -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: 等待源站部署
|
||||
|
@ -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 访问限制
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2025-01-15T00:34:11Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 前端
|
||||
|
||||
## tailwind
|
||||
|
@ -230,3 +230,4 @@ for ((;;)); do
|
||||
sleep $interval
|
||||
clear
|
||||
done
|
||||
```
|
||||
|
@ -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. 下载
|
||||
|
||||
|
@ -115,9 +115,7 @@ systemctl enable serverTZs.service
|
||||
```
|
||||
|
||||
> #赋权
|
||||
> #拷贝进系统服务目录
|
||||
> #重新加载系统服务
|
||||
> #启动服务端并设置开机自启
|
||||
> #拷贝进系统服务目录 #重新加载系统服务 #启动服务端并设置开机自启
|
||||
|
||||
### 在配置文件中增加服务器主机后重启
|
||||
|
||||
@ -198,8 +196,7 @@ systemctl enable serverTZc.service
|
||||
```
|
||||
|
||||
> #赋权
|
||||
> #拷贝进系统服务目录
|
||||
> #重新加载系统服务
|
||||
> #拷贝进系统服务目录 #重新加载系统服务
|
||||
> #启动服务端并设置开机自启
|
||||
|
||||
在配置文件中增加服务器主机后重启
|
||||
|
@ -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`
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
69
src/content/技术日志/windows/vscode配置.md
Normal file
69
src/content/技术日志/windows/vscode配置.md
Normal 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"
|
||||
},
|
||||
```
|
@ -28,7 +28,7 @@ tags: []
|
||||
|
||||
国外的公交车不适合i人,我打算从布城从地铁到市中心,需要先坐公交车到布城去,差10秒就赶上了,但是站台没人所以公交车司机没有停,第二次在休息区等了半个小时司机又没停,可能是司机没看到我吧,第三次我站在公交车站台等车的位置等待可是他还是没停,这次可能是没有给司机信号,第四次等公交车快到的时候我死死的看着司机,与他建立心灵链接,但他还是不停,浏览器查询原来要招手,用打车软件看了一下两公里,还是选择打车了
|
||||
|
||||
在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,酒店的位置和平台给的地址不同还好遇到一个好心的印度男人,打电话和酒店交流,告诉我酒店在哪里
|
||||
在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他也找不到,他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走再往哪走到一个塔下快到了问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
|
||||
|
||||
晚餐找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒`
|
||||
|
||||
|
@ -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
|
||||
|
@ -35,5 +35,4 @@ select @变量名=值
|
||||
|
||||
### go
|
||||
|
||||
1.等待go语句之前的代码执行完之后才能执行后面的代码
|
||||
2.批处理结束的一个标志
|
||||
1.等待 go 语句之前的代码执行完之后才能执行后面的代码 2.批处理结束的一个标志
|
||||
|
@ -3,6 +3,7 @@ title: 触发器
|
||||
date: 2024-06-06T23:51:43Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## instead of(事前触发器)
|
||||
|
||||
## After(事后触发器)
|
||||
|
@ -4,12 +4,11 @@ date: 2024-06-06T23:51:36Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
$n
|
||||
|
||||
> n 代表数字,$1-$9 代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
|
||||
|
||||
$*
|
||||
$\*
|
||||
|
||||
> 代表命令行中的所有参数(所有参数当作一个整体)
|
||||
|
||||
|
@ -9,7 +9,7 @@ tags: []
|
||||
## 系统变量
|
||||
|
||||
| 变量 | 作用 |
|
||||
| ------| --------------------------------------|
|
||||
| --------------- | ------------------------------------ |
|
||||
| `$USER` | 当前用户的用户名 |
|
||||
| `$HOME` | 当前用户的家目录 |
|
||||
| `$PATH` | 包含可执行文件的目录列表,用冒号分隔 |
|
||||
|
@ -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 ]]` |
|
||||
|
@ -41,7 +41,7 @@ case关键字表示开始一个case语句块。
|
||||
expression 是需要匹配的表达式或变量。
|
||||
pattern1, pattern2, pattern3 等是可能的匹配模式。
|
||||
command1, command2, command3 是与每个模式匹配时要执行的命令。
|
||||
*)是通配符,用于匹配所有不符合前面模式的情况。
|
||||
\*)是通配符,用于匹配所有不符合前面模式的情况。
|
||||
default_command 是当没有匹配模式时执行的命令。
|
||||
;;用于标识每个模式下命令的结束。
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 将 Shell 变量输出为环境变量
|
||||
|
||||
`export 变量名=变量值`
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 方法一:使用 function 关键字
|
||||
|
||||
```shell
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:38Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 脚本要求
|
||||
|
||||
脚本以`#!/bin/bash`开头
|
||||
|
@ -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
|
@ -4,8 +4,6 @@ date: 2024-06-06T23:51:10Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 颜色名
|
||||
|
||||
## 光的三原色
|
||||
|
@ -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.将已被追踪的文件的更改加入到暂存区
|
@ -68,13 +68,15 @@ tags: []
|
||||
|
||||
## 分组
|
||||
|
||||
* 普通分组
|
||||
- 普通分组
|
||||
|
||||
`()`
|
||||
* 非捕获分组
|
||||
|
||||
- 非捕获分组
|
||||
|
||||
`(?:<表达式>)`
|
||||
* 回溯分组
|
||||
|
||||
- 回溯分组
|
||||
|
||||
`\<number>`
|
||||
|
||||
@ -88,30 +90,28 @@ tags: []
|
||||
|
||||
|
||||
|
||||
* 正向
|
||||
- 正向
|
||||
|
||||
`(?=<表达式>)`
|
||||
|
||||
> 右边必须出现某个字符
|
||||
>
|
||||
* 反向
|
||||
|
||||
- 反向
|
||||
|
||||
`(?!<表达式>)`
|
||||
|
||||
> 右边不能出现某个字符
|
||||
>
|
||||
|
||||
### 后行断言
|
||||
|
||||
* 正向
|
||||
- 正向
|
||||
|
||||
`(?<=<表达式>)`
|
||||
|
||||
> 左边必须出现某个字符
|
||||
>
|
||||
* 反向
|
||||
|
||||
- 反向
|
||||
|
||||
`(?<!<表达式>)`
|
||||
|
||||
> 左边不能出现某个字符
|
||||
>
|
@ -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
|
||||
|
||||
#### 规则
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -4,7 +4,5 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
大端存储:把高位字节放在低字节
|
||||
小端存储:把低位字节放在高字节
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:39Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
1. while
|
||||
|
||||
```javascript
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:37Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
`&` 取地址调试符
|
||||
`%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*))
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
main 写 mian
|
||||
函数后面加;
|
||||
switch 里面的 case 不加 break
|
||||
|
@ -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 | 定义在其他文件中声明的全局变量或函数 |
|
||||
|
@ -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 文件的开头
|
||||
|
@ -35,7 +35,7 @@ tags: []
|
||||
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
|
||||
>
|
||||
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
|
||||
>
|
||||
|
||||
4. 共用体
|
||||
|
||||
```c
|
||||
|
@ -15,6 +15,7 @@ tags: []
|
||||
2. 转换为汇编代码
|
||||
|
||||
`-S`
|
||||
|
||||
3. 将汇编代码转化为机器指令
|
||||
|
||||
`-c`
|
||||
|
@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:44Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证 with 语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
|
||||
|
||||
语法结构
|
||||
|
@ -24,8 +24,7 @@ tags: []
|
||||
## 遍历
|
||||
|
||||
```html
|
||||
for i in enumerate(数组):
|
||||
print(i)
|
||||
for i in enumerate(数组): print(i)
|
||||
```
|
||||
|
||||
## 操作
|
||||
|
@ -27,9 +27,7 @@ tags: []
|
||||
|
||||
|
||||
```html
|
||||
"""
|
||||
需要注释的代码
|
||||
"""
|
||||
""" 需要注释的代码 """
|
||||
```
|
||||
|
||||
## 导入模块
|
||||
|
@ -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()`
|
||||
|
@ -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))`
|
||||
|
@ -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`:时区相关的类
|
||||
|
@ -22,7 +22,7 @@ raise [exception 类型(异常描述信息)]
|
||||
```
|
||||
|
||||
| 异常名称 | 描述 |
|
||||
| ---------------------------| ----------------------------------------------------|
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| BaseException | 所有异常的基类 |
|
||||
| SystemExit | 解释器请求退出 |
|
||||
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |
|
||||
|
@ -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
|
||||
|
||||
## 数据类型的转换
|
||||
|
||||
|
@ -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)` | |
|
||||
|
@ -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 编码占三个字节
|
||||
|
||||
## 关闭文件
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:30Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 请求
|
||||
|
||||
1 请求行 -> 请求方式 请求 url 地址 协议
|
||||
|
@ -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 和 200(200 是 OK,404 是 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 的字典、元组列表、字节或文件对象
|
||||
|
@ -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():返回一个数据
|
||||
|
@ -6,10 +6,12 @@ tags: []
|
||||
|
||||
## 爬虫源文件
|
||||
|
||||
```txt
|
||||
name:当前源文件的唯一标识
|
||||
allowed_domains:允许请求的域名
|
||||
start_urls:起始的 url 列表,作用:列表中存储的 url 会被 get 发送
|
||||
parse 方法:解析服务器返回的响应对象的解析方法
|
||||
```
|
||||
|
||||
## settings
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
```python
|
||||
from selenium import webdriver # 驱动
|
||||
from selenium.webdriver.common.by import By # 解析方式
|
||||
|
@ -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:样式
|
||||
>
|
||||
|
@ -7,4 +7,4 @@ tags: []
|
||||
1. 使用 socket 类创建一个套接字对象
|
||||
2. 使用 connect((host,port))设置连接的主机 IP 和主机设置的端口
|
||||
3. 使用 recv()/send()方法接收和发送数据
|
||||
3.使用close()关闭套接字
|
||||
4. 使用 close()关闭套接字
|
||||
|
@ -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()`:关闭套接字
|
||||
|
@ -5,7 +5,11 @@ tags: []
|
||||
---
|
||||
|
||||
`/`表示层级关系,第一个是根目录
|
||||
|
||||
`text()`拿文本
|
||||
|
||||
`//`后代
|
||||
|
||||
`*`:通配符
|
||||
|
||||
`@属性`:属性值
|
||||
|
@ -4,14 +4,13 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
| 描述 | 属性 |
|
||||
| ---------------------------------- | -------- |
|
||||
| ----------------------------------- | -------- |
|
||||
| 当前进程的实例别名,默认为 Process-N | `name` |
|
||||
| 当前进程对象的 PID 值 | `pid` |
|
||||
|
||||
| 描述 | 方法 |
|
||||
| --------------------------------------------------------- | ----------------- |
|
||||
| ------------------------------------------------------------- | ----------------- |
|
||||
| 进程是否执行完,没执行完结果为 True,否则为 False | `is_alive()` |
|
||||
| 等待结束或等待 timeout 秒 | `join(timeout)` |
|
||||
| 启动进程 | `start()` |
|
||||
|
@ -10,7 +10,11 @@ tags: []
|
||||
参数说明
|
||||
|
||||
> group:表示分组,实际上不使用,值默认为 None 即可
|
||||
>
|
||||
> target:表示子进程要执行的任务,支持函数名
|
||||
>
|
||||
> name:表示子进程的名称
|
||||
>
|
||||
> args:表示调用函数的位置参数,以元组的形式进行传递
|
||||
>
|
||||
>kwargs:表示调用函数的关键字参数,以字典的形式进行传参
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
> 队列是一种先进先出的数据结构
|
||||
|
||||
## 语法
|
||||
@ -14,7 +13,7 @@ tags: []
|
||||
## 常用方法
|
||||
|
||||
| 功能 | 方法 |
|
||||
| ------------------------------------------------------- | ------------------------ |
|
||||
| --------------------------------------------------------- | ------------------------ |
|
||||
| 获取当前队列包含的消息数量 | `qsize()` |
|
||||
| 判断队列是否为空,为空结果为 True,否则为 False | `empty()` |
|
||||
| 判断队列是否满了,满结果为 True,否则为 False | `full()` |
|
||||
|
@ -5,7 +5,7 @@ tags: []
|
||||
---
|
||||
|
||||
| 名称 | 单词 | 作用 |
|
||||
| ------| ------| ----------|
|
||||
| ---- | ---- | -------- |
|
||||
| and | 与 | 从左到右 |
|
||||
| or | 或 | 从左到右 |
|
||||
| not | 非 | 从右到左 |
|
||||
|
@ -43,10 +43,15 @@ A^B
|
||||
## 相关操作
|
||||
|
||||
如果 x 不在集合 s 中,则将 x 添加到集合 s
|
||||
|
||||
`s.add(x)`
|
||||
|
||||
如果 x 在集合中,将其删除,如何步骤集合中,程序报错
|
||||
|
||||
`s.remove(x)`
|
||||
|
||||
清楚集合所有元素
|
||||
|
||||
`s.clear()`
|
||||
|
||||
|
||||
|
378
src/content/理解计算机/rust.md
Normal file
378
src/content/理解计算机/rust.md
Normal 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
|
||||
|
||||
### 多线程
|
@ -14,7 +14,4 @@ mov AX,BX
|
||||
|
||||
机器码是单片工作且能识别和运行的一类代码,常见的格式有二进制 BIN 格式、十进制 HEX 格式等。
|
||||
|
||||
汇编语言三类组成
|
||||
1.汇编指令(机器码的助记符(能够直接翻译为机器码))
|
||||
2.伪指令(由编译器执行(计算机不能直接识别,编译器可以识别,由编译器转化为想表达的意思))
|
||||
3.其他符号(由编译器识别,如:+ - * /)
|
||||
汇编语言三类组成 1.汇编指令(机器码的助记符(能够直接翻译为机器码)) 2.伪指令(由编译器执行(计算机不能直接识别,编译器可以识别,由编译器转化为想表达的意思)) 3.其他符号(由编译器识别,如:+ - \* /)
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
预处理(Preprocessing):
|
||||
任务:预处理阶段对源代码进行预处理,展开宏、处理条件编译等。
|
||||
工具:预处理器,如 C 语言中的 cpp。
|
||||
|
@ -23,5 +23,4 @@ A --> B[系统调用的过程]
|
||||
B --> 陷入指令/Trap/访管
|
||||
B --> 有系统内核程序处理系统调用请求
|
||||
B --> 返回系统程序
|
||||
|
||||
```
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user