“可修”网址工具导航程序(付费开源)
这款程序可以进行工具展示和网址导航等等功能,已经集成二十款内部工具,二次开发也非常的简单,学习成本不高。
开发环境基于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>