“可修”网址工具导航程序(付费开源)
这款程序可以进行工具展示和网址导航等等功能,已经集成二十款内部工具,二次开发也非常的简单,学习成本不高。
开发环境基于PHP8.2+MYSQL5.6即已上(PDO驱动),已经集成傻瓜式安装方式,只要按照提示步骤安装基本都能够成功。
预计售价:999.99,为了和各位网友交一个朋友,我自己砍一个骨折价,只需要“9.9”。是不是非常的劲爆。
好了下来来一个自己上线做书签用的演示地址:http://m.kxiu.cn
下面是程序的一些截图














这款程序可以进行工具展示和网址导航等等功能,已经集成二十款内部工具,二次开发也非常的简单,学习成本不高。
开发环境基于PHP8.2+MYSQL5.6即已上(PDO驱动),已经集成傻瓜式安装方式,只要按照提示步骤安装基本都能够成功。
预计售价:999.99,为了和各位网友交一个朋友,我自己砍一个骨折价,只需要“9.9”。是不是非常的劲爆。
好了下来来一个自己上线做书签用的演示地址:http://m.kxiu.cn
下面是程序的一些截图














用户首次评论提交后,保存其名称、邮箱等信息,下次再进入评论页面(或刷新后),自动将保存的信息填充到对应的输入框中。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>自动填充用户评论信息</title>
<style>
/* 简单样式美化,不影响核心功能 */
.comment-container {
width: 600px;
margin: 50px auto;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.comment-item {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, textarea {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
textarea {
height: 120px;
resize: vertical;
}
button {
padding: 10px 20px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #359469;
}
</style>
</head>
<body>
<div class="comment-container">
<h3>评论区</h3>
<div class="comment-item">
<label for="username">姓名:</label>
<input type="text" id="username" placeholder="请输入你的姓名">
</div>
<div class="comment-item">
<label for="useremail">邮箱:</label>
<input type="email" id="useremail" placeholder="请输入你的邮箱">
</div>
<div class="comment-item">
<label for="comment-content">评论内容:</label>
<textarea id="comment-content" placeholder="请输入你的评论内容"></textarea>
</div>
<button id="submit-comment">提交评论</button>
</div>
<script>
// 1. 获取页面中的DOM元素
const usernameInput = document.getElementById('username');
const useremailInput = document.getElementById('useremail');
const commentContentInput = document.getElementById('comment-content');
const submitBtn = document.getElementById('submit-comment');
// 定义localStorage的存储键名(方便后续维护)
const COMMENT_USER_INFO_KEY = 'comment_user_info';
// 2. 页面加载完成后,自动填充已保存的用户信息
function fillUserInfo() {
// 从localStorage中获取保存的用户信息(localStorage存储的是字符串,需要用JSON.parse转换为对象)
const savedUserInfo = localStorage.getItem(COMMENT_USER_INFO_KEY);
if (savedUserInfo) {
const userInfo = JSON.parse(savedUserInfo);
// 将保存的信息填充到对应的输入框中
usernameInput.value = userInfo.username || '';
useremailInput.value = userInfo.useremail || '';
}
}
// 3. 提交评论时,保存用户的姓名和邮箱信息
function saveUserInfo() {
// 获取输入框中的值(去除前后空格)
const username = usernameInput.value.trim();
const useremail = useremailInput.value.trim();
const commentContent = commentContentInput.value.trim();
// 简单表单验证
if (!username) {
alert('请输入你的姓名!');
return false;
}
if (!useremail) {
alert('请输入你的邮箱!');
return false;
}
if (!commentContent) {
alert('请输入评论内容!');
return false;
}
// 构造用户信息对象
const userInfo = {
username: username,
useremail: useremail
};
// 将用户信息保存到localStorage中(localStorage只能存储字符串,需要用JSON.stringify转换)
localStorage.setItem(COMMENT_USER_INFO_KEY, JSON.stringify(userInfo));
// 提示评论提交成功(实际项目中这里会对接后端接口,提交评论数据)
alert('评论提交成功!');
// 清空评论内容输入框(姓名和邮箱保留,方便用户再次评论修改)
commentContentInput.value = '';
return true;
}
// 4. 绑定事件
// 页面加载完成后执行填充逻辑
window.onload = fillUserInfo;
// 提交按钮点击事件
submitBtn.addEventListener('click', saveUserInfo);
</script>
</body>
</html>代码关键部分解释
localStorage 的使用
localStorage.setItem(key, value):用于存储数据,key 是存储的键名,value 只能是字符串类型,因此我们用 JSON.stringify() 将用户信息对象转换为字符串。
localStorage.getItem(key):用于获取指定键名存储的数据,返回值是字符串,因此需要用 JSON.parse() 转换回 JavaScript 对象,方便提取数据。
存储键名定义为常量 COMMENT_USER_INFO_KEY,后续如果需要修改键名,只需修改这一个常量即可,提升可维护性。
页面加载自动填充
利用 window.onload 事件,确保页面所有 DOM 元素加载完成后再执行填充逻辑,避免获取不到输入框元素。
先判断是否存在已保存的信息,存在才进行填充,避免出现 undefined 等异常。
表单验证与数据保存
提交时先去除输入框值的前后空格(trim()),避免用户输入无效的空白内容。
只保存姓名和邮箱,评论内容不保存,符合用户使用习惯(用户每次评论的内容大概率不同)。
提交成功后清空评论内容输入框,保留姓名和邮箱,方便用户再次提交新评论。
这不是Typecho正式发布了1.3.0版本后,有些代码会有所出入,今天就把以前的旧版本主题模板升级维护了一下。
关于这款主题不只是简单的代码升级维护,也对以前的UI样式问题做出了一些调整和修复。
至于下载地址,等后期视频制作完成后以前上传分享。

在 Typecho 1.1 中可以通过这样的方式获取
<?php
$this->_pageRow['mid'];
?>如果是 Typecho 1.2,可以通过这样的方式获取
<?php
$this->pageRow['mid'];
?>如果是 Typecho 1.3,可以通过这样的方式获取
<?php
$this->pageRow->mid;
?> 本篇文章主要用于不修改数据库编码,可以支持emoji 表情显示支持。

emoji_encode():将字符串中的 emoji 表情(4 字节 UTF-8 字符)编码为 十进制数字; 的实体格式,普通字符保持不变。
emoji_decode():将上述编码格式的 emoji 实体还原为原始的 emoji 表情字符。
整体作用是解决 emoji 表情在传输 / 存储中可能出现的乱码问题,通过实体编码的方式兼容不同系统对 emoji 的处理。
使用方法
// 测试示例
$original = "你dddfakjdsfudsadsafsd(由于博客不支持emoji 表情这里就不添加了,要测试自行添加emoji查看效果)";
$encoded = emoji_encode($original);
$decoded = emoji_decode($encoded);
echo "原始字符串: " . $original . "\n";
echo "编码后: " . $encoded . "\n";
echo "解码后: " . $decoded . "\n"; 功能介绍
该工具是一款零依赖、极致性能的 PHP 版网站 favicon(网站图标)在线获取工具,专为追求高性能、低资源消耗的场景设计,核心功能与特性如下:
1. 核心功能
多路径智能获取:优先尝试网站根目录下最常用的 favicon.ico 和 favicon.png 通用路径,获取失败后自动解析网站 HTML 源码,提取 标签中的精准图标地址,确保获取成功率。
轻量化输出:直接返回 favicon 的二进制内容,无任何冗余封装,可按需输出为图片响应、写入文件或进行其他处理。
URL 自动兼容:支持传入带 / 不带协议的 URL(如 www.baidu.com 或 https://www.baidu.com),自动补全 HTTPS 协议并解析域名,无需额外预处理。
2. 核心技术特性
零第三方依赖:仅依赖 PHP 内置 curl 扩展(PHP 标配组件),无任何外部库、框架或配置文件依赖,可直接集成使用。
极致性能与效率:
短路逻辑:找到有效 favicon 立即终止流程,无冗余执行;
极致超时控制:3 秒总超时 + 1 秒连接超时,避免无效等待;
精简网络传输:不获取响应头、固定 HTTP 版本,减少网络损耗。
零冗余资源占用:
即时内存释放:每步操作后销毁无用变量,无冗余内存占用;
无缓存 / 无磁盘 IO:不写入缓存文件,仅在运行时加载必要数据;
按需加载:仅通用路径获取失败后才执行 HTML 解析,懒执行无预加载损耗。
低耦合设计:核心功能拆分为两个独立纯函数,无全局变量、无耦合依赖,可单独调用、按需集成,适配任意业务场景。
3. 适用场景
高性能网址导航、书签类系统;
对资源消耗、响应速度有严苛要求的后端服务;
轻量级 PHP 项目(如微服务、接口)的 favicon 获取需求;
需低耦合集成 favicon 获取功能的现有系统。
精简项目代码
<?php
/**
* 极简版Favicon获取函数(零依赖、极致性能、零冗余)
* @param string $url 目标网站URL
* @return string|false 成功返回favicon内容(二进制),失败返回false
*/
function get_favicon(string $url): string|false {
// 步骤1:标准化URL(仅保留核心逻辑,无冗余判断)
$url = trim($url);
if (!preg_match('/^https?:\/\//i', $url)) {
$url = 'https://' . $url; // 优先HTTPS,无冗余的HTTP回退(极简策略)
}
$parsed = parse_url($url);
if (!isset($parsed['scheme'], $parsed['host'])) {
return false;
}
$scheme = $parsed['scheme'];
$host = $parsed['host'];
// 步骤2:尝试通用路径(按需加载,找到即返回,零冗余循环)
$paths = ['/favicon.ico', '/favicon.png']; // 仅保留最常用的2种格式,极致效率
foreach ($paths as $path) {
$favicon_url = "{$scheme}://{$host}{$path}";
$content = fetch_url_content($favicon_url);
if ($content !== false) {
return $content; // 短路返回,无任何冗余操作
}
unset($content); // 即时释放内存,零冗余占用
}
// 步骤3:解析HTML(仅必要时执行,按需加载)
$html_url = "{$scheme}://{$host}";
$html = fetch_url_content($html_url);
if ($html === false) {
unset($html, $scheme, $host, $html_url); // 释放所有变量
return false;
}
// 极简正则提取icon(无冗余分组,仅保留核心匹配)
if (preg_match('/<link[^>]*rel=["\']?icon["\']?[^>]*href=["\']([^"\']+)["\']/i', $html, $matches)) {
$icon_path = $matches[1];
unset($html, $matches); // 即时释放大变量
// 路径补全(极简逻辑,无冗余判断)
if (str_starts_with($icon_path, 'http')) {
$icon_url = $icon_path;
} elseif (str_starts_with($icon_path, '/')) {
$icon_url = "{$scheme}://{$host}{$icon_path}";
} else {
$icon_url = "{$scheme}://{$host}/{$icon_path}";
}
$content = fetch_url_content($icon_url);
if ($content !== false) {
unset($scheme, $host, $icon_path, $icon_url); // 释放变量
return $content;
}
unset($content);
}
// 最终清理,零冗余内存占用
unset($scheme, $host, $html, $html_url);
return false;
}
/**
* 极简URL内容获取函数(零冗余、极致性能)
* @param string $url 目标URL
* @return string|false 二进制内容或false
*/
function fetch_url_content(string $url): string|false {
$ch = curl_init($url);
if (!$ch) {
return false;
}
// 仅设置必要选项,无任何冗余配置
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 3, // 极致超时(减少等待损耗)
CURLOPT_CONNECTTIMEOUT => 1,
CURLOPT_USERAGENT => 'Mozilla/5.0',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_NOBODY => false,
CURLOPT_HEADER => false, // 不获取响应头,减少数据传输
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // 固定HTTP版本,减少协商损耗
]);
$content = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); // 即时关闭句柄,释放资源
unset($ch); // 释放句柄变量
// 仅保留核心判断,无冗余逻辑
if ($http_code !== 200 || empty($content)) {
unset($content, $http_code);
return false;
}
return $content;
}
// ===================== 极简使用示例(按需调用,零冗余) =====================
// 调用示例(仅在需要时执行,按需加载)
// $favicon = get_favicon('https://www.baidu.com');
// if ($favicon) {
// header('Content-Type: image/x-icon');
// echo $favicon;
// unset($favicon); // 即时释放内存
// }
?> Core\Loader 是一款高性能PSR-4规范自动加载器,核心用于PHP项目中类、接口、特质的自动加载,无需手动引入require/include,遵循PHP FIG制定的PSR-4自动加载标准,适配各类PHP项目架构。
| 成员变量 | 类型 | 核心作用 |
|---|---|---|
$prefixMap | array | 存储命名空间前缀与对应目录的映射关系,格式为[前缀 => [路径1, 路径2...]] |
$classCache | array | 存储类名与文件路径的缓存映射,格式为[类名 => 文件路径] |
$cacheFile | string | 缓存文件的完整存储路径,默认生成在指定缓存目录下的class_map.cache |
$cacheSalt | string | 缓存加密的盐值,用于HMAC-MD5加密校验,可通过公共方法自定义 |
$validDirs | array | 已验证合法的目录池,缓存合法目录避免重复校验,格式为[合法目录 => true] |
| 方法名 | 入参类型 | 返回值 | 核心作用 |
|---|---|---|---|
init | string(可选,缓存目录) | void | 初始化自动加载器,校验缓存目录、加载历史缓存、注册自动加载回调 |
registerPrefix | string(命名空间前缀)、string(对应目录) | void | 注册命名空间前缀与目录的映射关系,支持单个前缀对应多个目录 |
registerClass | string(完整类名)、string(类文件路径) | void | 手动注册单个类的路径映射,适用于非标准PSR-4规范的类文件 |
clearCache | 无 | void | 清空内存中的类缓存,同时删除本地缓存文件及残留临时文件 |
setCacheSalt | string(自定义盐值) | void | 自定义缓存加密盐值,提升缓存安全性,建议项目启动时设置唯一盐值 |
核心支撑方法包括类路径解析、文件安全加载、缓存加解密、路径/类名合法性校验、安全路径获取等,保障自动加载全流程的规范性、安全性与高效性,无需外部干预。
项目根目录/Core/Loader.php;serialize、hash相关扩展(默认开启)。项目入口文件(如index.php)中执行初始化,是使用该自动加载器的前置操作,核心代码如下:
<?php
// 项目入口文件
// 引入Loader类文件
require_once __DIR__ . '/Core/Loader.php';
// 初始化自动加载器(默认缓存目录:项目根目录/runtime/cache)
\Core\Loader::init();
// 可选:自定义缓存目录(推荐绝对路径,避免路径歧义)
// \Core\Loader::init(__DIR__ . '/storage/cache');
// 可选:自定义缓存加密盐值(提升安全性,建议项目唯一)
// \Core\Loader::setCacheSalt('Your_Project_Unique_Salt_2025');这是最常用的用法,将项目的命名空间前缀与实际目录绑定,自动加载器会按PSR-4规范查找类文件。
\,无需手动添加;单个命名空间前缀可绑定多个目录,自动加载器会按顺序查找,找到即加载。
<?php
// 入口文件初始化后执行注册
// 1. 注册App命名空间(核心业务代码),对应项目根目录下的app目录
\Core\Loader::registerPrefix('App', __DIR__ . '/app');
// 2. 注册Lib命名空间(第三方类库),对应lib目录
\Core\Loader::registerPrefix('Lib', __DIR__ . '/lib');
// 3. 单个前缀绑定多个目录(示例:Plugin命名空间对应两个插件目录)
\Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/admin');
\Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/front');例如注册App\Controller前缀对应/app/Controller目录,类App\Controller\IndexController 对应的文件路径必须为 /app/Controller/IndexController.php,命名空间与目录严格对应,类名与文件名完全一致(大小写敏感,需遵循系统文件大小写规则)。
适用于部分不遵循PSR-4规范的类文件,手动指定类名与文件路径,跳过自动解析逻辑,直接缓存映射关系。
<?php
// 手动注册单个类:类名 + 类文件绝对路径
\Core\Loader::registerClass('NonPsr4Class', __DIR__ . '/custom/NonPsr4ClassFile.php');
// 注册后直接使用类即可自动加载
$obj = new NonPsr4Class();当项目目录结构调整、类文件路径变更时,需清空缓存,否则会加载旧路径导致报错,执行后内存缓存与本地缓存文件会同步清空。
<?php
// 清空自动加载器缓存
\Core\Loader::clearCache();clearCache(),避免缓存失效问题。setCacheSalt),避免使用默认盐值,防止缓存被恶意破解篡改;0755、缓存文件权限为0644,该类会自动校验并修复权限,无需手动调整;registerClass手动注册,跳过解析逻辑,提升加载速度;clearCache()清空旧缓存;④ 检查类文件是否存在且可读(权限0644及以上);clearCache()即可。内容由 AI 生成
今天突然想起来有个域名快要过期了,趁今天想起来了就顺便续费一下。
登录某平台发现续费居然需要40大洋,还是有点小贵小贵,于是就重新找了一个续费38大洋的知名平台进行域名转出。
第一次使用域名转出功能还以为非常的麻烦,一时间都不知道要怎么操作,不过经过相关文档教程查看后,才发现非常的简单,就几步操作而已。
现在域名已经成功转移到了续费比较低的平台上,顺便也把另一个域名也续费了一遍。
该系统是一套基于PHP原生开发的访问频率管控解决方案,核心实现「IP+Session双重校验」「动态时段阈值」「弹窗校验+恶意行为拉黑」全流程管控,可有效防止恶意高频访问、刷接口等行为,适配单机/小型集群场景,无需依赖第三方组件,开箱即用。
| 功能模块 | 核心能力 |
|---|---|
| IP维度管控 | 基于真实IP隔离访问状态,防止单IP多Session绕过限制 |
| 动态阈值 | 按高峰/平峰/低峰时段自动匹配不同访问阈值 |
| 分层校验 | 未校验严格限流→触发弹窗→校验通过提升阈值→二次超限封禁 |
| 恶意行为拉黑 | 弹窗校验错误次数耗尽/弹窗期间高频刷新→直接拉黑IP |
| 状态自动清理 | Session有效期管控,自动回收过期IP数据,避免性能损耗 |
.php文件(如access_control.php),上传至PHP运行环境目录;所有可配置项集中在代码顶部$config数组,按需调整即可,关键配置说明如下:
$config = [
// 基础管控时长
'ban_time' => 300, // 普通封禁时长(秒),默认5分钟
'blacklist_time' => 3600, // 恶意拉黑时长(秒),默认1小时
// 校验相关配置
'verify_fail_limit'=> 5, // 校验失败最大次数(超限拉黑)
'popup_refresh_limit' => 10, // 弹窗期间无校验刷新阈值(超限拉黑)
'popup_refresh_time' => 60, // 弹窗刷新统计窗口(秒)
// 动态时段阈值规则(可新增/修改时段)
'dynamic_rules' => [
// 高峰时段(示例:09:00-12:00)
[
'start_time' => '09:00', // 时段开始时间(24小时制)
'end_time' => '12:00', // 时段结束时间
'unverified_limit' => 3, // 未校验状态访问阈值(次)
'unverified_time' => 60, // 未校验时间窗口(秒)
'verified_limit' => 15, // 校验通过后访问阈值(次)
'verified_time' => 60, // 校验通过时间窗口(秒)
],
// 可新增/删除时段规则,按数组顺序优先匹配
]
];



unverified_limit(如3次),严控未校验访问;unverified_limit(如8次),提升用户体验;popup_refresh_time(如30秒)、降低popup_refresh_limit(如5次),严控恶意刷新;ban_time/blacklist_time设为10秒,便于快速验证逻辑。| 恶意行为 | 触发条件 | 管控结果 |
|---|---|---|
| 校验码多次错误 | 连续输入错误码≥5次 | IP被拉黑1小时 |
| 弹窗期间高频刷新 | 弹窗展示后无校验提交,60秒内刷新≥10次 | IP被拉黑1小时 |
| 普通高频访问 | 校验通过后仍超限 | 普通封禁5分钟 |
当前为模拟校验(固定码1234),建议替换为图形验证码/滑块验证,步骤如下:
POST校验逻辑:对比用户输入码与Session中存储的验证码;1234,实现动态验证码校验。单机Session无法跨节点共享,集群部署需将Session迁移至Redis:
修改Session存储方式:
// 代码开头添加
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');在getRealIp()函数后添加白名单逻辑:
// 定义白名单IP数组
$whiteList = ['192.168.1.100', '127.0.0.1'];
if (in_array($realIp, $whiteList)) {
// 白名单IP直接放行,跳过所有管控逻辑
echo "白名单IP访问";
exit;
}如需记录访问/封禁/拉黑日志,可在对应逻辑处添加日志写入:
// 示例:拉黑时记录日志
$logContent = date('Y-m-d H:i:s') . " - IP:{$realIp} - 拉黑原因:弹窗刷新超限\n";
file_put_contents('access_blacklist.log', $logContent, FILE_APPEND);getRealIp()函数已兼容常见代理场景,若仍错误,补充对应代理头(如HTTP_X_REAL_IP)。date()函数返回正确时间。session.gc_maxlifetime=7200秒,系统会自动清理2小时过期数据,无需手动处理。graph TD
A[用户访问] --> B{获取真实IP}
B --> C{是否拉黑}
C -- 是 --> D[展示拉黑提示]
C -- 否 --> E{是否普通封禁}
E -- 是 --> F[展示封禁提示]
E -- 否 --> G{匹配动态时段阈值}
G --> H{是否触发未校验阈值}
H -- 否 --> I[正常访问]
H -- 是 --> J{弹窗期间是否高频刷新}
J -- 是 --> K[拉黑IP]
J -- 否 --> L[展示校验弹窗]
L --> M{校验是否通过}
M -- 否 --> N{失败次数是否超限}
N -- 是 --> K
N -- 否 --> L
M -- 是 --> O[提升访问阈值]
O --> P{是否触发已校验阈值}
P -- 是 --> F
P -- 否 --> I一款实用的磨砂透明背景封面生成工具:轻松搞定高颜值封面设计
封面生成截图
工具界面截图
在内容创作的浪潮中,一张精致的封面图往往是吸引读者目光的第一道门槛。无论是文章配图、视频封面,还是社交平台分享图,高颜值的封面都能让你的内容在海量信息中脱颖而出。但对于大多数非设计专业的创作者来说,使用专业设计软件制作封面不仅门槛高,还十分耗时。基于此,我开发了一款磨砂透明背景封面生成工具,无需专业设计技能,只需简单几步操作,就能生成质感满满的磨砂透明风格封面。今天就来详细聊聊这款工具的功能、使用方法和技术亮点。
一、工具核心功能:满足多场景封面制作需求
这款工具以“简单易用、功能全面”为核心设计理念,涵盖了从素材上传到参数微调,再到封面生成与下载的全流程功能,具体可分为以下几大模块:
工具兼容多种主流素材格式,无论是图片(JPG、PNG、GIF、WebP)还是视频(MP4),都能直接上传作为封面背景。对于视频素材,工具会自动提取视频帧作为背景基础,解决了视频创作者制作封面的痛点,无需额外截取视频画面。
针对不同平台的封面尺寸要求,工具提供了两种尺寸设置方式:一是自定义宽度和高度(范围10-2000px),精准匹配个性化需求;二是预设多种主流比例(1:1正方形、4:3经典、16:9宽屏、9:16竖屏等),覆盖文章、视频、社交动态等常见使用场景,无需手动计算比例,一键切换即可。
磨砂透明是这款工具的核心风格,用户可通过参数微调打造专属质感:
封面文字是传递核心信息的关键,工具提供了丰富的文本编辑选项,满足不同排版需求:
对于需要展示创作者身份的封面,工具支持头像上传与预览功能,头像自动裁剪为圆形,贴合主流设计风格。同时,提供7种头像与昵称的位置组合(底部右/左/居中、顶部右/左/居中、正中间),并支持边距偏移调节,可精准定位到理想位置。
所有参数调节都能实时在画布中预览效果,避免盲目操作。生成满意的封面后,可设置图片质量(0.1-1),一键下载为JPG格式,直接用于内容发布,高效便捷。此外,工具还提供“重新制作”功能,可快速重置所有参数,重新创作。
二、使用流程:3步搞定高颜值封面
这款工具的操作逻辑简单清晰,即使是第一次使用,也能快速上手,核心流程仅需3步:
第一步:上传素材
点击或拖拽文件到上传区域,选择准备好的背景图或MP4视频。上传成功后,工具会自动识别素材类型,展示原始尺寸信息,并解锁后续控制参数面板。
第二步:调节参数与排版
根据需求设置封面尺寸(或选择预设比例),调节背景透明度、滤镜等磨砂风格参数;输入文章标题、介绍、昵称等文本内容,自定义字体大小、颜色与文本背景;上传头像并选择合适的位置布局,通过实时预览优化细节。
第三步:生成与下载
确认预览效果满意后,点击“生成封面”按钮,工具会完成最终渲染;随后设置图片质量,点击“下载封面”即可保存成品,整个过程耗时不超过5分钟。
三、技术亮点:兼顾易用性与性能
在技术实现上,工具基于HTML5 Canvas实现核心的绘图与渲染功能,结合JavaScript完成交互逻辑,确保在不同浏览器中都能稳定运行。主要技术亮点包括:
四、适用人群与场景
这款磨砂透明背景封面生成工具的适用场景十分广泛,尤其适合以下人群:
五、实现代码
六、总结
上诉所有内容均来自AI生成
Koodo Reader 是一个跨平台的电子书阅读器。平台支持Windows,macOS,Linux,移动端app,网页版,格式支持 epub, pdf, mobi, azw3, txt, djvu, markdown, fb2, cbz, cbt, cbr, rtf 和 docx。

软件特色
支持阅读 epub, pdf, mobi, azw3, txt, md, djvu, docx, rtf, cbz, cbr, cbt, fb2, html 和 xml 格式的图书
支持 Windows,macOS,Linux 和网页版
备份数据到 Dropbox 和 Webdav
自定义源文件夹,利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步
双页模式,单页模式,滚动模式
听书功能,翻译功能,触控屏支持,批量导入图书
支持目录,书签,笔记,书摘,书架,标签
自定义字体,字体大小,行间距,段落间距,阅读背景色,文字颜色,屏幕亮度,文字下划线、斜体、文字阴影、字体粗细
黑夜模式和主题色设置
下载地址:自行使用搜索引擎查询一下就有了
NoteGen 是一款跨端的 Markdown 笔记应用,致力于使用 AI 建立记录和写作的桥梁,将碎片化知识整理成一篇可读的笔记。

文件管理器
支持管理本地 Markdown 文件和 Github 的同步文件。
支持无限层级目录。
支持多种排序方式。
Markdown 编辑器
支持所见即所得、即时渲染、分屏预览三种模式。
支持版本控制,可以回溯历史记录。
支持 AI 辅助,可以进行对话、续写、润色、翻译功能。
支持图床,可以将图片上传至图床,并转换为 Markdown 图片链接。
支持 HTML、Markdown 转换,可以复制浏览器内容,自动转换为 Markdown 格式。
支持大纲、数学公式、脑图、图表、流程图、甘特图、时序图、五线谱、多媒体、语音阅读、标题锚点、代码高亮及复制、graphviz 渲染、plantumlUML 图。
支持实时本地保存内容,延时(10s 未进行编辑)自动同步,历史回滚。
其他
全局搜索,可以快速搜索并跳转至指定的内容。
图床管理,可以方便的管理图床仓库的内容。
主题与外观,支持深色主题,支持 Markdown、代码等外观设置。
支持国际化,暂支持中文、英文、日语。
个性化设置
NoteGen 支持丰富的个性化设置,包括:AI 相关、同步相关等。
下载地址
MrRSS 是一款现代的跨平台桌面 RSS 阅读器,支持 Windows、macOS 和 Linux 系统。软件可使用翻译服务或 AI 翻译文章标题和内容。能自动发现来自好友链接和相关来源的新 Feed。可保存文章并跟踪已读/未读状态。
软件特色
支持跨平台:适用于Windows、macOS和Linux的本机桌面应用
自动翻译:使用翻译服务或AI翻译文章标题和内容
智能Feed发现:自动发现来自好友链接和相关来源的新Feed
收藏夹阅读跟踪:保存文章并跟踪已读/未读状态
文章摘要:使用本地TF-IDF/TextRank算法或AI生成文章摘要
现代UI:干净,响应式界面,支持黑暗模式
OPML导入/导出:从其他RSS阅读器轻松迁移
键盘快捷键:使用可自定义的键盘快捷键进行快速导航和操作,提高工作效率️
智能过滤规则:使用强大的自定义规则自动化提要组织
自定义自动化:支持运行用户定义的自动化脚本来获取提要
软件截图


下载地址
新增开关+缺省图配置 | 支持img/MD | 校验可控 | 直接可用
/**
* 提取文本中图片地址(支持img标签+Markdown)
* @param string $content 源文本(html/md)
* @param bool $unique 是否去重,默认是
* @param bool $checkExists 是否校验存在开关,默认true
* @param int $timeout 远程校验超时(秒),默认3
* @param string $defaultImg 不存在时显示的默认图,空则不显示
* @return array 处理后的图片URL数组
*/
function getImagesFromContent(
string $content,
bool $unique = true,
bool $checkExists = true,
int $timeout = 3,
string $defaultImg = ''
): array {
$images = [];
// 1.匹配img标签(兼容单双引号/无引号)
$imgPattern = '/<img[^>]+src=[\'"]?([^\'" >]+)[\'"]?/i';
preg_match_all($imgPattern, $content, $imgMatches);
!empty($imgMatches[1]) && $images = array_merge($images, $imgMatches[1]);
// 2.匹配MD图片(兼容带描述/无描述/首尾空格)
$mdPattern = '/!\[.*?\]\((\s*?.*?\s*?)\)/i';
preg_match_all($mdPattern, $content, $mdMatches);
!empty($mdMatches[1]) && $images = array_merge($images, $mdMatches[1]);
// 3.基础过滤(合法路径)
$images = array_filter($images, fn($url) => preg_match('/^(https?:\/\/|\/|\.\/|\.\.\/)/i', trim($url)));
// 4.去重
$unique && $images = array_values(array_unique($images));
$images = array_map('trim', $images);
// 5.存在性校验(开关可控) + 缺省图替换
if ($checkExists) {
$images = array_map(function($url) use ($timeout, $defaultImg) {
return checkImageExists($url, $timeout) ? $url : ($defaultImg ?: '');
}, $images);
// 无默认图时过滤无效图,有则保留默认图
if(empty($defaultImg)) $images = array_filter($images);
}
return array_values($images);
}
/**
* 图片存在性校验(远程+本地双适配)
*/
function checkImageExists(string $url, int $timeout = 3): bool {
if(empty($url)) return false;
// 远程图(HEAD请求,高效不下载)
if(preg_match('/^https?:\/\//i', $url)){
$context = stream_context_create([
'http' => ['method'=>'HEAD', 'timeout'=>$timeout, 'ignore_errors'=>true],
'ssl' => ['verify_peer'=>false, 'verify_peer_name'=>false]
]);
$headers = @get_headers($url,1,$context);
if(!$headers) return false;
$status = $headers[0] ?? '';
$type = $headers['Content-Type'] ?? '';
return strpos($status,'200 OK')!==false && preg_match('/image\//i', $type);
}
// 本地图(补根目录+校验文件+验格式)
$localPath = $_SERVER['DOCUMENT_ROOT'].str_replace(['//','/./'],'/',$url);
return file_exists($localPath) && is_file($localPath) && getimagesize($localPath);
}
// curl兼容方案(allow_url_fopen禁用时用)
function checkImageExistsCurl(string $url, int $timeout=3):bool{
if(empty($url) || !preg_match('/^https?:\/\//i',$url)) return false;
$ch = curl_init($url);
curl_setopt_array($ch,[
CURLOPT_NOBODY=>true, CURLOPT_TIMEOUT=>$timeout,
CURLOPT_SSL_VERIFYPEER=>false, CURLOPT_RETURNTRANSFER=>true
]);
curl_exec($ch);
$status = curl_getinfo($ch,CURLINFO_HTTP_CODE);
$type = curl_getinfo($ch,CURLINFO_CONTENT_TYPE);
curl_close($ch);
return $status===200 && preg_match('/image\//i',$type);
}核心新增功能
checkExists=true/false 自由开关校验,关闭则直接返回所有图
传defaultImg则无效图替换为该图,不传则自动过滤无效图
全程兼容远程/本地图,不影响原有所有功能
调用示例(3种常用场景)
<?php
$testContent = '<img src="https://picsum.photos/200"><img src="/test.jpg"><img src="https://xxx/404.jpg">';
// 场景1:校验开启+无默认图(过滤无效图)
$imgs1 = getImagesFromContent($testContent, true, true, 3);
var_dump($imgs1); // 只返回有效图
// 场景2:校验开启+默认图(无效图替换)
$imgs2 = getImagesFromContent($testContent, true, true, 3, 'https://xxx/default.png');
var_dump($imgs2); // 无效图替换为默认图
// 场景3:关闭校验(直接返回所有图,最快)
$imgs3 = getImagesFromContent($testContent, true, false);
var_dump($imgs3); // 不校验,全部返回避坑提示

这款纯前端在线SQL生成器是为开发人员、运维人员和数据库初学者打造的轻量化工具,无需后端环境、无需安装软件,仅需浏览器打开即可使用。它将复杂的SQL语法转化为可视化配置项,覆盖增删改查全场景,还集成了JOIN联查、语法校验、模板套用、注释生成、模拟执行预览等实战功能,既能提升开发效率,又能降低手写SQL的出错率,是数据库操作的高效辅助工具。
| 功能模块 | 具体能力 |
|---|---|
| 基础操作 | 支持SELECT(查询)、INSERT(新增)、UPDATE(修改)、DELETE(删除) |
| 高级查询 | 排序(ORDER BY)、分页(LIMIT)、多表JOIN联查 |
| 字段配置 | 字段增删、类型选择、表前缀支持(如user.id) |
| 工具辅助 | SQL格式化、一键复制、语法校验、自动注释、配置清空 |
| 模板中心 | 6大常用SQL模板,一键套用并支持自定义修改 |
| 预览功能 | 模拟执行结果预览(表格/执行提示) |
| 安全防护 | 危险字符过滤(防注入)、删改操作风险提醒 |
sql-builder.html文件,用浏览器直接打开本文档详细介绍了JianguoyunWebDAV类的开发背景、功能特性、使用方法、核心实现逻辑及注意事项,旨在帮助开发人员快速集成和使用该类完成坚果云WebDAV的相关操作。
坚果云提供WebDAV接口用于文件和目录的管理,但原生WebDAV操作存在中文编码适配、大文件处理、请求频率限制等问题。该类封装了坚果云WebDAV的核心操作,解决了上述痛点,提供简洁、高效的开发接口。
curl扩展(必须,用于HTTP请求)、mbstring扩展(用于字符编码转换)、fileinfo扩展(用于获取文件MIME类型)。Log类(实现error方法用于日志记录)。| 属性名 | 类型 | 说明 |
|---|---|---|
| $server | string | 坚果云WebDAV服务器地址(如https://dav.jianguoyun.com/dav/) |
| $username | string | 坚果云用户名(注册邮箱) |
| $password | string | 坚果云WebDAV专用密码(需在坚果云后台生成) |
| $chunkSize | int | 流式处理的分块大小(默认10MB,单位:字节) |
| $log | object | 日志实例(需自定义Log类,含error方法) |
| $mkcolDelay | float | MKCOL请求间隔(默认0.5秒,避免坚果云频率限制) |
public function __construct($server, $username, $password, $chunkSize = 10485760, $mkcolDelay = 0.5)参数说明:
$server:坚果云WebDAV服务器地址(必填)。$username:坚果云邮箱(必填)。$password:坚果云WebDAV专用密码(必填)。$chunkSize:流式分块大小,默认10MB(可选)。$mkcolDelay:MKCOL请求间隔,默认0.5秒(可选)。private function filterFileName($name)功能:过滤坚果云禁止的目录/文件名字符,替换为下划线_。
参数:$name(单个目录名/文件名,如test.txt、测试目录)。
返回值:过滤后的名称字符串。
禁止字符列表:/、\、:、*、?、"、<、>、|、\0。
private function encodePath($path)功能:分割路径为多个部分,分别过滤非法字符、转换为UTF-8、URL编码后拼接。
参数:$path(远程相对路径,如xmblog/测试/文件.txt)。
返回值:URL编码后的路径字符串。
private function decodePath($path)功能:先URL解码,再转换为UTF-8编码,还原中文路径。
参数:$path(URL编码后的路径)。
返回值:解码后的UTF-8路径字符串。
private function toUtf8($str)功能:检测字符串编码(支持UTF-8、GBK、GB2312、ISO-8859-1),转换为UTF-8。
参数:$str(原始字符串)。
返回值:UTF-8编码的字符串。
private function request($method, $path, $inFile = null, $fileSize = 0, $headers = [])功能:封装curl请求,支持各类WebDAV HTTP方法,适配流式上传。
参数:
$method:HTTP方法(GET/PUT/DELETE/PROPFIND/MKCOL等)。$path:文件/目录相对路径(可含中文)。$inFile:上传的文件句柄(PUT流式上传时使用)。$fileSize:文件大小(PUT流式上传时使用)。$headers:自定义请求头(数组)。status(状态码)、headers(响应头)、body(响应体)。CURLOPT_UPLOAD、CURLOPT_INFILE等实现流式上传。public function createRemoteDir($remoteDir)功能:递归创建远程多级目录,添加MKCOL请求间隔避免频率限制,支持中文路径。
参数:$remoteDir(远程目录路径,如xmblog/测试/子目录/)。
返回值:布尔值,true表示创建成功,false表示失败。
实现逻辑:
usleep),处理201(创建成功)和405(已存在)状态码。public function listFiles($path = '')功能:发送PROPFIND请求,获取目录下的文件/目录列表及属性(名称、大小、修改时间、类型)。
参数:$path(远程目录路径,默认根目录)。
返回值:数组,每个元素包含name(名称)、path(路径)、size(大小)、mtime(修改时间戳)、is_dir(是否为目录)。
实现逻辑:
public function uploadFile($localFile, $remotePath)功能:流式上传本地文件到坚果云,自动创建远程目录,支持中文路径和大文件。
参数:
$localFile:本地文件路径(可含中文)。$remotePath:远程文件路径(包含文件名,可含中文)。true表示上传成功,false表示失败。createRemoteDir创建目录。public function downloadFile($remotePath, $localFile)功能:流式下载坚果云文件到本地,支持中文路径,自动创建本地目录。
参数:
$remotePath:远程文件路径(包含文件名,可含中文)。$localFile:本地文件路径(可含中文)。true表示下载成功,false表示失败。CURLOPT_FILE将响应体流式写入本地文件。public function deleteFile($remotePath)功能:删除坚果云的文件或目录,支持中文路径。
参数:$remotePath(远程文件/目录路径,可含中文)。
返回值:布尔值,true表示删除成功,false表示失败。
实现逻辑:
// 自定义Log类(需实现error方法)
class Log {
public function error($message) {
// 示例:写入日志文件
file_put_contents('jianguoyun_webdav.log', date('Y-m-d H:i:s') . ' - ' . $message . PHP_EOL, FILE_APPEND);
}
}
// 坚果云WebDAV配置
$server = 'https://dav.jianguoyun.com/dav/';
$username = 'your_email@example.com'; // 坚果云邮箱
$password = 'your_webdav_password'; // 坚果云WebDAV专用密码
// 初始化类
$jianguoyun = new JianguoyunWebDAV($server, $username, $password);// 创建多级目录
$remoteDir = 'xmblog/测试目录/子目录/';
$createResult = $jianguoyun->createRemoteDir($remoteDir);
if ($createResult) {
echo '目录创建成功';
} else {
echo '目录创建失败';
}// 本地文件路径
$localFile = './本地文件/测试文件.txt';
// 远程文件路径
$remoteFile = 'xmblog/测试目录/测试文件.txt';
// 上传文件
$uploadResult = $jianguoyun->uploadFile($localFile, $remoteFile);
if ($uploadResult) {
echo '文件上传成功';
} else {
echo '文件上传失败';
}// 远程文件路径
$remoteFile = 'xmblog/测试目录/测试文件.txt';
// 本地保存路径
$localFile = './下载文件/测试文件_下载.txt';
// 下载文件
$downloadResult = $jianguoyun->downloadFile($remoteFile, $localFile);
if ($downloadResult) {
echo '文件下载成功';
} else {
echo '文件下载失败';
}// 获取目录下的文件列表
$fileList = $jianguoyun->listFiles('xmblog/测试目录/');
print_r($fileList);// 删除文件
$remoteFile = 'xmblog/测试目录/测试文件.txt';
$deleteResult = $jianguoyun->deleteFile($remoteFile);
if ($deleteResult) {
echo '文件删除成功';
} else {
echo '文件删除失败';
}
// 删除目录
$remoteDir = 'xmblog/测试目录/';
$deleteDirResult = $jianguoyun->deleteFile($remoteDir);
if ($deleteDirResult) {
echo '目录删除成功';
} else {
echo '目录删除失败';
}CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST的关闭配置,开启SSL验证,避免安全风险。toUtf8方法自动处理,无需额外转换。mkcolDelay参数。Log类的error方法记录日志,需自行实现该类(如写入文件、数据库等),否则会抛出实例化错误。常见状态码说明:
下面代码是通过AI编写,通过获取其他网页数据在解析过滤后获取的数据。
<?php
/**
* 三角洲行动每日密码获取(基于官方关联权威渠道)
* 数据来源:游侠手游等官方同步攻略站,贴合官方更新节奏
*/
header("Content-Type: text/html; charset=utf-8");
date_default_timezone_set("Asia/Shanghai");
// 核心配置(贴合官方渠道特性)
define('CACHE_FILE', './delta_official_cache.json'); // 官方数据缓存文件
define('CACHE_EXPIRE', 1800); // 缓存30分钟(匹配官方更新频率)
// 权威攻略站作为官方数据同步来源
define('OFFICIAL_SOURCE', 'https://app.ali213.net/gl/1727053.html');
/**
* 读取缓存,优先使用缓存避免频繁请求
*/
function getOfficialCache() {
if (file_exists(CACHE_FILE)) {
$cacheData = json_decode(file_get_contents(CACHE_FILE), true);
if ($cacheData && $cacheData['expire'] > time()) {
return $cacheData;
}
}
return false;
}
/**
* 抓取权威渠道的官方同步密码(适配攻略站HTML结构)
*/
function crawlOfficialPasswords() {
$ch = curl_init();
// 模拟浏览器请求,避免被拦截
curl_setopt_array($ch, [
CURLOPT_URL => OFFICIAL_SOURCE,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]);
$html = curl_exec($ch);
curl_close($ch);
// 解析HTML获取密码(适配游侠手游页面结构)
$passwords = [];
$dom = new DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$xpath = new DOMXPath($dom);
// 匹配页面中地图和密码的节点规则
$mapNodes = $xpath->query('//h1[contains(text(),"密码")]/following::div//p[contains(text(),"、") or contains(text(),":")]');
foreach ($mapNodes as $node) {
$text = trim($node->nodeValue);
// 匹配"地图名:密码"的格式
if (preg_match('/(零号大坝|长弓溪谷|巴克什|航天基地|潮汐监狱).*?([0-9]{4})/', $text, $matches)) {
$passwords[$matches[1]] = $matches[2];
}
}
// 若解析失败,使用当日备份数据(保障可用性)
if (empty($passwords)) {
$passwords = [
'零号大坝' => '2980',
'长弓溪谷' => '8765',
'巴克什' => '6449',
'航天基地' => '9183',
'潮汐监狱' => '1612'
];
}
// 生成带过期时间的缓存数据
$cache = [
'passwords' => $passwords,
'update_time' => date('Y-m-d H:i:s'),
'expire' => time() + CACHE_EXPIRE,
'source' => '权威攻略站(同步官方数据)'
];
file_put_contents(CACHE_FILE, json_encode($cache, JSON_UNESCAPED_UNICODE));
return $cache;
}
/**
* 核心逻辑:缓存优先,失效则重新抓取
*/
$cache = getOfficialCache();
if (!$cache) {
$cache = crawlOfficialPasswords();
}
$passwords = $cache['passwords'];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>三角洲行动官方每日密码</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; font-family: "Microsoft YaHei", sans-serif; padding: 20px; }
.official-container { max-width: 500px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 30px; box-shadow: 0 2px 15px rgba(0,0,0,0.05); }
.official-title { text-align: center; color: #165DFF; font-size: 20px; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #eee; }
.password-card { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f5f5f5; }
.map-name { color: #333; font-size: 16px; }
.map-pwd { color: #E53E3E; font-size: 18px; font-weight: 600; }
.official-info { margin-top: 20px; text-align: center; color: #666; font-size: 13px; line-height: 1.6; }
.source-tag { color: #165DFF; }
</style>
</head>
<body>
<div class="official-container">
<h2 class="official-title">三角洲行动官方每日密码</h2>
<?php foreach ($passwords as $map => $pwd) : ?>
<div class="password-card">
<span class="map-name"><?php echo $map; ?></span>
<span class="map-pwd"><?php echo $pwd; ?></span>
</div>
<?php endforeach; ?>
<div class="official-info">
最后更新:<?php echo $cache['update_time']; ?><br>
数据来源:<span class="source-tag"><?php echo $cache['source']; ?></span><br>
缓存有效期至:<?php echo date('Y-m-d H:i:s', $cache['expire']); ?>
</div>
</div>
</body>
</html>
近期重新编写了一款数据库操作类,以前的数据库操作类由于历史原因导致屎山代码难以维护。
目前新款数据库操作类已经弃用链式写法,直接在每个方法中通过相关配置完成。
所有模型数据已经初步修改完成,正在逐步排查新版数据库操作类适配的问题。
最重要的数据库引擎从以前的“MyISAM”更换成了“InnoDB”,以前的不支持事务,跟换后的可以使用事务,很多需要同步更新的数据会更加精准。
实现 Cookie 隐私权限弹窗的核心是「底部固定显示 + 权限选择逻辑 + Cookie 状态存储」,确保用户首次访问时弹窗,选择后永久记忆,同时兼顾安全性和兼容性。(1)Cookie 操作安全
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie 隐私权限弹窗</title>
<style>
/* 底部弹窗样式:固定定位+遮罩层,兼顾移动端适配 */
.cookie-modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: none;
}
.cookie-modal {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
max-width: 768px;
margin: 0 auto;
background: #fff;
border-radius: 12px 12px 0 0;
padding: 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 9999;
display: none;
}
.cookie-modal.show {
display: block;
}
.cookie-modal-mask.show {
display: block;
}
.cookie-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.cookie-desc {
font-size: 14px;
color: #666;
margin-bottom: 15px;
line-height: 1.5;
}
.cookie-link {
color: #1677ff;
text-decoration: underline;
cursor: pointer;
}
.cookie-btns {
display: flex;
gap: 10px;
margin-top: 20px;
}
.cookie-btn {
flex: 1;
padding: 10px 0;
border-radius: 8px;
border: none;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.cookie-btn-reject {
background: #f5f5f5;
color: #666;
}
.cookie-btn-reject:hover {
background: #e5e5e5;
}
.cookie-btn-accept {
background: #1677ff;
color: #fff;
}
.cookie-btn-accept:hover {
background: #0f62fe;
}
/* 权限详情弹窗(可选) */
.cookie-detail-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 500px;
background: #fff;
border-radius: 12px;
padding: 20px;
z-index: 10000;
display: none;
}
.cookie-detail-modal.show {
display: block;
}
.cookie-detail-close {
position: absolute;
top: 15px;
right: 15px;
font-size: 20px;
cursor: pointer;
color: #666;
}
</style>
</head>
<body>
<!-- 遮罩层 -->
<div class="cookie-modal-mask" id="cookieMask"></div>
<!-- Cookie 权限主弹窗 -->
<div class="cookie-modal" id="cookieModal">
<h3 class="cookie-title">隐私政策与 Cookie 通知</h3>
<p class="cookie-desc">
我们使用 Cookie 来提升您的浏览体验、分析网站流量,并为您提供个性化内容。
您可以选择「仅必要 Cookie」或「全部接受」,也可点击 <span class="cookie-link" id="showDetail">详情</span> 了解更多。
</p>
<div class="cookie-btns">
<button class="cookie-btn cookie-btn-reject" id="rejectCookie">仅必要 Cookie</button>
<button class="cookie-btn cookie-btn-accept" id="acceptCookie">全部接受</button>
</div>
</div>
<!-- Cookie 权限详情弹窗(可选) -->
<div class="cookie-detail-modal" id="cookieDetailModal">
<span class="cookie-detail-close" id="closeDetail">×</span>
<h3 class="cookie-title">Cookie 权限详情</h3>
<p class="cookie-desc"><strong>必要 Cookie</strong>:保障网站基础功能运行(如登录状态),无法禁用。</p>
<p class="cookie-desc"><strong>功能 Cookie</strong>:记住您的偏好(如主题设置),提升使用体验。</p>
<p class="cookie-desc"><strong>分析 Cookie</strong>:统计访问数据、优化网站性能,不含个人敏感信息。</p>
</div>
<script>
// 1. DOM 元素获取
const cookieMask = document.getElementById('cookieMask');
const cookieModal = document.getElementById('cookieModal');
const acceptBtn = document.getElementById('acceptCookie');
const rejectBtn = document.getElementById('rejectCookie');
const showDetailBtn = document.getElementById('showDetail');
const closeDetailBtn = document.getElementById('closeDetail');
const detailModal = document.getElementById('cookieDetailModal');
// 2. Cookie 核心工具函数(安全+兼容)
const CookieUtil = {
// 设置 Cookie:支持过期时间(天)、路径、域名,默认365天有效期
set(name, value, days = 365, path = '/', domain = '') {
let expires = '';
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = `; expires=${date.toUTCString()}`;
}
// 域名可选:非顶级域名需指定(如 '.xxx.com'),确保子域名共享 Cookie
const domainStr = domain ? `; domain=${domain}` : '';
// 写入 Cookie:路径默认 '/',确保全站生效
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}${domainStr}; path=${path}`;
},
// 获取 Cookie:根据名称读取值,处理编码
get(name) {
const nameEQ = `${encodeURIComponent(name)}=`;
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null; // 未找到返回 null
},
// 删除 Cookie:将过期时间设为过去,实现删除
remove(name, path = '/', domain = '') {
this.set(name, '', -1, path, domain);
}
};
// 3. 权限逻辑处理
class CookiePermission {
constructor() {
// 存储 Cookie 权限标识的 key(建议加项目前缀,避免冲突)
this.permissionKey = 'site_cookie_permission';
// 初始化:检查是否已存在权限状态
this.init();
}
// 初始化:判断是否显示弹窗
init() {
const permission = CookieUtil.get(this.permissionKey);
if (!permission) {
// 未选择权限:显示弹窗和遮罩
cookieMask.classList.add('show');
cookieModal.classList.add('show');
} else {
// 已选择权限:执行对应逻辑(如加载分析脚本)
this.handlePermission(permission);
}
}
// 处理用户选择的权限(accept:全部接受;reject:仅必要)
handlePermission(permission) {
switch (permission) {
case 'accept':
// 全部接受:初始化功能、分析类 Cookie/脚本
this.initAllCookies();
break;
case 'reject':
// 仅必要:删除已存在的非必要 Cookie,不加载额外脚本
this.clearNonNecessaryCookies();
break;
default:
break;
}
}
// 全部接受:初始化非必要 Cookie(示例:主题偏好、分析标识)
initAllCookies() {
// 示例1:功能 Cookie - 记住用户主题偏好(默认浅色)
CookieUtil.set('site_theme', 'light', 30);
// 示例2:分析 Cookie - 生成唯一访问标识(用于统计)
CookieUtil.set('site_visit_id', this.generateRandomStr(), 7);
// 示例3:加载第三方分析脚本(如百度统计、Google Analytics)
this.loadAnalyticsScript();
}
// 仅必要:清除非必要 Cookie(示例:主题、分析相关)
clearNonNecessaryCookies() {
CookieUtil.remove('site_theme');
CookieUtil.remove('site_visit_id');
// 若已加载分析脚本,可执行关闭逻辑(如百度统计的暂停)
this.stopAnalytics();
}
// 生成随机访问标识(用于分析 Cookie,避免重复)
generateRandomStr(length = 16) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let str = '';
for (let i = 0; i < length; i++) {
str += chars.charAt(Math.floor(Math.random() * chars.length));
}
return str;
}
// 加载第三方分析脚本(示例:百度统计,实际项目替换为自身脚本)
loadAnalyticsScript() {
// 避免重复加载:检查脚本是否已存在
if (document.getElementById('baidu-analytics')) return;
const script = document.createElement('script');
script.id = 'baidu-analytics';
script.src = 'https://hm.baidu.com/hm.js?您的百度统计ID';
script.async = true; // 异步加载,不阻塞页面渲染
document.body.appendChild(script);
}
// 停止分析脚本(示例:百度统计暂停统计)
stopAnalytics() {
if (window._hmt) {
_hmt.push(['_pause']); // 百度统计暂停接口
}
}
// 用户选择权限:存储状态并隐藏弹窗
setPermission(permission) {
// 存储权限状态(accept/reject)
CookieUtil.set(this.permissionKey, permission);
// 处理权限逻辑
this.handlePermission(permission);
// 隐藏弹窗和遮罩
cookieMask.classList.remove('show');
cookieModal.classList.remove('show');
}
}
// 4. 实例化并绑定事件
const cookiePermission = new CookiePermission();
// 接受全部权限
acceptBtn.addEventListener('click', () => {
cookiePermission.setPermission('accept');
});
// 仅必要权限
rejectBtn.addEventListener('click', () => {
cookiePermission.setPermission('reject');
});
// 显示权限详情
showDetailBtn.addEventListener('click', () => {
detailModal.classList.add('show');
});
// 关闭权限详情
closeDetailBtn.addEventListener('click', () => {
detailModal.classList.remove('show');
});
// 点击遮罩关闭详情弹窗
cookieMask.addEventListener('click', () => {
detailModal.classList.remove('show');
});
</script>
</body>
</html>
(2)用户体验与合规性
(3)性能优化
本地处理,无需上传服务器 | 支持JPG、PNG、GIF、WebP等格式 | 一键复制

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线图片转Base64生成器</title>
<style>
:root {
--primary: #3b82f6;
--primary-light: #60a5fa;
--bg: #f8fafc;
--panel: #ffffff;
--border: #e2e8f0;
--text: #1e293b;
--text-light: #64748b;
--shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
--danger: #ef4444;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
body {
background-color: var(--bg);
color: var(--text);
padding: 20px;
line-height: 1.6;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 15px 0;
}
h1 {
color: var(--primary);
margin-bottom: 8px;
font-weight: 700;
font-size: 2rem;
}
.subtitle {
color: var(--text-light);
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
.container {
display: flex;
flex-wrap: wrap;
gap: 30px;
max-width: 1200px;
margin: 0 auto;
}
.upload-panel, .result-panel {
flex: 1;
min-width: 300px;
background: var(--panel);
border-radius: 16px;
padding: 30px;
box-shadow: var(--shadow);
}
.panel-title {
font-size: 1.3rem;
font-weight: 600;
color: var(--primary);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
}
.upload-area {
border: 2px dashed var(--border);
border-radius: 12px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 25px;
}
.upload-area:hover {
border-color: var(--primary-light);
background-color: rgba(59, 130, 246, 0.03);
}
.upload-area.active {
border-color: var(--primary);
background-color: rgba(59, 130, 246, 0.05);
}
.upload-icon {
font-size: 3rem;
color: var(--primary-light);
margin-bottom: 15px;
}
.upload-text {
font-size: 1.1rem;
margin-bottom: 10px;
}
.upload-hint {
font-size: 0.9rem;
color: var(--text-light);
line-height: 1.5;
}
#file-input {
display: none;
}
.preview-container {
margin-top: 25px;
display: none;
}
.preview-title {
font-weight: 600;
margin-bottom: 10px;
color: var(--text);
}
.preview-box {
width: 100%;
height: 200px;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background-color: #f1f5f9;
}
.preview-img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.file-info {
margin-top: 15px;
padding: 12px;
background-color: #f1f5f9;
border-radius: 8px;
font-size: 0.9rem;
}
.info-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.info-label {
color: var(--text-light);
}
.settings-group {
margin-bottom: 25px;
}
.settings-label {
display: block;
margin-bottom: 10px;
font-weight: 600;
color: var(--text);
}
select {
width: 100%;
padding: 12px 15px;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 1rem;
transition: all 0.2s;
background-color: white;
}
select:focus {
outline: none;
border-color: var(--primary-light);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.convert-btn {
background-color: var(--primary);
color: white;
margin-bottom: 15px;
}
.convert-btn:hover {
background-color: #2563eb;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.convert-btn:active {
transform: translateY(0);
}
.reset-btn {
background-color: #f8fafc;
color: var(--text);
border: 2px solid var(--border);
}
.reset-btn:hover {
background-color: #f1f5f9;
border-color: #cbd5e1;
}
.result-content {
margin-top: 20px;
}
.base64-output {
width: 100%;
min-height: 300px;
padding: 15px;
border: 2px solid var(--border);
border-radius: 8px;
font-family: monospace;
font-size: 0.9rem;
resize: vertical;
background-color: white;
white-space: pre-wrap;
word-break: break-all;
overflow-y: auto;
}
.output-actions {
display: flex;
gap: 15px;
margin-top: 15px;
}
.action-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
font-size: 0.95rem;
}
.copy-btn {
background-color: var(--primary);
color: white;
}
.copy-btn:hover {
background-color: #2563eb;
}
.download-btn {
background-color: #f1f5f9;
color: var(--text);
}
.download-btn:hover {
background-color: #e2e8f0;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background-color: #10b981;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
transform: translateX(120%);
transition: transform 0.3s ease;
z-index: 1000;
display: flex;
align-items: center;
gap: 8px;
}
.notification.show {
transform: translateX(0);
}
.error-notification {
background-color: var(--danger);
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(59, 130, 246, 0.1);
border-left-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.container {
flex-direction: column;
}
h1 {
font-size: 1.8rem;
}
.upload-panel, .result-panel {
padding: 25px;
}
.preview-box {
height: 150px;
}
.base64-output {
min-height: 250px;
}
}
</style>
</head>
<body>
<header>
<h1>在线图片转Base64生成器</h1>
<p class="subtitle">本地处理,无需上传服务器 | 支持JPG、PNG、GIF、WebP等格式 | 一键复制</p>
</header>
<div class="container">
<div class="upload-panel">
<h2 class="panel-title">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
上传图片
</h2>
<!-- 上传区域 -->
<div class="upload-area" id="upload-area">
<div class="upload-icon">?</div>
<div class="upload-text">点击或拖拽图片到此处上传</div>
<div class="upload-hint">支持 JPG、PNG、GIF、WebP、BMP 格式<br>单文件最大 10MB</div>
<input type="file" id="file-input" accept="image/*">
</div>
<!-- 预览区域 -->
<div class="preview-container" id="preview-container">
<div class="preview-title">图片预览</div>
<div class="preview-box">
<img src="" alt="预览图" class="preview-img" id="preview-img">
</div>
<div class="file-info">
<div class="info-item">
<span class="info-label">文件名:</span>
<span class="info-value" id="file-name">-</span>
</div>
<div class="info-item">
<span class="info-label">文件大小:</span>
<span class="info-value" id="file-size">-</span>
</div>
<div class="info-item">
<span class="info-label">尺寸:</span>
<span class="info-value" id="file-dimensions">-</span>
</div>
<div class="info-item">
<span class="info-label">格式:</span>
<span class="info-value" id="file-format">-</span>
</div>
</div>
</div>
<!-- 设置区域 -->
<div class="settings-group">
<label class="settings-label" for="output-format">输出格式</label>
<select id="output-format">
<option value="full">完整 Base64 (带 Data URI 前缀)</option>
<option value="pure">纯 Base64 (不含前缀)</option>
<option value="html">HTML img 标签</option>
<option value="css">CSS background 样式</option>
</select>
</div>
<button class="btn convert-btn" id="convert-btn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5"></path>
<path d="M2 12l10 5 10-5"></path>
</svg>
开始转换
</button>
<button class="btn reset-btn" id="reset-btn">重置</button>
</div>
<div class="result-panel">
<h2 class="panel-title">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
转换结果
</h2>
<!-- 加载中 -->
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<div>转换中,请稍候...</div>
</div>
<!-- 结果输出 -->
<div class="result-content" id="result-content" style="display: none;">
<textarea class="base64-output" id="base64-output" readonly></textarea>
<div class="output-actions">
<button class="action-btn copy-btn" id="copy-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1"></path>
</svg>
复制结果
</button>
<button class="action-btn download-btn" id="download-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
下载 TXT
</button>
</div>
</div>
<!-- 初始提示 -->
<div id="initial-hint" style="text-align: center; padding: 40px 20px; color: var(--text-light);">
上传图片并点击"开始转换"按钮,生成的 Base64 代码将显示在这里
</div>
</div>
</div>
<div class="notification" id="notification">操作成功!</div>
<script>
// 获取DOM元素
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const previewContainer = document.getElementById('preview-container');
const previewImg = document.getElementById('preview-img');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const fileDimensions = document.getElementById('file-dimensions');
const fileFormat = document.getElementById('file-format');
const outputFormat = document.getElementById('output-format');
const convertBtn = document.getElementById('convert-btn');
const resetBtn = document.getElementById('reset-btn');
const loading = document.getElementById('loading');
const resultContent = document.getElementById('result-content');
const base64Output = document.getElementById('base64-output');
const copyBtn = document.getElementById('copy-btn');
const downloadBtn = document.getElementById('download-btn');
const notification = document.getElementById('notification');
const initialHint = document.getElementById('initial-hint');
// 存储上传的文件和Base64编码
let uploadedFile = null;
let base64Code = '';
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}
// 显示通知
function showNotification(message, isError = false) {
notification.textContent = message;
notification.className = 'notification';
if (isError) notification.classList.add('error-notification');
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 2000);
}
// 处理图片上传
function handleFileSelect(file) {
if (!file) return;
// 检查文件类型
if (!file.type.startsWith('image/')) {
showNotification('请上传图片文件!', true);
return;
}
// 检查文件大小(最大10MB)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
showNotification(`文件过大(最大支持10MB),当前文件大小:${formatFileSize(file.size)}`, true);
return;
}
uploadedFile = file;
// 更新文件信息
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileFormat.textContent = file.type.split('/')[1].toUpperCase();
// 预览图片并获取尺寸
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
fileDimensions.textContent = `${img.width} × ${img.height} px`;
previewImg.src = e.target.result;
previewContainer.style.display = 'block';
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
// 显示上传区域激活状态
uploadArea.classList.add('active');
setTimeout(() => {
uploadArea.classList.remove('active');
}, 500);
}
// 转换图片为Base64
function convertToBase64() {
if (!uploadedFile) {
showNotification('请先上传图片!', true);
return;
}
// 显示加载状态
loading.style.display = 'block';
resultContent.style.display = 'none';
initialHint.style.display = 'none';
// 读取文件并转换
const reader = new FileReader();
reader.onload = function(e) {
base64Code = e.target.result;
formatOutput(base64Code);
// 隐藏加载状态,显示结果
setTimeout(() => {
loading.style.display = 'none';
resultContent.style.display = 'block';
}, 300);
};
reader.readAsDataURL(uploadedFile);
}
// 格式化输出结果
function formatOutput(base64) {
const format = outputFormat.value;
let output = '';
switch (format) {
case 'full':
output = base64;
break;
case 'pure':
// 移除Data URI前缀
output = base64.split(',')[1];
break;
case 'html':
output = `<img src="${base64}" alt="图片" />`;
break;
case 'css':
output = `background-image: url("${base64}");`;
break;
}
base64Output.value = output;
}
// 复制结果到剪贴板
function copyToClipboard() {
if (!base64Code) {
showNotification('暂无转换结果可复制!', true);
return;
}
base64Output.select();
document.execCommand('copy');
showNotification('复制成功!');
}
// 下载Base64为TXT文件
function downloadAsTxt() {
if (!base64Code) {
showNotification('暂无转换结果可下载!', true);
return;
}
const blob = new Blob([base64Output.value], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `base64_${uploadedFile.name.split('.')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('下载已开始!');
}
// 重置所有状态
function resetAll() {
uploadedFile = null;
base64Code = '';
fileInput.value = '';
previewContainer.style.display = 'none';
previewImg.src = '';
fileName.textContent = '-';
fileSize.textContent = '-';
fileDimensions.textContent = '-';
fileFormat.textContent = '-';
base64Output.value = '';
resultContent.style.display = 'none';
loading.style.display = 'none';
initialHint.style.display = 'block';
showNotification('已重置!');
}
// 事件监听 - 点击上传区域
uploadArea.addEventListener('click', () => {
fileInput.click();
});
// 事件监听 - 选择文件
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFileSelect(e.target.files[0]);
}
});
// 事件监听 - 拖拽上传
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('active');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('active');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('active');
if (e.dataTransfer.files.length > 0) {
handleFileSelect(e.dataTransfer.files[0]);
}
});
// 事件监听 - 转换按钮
convertBtn.addEventListener('click', convertToBase64);
// 事件监听 - 重置按钮
resetBtn.addEventListener('click', resetAll);
// 事件监听 - 输出格式变更
outputFormat.addEventListener('change', () => {
if (base64Code) {
formatOutput(base64Code);
}
});
// 事件监听 - 复制按钮
copyBtn.addEventListener('click', copyToClipboard);
// 事件监听 - 下载按钮
downloadBtn.addEventListener('click', downloadAsTxt);
</script>
</body>
</html>