NSIS打包比较配置文件手动编辑起来非常麻烦,使用HM NIS EDIT
向导的话就容易多了。
添加目录以及子目录文件
File /r "bin\Release\*.*"
删除目录以及子目录
RMDir /r "$INSTDIR"
NSIS打包比较配置文件手动编辑起来非常麻烦,使用HM NIS EDIT
向导的话就容易多了。
添加目录以及子目录文件
File /r "bin\Release\*.*"
删除目录以及子目录
RMDir /r "$INSTDIR"
AutoUpdater.NET是一个.net库,允许winforms和wpf应用实现自动更新,只需要以下几步:
1,定义一个升级文件;
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>2.0.0.0</version>
<url>http://rbsoft.org/downloads/AutoUpdaterTest.zip</url>
<changelog>https://github.com/ravibpatel/AutoUpdater.NET/releases</changelog>
<mandatory>false</mandatory>
</item>
2, 安装 Autoupdater.NET.Official扩展
PM> Install-Package Autoupdater.NET.Official
3,使用只需要在程序加载前加入以下代码即可
protected override void OnStartup(StartupEventArgs e)
{
AutoUpdater.ParseUpdateInfoEvent += AutoUpdaterOnParseUpdateInfoEvent;
//AutoUpdater.ReportErrors = true;
AutoUpdater.Start("https://api.yunyin.la/file-helper/upgrade");
}
将xml修改为json
private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
{
dynamic json = JsonConvert.DeserializeObject(args.RemoteData);
args.UpdateInfo = new UpdateInfoEventArgs
{
CurrentVersion = json.version,
DownloadURL = json.url,
Mandatory = new Mandatory
{
Value = json.mandatory.value,
UpdateMode = json.mandatory.mode,
}
};
}
json样例
{
"version":"2.0.0.0",
"url":"http://rbsoft.org/downloads/AutoUpdaterTest.zip",
"changelog":"https://github.com/ravibpatel/AutoUpdater.NET/releases",
"mandatory":{
"value":true,
"minVersion": "2.0.0.0",
"mode":1
},
"checksum":{
"value":"E5F59E50FC91A9E52634FFCB11F32BD37FE0E2F1",
"hashingAlgorithm":"SHA1"
}
}
包含点击关闭按钮后最小化系统托盘,点击系统托盘图标后自动弹出窗口,点击托盘右键菜单退出
public partial class MainWindow : Window
{
private readonly System.Windows.Forms.NotifyIcon nIcon = new System.Windows.Forms.NotifyIcon();
public MainWindow()
{
InitializeComponent();
initNotifyIcon();
}
private void initNotifyIcon()
{
nIcon.Visible = true;
nIcon.Icon = new Icon("./favicon.ico");
nIcon.Text = "测试程序";
nIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(show_Click);
nIcon.ContextMenu = new System.Windows.Forms.ContextMenu();
System.Windows.Forms.MenuItem show = new System.Windows.Forms.MenuItem("打开");
show.Click += new EventHandler(show_Click);
nIcon.ContextMenu.MenuItems.Add(show);
System.Windows.Forms.MenuItem exit = new System.Windows.Forms.MenuItem("退出");
exit.Click += new EventHandler(exit_Click);
nIcon.ContextMenu.MenuItems.Add(exit);
}
private void exit_Click(object sender, EventArgs e)
{
Environment.Exit(0);
}
private void show_Click(object Sender, EventArgs e)
{
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
Show();
Activate();
}
protected override void OnStateChanged(EventArgs e)
{
if (WindowState == WindowState.Minimized) Hide();
base.OnStateChanged(e);
}
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
Hide();
base.OnClosing(e);
}
}
<Grid>
<ListView VerticalAlignment="Top" HorizontalAlignment="Stretch">
<ListViewItem>
<TextBlock Text="xxxx1"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx2"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx3"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx4"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx5"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx6"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx7"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx8"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx9"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx10"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx11"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx12"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx13"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx14"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx15"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx16"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx17"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx18"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx19"/>
</ListViewItem>
<ListViewItem>
<TextBlock Text="xxxx20"/>
</ListViewItem>
</ListView>
</Grid>
<Style x:Key="ScrollBarLineButton"
TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="OverridesDefaultStyle"
Value="true" />
<Setter Property="Focusable"
Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border x:Name="Border"
Margin="1"
CornerRadius="2"
BorderThickness="1">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderMediumColor}"
Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}"/>
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Panel.Background).
(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="{StaticResource ControlPressedColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Arrow"
Storyboard.TargetProperty="(Shape.Fill).
(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="{StaticResource DisabledForegroundColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path x:Name="Arrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="{Binding Content,
RelativeSource={RelativeSource TemplatedParent}}" >
<Path.Fill>
<SolidColorBrush Color="{DynamicResource GlyphColor}"/>
</Path.Fill>
</Path>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarPageButton"
TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="OverridesDefaultStyle"
Value="true" />
<Setter Property="IsTabStop"
Value="false" />
<Setter Property="Focusable"
Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarThumb"
TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="OverridesDefaultStyle"
Value="true" />
<Setter Property="IsTabStop"
Value="false" />
<Setter Property="Focusable"
Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border CornerRadius="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="VerticalScrollBar"
TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18" />
<RowDefinition Height="0.00001*" />
<RowDefinition MaxHeight="18" />
</Grid.RowDefinitions>
<Border Grid.RowSpan="3"
CornerRadius="2"
Background="#F0F0F0" />
<RepeatButton Grid.Row="0"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineUpCommand"
Content="M 0 4 L 8 4 L 4 0 Z" />
<Track x:Name="PART_Track"
Grid.Row="1"
IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}"
Margin="1,0,1,0">
<Thumb.BorderBrush>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderLightColor}"
Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.BorderBrush>
<Thumb.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="0.0" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Row="2"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineDownCommand"
Content="M 0 0 L 4 4 L 8 0 Z" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="HorizontalScrollBar"
TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="18" />
<ColumnDefinition Width="0.00001*" />
<ColumnDefinition MaxWidth="18" />
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="3"
CornerRadius="2"
Background="#F0F0F0" />
<RepeatButton Grid.Column="0"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineLeftCommand"
Content="M 4 0 L 4 8 L 0 4 Z" />
<Track x:Name="PART_Track"
Grid.Column="1"
IsDirectionReversed="False">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageLeftCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}"
Margin="0,1,0,1">
<Thumb.BorderBrush>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderLightColor}"
Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.BorderBrush>
<Thumb.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="0.0" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageRightCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Column="2"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineRightCommand"
Content="M 0 0 L 4 4 L 0 8 Z" />
</Grid>
</ControlTemplate>
<Style x:Key="{x:Type ScrollBar}"
TargetType="{x:Type ScrollBar}">
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="OverridesDefaultStyle"
Value="true" />
<Style.Triggers>
<Trigger Property="Orientation"
Value="Horizontal">
<Setter Property="Width"
Value="Auto" />
<Setter Property="Height"
Value="18" />
<Setter Property="Template"
Value="{StaticResource HorizontalScrollBar}" />
</Trigger>
<Trigger Property="Orientation"
Value="Vertical">
<Setter Property="Width"
Value="18" />
<Setter Property="Height"
Value="Auto" />
<Setter Property="Template"
Value="{StaticResource VerticalScrollBar}" />
</Trigger>
</Style.Triggers>
</Style>
需要的资源
<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>
<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>
<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>
<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>
<LinearGradientBrush x:Key="MenuPopupBrush"
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="0" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="0.5" />
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill"
StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#000000FF"
Offset="0" />
<GradientStop Color="#600000FF"
Offset="0.4" />
<GradientStop Color="#600000FF"
Offset="0.6" />
<GradientStop Color="#000000FF"
Offset="1" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<!--Scrollbar Thumbs-->
<Style x:Key="ScrollThumbs" TargetType="{x:Type Thumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Grid x:Name="Grid">
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Fill="Transparent" />
<Border x:Name="Rectangle1" CornerRadius="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Background="{TemplateBinding Background}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="Horizontal">
<Setter TargetName="Rectangle1" Property="Width" Value="Auto" />
<Setter TargetName="Rectangle1" Property="Height" Value="7" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--ScrollBars-->
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsFlicksEnabled" Value="false" />
<Setter Property="Foreground" Value="#8C8C8C" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Width" Value="8" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollBar}">
<Grid x:Name="GridRoot" Width="8" Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="0.00001*" />
</Grid.RowDefinitions>
<Track x:Name="PART_Track" Grid.Row="0" IsDirectionReversed="true" Focusable="false">
<Track.Thumb>
<Thumb x:Name="Thumb" Background="{TemplateBinding Foreground}" Style="{DynamicResource ScrollThumbs}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton x:Name="PageUp" Command="ScrollBar.PageDownCommand" Opacity="0" Focusable="false" />
</Track.IncreaseRepeatButton>
<Track.DecreaseRepeatButton>
<RepeatButton x:Name="PageDown" Command="ScrollBar.PageUpCommand" Opacity="0" Focusable="false" />
</Track.DecreaseRepeatButton>
</Track>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="Thumb" Property="IsMouseOver" Value="true">
<Setter Value="{DynamicResource ButtonSelectBrush}" TargetName="Thumb" Property="Background" />
</Trigger>
<Trigger SourceName="Thumb" Property="IsDragging" Value="true">
<Setter Value="{DynamicResource DarkBrush}" TargetName="Thumb" Property="Background" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Thumb" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Orientation" Value="Horizontal">
<Setter TargetName="GridRoot" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
<Setter TargetName="PART_Track" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="8" />
<Setter TargetName="Thumb" Property="Tag" Value="Horizontal" />
<Setter TargetName="PageDown" Property="Command" Value="ScrollBar.PageLeftCommand" />
<Setter TargetName="PageUp" Property="Command" Value="ScrollBar.PageRightCommand" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Livewire – 不用一行JS实现vue/ng/react等框架的数据绑定效果
构建现代的Web应用程序很困难。 Vue和React之类的工具功能非常强大,但是它们增加了全栈开发人员工作流程的复杂性。
Livewire是Laravel的全栈框架,可简化构建交互式页面的过程。不用一行JS实现vue/ng/react等框架的数据绑定效果。
理解它的最好方法就是看代码。
下面构建一个搜索组件,当用户键入搜索输入时,用户列表将实时更新。
组件PHP代码:
use Livewire\Component;
class SearchUsers extends Component
{
public $search = '';
public function render()
{
return view('livewire.search-users', [
'users' => User::where('username', $this->search)->get(),
]);
}
}
组件视图
<div>
<input wire:model="search" type="text" placeholder="Search users..."/>
<ul>
@foreach($users as $user)
<li>{{ $user->username }}</li>
@endforeach
</ul>
</div>
引用组件
<body>
...
@livewire('search-users')
...
</body>
教程非常简单,客户端点击一次按钮,实现客户端和服务端计数器同时加一。
composer require livewire/livewire
在视图中保护livewire的css和js
@livewireStyles
</head>
<body>
...
@livewireScripts
</body>
</html>
php artisan make:livewire counter
组件文件内容
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
public function render()
{
return view('livewire.counter');
}
}
<div style="text-align: center">
<button wire:click="increment">+</button>
<h1>{{ $count }}</h1>
</div>
<head>
...
@livewireStyles
</head>
<body>
<livewire:counter />
...
@livewireScripts
</body>
</html>
wkhtmltopdf使用的qt版本比较久,不支持很多css3和html5特性,于是用qt写了一个html转PDF工具,需要常驻后台运行,采用supervisord守护,无需安装图形界面,这时候需要用到Xvfb。
Xvfb or X virtual framebuffer is a display server implementing the X11 display server protocol. In contrast to other display servers, Xvfb performs all graphical operations in virtual memory without showing any screen output.
简单说就是Xvfb是一个虚拟的X11显示服务,在虚拟内存中执行不需要显示图像。可以用来做远程左面显示,以及将桌面程序在非桌面环境使用(如headless的图形界面测试)。
安装xvfb
sudo apt-get install xvfb
命令行运行虚拟桌面
Xvfb :2 -screen 0 1024x768x16 &
x11vnc -listen 0.0.0.0 -rfbport 5900 -noipv6 -passwd xxxxxx -display 2
export DISPLAY=:2
from xvfbwrapper import Xvfb
with Xvfb(width=1920, height=1024, colordepth=24) as xvfb:
# launch stuff inside virtual display here.
# Xvfb will stop when this block completes
sudo apt install git
git clone https://github.com/DamionGans/ubuntu-wsl2-systemd-script.git
cd ubuntu-wsl2-systemd-script/
bash ubuntu-wsl2-systemd-script.sh
yay dotnet
yay -S genie-systemd
sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
sudo apt update -y
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
若要检查 Windows 版本及内部版本号,选择 Windows 徽标键 + R,然后键入“winver”,选择“确定”。 (或者在 Windows 命令提示符下输入 ver 命令)。 更新到“设置”菜单中的最新 Windows 版本。
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
wsl --set-default-version 2
打开 Microsoft Store,并选择你偏好的 Linux 分发版。
https://docs.microsoft.com/zh-cn/windows/terminal/get-started
以debian/ubuntu为例,修改Dockerfile使用deiban和pip镜像。
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple \
&& pip config set install.trusted-host mirrors.aliyun.com
使用npm镜像
RUN npm config set registry https://registry.npm.taobao.org
'redis' => [
'class' => 'yii\redis\Connection',
'hostname' => '127.0.0.1',
'retries' => 3,
'port' => 6379,
'password' => '',
'socketClientFlags' => STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
],
$client = new \Predis\Client([
'scheme' => 'tcp',
'host' => $redis->hostname,
'port' => $redis->port,
'password' => $redis->password,
'persistent'=>true,
]);
$redis->pconnect('127.0.0.1', 6379);
本文通过简单的例子比较了django状态管理扩展的使用,涵盖django-fsm,xworkflows,django_transitions以及参考
https://github.com/viewflow/django-fsm
state = FSMField(
default=State.DRAFT,
verbose_name='Publication State',
choices=State.CHOICES,
protected=True,
)
@transition(field=state, source=[State.APPROVED, State.EXPIRED],
target=State.PUBLISHED,
conditions=[can_display])
def publish(self):
'''
Publish the object.
'''
email_the_team()
update_sitemap()
busta_cache()
https://github.com/gadventures/django-fsm-admin
from fsm_admin.mixins import FSMTransitionMixin
class YourModelAdmin(FSMTransitionMixin, admin.ModelAdmin):
# The name of one or more FSMFields on the model to transition
fsm_field = ['state',]
readonly_fields = ['state', ]
admin.site.register(YourModel, YourModelAdmin)
https://github.com/rbarrois/xworkflows
import xworkflows
class MyWorkflow(xworkflows.Workflow):
# A list of state names
states = (
('foo', "Foo"),
('bar', "Bar"),
('baz', "Baz"),
)
# A list of transition definitions; items are (name, source states, target).
transitions = (
('foobar', 'foo', 'bar'),
('gobaz', ('foo', 'bar'), 'baz'),
('bazbar', 'baz', 'bar'),
)
initial_state = 'foo'
class MyObject(xworkflows.WorkflowEnabled):
state = MyWorkflow()
@xworkflows.transition()
def foobar(self):
return 42
# It is possible to use another method for a given transition.
@xworkflows.transition('gobaz')
def blah(self):
return 13
LiveStatus Status
class LiveStatus(StatusBase):
"""Workflow for Lifecycle."""
# Define the states as constants
DEVELOP = 'develop'
LIVE = 'live'
MAINTENANCE = 'maintenance'
DELETED = 'deleted'
# Give the states a human readable label
STATE_CHOICES = (
(DEVELOP, 'Under Development'),
(LIVE, 'Live'),
(MAINTENANCE, 'Under Maintenance'),
(DELETED, 'Deleted'),
)
# Define the transitions as constants
PUBLISH = 'publish'
MAKE_PRIVATE = 'make_private'
MARK_DELETED = 'mark_deleted'
REVERT_DELETED = 'revert_delete'
# Give the transitions a human readable label and css class
# which will be used in the django admin
TRANSITION_LABELS = {
PUBLISH : {'label': 'Make live', 'cssclass': 'default'},
MAKE_PRIVATE: {'label': 'Under maintenance'},
MARK_DELETED: {'label': 'Mark as deleted', 'cssclass': 'deletelink'},
REVERT_DELETED: {'label': 'Revert Delete', 'cssclass': 'default'},
}
# Construct the values to pass to the state machine constructor
# The states of the machine
SM_STATES = [
DEVELOP, LIVE, MAINTENANCE, DELETED,
]
# The machines initial state
SM_INITIAL_STATE = DEVELOP
# The transititions as a list of dictionaries
SM_TRANSITIONS = [
# trigger, source, destination
{
'trigger': PUBLISH,
'source': [DEVELOP, MAINTENANCE],
'dest': LIVE,
},
{
'trigger': MAKE_PRIVATE,
'source': LIVE,
'dest': MAINTENANCE,
},
{
'trigger': MARK_DELETED,
'source': [
DEVELOP, LIVE, MAINTENANCE,
],
'dest': DELETED,
},
{
'trigger': REVERT_DELETED,
'source': DELETED,
'dest': MAINTENANCE,
},
]
Model:
class Lifecycle(LifecycleStateMachineMixin, models.Model):
"""
A model that provides workflow state and workflow date fields.
This is a minimal example implementation.
"""
class Meta: # noqa: D106
abstract = False
wf_state = models.CharField(
verbose_name = 'Workflow Status',
null=False,
blank=False,
default=LiveStatus.SM_INITIAL_STATE,
choices=LiveStatus.STATE_CHOICES,
max_length=32,
help_text='Workflow state',
)
wf_date = models.DateTimeField(
verbose_name = 'Workflow Date',
null=False,
blank=False,
default=timezone.now,
help_text='Indicates when this workflowstate was entered.',
)
Admin
# -*- coding: utf-8 -*-
"""Example django admin."""
from django_transitions.admin import WorkflowAdminMixin
from django.contrib import admin
from .models import Lifecycle
class LifecycleAdmin(WorkflowAdminMixin, admin.ModelAdmin):
"""
Minimal Admin for Lifecycles Example.
You probably want to make the workflow fields
read only so yo can not change these values
manually.
readonly_fields = ['wf_state', 'wf_date']
"""
list_display = ['wf_date', 'wf_state']
list_filter = ['wf_state']
admin.site.register(Lifecycle, LifecycleAdmin)