@

富文本编辑器是一种所见即所得(what you see is what you get 简称 WYSIWYG)文本样式编辑器,用户在编辑器中输入内容和所做的样式修改,都会直接反映在编辑器中。

在Web端常见的有QuillTinyMCE这些开源免费的富文本编辑器,而目前.NET MAUI方面没有类似的富文本编辑器可以免费使用。

使用.NET MAUI实现一个富文本编辑器并不难,今天就来写一个

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。由于篇幅本文只展示Android平台的代码。

原理

.NET MAUI提供了编辑器控件,允许输入和编辑多行文本,虽然提供了字号,字体,颜色等控件属性,但我们无法为每个字符设置样式。我们将通过原生控件提供的范围选择器实现这一功能。

.NET MAUI提供了Handler的跨平台特性,我们将利用Handler实现所见即所得内容编辑器组件。这篇博文介绍了如何用Handler实现自定义跨平台控件,请阅读[MAUI程序设计] 用Handler实现自定义跨平台控件

在各平台中,我们将使用原生控件实现所见即所得的内容编辑器

  • Android使用SpannableString设置文本的复合样式,可以查看https://www.cnblogs.com/jisheng/archive/2013/01/10/2854088.html

  • *iOS使用NSAttributeString设置文本的复合样式,可以参考https://blog.csdn.net/weixin_44544690/article/details/124154949

创建编辑器

新建.NET MAUI项目,命名RichTextEditor

在Controls目录中创建WysiwygContentEditor,继承自Editor,用于实现所见即所得的内容编辑器

构造函数中注册HandlerChanged和HandlerChanging事件


public class WysiwygContentEditor : Editor
{
public WysiwygContentEditor()
{
HandlerChanged+=WysiwygContentEditor_HandlerChanged;
HandlerChanging+=WysiwygContentEditor_HandlerChanging;
} }

在HandlerChanged事件中,获取Handler对象,通过它访问虚拟视图和本机视图。

private void WysiwygContentEditor_HandlerChanged(object sender, EventArgs e)
{
var handler = Handler;
if (handler != null)
{
}
}

android端原生控件为AppCompatEditText,iOS端原生控件为UITextView

//Android
var platformView = handler.PlatformView as AppCompatEditText;
//iOS
var platformView = handler.PlatformView as UITextView;

不同平台的代码,通过.Net6的条件编译实现,有关条件编译的详细信息,请参考官方文档。这次实现的是Android和iOS平台,所以在代码中条件编译语句如下

#if ANDROID

//android codes
... #endif #if IOS //iOS codes
... #endif

定义

定义StyleType枚举,用于控件可以处理的文本样式更改请求类型。

  • underline:字体下划线
  • italic:字体斜体
  • bold:字体加粗
  • backgoundColor:字体背景色
  • foregroundColor:字体前景色
  • size:字体大小
public enum StyleType
{
underline, italic, bold, backgoundColor, foregroundColor, size
}

以及StyleArgs类,用于传递样式变更请求的参数

public class StyleArgs : EventArgs
{
public StyleType Style; public string Params;
public StyleArgs(StyleType style, string @params = null)
{
Style = style;
Params=@params;
}
}

定义SelectionArgs类,用于传递选择范围变更请求的参数

public class SelectionArgs : EventArgs
{
public int Start;
public int End;
public SelectionArgs(int start, int end)
{
Start = start;
End = end;
}
}

定义事件用于各平台本机代码的调用

public event EventHandler GetHtmlRequest;
public event EventHandler<string> SetHtmlRequest;
public event EventHandler<StyleArgs> StyleChangeRequested;
public event EventHandler<SelectionArgs> SelectionChangeHandler;

创建StyleChangeRequested的订阅事件以响应样式变更请求,对应不同的样式类型,调用不同的方法实现样式变更。


StyleChangeRequested =new EventHandler<StyleArgs>(
(sender, e) => {
var EditableText = platformView.EditableText; switch (e.Style)
{
case StyleType.underline:
UpdateUnderlineSpans(EditableText);
break;
case StyleType.italic:
UpdateStyleSpans(TypefaceStyle.Italic, EditableText);
break;
case StyleType.bold:
UpdateStyleSpans(TypefaceStyle.Bold, EditableText);
break;
case StyleType.backgoundColor:
UpdateBackgroundColorSpans(EditableText, Microsoft.Maui.Graphics.Color.FromArgb(e.Params));
break;
case StyleType.foregroundColor:
UpdateForegroundColorSpans(EditableText, Microsoft.Maui.Graphics.Color.FromArgb(e.Params));
break;
case StyleType.size:
UpdateAbsoluteSizeSpanSpans(EditableText, int.Parse(e.Params));
break;
default:
break;
} });

实现复合样式

选择范围

android端使用SelectionStart和SelectionEnd获取选择范围,iOS端使用SelectedRange获取选择范围

//Android

int getSelectionStart() => platformView.SelectionStart;
int getSelectionEnd() => platformView.SelectionEnd; //iOS
NSRange getSelectionRange() => platformView.SelectedRange;

字号

MAUI控件中字号使用FontSize属性单位为逻辑像素,与DPI设置相关联。

在android本机平台中,字号通过为EditableText对象设置AbsoluteSizeSpan实现,代码如下


void UpdateAbsoluteSizeSpanSpans(IEditable EditableText, int size)
{ var spanType = SpanTypes.InclusiveInclusive; EditableText.SetSpan(new AbsoluteSizeSpan(size, true), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
}

字体颜色与背景色

Android平台中,字体颜色与背景色通过为EditableText对象设置ForegroundColorSpan和BackgroundColorSpan实现

void UpdateForegroundColorSpans(IEditable EditableText, Microsoft.Maui.Graphics.Color color)
{
var spanType = SpanTypes.InclusiveInclusive;
EditableText.SetSpan(new ForegroundColorSpan(color.ToAndroid()), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
} void UpdateBackgroundColorSpans(IEditable EditableText, Microsoft.Maui.Graphics.Color color)
{
var spanType = SpanTypes.InclusiveInclusive;
EditableText.SetSpan(new BackgroundColorSpan(color.ToAndroid()), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
}

字体下划线

将选择文本选择范围内若包含下划线,则移除下划线,否则添加下划线

Android平台中通过为EditableText对象设置UnderlineSpan实现为文本添加下划线,通过RemoveSpan方法可以移除下划线,

但选择范围可能已包含下划线片段的一部分,因此移除此下划线片段后,需要重新添加下划线片段,以实现部分移除的效果


void UpdateUnderlineSpans(IEditable EditableText)
{ var underlineSpans = EditableText.GetSpans(getSelectionStart(), getSelectionEnd(), Java.Lang.Class.FromType(typeof(UnderlineSpan))); bool hasFlag = false;
var spanType = SpanTypes.InclusiveInclusive; foreach (var span in underlineSpans)
{
hasFlag = true; var spanStart = EditableText.GetSpanStart(span);
var spanEnd = EditableText.GetSpanEnd(span);
var newStart = spanStart;
var newEnd = spanEnd;
var startsBefore = false;
var endsAfter = false; if (spanStart < getSelectionStart())
{
newStart = getSelectionStart();
startsBefore = true;
}
if (spanEnd > getSelectionEnd())
{
newEnd = getSelectionEnd();
endsAfter = true;
} EditableText.RemoveSpan(span); if (startsBefore)
{
EditableText.SetSpan(new UnderlineSpan(), spanStart, newStart, SpanTypes.ExclusiveExclusive);
}
if (endsAfter)
{
EditableText.SetSpan(new UnderlineSpan(), newEnd, spanEnd, SpanTypes.ExclusiveExclusive);
}
} if (!hasFlag)
{
EditableText.SetSpan(new UnderlineSpan(), getSelectionStart(), getSelectionEnd(), spanType);
}
SetEditableText(EditableText, platformView);
}

字体加粗与斜体

Android平台中,字体粗细与斜体通过为EditableText对象设置StyleSpan实现,与设置字体下划线一样,需要处理选择范围内已包含StyleSpan的情况

TypefaceStyle提供了Normal、Bold、Italic、BoldItalic四种字体样式,粗体+斜体样式是通过组合实现的,因此需要处理样式叠加问题


void UpdateStyleSpans(TypefaceStyle flagStyle, IEditable EditableText)
{
var styleSpans = EditableText.GetSpans(getSelectionStart(), getSelectionEnd(), Java.Lang.Class.FromType(typeof(StyleSpan)));
bool hasFlag = false;
var spanType = SpanTypes.InclusiveInclusive; foreach (StyleSpan span in styleSpans)
{
var spanStart = EditableText.GetSpanStart(span);
var spanEnd = EditableText.GetSpanEnd(span);
var newStart = spanStart;
var newEnd = spanEnd;
var startsBefore = false;
var endsAfter = false; if (spanStart < getSelectionStart())
{
newStart = getSelectionStart();
startsBefore = true;
}
if (spanEnd > getSelectionEnd())
{
newEnd = getSelectionEnd();
endsAfter = true;
} if (span.Style == flagStyle)
{
hasFlag = true;
EditableText.RemoveSpan(span);
EditableText.SetSpan(new StyleSpan(TypefaceStyle.Normal), newStart, newEnd, spanType);
}
else if (span.Style == TypefaceStyle.BoldItalic)
{
hasFlag = true;
EditableText.RemoveSpan(span);
var flagLeft = TypefaceStyle.Bold;
if (flagStyle == TypefaceStyle.Bold)
{
flagLeft = TypefaceStyle.Italic;
}
EditableText.SetSpan(new StyleSpan(flagLeft), newStart, newEnd, spanType);
} if (startsBefore)
{
EditableText.SetSpan(new StyleSpan(span.Style), spanStart, newStart, SpanTypes.ExclusiveExclusive);
}
if (endsAfter)
{
EditableText.SetSpan(new StyleSpan(span.Style), newEnd, spanEnd, SpanTypes.ExclusiveExclusive);
} }
if (!hasFlag)
{
EditableText.SetSpan(new StyleSpan(flagStyle), getSelectionStart(), getSelectionEnd(), spanType);
} SetEditableText(EditableText, platformView);
}

序列化和反序列化

所见即所得的内容需要被序列化和反序列化以便存储或传输,我们仍然使用HTML作为中间语言,好在Android和iOS平台都有HTML互转的对应实现。

  • Android平台中,Android.Text.Html提供了FromHtml()和Html.ToHtml(),
  • iOS中的NSAttributedStringDocumentAttributes提供了DocumentType属性,可以设置为NSHTMLTextDocumentType,使用它初始化AttributedString或调用AttributedString.GetDataFromRange()方法实现HTML和NSAttributedString的互转。

跨平台实现

在Platform/Android目录下创建HtmlParser.Android作为Android平台序列化和反序列化的实现。

public static class HtmlParser_Android
{
public static ISpanned HtmlToSpanned(string htmlString)
{
ISpanned spanned = Html.FromHtml(htmlString, FromHtmlOptions.ModeCompact);
return spanned;
} public static string SpannedToHtml(ISpanned spanned)
{
string htmlString = Html.ToHtml(spanned, ToHtmlOptions.ParagraphLinesIndividual);
return htmlString;
}
}

在Platform/iOS目录下创建HtmlParser.iOS作为iOS平台序列化和反序列化的实现。

public static class HtmlParser_iOS
{
static nfloat defaultSize = UIFont.SystemFontSize;
static UIFont defaultFont; public static NSAttributedString HtmlToAttributedString(string htmlString)
{
var nsString = new NSString(htmlString);
var data = nsString.Encode(NSStringEncoding.UTF8);
var dictionary = new NSAttributedStringDocumentAttributes();
dictionary.DocumentType = NSDocumentType.HTML;
NSError error = new NSError();
var attrString = new NSAttributedString(data, dictionary, ref error);
var mutString = ResetFontSize(new NSMutableAttributedString(attrString)); return mutString;
} static NSAttributedString ResetFontSize(NSMutableAttributedString attrString)
{
defaultFont = UIFont.SystemFontOfSize(defaultSize); attrString.EnumerateAttribute(UIStringAttributeKey.Font, new NSRange(0, attrString.Length), NSAttributedStringEnumeration.None, (NSObject value, NSRange range, ref bool stop) =>
{
if (value != null)
{
var oldFont = (UIFont)value;
var oldDescriptor = oldFont.FontDescriptor; var newDescriptor = defaultFont.FontDescriptor; bool hasBoldFlag = false;
bool hasItalicFlag = false; if (oldDescriptor.SymbolicTraits.HasFlag(UIFontDescriptorSymbolicTraits.Bold))
{
hasBoldFlag = true;
}
if (oldDescriptor.SymbolicTraits.HasFlag(UIFontDescriptorSymbolicTraits.Italic))
{
hasItalicFlag = true;
} if (hasBoldFlag && hasItalicFlag)
{
uint traitsInt = (uint)UIFontDescriptorSymbolicTraits.Bold + (uint)UIFontDescriptorSymbolicTraits.Italic;
newDescriptor = newDescriptor.CreateWithTraits((UIFontDescriptorSymbolicTraits)traitsInt);
}
else if (hasBoldFlag)
{
newDescriptor = newDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold);
}
else if (hasItalicFlag)
{
newDescriptor = newDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Italic);
} var newFont = UIFont.FromDescriptor(newDescriptor, defaultSize); attrString.RemoveAttribute(UIStringAttributeKey.Font, range);
attrString.AddAttribute(UIStringAttributeKey.Font, newFont, range);
} }); return attrString;
} public static string AttributedStringToHtml(NSAttributedString attributedString)
{
var range = new NSRange(0, attributedString.Length);
var dictionary = new NSAttributedStringDocumentAttributes();
dictionary.DocumentType = NSDocumentType.HTML;
NSError error = new NSError();
var data = attributedString.GetDataFromRange(range, dictionary, ref error);
var htmlString = new NSString(data, NSStringEncoding.UTF8);
return htmlString;
}
}

集成至编辑器

在所见即所得编辑器中设置两个方法,一个用于获取编辑器中的内容,一个用于设置编辑器中的内容。


public void SetHtmlText(string htmlString)
{
HtmlString = htmlString;
SetHtmlRequest(this, htmlString);
} public string GetHtmlText()
{ GetHtmlRequest(this, new EventArgs());
return HtmlString;
}

在HandlerChanged事件方法中的各平台代码段中添加如下代码:

GetHtmlRequest = new EventHandler(
(sender, e) =>
{
var editor = (WysiwygContentEditor)sender;
HtmlString=HtmlParser_Android.SpannedToHtml(platformView.EditableText);
}
);
SetHtmlRequest =new EventHandler<string>(
(sender, htmlString) =>
{
platformView.TextFormatted = HtmlParser_Android.HtmlToSpanned(htmlString);
}
);

在富文本编辑器中的内容,最终会生成一个带有内联样式的HTML字符串,如下所示:

创建控件

控件由所见即所得编辑器和工具栏组成,所见即所得编辑器用于显示和编辑内容,工具栏用于设置字号、颜色、加粗、斜体、下划线

创建RichTextEditor的带有Xaml的ContentView。将所见即所得编辑器放置中央,工具栏放置在底部。

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:RichTextEditor.Controls;assembly=RichTextEditor"
x:Class="RichTextEditor.Controls.RichTextEditor">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<controls:WysiwygContentEditor MinimumHeightRequest="150"
AutoSize="TextChanges"
BackgroundColor="{StaticResource PhoneContrastBackgroundBrush}"
IsSpellCheckEnabled="false"
x:Name="MainEditor"></controls:WysiwygContentEditor>
</Grid>
</Border>
</ContentView>

工具栏内的按钮横向排列

<HorizontalStackLayout Grid.Row="3"
Spacing="5"
Margin="0,10">
<Button Text="{Binding Source={x:Reference TextSizeCollectionView}, Path=SelectedItem.Name, FallbackValue=Auto}"
Style="{StaticResource RichTextButtonStyle}"
Clicked="TextSizeButton_Clicked"
x:Name="TextSizeButton"></Button>
<Button Text="Color"
TextColor="{Binding Source={x:Reference ColorCollectionView}, Path=SelectedItem}"
Style="{StaticResource RichTextButtonStyle}"
Clicked="TextColorButton_Clicked"
x:Name="TextColorButton"></Button>
<Button Text="B"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="Bold"
x:Name="BoldButton"
Clicked="BoldButton_Clicked"></Button>
<Button Text="I"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="Italic"
x:Name="ItalicButton"
Clicked="ItalicButton_Clicked"></Button>
<Button Text="U"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="None"
x:Name="UnderLineButton"
Clicked="UnderLineButton_Clicked"></Button>
</HorizontalStackLayout>

配置两个选择器:TextSizeCollectionView为字体大小选择器,ColorCollectionView为字体颜色选择器。

当点击字体大小选择器时,弹出字体大小选择器,当点击字体颜色选择器时,弹出字体颜色选择器。

<VerticalStackLayout x:Name="OptionsLayout"
Grid.Row="2"
Spacing="5">
<CollectionView x:Name="TextSizeCollectionView"
Background="Transparent"
SelectionChanged="TextSizeCollectionView_SelectionChanged"
SelectionMode="Single"
HeightRequest="45">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="5"></LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate> <Border x:Name="TargetElement"
Style="{StaticResource SelectableLayoutStyle}"
Background="{StaticResource PhoneContrastBackgroundBrush}"
Padding="5,0">
<Label Text="{Binding Name}"
TextColor="{StaticResource PhoneForegroundBrush}"
VerticalOptions="Center"
FontSize="{Binding Value}"></Label>
</Border> </DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView> <CollectionView x:Name="ColorCollectionView"
SelectionChanged="ColorCollectionView_SelectionChanged"
SelectionMode="Single"
HeightRequest="45">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="5"></LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate> <Border x:Name="TargetElement"
Style="{StaticResource SelectableLayoutStyle}"
BackgroundColor="{Binding}"
WidthRequest="40"
HeightRequest="40"
StrokeShape="RoundRectangle 40"> </Border> </DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>

后端代码,绑定一些默认值


public static List<Color> DefaultTextColorList = new List<Color>() {
Color.FromArgb("#000000"),
Color.FromArgb("#F9371C"),
Color.FromArgb("#F97C1C"),
Color.FromArgb("#F9C81C"),
Color.FromArgb("#41D0B6"),
Color.FromArgb("#2CADF6"),
Color.FromArgb("#6562FC")
}; public static List<TextSize> DefaultTextSizeList = new List<TextSize>() {
new TextSize(){Name="Large", Value=22},
new TextSize(){Name="Middle", Value=18},
new TextSize(){Name="Small", Value=12},
};

效果如下:

使用控件

在MainPage中使用RichTextEditor,代码如下


<controls:RichTextEditor
x:Name="MainRichTextEditor"
Text="{Binding Content}"
Placeholder="{Binding PlaceHolder}"></controls:RichTextEditor>

MainRichTextEditor.GetHtmlText()测试获取富文本编辑器Html序列化功能。

private async void Button_Clicked(object sender, EventArgs e)
{
var html = this.MainRichTextEditor.GetHtmlText();
await DisplayAlert("GetHtml()", html, "OK");
}

最终效果

已知问题

  • HTML样式会重复添加

项目地址

我在maui-sample项目中的一些控件,打算做成一个控件库,方便大家使用。控件库地址在下方。

maui-sample项目作为控件库孵化器,代码可能会有点乱,也没有经过严格的测试。当控件完善到一定程度,我会把控件封装起来放到控件库中。如果你有好的控件,欢迎pull request。

maui-sample:

Github:maui-sample

Mato.Maui控件库

Mato.Maui

[MAUI]写一个跨平台富文本编辑器的更多相关文章

  1. 整理15款实用javascript富文本编辑器

    百度UEditor 官方网址:http://ueditor.baidu.com/website/ UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验 ...

  2. [前端随笔][JavaScript] 制作一个富文本编辑器

    写在前面 现在网上有很多现成的富文本编辑器,比如百度家的UEditor,kindeditor,niceditor等等,功能特别的多,API也很多,要去熟悉他的规则也很麻烦,所以想自己了解一下原理,做一 ...

  3. 基于ABP做一个简单的系统——实战篇:4.基于富文本编辑器,Razor模板引擎生成内容并导出Word 填坑记录

    起因 需求是这样的,有一种协议需要生成,协议的模板是可配置的,在生成过程中,模板中的内容可以根据约定的标记进行替换(就像mvc的razor模板一样).生成后的内容还需要导出成word或pdf. 常见的 ...

  4. 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器

    这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 富文本编辑器 万里长征 ...

  5. ASP.NET MVC + 百度富文本编辑器 + EasyUi + EntityFrameWork 制作一个添加新闻功能

    本文将交大伙怎么集成ASP.NET MVC + 百度富文本编辑器 + EasyUi + EntityFrameWork来制作一个新闻系统 先上截图: 添加页面如下: 下面来看代码部分 列表页如下: @ ...

  6. [译] 通过 contentEditable 属性创建一个所见即所得的编辑器(富文本编辑器)

    译者注 这只是一篇入门教程,介绍了一些基础知识,仅供参考,切不可因此觉得富文本编辑器很简单. 创建富文本编辑器是一个非常复杂的工程,需要考虑到方方面面,也有很多坑(请参考原文第一条评论). 为免误导大 ...

  7. 从零开始, 开发一个 Web Office 套件 (2): 富文本编辑器

    书接前文: 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Of ...

  8. iView + vue-quill-editor 实现一个富文本编辑器(包含图片,视频上传)

    1. 引入插件(注意IE10以下不支持) npm install vue-quill-editor --savenpm install quill --save (Vue-Quill-Editor需要 ...

  9. 富文本编辑器Simditor的简易使用

    最近打算自己做一个博客系统,并不打算使用帝国cms或者wordpress之类的做后台管理!自己处于学习阶段也就想把从前台到后台一起谢了.好了,废话不多说了,先来看看富文本编辑器SimDitor,这里是 ...

  10. 个人网站对xss跨站脚本攻击(重点是富文本编辑器情况)和sql注入攻击的防范

    昨天本博客受到了xss跨站脚本注入攻击,3分钟攻陷--其实攻击者进攻的手法很简单,没啥技术含量.只能感叹自己之前竟然完全没防范. 这是数据库里留下的一些记录.最后那人弄了一个无限循环弹出框的脚本,估计 ...

随机推荐

  1. Windows的压缩文件夹(zip/cab)

    https://weibo.com/1114096665/DtHXgvnva #windows10# 硬要把zip.cab文件当文件夹,不爽怎么解决? 删除注册表 "HKEY_CLASSES ...

  2. ABAP READ内表新老语法对比

    1.读取内表行新语法 740新语法中,对标READ,提出了新的语法,如下: 1.1.根据字段值查找 "-----------------------------@斌将军----------- ...

  3. vue中使用西瓜视频中引入自定义样式,绝对可以

    首先配置sass-loader和raw-loader 方法,再vue-config.js中加上这一段代码 module.exports = { chainWebpack: config => { ...

  4. python之中文符号转英文符号

    maketrans内置方法, 可以将中文符号转换为英文符号.以下代码中,事先定义(中文符号)和其对应的(英文符号),也就是定义中文符号, 也要有对应的英文符号,否则会报错.这个功能其实和替换功能差不多 ...

  5. 免费注册 Redhat 开发者并且进行订阅和激活

    注册 一.进入 https://www.redhat.com/wapps/ugc/register.html 进行注册 二.然后通过这个网址进入开发者平台 https://developers.red ...

  6. window启动和停止服务命令

    NET STOP serviceNET STOP 用于终止 Windows 服务.终止一个服务可以取消这个服务正在使用的任何一个网络连接.同样的一些服务是依赖于另外一些服务的.终止一个服务就会终止其它 ...

  7. [大数据]Hadoop HDFS文件系统命令集

    基本格式: hadoop fs -cmd [args] 1 Query 显示命令的帮助信息 # hadoop fs -help [cmd] 查看hadoop/hdfs的用户 # hdfs dfs -l ...

  8. 1.封装PageHelper实现分页

    前言 这几天想着动手将一些技术融合到项目中,昨天思考了会儿,想起了我与亲戚的对话:我说:"我想将若依项目完整的实现一遍",亲戚给我反馈到"你没必要完整复现若依项目,而且你 ...

  9. Java:如何加密或解密PDF文档?

    在工作中,我们会将重要的文档进行加密,并且设置用户的访问权限,其他外部人员均无法打开,只有获取该权限的用户才有资格打开文档.此外,限制用户的使用权限,极大程度上阻止了那些有意要篡改.拷贝其中内容的人, ...

  10. SkyWalking的学习之一

    SkyWalking的学习之一 前言 最近在学习应用调优诊断等内容. 现在实际工作中实质上的拆分和微服务在售前阶段 所以真正用到链路的地方比较少. 但是人生都是要向前看的. 想着一方面提高自己. 一方 ...