前言 完整项目地址: https://github.com/chr233/XinjingdailyBot 因为项目之前是使用运行时反射来完成服务的动态注册，而源生成器是在编译前完成代码生成并参与编译，完全没有运行时的性能开销 运行时反射 - 旧的方案 标记特性 首先创建一个 Attribute, 用来标注需要扫描的服务类 namespace XinjingdailyBot.Infrastructure.Attribute; /// /// 标记服务 /// 1、如果服务是本身 直接在类上使用[AppService] /// 2、如果服务是接口 在类上使用 [AppService(ServiceType = typeof(实现接口))] /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class AppServiceAttribute : System.Attribute { /// /// 服务声明周期 /// 不给默认值的话注册的是AddSingleton /// public LifeTime ServiceLifetime { get; set; } = LifeTime.Scoped; /// /// 指定服务类型 /// public Type? ServiceType { get; set; } /// /// 是否可以从第一个接口获取服务类型 /// public bool InterfaceServiceType { get; set; } /// /// 标记服务 /// /// public AppServiceAttribute(LifeTime serviceLifetime) { ServiceLifetime = serviceLifetime; } /// /// 标记服务 /// /// /// public AppServiceAttribute(Type? serviceType, LifeTime serviceLifetime) { ServiceLifetime = serviceLifetime; ServiceType = serviceType; } /// /// 标记服务 /// /// /// /// public AppServiceAttribute(Type? serviceType, LifeTime serviceLifetime, bool interfaceServiceType) { ServiceLifetime = serviceLifetime; ServiceType = serviceType; InterfaceServiceType = interfaceServiceType; } } /// /// 生命周期 /// public enum LifeTime { /// /// 瞬时 /// Transient, /// /// 范围 /// Scoped, /// /// 单例 /// Singleton } 基于反射的服务扫描器 然后再主项目启动的时候使用反射扫描添加了该 Attribute 的所有 Class 并注册服务 using System.Diagnostics.CodeAnalysis; using System.Reflection; using XinjingdailyBot.Infrastructure.Attribute; namespace XinjingdailyBot.WebAPI.Extensions; /// /// 动态注册服务扩展 /// public static class AppServiceExtensions { private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 注册引用程序域中所有有AppService标记的类的服务 /// /// [RequiresUnreferencedCode("不兼容剪裁")] public static void AddAppService(this IServiceCollection services) { var cls = new string[] { "XinjingdailyBot.Repository", "XinjingdailyBot.Service", "XinjingdailyBot.Command", "XinjingdailyBot.WebAPI", }; foreach (var item in cls) { var assembly = Assembly.Load(item); Register(services, assembly); } } /// /// 动态注册服务 /// /// /// [RequiresUnreferencedCode("不兼容剪裁")] private static void Register(IServiceCollection services, Assembly assembly) { string? name = assembly.GetName().Name; _logger.Debug($"===== 注册 {name} 中的服务 ====="); uint count = 0; foreach (var type in assembly.GetTypes()) { var serviceAttribute = type.GetCustomAttribute<AppServiceAttribute>(); if (serviceAttribute != null) { count++; var serviceType = serviceAttribute.ServiceType; //情况1 适用于依赖抽象编程，注意这里只获取第一个 if (serviceType == null && serviceAttribute.InterfaceServiceType) { serviceType = type.GetInterfaces().FirstOrDefault(); } //情况2 不常见特殊情况下才会指定ServiceType，写起来麻烦 if (serviceType == null) { serviceType = type; } var lifetime = serviceAttribute.ServiceLifetime; switch (lifetime) { case LifeTime.Singleton: services.AddSingleton(serviceType, type); break; case LifeTime.Scoped: services.AddScoped(serviceType, type); break; case LifeTime.Transient: services.AddTransient(serviceType, type); break; default: services.AddTransient(serviceType, type); break; } _logger.Debug($"{lifetime} - {serviceType}"); } } _logger.Debug($"===== 注册了 {count} 个服务 ====="); } } 也不是不能用，就是感觉不够完美，而且因为使用了反射的相关特性，无法简单地启用程序集裁剪和应用 AOT，所有就想到利用 .Net 5.0 新加入的源生成器特性来实现相同的功能，避免使用反射特性和降低运行时的额外开销 源生成器 - 新的方案 源生成器官方文档 因为我的项目是按照命名空间分成了几个子项目，而源生成器只能分析当前项目的语法树，如果用文件 IO 的方式读取本地 .cs 文件也是可以实现的，但是会显示警告，因为官方并不推荐在源生成器中使用文件 IO，这会降低编译速度，而且服务类也不是经常新增，每次编译都扫描一遍也是完全没必要的 综上，我的解决方案是写一个 Powershell 脚本来扫描解决方案中的所有服务，并保存到一个文件中，然后在源生成器中读取该文件来生成服务注册的代码 服务扫描脚本 首先是服务扫描脚本 scan_service.ps1 # # @Author : Chr_ # @Date : 2024-01-18 14:25:15 # @LastEditors : Chr_ # @LastEditTime : 2024-01-18 20:15:18 # @Description : 生成依赖服务信息 # # 工作目录 $workDir = (Get-Location).Path; $outputPath = Join-Path -Path (Join-Path -Path $workDir -ChildPath "XinjingdailyBot.WebAPI") -ChildPath "Properties"; # 包名列表 $libNames = "XinjingdailyBot.Model", "XinjingdailyBot.Repository", "XinjingdailyBot.Interface", "XinjingdailyBot.Service", "XinjingdailyBot.Tasks", "XinjingdailyBot.Command"; # 输出路径 $appServicePath = Join-Path -Path $outputPath -ChildPath "appService.json"; # 扫描源文件列表 function GetSourceFileList { param ( [string]$Path ) $result = @(); # 去除 bin obj 目录 $dirs = Get-ChildItem -Path $Path -Directory -Exclude "bin", "obj" # 遍历所有 .cs 文件 foreach ($dir in $dirs) { $files = Get-ChildItem -Path $dir -Filter "*.cs" -File -Recurse # 遍历子目录文件 foreach ($file in $files) { $result += $file } } $files = Get-ChildItem -Path $Path -Filter "*.cs" -File # 遍历所有 .cs 文件 foreach ($file in $files) { $result += $file } Write-Output $result $result; } # 读取命名空间 function GetNamespace { param ( [string] $Content ) if ($Content -match "namespace\s+([^;{]+)") { $Matches[1]; } } # 读取类名/接口名 function GetClassName { param ( [string] $Content ) if ($Content -match "(?:class|interface|record)\s+([^({:\s]+)") { $Matches[1]; } } # 扫描AppServiceAttribute function ScanAppServiceAttribute { param ( [string] $Content ) if ($Content -match "\[AppService(?:Attribute)?\(([^\]]+)\)\]") { $result = @{} $arguments = $Matches[1].Split(","); foreach ($argument in $arguments) { if ($argument -match "LifeTime\.(Singleton|Transient|Scoped)") { $result["LifeTime"] = $Matches[1]; } elseif ($argument -match "typeof\(([^()]+)\)") { $result["Interface"] = $Matches[1]; } elseif ($argument -match "true|false") { $result["AutoInterface"] = $Matches[1]; } else { Write-Output $argument; } } $result; } } # 类全名表 $clsNamespaces = @{}; $appServiceAttributes = @{}; foreach ($libName in $libNames) { # 库文件路径 $libPath = Join-Path -Path $workDir -ChildPath $libName; # 获取所有源文件路径 $filePaths = GetSourceFileList -Path $libPath; # 读取Service信息和类命名空间; foreach ($filePath in $filePaths) { $fileContent = Get-Content -Path $filePath.FullName -Raw; $namespace = GetNameSpace -Content $fileContent; if ($null -eq $namespace -or $namespace -eq "" ) { continue; } $clsName = GetClassName -Content $fileContent; if ($null -eq $namespace -or $namespace -eq "" ) { continue; } $clsNamespaces[$clsName] = $namespace; $appService = ScanAppServiceAttribute -Content $fileContent; if ($null -ne $appService) { $appServiceAttributes[$clsName] = $appService; } } } $services = $appServiceAttributes.Clone(); # 生成服务信息 foreach ($service in $services.Keys) { $serviceInfo = $appServiceAttributes[$service]; $namespace = $clsNamespaces[$service]; $serviceInfo["Class"] = "$namespace.$service"; $interface = $serviceInfo["Interface"]; if ($null -ne $interface) { $ns = $clsNamespaces[$interface]; $serviceInfo["Interface"] = "$ns.$interface"; } $appServiceAttributes[$service] = $serviceInfo; } Write-Output "扫描到 $($appServiceAttributes.Count) 个服务"; $appServiceAttributes | ConvertTo-Json | Out-File -FilePath $appServicePath Write-Output "文件保存至 $appServicePath" 这个脚本的功能是遍历定义的子项目文件，然后扫描所有类和接口，以及所有带有 AppServiceAttribute 特性的服务类，最后生成一个 appService.json 运行脚本生成文件以后记得修改文件生成操作，不然在分析器里这个文件是不可见的 创建源生成器 跟创建类库一样，但是需要编辑 .csproj 文件，完整文件如下 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> <IsRoslynComponent>true</IsRoslynComponent> <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn> <NoWarn>CS1570;CS1587</NoWarn> </PropertyGroup> <ItemGroup> <None Include="..\.editorconfig" Link=".editorconfig" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.Common"> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.CSharp"> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Newtonsoft.Json"> <GeneratePathProperty>true</GeneratePathProperty> <PrivateAssets>all</PrivateAssets> </PackageReference> </ItemGroup> <ItemGroup> <Compile Update="Templates.Designer.cs"> <DesignTime>True</DesignTime> <AutoGen>True</AutoGen> <DependentUpon>Templates.resx</DependentUpon> </Compile> </ItemGroup> <ItemGroup> <EmbeddedResource Update="Templates.resx"> <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>Templates.Designer.cs</LastGenOutput> </EmbeddedResource> </ItemGroup> <ItemGroup> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> <Target Name="GetDependencyTargetPaths"> <ItemGroup> <TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" /> </ItemGroup> </Target> </Project> 我所做的修改如下 将目标框架改为 netstandard2.0，目前源生成器必须用该版本 添加 EnforceExtendedAnalyzerRules 和 IsRoslynComponent 项，第一个是排除别的分析器，第二个是定义该项目是 Roslyn 组件，添加了以后会有额外的调试配置 添加 GetTargetPathDependsOn 项，意思是从定义的路径中寻找依赖库 添加 Microsoft.CodeAnalysis.Analyzers 和 Microsoft.CodeAnalysis.Common 和 Microsoft.CodeAnalysis.CSharp 库，原样从微软文档拷来的，生成器必须的依赖 添加 Newtonsoft.Json 库（可选），我使用了 Newtonsoft.Json 作为 Json 反序列化库，因此还需要额外配置引用信息（因为源生成器相当于是编译期的扩展，如果不做额外处理会报找不到Dll的错），需要在下面定义 <ItemGroup> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> <Target Name="GetDependencyTargetPaths"> <ItemGroup> <TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" /> </ItemGroup> </Target> 然后再主项目中添加项目引用，并修改引用的属性，将被引用项目类型改成分析器，然后排除所有的生成资产 </ItemGroup> <ProjectReference Include="..\XinjingdailyBot.Generator\XinjingdailyBot.Generator.csproj"> <OutputItemType>Analyzer</OutputItemType> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> </ProjectReference> </ItemGroup> 服务注册源生成器 代码模板存放在 Templates.resx 中，内容如图 可以根据输出的结果反推 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Newtonsoft.Json; using System.Text; using XinjingdailyBot.Generator.Data; namespace XinjingdailyBot.Generator; [Generator] internal sealed class AppServiceGenerator : ISourceGenerator { const string InputFileName = "appService.json"; const string OutoutFileName = "GeneratedAppServiceExtensions.g.cs"; /// <inheritdoc/> public void Initialize(GeneratorInitializationContext context) { // 无需处理 } /// <inheritdoc/> public void Execute(GeneratorExecutionContext context) { try { var fileText = context.AdditionalFiles.Where(static x => x.Path.EndsWith(InputFileName)).FirstOrDefault() ?? throw new FileNotFoundException("缺少配置文件, 请使用 scan_service.ps1 生成"); ProcessSettingsFile(fileText, context); } catch (Exception e) { context.ReportDiagnostic( Diagnostic.Create( "XJB_01", nameof(AppServiceGenerator), $"生成注入代码失败，{e.Message}", defaultSeverity: DiagnosticSeverity.Error, severity: DiagnosticSeverity.Error, isEnabledByDefault: true, warningLevel: 0)); } } /// /// 生成文件 /// /// /// private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) { var text = xmlFile.GetText(context.CancellationToken)?.ToString() ?? throw new FileLoadException("文件读取失败"); var json = JsonConvert.DeserializeObject<AppServiceData>(text) ?? throw new FileLoadException("文件读取失败"); var sb = new StringBuilder(); sb.AppendLine(Templates.AppServiceHeader); foreach (var kv in json) { var entry = kv.Value; var lifeTime = entry.LifeTime?.ToLowerInvariant() switch { "singleton" or "scoped" or "transient" => entry.LifeTime, _ => null, }; if (string.IsNullOrEmpty(lifeTime)) { continue; } if (!string.IsNullOrEmpty(entry.Class)) { if (string.IsNullOrEmpty(entry.Interface)) { sb.AppendLine(string.Format(Templates.AppServiceContent1, lifeTime, entry.Class)); } else { sb.AppendLine(string.Format(Templates.AppServiceContent2, lifeTime, entry.Interface, entry.Class)); } } } sb.AppendLine(Templates.AppServiceFooter); context.AddSource(OutoutFileName, SourceText.From(sb.ToString(), Encoding.UTF8)); } } 生成的代码示例，为避免使用 using，这里使用完整类名引用 using Microsoft.Extensions.DependencyInjection; namespace XinjingdailyBot.WebAPI.Extensions; /// /// 动态注册服务扩展 /// public static class GeneratedAppServiceExtensions { private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 注册引用程序域中所有有AppService标记的类的服务 /// /// public static void AddAppServiceGenerated(this IServiceCollection services) { services.AddTransient<XinjingdailyBot.Repository.OldPostRepository>(); services.AddScoped<XinjingdailyBot.Interface.Bot.Common.IDispatcherService, XinjingdailyBot.Service.Bot.Common.DispatcherService>(); services.AddSingleton<XinjingdailyBot.Repository.TagRepository>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Common.IChannelService, XinjingdailyBot.Service.Bot.Common.ChannelService>(); services.AddTransient<XinjingdailyBot.Interface.Data.IAttachmentService, XinjingdailyBot.Service.Data.AttachmentService>(); services.AddScoped<XinjingdailyBot.Command.ReviewCommand>(); services.AddScoped<XinjingdailyBot.Interface.Bot.Handler.IJoinRequestHandler, XinjingdailyBot.Service.Bot.Handler.JoinRequestHandler>(); services.AddTransient<XinjingdailyBot.Interface.Data.IBanRecordService, XinjingdailyBot.Service.Data.BanRecordService>(); services.AddSingleton<XinjingdailyBot.Repository.RejectReasonRepository>(); services.AddScoped<XinjingdailyBot.Interface.Bot.Common.IUpdateService, XinjingdailyBot.Service.Bot.Common.UpdateService>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.IForwardMessageHandler, XinjingdailyBot.Service.Bot.Handler.ForwardMessageHandler>(); services.AddScoped<XinjingdailyBot.Command.CommonCommand>(); services.AddSingleton<XinjingdailyBot.Repository.LevelRepository>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.ICommandHandler, XinjingdailyBot.Service.Bot.Handler.CommandHandler>(); services.AddTransient<XinjingdailyBot.Interface.Data.IDialogueService, XinjingdailyBot.Service.Data.DialogueService>(); services.AddSingleton<XinjingdailyBot.Interface.Data.IPostService, XinjingdailyBot.Service.Data.PostService>(); services.AddTransient<XinjingdailyBot.Interface.Data.IChannelOptionService, XinjingdailyBot.Service.Data.ChannelOptionService>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.IChannelPostHandler, XinjingdailyBot.Service.Bot.Handler.ChannelPostHandler>(); services.AddSingleton<XinjingdailyBot.Interface.Data.IUserService, XinjingdailyBot.Service.Data.UserService>(); services.AddTransient<XinjingdailyBot.Interface.Helper.IImageHelperService, XinjingdailyBot.Service.Helper.ImageHelperService>(); services.AddSingleton<XinjingdailyBot.Interface.Data.INameHistoryService, XinjingdailyBot.Service.Data.NameHistoryService>(); services.AddScoped<XinjingdailyBot.Command.NormalCommand>(); services.AddSingleton<XinjingdailyBot.Interface.Data.IMediaGroupService, XinjingdailyBot.Service.Data.MediaGroupService>(); services.AddScoped<XinjingdailyBot.Command.ObsoleteCommand>(); services.AddScoped<XinjingdailyBot.Command.PostCommand>(); services.AddTransient<XinjingdailyBot.Interface.Data.IAdvertisePostService, XinjingdailyBot.Service.Data.AdvertisePostsService>(); services.AddTransient<XinjingdailyBot.Interface.Data.IAdvertiseService, XinjingdailyBot.Service.Data.AdvertiseService>(); services.AddSingleton<XinjingdailyBot.Repository.GroupRepository>(); services.AddTransient<XinjingdailyBot.Interface.Helper.IHttpHelperService, XinjingdailyBot.Service.Helper.HttpHelperService>(); services.AddTransient<XinjingdailyBot.Interface.Helper.ITextHelperService, XinjingdailyBot.Service.Helper.TextHelperService>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.IMessageHandler, XinjingdailyBot.Service.Bot.Handler.MessageHandler>(); services.AddTransient<XinjingdailyBot.Interface.Data.ICmdRecordService, XinjingdailyBot.Service.Data.CmdRecordService>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.IInlineQueryHandler, XinjingdailyBot.Service.Bot.Handler.InlineQueryHandler>(); services.AddTransient<XinjingdailyBot.Repository.PostRepository>(); services.AddSingleton<XinjingdailyBot.Interface.Data.IUserTokenService, XinjingdailyBot.Service.Data.UserTokenService>(); services.AddTransient<XinjingdailyBot.Interface.Helper.IMarkupHelperService, XinjingdailyBot.Service.Helper.MarkupHelperService>(); services.AddScoped<XinjingdailyBot.Command.AdminCommand>(); services.AddScoped<XinjingdailyBot.Command.SuperCommand>(); services.AddTransient<XinjingdailyBot.Interface.Data.IReviewStatusService, XinjingdailyBot.Service.Data.ReviewStatusService>(); services.AddSingleton<XinjingdailyBot.Interface.Bot.Handler.IGroupMessageHandler, XinjingdailyBot.Service.Bot.Handler.GroupMessageHandler>(); } } 调试源生成器 首先删除源生成器项目的所有调试目标，然后新增 Roslyn Component 目标，并在右边将 Target Project 设为主项目，然后将启动项目改为生成器 然后在生成器的 Initialize 方法中开启调试器 public void Initialize(GeneratorInitializationContext context) { // 无需处理 if (!Debugger.IsAttached) { Debugger.Launch(); } } 这样就可以动态调试生成器代码了 实际使用 将启动项目改为主功能，去掉源生成器的调试代码，然后重新生成一遍解决方案，如果一切正常，将会在分析器中看到生成的代码文件 如果后续有新的服务，重新执行 scan_service.ps1 即可更新配置文件
前言 消失大半年，其中一个原因是工作了，大部分时间在给公司做开发，业余时间在写外包赚米，另一个原因就是拖延症犯了，越拖越想拖 过年放了半个月假，稍微有点时间做点自己一直以来就很想做的事——给NGA去广告，当然，JS教程也会安排上的 [...]
【油猴脚本教程】P2 初识油猴脚本 & 油猴脚本的基本构成
P2 初识油猴脚本 教程索引 视频链接 光讲JS感觉不是特别直观，用两个不涉及网络请求的脚本进行讲解 第一个脚本 - Remove Live.js 脚本链接： https://github.com/chr233/GM_Scripts/blob/master/Bilibili/Remove_Live.js 脚本功能： 提供一个开关，用于移除直播播放器，并且带有记忆功能 源码： // ==UserScript== // @name Remove_Live // @name:zh-CN 去除B站直播间播放器 // @namespace https://blog.chrxw.com // @version 1.1 // @description 去除B站多余的直播播放器（滑稽） // @author Chr_ // @include https://live.bilibili.com/* // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // ==/UserScript== (() => { "use strict"; //去除播放器的开关 let VEnable = localStorage.getItem("VEnable") === "true"; if (VEnable) { setTimeout(() => { document.getElementById("live-player").remove(); }, 3000); } let btnArea = document.querySelector(".right-ctnr"); let btn = document.createElement("button"); btn.textContent = VEnable ? "恢复播放器" : "移除播放器"; btn.addEventListener("click", () => { VEnable = !VEnable; localStorage.setItem("VEnable", VEnable); btn.textContent = VEnable ? "恢复播放器" : "移除播放器"; if (VEnable) { document.getElementById("live-player").remove(); } else { location.reload(); } }); btnArea.insertBefore(btn, btnArea.children[0]); })(); 第二个脚本 - SteamDB CN.js 脚本链接： https://github.com/chr233/GM_Scripts/blob/master/SteamDB/SteamDB_CN.js 脚本功能： 汉化SteamDB 源码： // ==UserScript== // @name:zh-CN SteamDB汉化 // @name SteamDB_CN // @namespace https://blog.chrxw.com // @supportURL https://blog.chrxw.com/scripts.html // @contributionURL https://afdian.net/@chr233 // @version 1.32 // @description SteamDB汉化插件 // @description:zh-cn SteamDB汉化插件 // @author Chr_ // @match https://steamdb.info/* // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // @resource data https://cf.chrxw.com/SteamDB/SteamDB_CN.json // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_registerMenuCommand // ==/UserScript== (() => { "use strict"; //是否打开调试模式（使用自定义汉化规则而不是在线规则） const DEBUG = localStorage["dbg_mode"] == "开"; //是否在控制台输出未汉化文本 const OUTPUT = localStorage["out_word"] == "开"; GM_registerMenuCommand(`调试汉化文本: 【${DEBUG ? "开" : "关"}】`, () => { window.localStorage["dbg_mode"] = DEBUG ? "关" : "开"; window.location.reload(); }); GM_registerMenuCommand(`在控制台输出未匹配文本: 【${OUTPUT ? "开" : "关"}】`, () => { window.localStorage["out_word"] = OUTPUT ? "关" : "开"; window.location.reload(); }); document.querySelector("html").setAttribute("lang", "zh-CN"); let Locales; if (DEBUG) { const template = `{"DOC":{"更新时间":"调试模式","贡献名单":["调试模式"]},\n"STATIC":\n{\n\n},\n"INPUT":\n{\n\n},\n"LABEL":\n{\n\n},\n"DYNAMIC":\n{\n\n}\n}`; const box = document.createElement("div"); box.className = "sdc"; const text = document.createElement("textarea"); box.appendChild(text); const action = document.createElement("div"); action.className = "sdc-links"; box.appendChild(action); const btnSave = document.createElement("a"); btnSave.innerText = "💾 保存并应用"; btnSave.addEventListener("click", () => { const raw = text.value.trim(); if (!raw) { alert("翻译文本不能为空!!!"); } else { try { JSON.parse(raw); window.localStorage["sdb_lang"] = raw; window.location.reload(); } catch (e) { alert("翻译文本不是有效的JSON格式!!!"); } } }); action.appendChild(btnSave); const btnReset = document.createElement("a"); btnReset.textContent = "🗑️ 清空文本"; btnReset.addEventListener("click", () => { window.localStorage["sdb_lang"] = template; window.location.reload(); }); action.appendChild(btnReset); const btnOnline = document.createElement("a"); btnOnline.textContent = "📄 当前在线文本"; btnOnline.addEventListener("click", () => { if (confirm("替换为在线版本后当前所做修改将会丢失, 确定要继续吗?")) { text.value = GM_getResourceText("data"); } }); action.appendChild(btnOnline); const about = document.createElement('a'); about.href = "https://blog.chrxw.com" about.innerText = "🔗 By Chr_ © 2022"; action.appendChild(about); const father = document.getElementById("main"); father.insertBefore(box, father.firstChild); const customLang = window.localStorage["sdb_lang"] ?? template; text.value = customLang; Locales = JSON.parse(customLang); } else { Locales = JSON.parse(GM_getResourceText("data")); } //计时 const Start = new Date().getTime(); {//静态元素 for (const [css, dic] of Object.entries(Locales.STATIC)) { if (OUTPUT) { console.log(`〖${css}〗`); } const elements = document.querySelectorAll(css); if (elements.length > 0) { for (let i = 0; i < elements.length; i++) { const element = elements[i]; if (element.childElementCount === 0) {//节点内部无其他元素 const raw = element.innerText?.trim(); if (!raw || raw.length <= 2) { continue; } const txt = dic[raw]; if (txt) { element.innerText = txt; } else if (OUTPUT) { console.log(`"${raw}": "",`); } } else {//节点内部有其他元素 const nodes = element.childNodes; for (let j = 0; j < nodes.length; j++) { const node = nodes[j]; if (node.nodeType === Node.TEXT_NODE) { const raw = node.textContent?.trim(); if (!raw || raw.length <= 2) { continue; } const txt = dic[raw]; if (txt) { node.textContent = txt; } else if (OUTPUT) { console.log(`"${raw}": "",`); } } } } } } else { if (OUTPUT) { console.warn(`CSS选择器未匹配到任何元素: ${css}`); } } } } {//输入框 const inputs = Locales.INPUT; if (OUTPUT) { console.log("〖输入框〗"); } const elements = document.querySelectorAll("input"); for (let i = 0; i < elements.length; i++) { const element = elements[i]; const raw = element.placeholder; if (!raw) { continue; } const txt = inputs[raw]; if (txt) { element.placeholder = txt; } else if (OUTPUT) { console.log(`"${raw}": "",`); } } } {//悬浮提示 const labels = Locales.LABEL; if (OUTPUT) { console.log("〖提示文本〗"); } const elements = document.querySelectorAll("*[aria-label]"); for (let i = 0; i < elements.length; i++) { const element = elements[i]; const raw = element.getAttribute("aria-label"); if (!raw) { continue; } const txt = labels[raw]; if (txt) { element.setAttribute("aria-label", txt); } else if (OUTPUT) { console.log(`"${raw}": "",`); } } } const { script: { version } } = GM_info; //const version = GM_info.script.version; const { DOC: { "更新时间": update, "贡献名单": contribution } } = Locales; const End = new Date().getTime(); // 统计耗时 console.log("执行耗时", `${End - Start} ms`); console.log("================================="); console.log(`插件版本: ${version}`); console.log(`更新时间: ${update}`); console.log(`贡献名单: ${contribution.join(", ")}`); console.log("================================="); console.log("迷茫同学:\n『没有恶意 请问直接用谷歌翻译整个网页不香吗』") // 添加按钮 const headerUl=document.querySelector(".header-menu-container>div>ul:nth-child(1)"); const footerUl=document.querySelector(".footer-container>div>ul:nth-child(1)"); const scriptLink = document.createElement("li"); scriptLink.innerHTML = `SteamDB 汉化 V${version}`; headerUl.appendChild(scriptLink); footerUl.appendChild(scriptLink.cloneNode(true)); // 添加样式 GM_addStyle(` .tabnav-tabs > a { min-width: 80px; } .sdc { display: flex; } .sdc > textarea { width: 100%; height: 200px; min-height: 200px; resize: vertical; } .sdc > div.sdc-links { width: 150px; margin: 5px; } .sdc > div.sdc-links > a { width: 100%; margin-bottom: 10px; background-color: #213145; color: white; font-size: 12px; border-radius: 0px; display: inline-block; height: 30px; } .sdc > div.sdc-links > a:last-child { width: 100%; margin-top: 30px; margin-bottom: 0px; color: #67c1f5; background-color: #273b4b; } `); })(); 油猴脚本基本构成 元信息 记录脚本的额外信息，供油猴插件以及 GreasyFork 使用 // ==UserScript== // @name:zh-CN 脚本名称(可选，可以使用语言代码来支持多语言) // @name 脚本名称 // @namespace 命名空间 // @version 脚本版本 // @description 脚本描述 // @description:zh-cn 脚本描述(可选，可以使用语言代码来支持多语言) // @author 作者 // @include 脚本生效域名 // @match 脚本生效域名 // @license 共享协议 // @icon 脚本图标(可选) // @resource 外部资源链接，以及访问名称(可选) // @grant 申请额外权限(可选) // ==/UserScript== 脚本正文 略
前言 项目演示: BV1Sy4y1u7s8 固件源码: WiFi_VFD 配置工具源码: WiFi_VFD_tools 默认连接密码为CHR233，AP模式热点名为WIFI_VFD_xxxxxx，AP密码为CHRXWCHRXW 注意事项 API请求方法不限，推荐使用GET方法，只接受位于Params的参数 未给定的参数均视为 【“”】或【0】 文档性API 所有文档性API均不需要身份验证 / 说明 返回在线文档地址 请求参数 无需参数 返回值示例 online doc: https://blog.chrxw.com/archives/1970/01/01/1569.html /version 说明 返回固件版本号和编译日期 请求参数 无需参数 返回值示例 Firmware version: 1.7.0 Build date: May 7 2021 23:59:18 /test 说明 固定返回 success , 用于检查固件是否正常 请求参数 无需参数 返回值示例 success 功能性API 所有功能性API均需要提供一个名为【k】的连接密码参数，如果密码错误则返回【403】错误 如果设备的连接密码为空，代表不需要身份验证，传入任意的【k】或者不传入【k】都能通过身份验证 设备重启后会自动从闪存读取上一次保存的设置 为了降低闪存的读写损耗，默认不会将修改的设置写入闪存，如果需要保存此次设置，可以传入名为【f】的任意非空值作为参数 例如 http://....../set/mode?m=1&f=1 所有功能性API如果调用成功均返回 success, 后文均省略返回值 功能性API - 通用 /set/mode 说明 设置当前模式 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" m 模式代码 1-3 【1】:时钟模式, 2:文字模式, 3: 进度条模式 必须 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/wifi 说明 设置WiFi连接，无需【f】参数 如果测试连接成功会自动写入闪存并自动重启 如果测试连接失败则会使用上一次的设置 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| |s|WiFi SSID|文本|WiFi的名称|必须| |p|WiFi 密码|文本|WiFi的密码|必须| /set/password 说明 设置设备的连接密码，默认连接密码为【CHR233】，无需【f】参数 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| |kn|新的连接密码|文本|要设置的连接密码|""| /set/bright 说明 设置显示屏亮度 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" b 显示屏亮度 0-255 显示屏亮度，0:完全关闭显示屏，255:最高亮度 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/reboot 说明 重启设备 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| /set/reset 说明 格式化内置闪存，清除所有设置 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| 功能性API - 模式1 - 时钟模式 调用该模式的API后, 会自动切换到模式2, 但是对模式的修改不会写入闪存 /set/time/config 说明 设置时钟的样式 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" h 24小时 布尔 【""/0】:12小时制, 其他值:24小时制 "" a 显示上下午 布尔 【""/0】:不显示AM/PM, 其他值:显示AM/PM "" w 显示星期 布尔 【""/0】:不显示星期, 其他值:显示星期 "" f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 功能性API - 模式2 - 文字模式 调用该模式的API后, 会自动切换到模式2, 但是对模式的修改不会写入闪存 /set/text/icon 说明 控制显示屏上面一排的图标显示 参数【i】当做二进制数处理，从低到高分别对应从左到右的12个图标, 1代表点亮, 0代表熄灭 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" i 0-4095 整数 从高到低的12个二进制位代表从左到右12个图标的亮灭状态 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/text/spare 说明 单独控制显示屏下面一排某一位显示的内容, 显示的字符由VFD屏内置的字库决定, 传入的参数为要显示的字符编码 字库兼容ASCII编码, 非ASCII编码的区域存放了一些特殊符号, 这些特殊符号只能通过此API显示出来 未指定的位置 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" 1 第1位 0-255 【0】:无动作, 其他数值:修改第1位的字符编码 0 2 第2位 0-255 【0】:无动作, 其他数值:修改第2位的字符编码 0 3 第3位 0-255 【0】:无动作, 其他数值:修改第3位的字符编码 0 x 第x位 0-255 【0】:无动作, 其他数值:修改第x位的字符编码 0 10 第10位 0-255 【0】:无动作, 其他数值:修改第10位的字符编码 0 11 第11位 0-255 【0】:无动作, 其他数值:修改第11位的字符编码 0 12 第12位 0-255 【0】:无动作, 其他数值:修改第12位的字符编码 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/text/group 说明 设置需要显示的字符串, 字符串长度最大为300, 超出的部分会被截断 由于显示屏只能显示12位字符, 可以开启滚动显示模式来循环播放文本 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" w 屏显字符串 文本 要显示在屏幕上的字符串 "" f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/text/mode 说明 设置文本显示模式, 可以设置滚动显示的模式 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" m 文字显示模式 0-3 【0】:静态文本, 1:向左滚动, 2:向右滚动, 3:左右滚动 【0】 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 功能性API - 模式3 - 进度条模式 调用该模式的API后, 会自动切换到模式3, 但是对模式的修改不会写入闪存 /set/proc/config 说明 设置进度的条样式 因为屏幕只能显示12个字符, 因此只能显示4位 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" l 是否显示标签 布尔 【""/0】不显示文字标签, 其他值:显示文字标签 "" n 是否显示百分比 布尔 【""/0】不显示百分比, 其他值:显示百分比 "" i 是否启用图标进度条 布尔 【""/0】用“▮”和“ ”绘制进度条, 其他值:利用上方的图标绘制进度条(模式2的图标设定会暂时失效) "" f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/proc/value 说明 修改进度条数值 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" v 进度条百分比 0-100 进度条当前的进度 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/proc/string 说明 修改进度条标签（和模式2共用一个缓冲区） 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" w 屏显字符串 文本 只会显示前4位, 并且需要开启“显示标签”才会显示 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0
i > 宝塔面板从7.4.5开始需要强制绑定账号了，虽然可能是出于安全考虑，不过我不喜欢绑定账号，跳过的方法也很简单 文件位置：/www/server/panel/BTPanel/static/js/index.js 修改内容： 在show_force_bind函数里面写个return;强制返回即可。 用快捷键Ctrl + F5或者清除浏览器缓存即可跳过账号绑定。
前言 本文仅供学习探讨之用，如果侵犯您的权益请联系我删除。 上文 [post cid="714"/] [post cid="1347"/] 静态分析 之前解决了验证码参数hkey的计算，但是还有一种加密请求，本文就是找出他的加密方式 这是参加Roll房的请求，请求参数中并没有房间ID，猜测这个参数在加密后的请求体中。 @> 全文评论后可见 后续 [post cid="1383"/]
前言 Handsome主题又更新了，改动挺大的，还好之前有记录，摸索着又加了个性化内容。 handsome 6.0 的修改记录参见： [post cid="814"/] !> 修改前务必先备份 超链接特效 修改/usr/themes/handsome/assets/css/handsome.min.css，删除这两节： .comment-content-true a:hover,.wrapper-lg .entry-content a:hover{ color:#222;border-bottom-color:#222 } .comment-content-true a,.wrapper-lg .entry-content a{ color:#58666e;border-bottom-color:#222;border-bottom:1px solid #999;word-wrap:break-word;word-break:break-all } 自定义CSS： /*超链接特效*/ .comment-content-true a:not(.light-link):not(.post_inser_a),.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a){position:relative;margin:auto 4px;color:#23b7e5;-webkit-transition:color .3s;transition:color .3s;-webkit-perspective:600px;perspective:600px;-webkit-perspective-origin:50% 100%;perspective-origin:50% 100%;word-wrap:break-word;word-break:break-all;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;max-width:inherit;width:inherit} .comment-content-true a:not(.light-link):not(.post_inser_a):focus,.comment-content-true a:not(.light-link):not(.post_inser_a):hover,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):focus,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):hover{color:#fff} .comment-content-true a:not(.light-link):not(.post_inser_a)::after,.comment-content-true a:not(.light-link):not(.post_inser_a)::before,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::after,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::before{position:absolute;top:0;left:-4px;z-index:-1;box-sizing:content-box;padding:0 4px;width:100%;height:100%;content:''} .comment-content-true a:not(.light-link):not(.post_inser_a)::before,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::before{background-color:#23b7e5;-webkit-transition:-webkit-transform .2s;transition:transform .2s;-webkit-transition-timing-function:cubic-bezier(.7,0,.3,1);transition-timing-function:cubic-bezier(.7,0,.3,1);-webkit-transform:rotateX(90deg);transform:rotateX(90deg);-webkit-transform-origin:50% 100%;transform-origin:50% 100%} .comment-content-true a:not(.light-link):not(.post_inser_a):focus::before,.comment-content-true a:not(.light-link):not(.post_inser_a):hover::before,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):focus::before,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):hover::before{-webkit-transform:rotateX(0);transform:rotateX(0)} .comment-content-true a:not(.light-link):not(.post_inser_a)::after,.wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::after{border-bottom:1px solid #23b7e5} 如果使用版权提示插件，链接有可能会超出提示框，添加以下css解决： .content-copyright {overflow: hidden} 自定义左侧栏 !> 有一定风险，修改前一定要先备份 文件路径/usr/themes/handsome/component/aside.php，大概在85行~212行，替换 到<?php endif; ?>之间的内容： <?php _me("导航") ?> <?php $hideHomeItem = false; if (!empty(Typecho_Widget::widget('Widget_Options')->asideItems)) { $json = '[' . Utils::remove_last_comma(Typecho_Widget::widget('Widget_Options')->asideItems) . ']'; $asideItems = json_decode($json); $asideItemsOutput = ""; foreach ($asideItems as $asideItem) { @$itemName = $asideItem->name; @$itemStatus = $asideItem->status; @$itemSub = $asideItem->sub; if ($itemName === 'home' && strtoupper($itemStatus) === 'HIDE') { $hideHomeItem = true; continue; //本次循环结束，不再执行下面内容 } $haveSub = false; $subListHtml = ""; // print_r($itemSub); if (is_array($itemSub)) { $haveSub = true; $subListHtml .= ' '; foreach ($itemSub as $subItem) { $subListHtml .= Content::returnLeftItem($subItem, false, ""); } $subListHtml .= ' '; } $asideItemsOutput .= Content::returnLeftItem($asideItem, $haveSub, $subListHtml); } } ?> <?php if (!$hideHomeItem) : ?> <a href="<?php $this->options->rootUrl(); ?>/" class="auto"> <?php _me("博客首页") ?> <?php endif; ?> <?php echo @$asideItemsOutput ?> <?php if (@!in_array('component', $this->options->asideSetting)) : ?> <?php _me("一级分类") ?> <li <?php echo $class; ?>> <?php _me("更多分类") ?> <?php _me("分类") ?> <?php _me("二级分类") ?> <?php _me("页面") ?> <a data-no-instant> <?php _me("页面") ?> <?php _me("友链墙") ?> <a data-no-instant> <?php _me("友链") ?> <?php $mypattern1 = " {name} "; Handsome_Plugin::output($mypattern1, 0, "ten"); ?> <?php _me("独立页面") ?> <?php endif; ?> 导航模板： <?php _me("名称") ?> 自定义光标 √> 图标素材下载：链接 解压后上传服务器，nomral.cur是默认光标，link.cur是手掌光标。 以下内容放进自定义CSS里，路径自行修改。 /*自定义光标*/ *{ cursor:url(/usr/customize/normal.cur),auto!important } a,a *,button,button *,div.OwO-logo,div.OwO-logo *,div.panel-collapse,div.panel-collapse *,li.OwO-item,ul.OwO-packages *{ cursor:url(/usr/customize/link.cur),auto!important } 增加滑稽表情，微调表情栏 √> 下载链接：改版滑稽表情包.zip 食用方法： 下载表情包以后先解压 把huaji文件夹丢进/usr/themes/handsome/assets/img/emotion/里 用OwO.json替换掉/usr/themes/handsome/usr/OwO.json 如果嫌表情太大，可以在自定义CSS里加上这些： img[class^="emotion-"] {max-height: 3em !important} * 去除烦人的`OwO`标题： 打开`/usr/themes/handsome/assets/js/features/OwO.min.js`，删除这句： ```javascript OwO 404页面添加返回按钮 打开/usr/themes/handsome/404.php 在第95行左右，把 后面插入： <?php _me("返回") ?> <a href="<?php $this->options->rootUrl(); ?>" class="list-group-item"> <?php _me("首页") ?> Mac风格代码框 √> 插件下载&食用方法：链接 稍微改了下BlackMac的主题，去除了阴影，然后让复制按钮和代码类型一直显示。 文件路径：/usr/plugins/CodePrettify/static/styles/BlackMac.css code[class*=language-],pre[class*=language-]{color:#f8f8f2;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:2;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;font-size:13px}pre[class*=language-]{padding:.5em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#21252a}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#997f66}.token.punctuation{opacity:.7}.namespace{opacity:.7}.token.boolean,.token.constant,.token.number,.token.property,.token.symbol,.token.tag{color:#d1939e}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#bce051}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f4b73d}.token.atrule,.token.attr-value,.token.keyword{color:#d1939e}.token.important,.token.regex{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.deleted{color:red}pre.line-numbers{padding-left:3.3em;counter-reset:linenumber}pre.line-numbers>code{white-space:inherit font-size: 15px}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:0;width:3em;letter-spacing:-1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.9em;text-align:right;background:#272c33}div.code-toolbar{font-size:100%;border-radius:4px;position:relative;padding-top:30px;background-color:#161616;margin:20px 0 20px 0}.code-toolbar:before{content:" ";position:absolute;-webkit-border-radius:50%;border-radius:50%;background:#fc625d;width:11px;height:11px;left:10px;top:10px;z-index:2}div.code-toolbar>.toolbar{padding-right:.4em;position:absolute;top:.09em;right:.2em;width:100%;text-align:center}div.code-toolbar>.toolbar .toolbar-item{padding:0 1px 1px 3px;display:inline-block}div.code-toolbar>.toolbar button{cursor:pointer;position:absolute;background-color:transparent;right:2px;top:4px;font-size:12px;line-height:12px;padding:3px 5px;border:none;color:#fff}div.code-toolbar>.toolbar:hover{text-decoration:none!important}div.code-toolbar>.div.code-toolbar>.toolbar button,div.code-toolbar>.toolbar span{font-family:Ubuntu,sans-serif;font-weight:700;font-size:.9em;color:#fff}#post-content .code-block-fullscreen{padding-top:32px;position:fixed;width:80vw;height:80vh;min-height:80vh;top:0;left:0;right:0;bottom:0;margin:auto;z-index:9999999;animation:elastic 1s;overflow:hidden;background:#21252a}.code-block-fullscreen code{--widthA:100%;--widthB:calc(var(--widthA) - 30px);height:var(--widthB);min-height:99%;overflow-y:scroll;height:100%}.code-block-fullscreen-html-scroll{overflow:hidden}.shelter{width:6.5px;height:6.5px;z-index:100;background:#313238;position:absolute;bottom:0;right:0}.max-img::-webkit-scrollbar-track-piece{background:#eee}.max-img::-webkit-scrollbar{width:8px;height:6px}.max-img::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}.max-img::-webkit-scrollbar-thumb:hover{background-color:#bbb}pre::-webkit-scrollbar{width:8px;height:6px}pre::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}pre::-webkit-scrollbar-thumb:hover{background-color:#bbb}code::-webkit-scrollbar{width:6px;height:6px}code::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}code::-webkit-scrollbar-thumb:hover{background-color:#bbb}html::-webkit-scrollbar-track-piece{background:#eee}html::-webkit-scrollbar{width:8px;height:6px}html::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}html::-webkit-scrollbar-thumb:hover{background-color:#bbb} 复制后提示 (与5.3.1相同) √> layer下载地址：链接 解压后把layer文件夹丢进/usr/customize/下 添加自定义javascript document.body.oncopy = function() {layer.msg('复制成功,转载请保留原文链接！');}; 添加尾部HTML代码： 其他微调 添加到自定义CSS里： /* 右侧栏气泡颜色加深 */ div.post-tags>a,aside.asideBar .badge{background-color:#777} /* 时光机圆形头像 */ .img-square {border-radius: 50%;} .list-group-item .thumb-sm .img-square {border-radius: 5px;}
前言 记录一下本博客针对Handsome 6.0主题的修改 handsome 7.X 的修改记录参见： [post cid="1315"/] !> 修改前务必备份 博客图标修改 feathericon图标修改方法：搜索data-feather，把后面的值改掉即可 顶部图标： 文件路径：/usr/themes/handsome/component/headnav.php 图标(从左往右)：music，message-square，log-in 右栏图标： 文件路径：/usr/themes/handsome/component/sidebar.php 我使用的图标： 图标1(从左往右)：thumbs-up，message-square，coffee 图标2(从上往下)：archive，message-square，calendar，activity 右侧悬浮按钮： 文件路径：/usr/themes/handsome/component/sidebar.php，/usr/themes/handsome/component/footer.php 图标(从上往下)：list，arrow-up 原图标改成feathericon以后会有轻微的位移，所以在自定义CSS里修正： /*右侧悬浮按钮*/ .pos-abt svg { margin-top: 2px; } 超链接特效 (与5.3.1相同) 修改/usr/themes/handsome/assets/css/handsome.min.css，删掉下面的内容： .comment-content-true a:hover,.wrapper-lg .entry-content a:hover{ color:#222;border-bottom-color:#222 } .comment-content-true a,.wrapper-lg .entry-content a{ color:#58666e;border-bottom-color:#222;border-bottom:1px solid #999;word-wrap:break-word;word-break:break-all } 自定义CSS： /*超链接特效*/ .comment-content-true a:not(.light-link):not(.post_inser_a), .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a) { position: relative; margin: auto 4px; color: #23b7e5; -webkit-transition: color 0.3s; transition: color 0.3s; -webkit-perspective: 600px; perspective: 600px; -webkit-perspective-origin: 50% 100%; perspective-origin: 50% 100%; word-wrap: break-word; word-break: break-all; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-width: inherit; width: inherit; } .comment-content-true a:not(.light-link):not(.post_inser_a):hover, .comment-content-true a:not(.light-link):not(.post_inser_a):focus, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):hover, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):focus { color: #fff; } .comment-content-true a:not(.light-link):not(.post_inser_a)::before, .comment-content-true a:not(.light-link):not(.post_inser_a)::after, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::before, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::after { position: absolute; top: 0; left: -4px; z-index: -1; box-sizing: content-box; padding: 0 4px; width: 100%; height: 100%; content: ''; } .comment-content-true a:not(.light-link):not(.post_inser_a)::before, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::before { background-color: #23b7e5; -webkit-transition: -webkit-transform 0.2s; transition: transform 0.2s; -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); -webkit-transform: rotateX(90deg); transform: rotateX(90deg); -webkit-transform-origin: 50% 100%; transform-origin: 50% 100%; } .comment-content-true a:not(.light-link):not(.post_inser_a):hover::before, .comment-content-true a:not(.light-link):not(.post_inser_a):focus::before, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):hover::before, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a):focus::before { -webkit-transform: rotateX(0deg); transform: rotateX(0deg); } .comment-content-true a:not(.light-link):not(.post_inser_a)::after, .wrapper-lg .entry-content a:not(.light-link):not(.post_inser_a)::after { border-bottom: 1px solid #23b7e5; } 自定义光标 (与5.3.1相同) 炫彩鼠标插件下载【链接】 因为这个插件不支持PJAX，效果不好，所以我把光标资源提取出来手写了个CSS。 以下内容放进自定义CSS里，光标路径改对，nomral.cur是默认光标，link.cur是手掌光标。 /*自定义光标*/ *{ cursor: url("/usr/customize/normal.cur"),auto !important; } a,a *,button,button *,.OwO-item,.OwO-logo,.OwO-logo *,.OwO-packages *{ cursor: url("/usr/customize/link.cur"),auto !important; } 修改版滑稽表情，微调表情栏 (与5.3.1相同) 打开/usr/themes/handsome/assets/js/features/OwO.min.js 删除这句： OwO 我个人给每个表情加上了注释，略微修改了顺序。 √> 下载链接：改版滑稽表情包.zip 食用方方法： 解压 把huaji文件夹丢进/usr/themes/handsome/usr/img/emotion/里 用OwO.json替换掉/usr/themes/handsome/usr/OwO.json 如果嫌表情太大，可以在自定义CSS里加上 img[class^="emotion-"] { max-height: 3em !important } 404页面按钮修改 打开/usr/themes/handsome/404.php 在第132行左右，把 到 之间改成下面这样： <?php _me("返回") ?> <a href="<?php $this->options->rootUrl(); ?>" class="list-group-item"> <?php _me("首页") ?> <?php if (!@in_array('hideLogin',$this->options->featuresetup)): ?> <a href="<?php $this->options->rootUrl(); ?>/admin/login.php" class="list-group-item"> <?php _me("登录") ?> <a href="<?php $this->options->rootUrl(); ?>/admin/register.php" class="list-group-item"> <?php _me("注册") ?> <?php endif; ?> ## Mac风格代码框 (与5.3.1相同) 插件下载&食用方法：【[链接](https://www.xcnte.com/archives/523/)】 略微修改了黑色主题的css，减少了代码框的外阴影，微移了复制按钮的位置 文件路径：`/usr/plugins/CodePrettify/static/styles/BlackMac.css` ```css code[class*="language-"],pre[class*="language-"]{color:#f8f8f2;background:#313238;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:2;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;}pre[class*="language-"]{padding:.5em;margin:.5em 0;overflow:auto;}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#21252a}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999;}.token.punctuation{color:#ccc;}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a;}.token.function-name{color:#6196cc;}.token.boolean,.token.number,.token.function{color:#f08d49;}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555;}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd;}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699;}.token.operator,.token.entity,.token.url{color:#67cdcc;}.token.important,.token.bold{font-weight:bold;}.token.italic{font-style:italic;}.token.entity{cursor:help;}.token.inserted{color:green;}pre.line-numbers{padding-bottom:.8em;padding-left:3.3em;counter-reset:linenumber}pre.line-numbers>code{white-space:inherit font-size:15px}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-.1em;width:3em;letter-spacing:-1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.9em;text-align:right;background:#272c33}div.code-toolbar{font-size:100%;border-radius:4px;position:relative;box-shadow:0 0 10px 1px rgba(0,0,0,.4);padding-top:30px;background-color:#161616;margin:20px 4 20px 4}.code-toolbar:before{content:" ";position:absolute;-webkit-border-radius:50%;border-radius:50%;background:#fc625d;width:11px;height:11px;left:10px;top:10px;-webkit-box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;z-index:2}div.code-toolbar>.toolbar{padding-right:.4em;position:absolute;top:.09em;right:0;width:100%;text-align:center;}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar>.toolbar .toolbar-item{padding:0 1px 1px 3px;display:inline-block}div.code-toolbar>.toolbar button{cursor:pointer;transition:all .3s;position:absolute;background-color:transparent;right:4px;top:4px;font-size:12px;line-height:12px;padding:3px 5px;border:1px solid;border-radius:6px;opacity:0;color:#FFF;}div.code-toolbar:hover button{opacity:1;}div.code-toolbar>.toolbar:hover{text-decoration:none!important}div.code-toolbar>.div.code-toolbar>.toolbar button,div.code-toolbar>.toolbar span{font-family:'Ubuntu',sans-serif;font-weight:bold;font-size:.9em;opacity:0;color:#FFF;}div.code-toolbar:hover .toolbar span{opacity:1;}#post-content .code-block-fullscreen{padding-top:32px;position:fixed;width:80vw;height:80vh;min-height:80vh;top:0;left:0;right:0;bottom:0;margin:auto;z-index:9999999;box-shadow:0 0 20px 0 rgba(255,255,255,.4);animation:elastic 1s;overflow:hidden;background:#21252a;}.code-block-fullscreen code{--widthA:100%;--widthB:calc(var(--widthA) - 30px);height:var(--widthB);min-height:99%;overflow-y:scroll;height:100%;}.code-block-fullscreen-html-scroll{overflow:hidden;}.shelter{width:6.5px;height:6.5px;z-index:100;background:#313238;position:absolute;bottom:0;right:0;}.max-img::-webkit-scrollbar-track-piece{background:#eee}.max-img::-webkit-scrollbar{width:8px;height:6px}.max-img::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}.max-img::-webkit-scrollbar-thumb:hover{background-color:#bbb}pre::-webkit-scrollbar-track-piece{}pre::-webkit-scrollbar{width:8px;height:6px}pre::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}pre::-webkit-scrollbar-thumb:hover{background-color:#bbb}code::-webkit-scrollbar-track-piece{}code::-webkit-scrollbar{width:6px;height:6px}code::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}code::-webkit-scrollbar-thumb:hover{background-color:#bbb}html::-webkit-scrollbar-track-piece{background:#eee}html::-webkit-scrollbar{width:8px;height:6px}html::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}html::-webkit-scrollbar-thumb:hover{background-color:#bbb} 复制后提示 (与5.3.1相同) 下载layer【链接】 解压后把layer文件夹丢进/usr/themes/handsome/assets/js/下 在主题外观设置里添加自定义javascriptdocument.body.oncopy = function() {layer.msg('复制成功,转载请保留原文链接！');}; 赞赏图标动画 (与5.3.1相同) 放进自定义CSS里： /*赞赏按钮动画*/ .btn-pay { border-radius: 15px 5px; transition: border-radius .1s } .btn-pay:hover { border-radius: 5px 15px; transition: border-radius .2s } 微调底栏 添加到自定义CSS里： /*底栏*/ .wrapper { padding: 10px } 其他微调 /* 时光机 */ .img-square { border-radius: 50%; } .list-group-item .thumb-sm .img-square { border-radius: 5px; } /* TAB部件 */ .post_tab li.active>a { background-color: #ebf9fd !important; } /* 其他文章部件 */ div.preview { transition: all 0.3s; } div.preview:hover { box-shadow: 0px 0px 10px rgba(30, 30, 30, 0.2); }
前言 开站以来，各种嗅探就没有断过，后台还是要伪装一下增加一点安全性。 修改后台地址 进入网站根目录 删除 install 文件夹(里面是安装用的文件，留着没什么用) 重命名 admin 文件夹为一个比较难猜的名字，比如 houtai12138 打开 config.inc.php ，大概在20行，把 /admin/ 改成后台文件夹的名字 修改前：/** 后台路径(相对路径) */ define('__TYPECHO_ADMIN_DIR__', '/admin/'); 修改后( houtai12138 改成新的后台地址)： /** 后台路径(相对路径) */ define('__TYPECHO_ADMIN_DIR__', '/houtai12138/'); 设置“蜜罐” 下载地址：链接 在线演示：链接 解压后把admin文件夹放到网站根目录即可，可以按需修改。 不要忘记修改标题和最后返回主页的链接地址 不要忘记修改标题和最后返回主页的链接地址 不要忘记修改标题和最后返回主页的链接地址 输入用户名为root，密码为passw0rd时会跳到百度搜索页，其他时候会弹个窗提示密码错误 ::huaji:pangcihuaji:: index.php： 登录到Chr_小屋 - Chr_小屋 - Powered by Typecho Typecho 用户名 密码 登录 下次自动登录 返回首页
前言 Handsome主题现已更新至7.1，本文章仅作存档之用。 handsome 7.X 修改记录请前往： [post cid="1315"/] handsome 6.0 修改记录请前往： [post cid="814"/] 原文： 使用Handsome主题一年了，主题从5.1升级到5.3.1了，最近刚好迁移了网站，也顺带着把整个博客做了一些改动，写这篇主要方便我升级主题的时候还能保留DIY的特性。 超链接特效 修改/usr/themes/handsome/assets/css/handsome.min.css，删掉下面的内容： comment-content-true a:hover,.wrapper-lg .entry-content a:hover{ color:#222;border-bottom-color:#222 } .comment-content-true a,.wrapper-lg .entry-content a{ color:#58666e;border-bottom-color:#222;border-bottom:1px solid #999;word-wrap:break-word;word-break:break-all } 自定义CSS： /*超链接特效*/ .comment-content-true a:not(.light-link), .wrapper-lg .entry-content a:not(.light-link) { position: relative; margin: auto 4px; color: #23b7e5; -webkit-transition: color 0.3s; transition: color 0.3s; -webkit-perspective: 600px; perspective: 600px; -webkit-perspective-origin: 50% 100%; perspective-origin: 50% 100%; word-wrap: break-word; word-break: break-all; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-width: inherit; width: inherit; } .comment-content-true a:not(.light-link):hover, .comment-content-true a:not(.light-link):focus, .wrapper-lg .entry-content a:not(.light-link):hover, .wrapper-lg .entry-content a:not(.light-link):focus { color: #fff; } .comment-content-true a:not(.light-link)::before, .comment-content-true a:not(.light-link)::after, .wrapper-lg .entry-content a:not(.light-link)::before, .wrapper-lg .entry-content a:not(.light-link)::after { position: absolute; top: 0; left: -4px; z-index: -1; box-sizing: content-box; padding: 0 4px; width: 100%; height: 100%; content: ''; } .comment-content-true a:not(.light-link)::before, .wrapper-lg .entry-content a:not(.light-link)::before { background-color: #23b7e5; -webkit-transition: -webkit-transform 0.2s; transition: transform 0.2s; -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); -webkit-transform: rotateX(90deg); transform: rotateX(90deg); -webkit-transform-origin: 50% 100%; transform-origin: 50% 100%; } .comment-content-true a:not(.light-link):hover::before, .comment-content-true a:not(.light-link):focus::before, .wrapper-lg .entry-content a:not(.light-link):hover::before, .wrapper-lg .entry-content a:not(.light-link):focus::before { -webkit-transform: rotateX(0deg); transform: rotateX(0deg); } .comment-content-true a:not(.light-link)::after, .wrapper-lg .entry-content a:not(.light-link)::after { border-bottom: 1px solid #23b7e5; } .content-copyright { overflow: hidden; } 自定义光标 炫彩鼠标插件下载【链接】 因为这个插件不支持PJAX，效果不好，所以我把光标资源提取出来手写了个CSS。 以下内容放进自定义CSS里，光标路径改对，nomral.cur是默认光标，link.cur是手掌光标。 /*自定义光标*/ *{ cursor: url("/usr/customize/normal.cur"),auto !important; } a,a *,button,button *,.OwO-item,.OwO-logo,.OwO-logo *,.OwO-packages *{ cursor: url("/usr/customize/link.cur"),auto !important; } 时光机UA显示 引入字体 添加到自定义HTML里 修改/usr/themes/handsome/libs/UA.php /* 第148行 */ public function returnOS(){ $ua = $this->ua; $title = "未知浏览器"; $icon = "fa fa-question-circle"; if (preg_match('/win/i', $ua)) { $icon ="fa fa-windows"; if (preg_match('/Windows NT 6.1/i', $ua)) { $title = "Windows 7"; //$icon = "iconfont icon-win"; }elseif (preg_match('/Windows 98/i', $ua)) { $title = "Windows 98"; //$icon = "iconfont icon-win2"; }elseif (preg_match('/Windows NT 5.0/i', $ua)) { $title = "Windows 2000"; //$icon = "iconfont icon-win2"; }elseif (preg_match('/Windows NT 5.1/i', $ua)) { $title = "Windows XP"; //$icon = "iconfont icon-win"; }elseif (preg_match('/Windows NT 5.2/i', $ua)) { if (preg_match('/Win64/i', $ua)) { $title = "Windows XP 64 bit"; } else { $title = "Windows Server 2003"; } //$icon = 'iconfont icon-win'; }elseif (preg_match('/Windows NT 6.0/i', $ua)) { $title = "Windows Vista"; //$icon = "fa fa-windows"; }elseif (preg_match('/Windows NT 6.2/i', $ua)) { $title = "Windows 8"; //$icon = "iconfont icon-win8"; }elseif (preg_match('/Windows NT 6.3/i', $ua)) { $title = "Windows 8.1"; //$icon = "iconfont icon-win8"; }elseif (preg_match('/Windows NT 10.0/i', $ua)) { $title = "Windows 10"; //$icon = "iconfont icon-win3"; }elseif (preg_match('/Windows Phone/i', $ua)) { $matches = explode(';',$ua); $title = $matches[2]; //$icon = "iconfont icon-winphone"; $icon = "fa fa-mobile"; } } elseif (preg_match('#iPod.*.CPU.([a-zA-Z0-9.( _)]+)#i', $ua, $matches)) { $title = "iPod ";//.$matches[1] //$icon = "iconfont icon-ipod"; $icon = "fa fa-apple"; } elseif (preg_match('/iPhone OS ([_0-9]+)/i', $ua, $matches)) { $title = "Iphone ";//.$matches[1] //$icon = "iconfont icon-iphone"; $icon = "fa fa-apple"; } elseif (preg_match('/iPad; CPU OS ([_0-9]+)/i', $ua, $matches)) { $title = "iPad ";//.$matches[1] //$icon = "iconfont icon-ipad"; $icon = "fa fa-apple"; } elseif (preg_match('/Mac OS X ([0-9_]+)/i', $ua, $matches)) { if(count(explode(7,$matches[1]))>1) $matches[1] = 'Lion '.$matches[1]; elseif(count(explode(8,$matches[1]))>1) $matches[1] = 'Mountain Lion '.$matches[1]; $title = "Mac OSX"; //$icon = "iconfont icon-macosdeicon"; $icon = "fa fa-apple"; } elseif (preg_match('/Macintosh/i', $ua)) { $title = "Mac OS"; //$icon = "iconfont icon-iconmacos"; $icon = "fa fa-apple"; } elseif (preg_match('/CrOS/i', $ua)){ $title = "Google Chrome OS"; //$icon = "iconfont icon-iconchromeos"; $icon = "fa fa-chrome"; } elseif (preg_match('/Linux/i', $ua)) { $title = 'Linux'; $icon = 'fa fa-linux'; if (preg_match('/Ubuntu/i', $ua)) { $title = "Ubuntu Linux"; //$icon = "iconfont icon-ubuntu"; }elseif(preg_match('#Debian#i', $ua)) { $title = "Debian GNU/Linux"; //$icon = "iconfont icon-debian"; }elseif (preg_match('#Fedora#i', $ua)) { $title = "Fedora Linux"; //$icon = "iconfont icon-fedora"; }elseif (preg_match('/Android.([0-9. _]+)/i',$ua, $matches)) { $title= "Android"; //$icon = "iconfont icon-android"; $icon = "fa fa-android"; } } elseif (preg_match('/Android.([0-9. _]+)/i',$ua, $matches)) { $title= "Android"; //$icon = "iconfont icon-android"; $icon = "fa fa-android"; } return array("title"=>$title, "icon"=>$icon); } /** * 时光机页面ua，如果是手机设备，只显示设备类型，如果是电脑设备只显示电脑设备类型，如果是扩展发送，显示发送自「扩展」，如果是微信公众号，显示 */ public function returnTimeUa(){ if ($this->ua == "weixin" || $this->ua == "weChat"){ return array("title"=>_mt("微信公众号"), "icon"=>"fa fa-wechat"); }elseif ($this->ua == "crx"){ return array("title"=>_mt("Chrome扩展"), "icon"=>"fa fa-chrome"); }elseif ($this->ua == "qq"){ return array("title"=>_mt("QQ机器人"), "icon"=>"fa fa-qq"); }else{ return $this->returnOS(); } } } 修改/usr/themes/handsome/component/say.php /* 第78行 */ 改成这样 <i class="<?php echo $ua->returnTimeUa()['icon'] ?> m-l-sm"> 头像动画 添加到自定义CSS里 第2行的width:90px是头像整体缩放 第20行scale(1.1)，1.1是旋转后的缩放倍率，改成1就是不放大 /*头像动画*/ .thumb-lg{ width:90px } .avatar{ -webkit-transition: 0.4s; -webkit-transition: -webkit-transform 0.4s ease-out; transition: transform 0.4s ease-out; -moz-transition: -moz-transform 0.4s ease-out; } .avatar:hover{ transform: rotateZ(360deg); -webkit-transform: rotateZ(360deg); -moz-transform: rotateZ(360deg); } #aside-user span.avatar{ animation-timing-function:cubic-bezier(0,0,.07,1)!important; border:0 solid } #aside-user span.avatar:hover{ transform:rotate(360deg) scale(1.1); border-width:5px; animation:avatar .5s } 修改版滑稽表情，微调表情栏 打开/usr/themes/handsome/assets/js/features/OwO.min.js 删除这句： OwO 我个人给每个表情加上了注释，略微修改了顺序。 √> 下载链接：改版滑稽表情包.zip 食用方方法： 解压 把huaji文件夹丢进/usr/themes/handsome/usr/img/emotion/里 用OwO.json替换掉/usr/themes/handsome/usr/OwO.json 如果嫌表情太大，可以在自定义CSS里加上 img[class^="emotion-"] { max-height: 3em !important } 404页面自动返回 打开/usr/themes/handsome/404.php 在第130行左右，在 404 后面添加： 10秒后自动返回··· 立刻返回 然后把下面这段放到最后的 前 把下面的删掉就能隐藏注册按钮(个人博客不需要) <a href="<?php $this->options->rootUrl(); ?>/admin/register.php" class="list-group-item"> <?php _me("注册") ?> 在侧栏添加响应耗时 打开Web根目录/usr/themes/handsome/functions.php 把下面的函数添加到合适的位置 function timer_start() { global $timestart; $mtime = explode( ' ', microtime() ); $timestart = $mtime[1] + $mtime[0]; return true; } timer_start(); function timer_stop( $display = 0, $precision = 3 ) { global $timestart, $timeend; $mtime = explode( ' ', microtime() ); $timeend = $mtime[1] + $mtime[0]; $timetotal = number_format( $timeend - $timestart, $precision ); $r = $timetotal < 1 ? $timetotal * 1000 . " ms" : $timetotal . " s"; if ( $display ) { echo $r; } return $r; } 然后打开 打开Web根目录/usr/themes/handsome/component/sidebar.php 把这句话插到91行左右，最后活动的后面 <?php echo timer_stop();?><?php _me("加载用时") ?> Mac风格代码框 插件下载&食用方法：【链接】 略微修改了黑色主题的css，减少了代码框的外阴影，微移了复制按钮的位置 文件路径：/usr/plugins/CodePrettify/static/styles/BlackMac.css code[class*="language-"],pre[class*="language-"]{color:#f8f8f2;background:#313238;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:2;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;}pre[class*="language-"]{padding:.5em;margin:.5em 0;overflow:auto;}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#21252a}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999;}.token.punctuation{color:#ccc;}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a;}.token.function-name{color:#6196cc;}.token.boolean,.token.number,.token.function{color:#f08d49;}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555;}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd;}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699;}.token.operator,.token.entity,.token.url{color:#67cdcc;}.token.important,.token.bold{font-weight:bold;}.token.italic{font-style:italic;}.token.entity{cursor:help;}.token.inserted{color:green;}pre.line-numbers{padding-bottom:.8em;padding-left:3.3em;counter-reset:linenumber}pre.line-numbers>code{white-space:inherit font-size:15px}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-.1em;width:3em;letter-spacing:-1px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.9em;text-align:right;background:#272c33}div.code-toolbar{font-size:100%;border-radius:4px;position:relative;box-shadow:0 0 10px 1px rgba(0,0,0,.4);padding-top:30px;background-color:#161616;margin:20px 4 20px 4}.code-toolbar:before{content:" ";position:absolute;-webkit-border-radius:50%;border-radius:50%;background:#fc625d;width:11px;height:11px;left:10px;top:10px;-webkit-box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;box-shadow:20px 0 #fdbc40,40px 0 #35cd4b;z-index:2}div.code-toolbar>.toolbar{padding-right:.4em;position:absolute;top:.09em;right:0;width:100%;text-align:center;}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar>.toolbar .toolbar-item{padding:0 1px 1px 3px;display:inline-block}div.code-toolbar>.toolbar button{cursor:pointer;transition:all .3s;position:absolute;background-color:transparent;right:4px;top:4px;font-size:12px;line-height:12px;padding:3px 5px;border:1px solid;border-radius:6px;opacity:0;color:#FFF;}div.code-toolbar:hover button{opacity:1;}div.code-toolbar>.toolbar:hover{text-decoration:none!important}div.code-toolbar>.div.code-toolbar>.toolbar button,div.code-toolbar>.toolbar span{font-family:'Ubuntu',sans-serif;font-weight:bold;font-size:.9em;opacity:0;color:#FFF;}div.code-toolbar:hover .toolbar span{opacity:1;}#post-content .code-block-fullscreen{padding-top:32px;position:fixed;width:80vw;height:80vh;min-height:80vh;top:0;left:0;right:0;bottom:0;margin:auto;z-index:9999999;box-shadow:0 0 20px 0 rgba(255,255,255,.4);animation:elastic 1s;overflow:hidden;background:#21252a;}.code-block-fullscreen code{--widthA:100%;--widthB:calc(var(--widthA) - 30px);height:var(--widthB);min-height:99%;overflow-y:scroll;height:100%;}.code-block-fullscreen-html-scroll{overflow:hidden;}.shelter{width:6.5px;height:6.5px;z-index:100;background:#313238;position:absolute;bottom:0;right:0;}.max-img::-webkit-scrollbar-track-piece{background:#eee}.max-img::-webkit-scrollbar{width:8px;height:6px}.max-img::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}.max-img::-webkit-scrollbar-thumb:hover{background-color:#bbb}pre::-webkit-scrollbar-track-piece{}pre::-webkit-scrollbar{width:8px;height:6px}pre::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}pre::-webkit-scrollbar-thumb:hover{background-color:#bbb}code::-webkit-scrollbar-track-piece{}code::-webkit-scrollbar{width:6px;height:6px}code::-webkit-scrollbar-thumb{border-radius:6px;background-color:#777}code::-webkit-scrollbar-thumb:hover{background-color:#bbb}html::-webkit-scrollbar-track-piece{background:#eee}html::-webkit-scrollbar{width:8px;height:6px}html::-webkit-scrollbar-thumb{border-radius:4px;background-color:#cbcbcb}html::-webkit-scrollbar-thumb:hover{background-color:#bbb} 复制后提示 下载layer【链接】 解压后把layer文件夹丢进/usr/themes/handsome/assets/js/下 在主题外观设置里添加自定义javascriptdocument.body.oncopy = function() {layer.msg('复制成功,转载请保留原文链接！');}; 在自定义HTML头部或尾部里添加 赞赏图标动画 放进自定义CSS里： /*赞赏按钮动画*/ .btn-pay { border-radius: 15px 5px; transition: border-radius .1s } .btn-pay:hover { border-radius: 5px 15px; transition: border-radius .2s } 上一页下一页微调 丢进自定义CSS里： /*上一页按钮*/ li.previous a { border-radius: 5px 15px; transition: border-radius .2s } li.previous a:hover { border-radius: 15px 5px; transition: border-radius .2s } /*下一页按钮*/ li.next a { border-radius: 15px 5px; transition: border-radius .2s } li.next a:hover { border-radius: 5px 15px; transition: border-radius .2s } 评论按钮微调 丢进自定义CSS里： /*评论按钮*/ .btn-success { border-radius: 5px 5px; transition: border-radius .2s } .btn-success:hover { border-radius: 15px 15px; transition: border-radius .2s } 首页文章列表悬停上浮 放进自定义CSS里： /*首页文章列表悬停上浮*/ .blog-post .panel:not(article) { transition: all 0.3s; } .blog-post .panel:not(article):hover { transform: translateY(-5px); box-shadow: 0 4px 5px rgba(73, 90, 47, 0.47) }