Minimal and Clean blog theme for Hugo
Vitejs
创建项目
# npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
模板:vanilla,vanilla-ts,vue,vue-ts,react,react-ts,preact,preact-ts,lit,lit-ts,svelte,svelte-ts
…Go处理图片
标准库中有一个 image 包,是用来处理图片的。image package - image - Go Packages
通过IO读取到的图片文件要进行转换
func Decode(r io.Reader) (image.Image, error)
将png图片解码为 Image 数据,要先png图片文件搞到Reader中,在从Reader中将内容写入到Image中
func Encode(w io.Writer, m image.Image) error
将 Image 编码为 png 图片,将 png图片写入到 Writer 中
…模板Template
解析模板文件并绑定方法和数据
func view(w http.ResponseWriter, r *http.Request) {
funcMap := template.FuncMap{"minus": func(a int, b int){
return a - b
}}
temp, _ := template.New("index.gohtml").Funcs(funcMap).ParseFiles("tpl/index.gohtml")
data := "123"
temp.Execute(w, *data)
}
将data传入“tpl/index.gohtml”模板进行解析,将解析后的数据传入到writer,writer可以是http请求的responseWriter,也可以是file(file实现了writer接口)。 也就是说,可以作为响应数据返回到浏览器前端,也可以保存到文件中。
template的Execute方法的文档:
Execute 将已解析的模板应用于指定的数据对象,并将输出写入 wr。
如果执行模板或写入其输出时发生错误,执行将停止,但部分结果可能已写入输出写入器。
模板可以安全地并行执行,但如果并行执行共享一个 Writer,则输出可能是交错的。
定义模板
{{define "output"}}
<div>
<label for="orange">橙</label>
<span>{{.orange}}</span>
</div>
{{end}}
使用模板
<fieldset>
<legend>原神材料合成计算器</legend>
{{template "output" .}}
</fieldset>
使用数据
举几个例子
{{range $b, $u := .Users}}
<span>{{$u}}</span>,
{{end}}
{{$Users := .Users}}
文档中的 pipeline 指的数据
{{with pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated;
otherwise, dot is set to the value of the pipeline and T1 is
executed.
{{with pipeline}} T1 {{else}} T0 {{end}}
If the value of the pipeline is empty, dot is unaffected and T0
is executed; otherwise, dot is set to the value of the pipeline
and T1 is executed.
策略模式
abstract class Duck {
quack(){
console.log('呱呱叫')
}
swim(){
console.log('游泳')
}
abstract display() // 各种鸭子的外观不同,此为抽象方法
}
class GreenDuck extends Duck {
display() {
console.log('绿色的头')
}
}
class RedDuck extends Duck {
display() {
console.log('红色的头')
}
}
// 还有很多其他类型的鸭子继承了Duck
class OtherDuck extends Duck {
display() {
}
}
|
|
|
|
interface Flyable{
fly()
}
interface Quackable {
quack()
}
class Duck{
swim(){}
display(){}
}
class MallardDuck extends Duck implements Flyable, Quackable {
display(){}
fly(){}
quack(){}
}
class RedheadDuck extends Duck implements Flyable, Quackable {
display(){}
fly(){}
quack(){}
}
class RubberDuck extends Duck implements Quackable {
display(){}
quack(){}
}
class DecoyDuck extends Duck {
display(){}
}
interface QuackBehavior {
quack()
}
interface FlyBehavior {
fly()
}
class FlyWithWings implements FlyBehavior {
fly() {
// 实现鸭子的飞行动作
}
}
class FlyNoWay implements FlyBehavior {
fly() {
// 什么都不做,不会飞
}
}
class Duck {
protected flyBehavior: FlyBehavior
protected quackBehavior: QuackBehavior
swim() {
}
display() {
}
performFly() {
this.flyBehavior.fly()
}
performQuack() {
this.quackBehavior.quack()
}
setFlyBehavior(flyBehavior: FlyBehavior) {
this.flyBehavior = flyBehavior
}
setQuackBehavior(quackBehavior: QuackBehavior) {
this.quackBehavior = quackBehavior
}
}
class RedDuck extends Duck {
constructor(props) {
super(props);
this.flyBehavior = new FlyWithWings()
this.quackBehavior = new Quack()
}
}
// 模型鸭
class ModelDuck extends Duck {
constructor() {
super();
// 不会飞
this.flyBehavior = new FlyNoWay()
this.quackBehavior = new Quack()
}
}
// 火箭驱动
class FlyRocketPowered implements FlyBehavior {
fly() {
console.log("I'm flying with a rocket!")
}
}
// 模型鸭
const model: Duck = new ModelDuck()
model.performFly()
model.setFlyBehavior(new FlyRocketPowered())
model.performFly()
异步组件
利用 React.lazy 将普通组件改造为异步组件
const Todo = React.lazy(
() => new Promise<{ default: React.ComponentType<any> }>(async (resolve) => {
const module = await import("./Todo");
db.read("todos").then((t) => {
resolve({
...module,
default: module.default(todos) as unknown as React.ComponentType<any>,
});
});
})
);
function Tab() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Todo />
</Suspense>
</div>
);
}
通常 React.lazy 只是用于异步加载组件。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
import 返回的是一个 Promise<{ default: React.ComponentType<any> }>
。只有我们自己构建一个能返回该类型的Promise,在返回Promise之前就可以做一些异步请求数据或初始化之类的操作。
Hugo主题的构成
博客详情页
文件为 themes/meme/layouts/partials/pages/post.html
此文件主要包含两部分:article
标签内为文章主题内容,各种 partial
。
partial
有如下几个:
- 版权
- 更新日期徽章
- git信息
- 分享栏
- 相关文章
- tags
- minimal-footer
- minimal-footer-about
- 上下页导航
- 评论
这些 partial 的文件都在 themes/meme/layouts/partials/components
目录下。
网站首页
首页内容文件 themes/meme/layouts/index.html
,可以通过配置展示不同类型的内容。
类型有:
- poetry(诗词)
- footage(电影胶片、素材)
- posts(几篇最近的文章)
- page(某种页面吧?)
themes/meme/layouts/partials/pages/home-footage-posts.html
分类
调试
可以将数据打印为 json 来调试数据
{{ dict "title" .Title "content" .Plain | jsonify }}
HTTP服务器
首先要开启一个服务器,并能响应一些url,先看一下官方文档。
ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
More control over the server’s behavior is available by creating a custom Server:
s := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
上面是官方文档介绍的 HTTP 服务器,Handle 与 HandleFunc 的区别是它们的第二个参数一个是结构体一个是处理函数。一般用 HandleFunc 就行了, 处理函数内部可以按数据接口或者网页来处理请求。
…设计模式实践
策略模式
class D {
createCode(parentId, followTaskId): string {
const parent = this.getTaskById(parentId);
let code = '';
if (parent) {
const parentCode = parent.taskNbr;
if (followTaskId) {
code = `${parentCode}${parentCode ? '.' : ''}${nbrArr.length ? Number(nbrArr[nbrArr.length - 1]) + 1 : ''}`;
} else {
code = `${parentCode}${parentCode ? '.' : ''}${children.length + 1}`;
}
} else {
code = `${index + 2}`;
}
return code;
}
public createTaskResolver(data, mode) {
const task: any = {};
const parentId = String(data.parentId ? data.parentId : '')
if (parentId) {
task.parent = {id: parentId}
}
if (data && data.followTaskIndex >= 0) {
task.followTaskIndex = data.followTaskIndex;
}
return task
}
public setCreatePullData(task: IProjectTask) {
let topLevel = this.getTopLevelTasks();
if (!oc(task.parent).id('')) {
} else {
}
if (task.followTaskId && !task[F_ProjectTask_scheduledStart]) {
}
return toJS(task);
}
public getFollowTaskId(data, allData): string {
if (data.followTaskId) {
} else if (data.parentId) {
}
}
}
xx模式
class E {
private showDialog = (
mode: BizFormModeEnum,
dataId: string,
options: Optional<IBizFormDialogOptions> = {},
) => {
// 如果dialogId存在,说明弹窗已经生成,那么就先关闭那个弹窗,防止多次点击出现多个弹窗
if (this.mediator.dialogId) this.mediator.closeTaskDialog();
this.presenter.getBean(PageBeanNames.NestFormController).showNestBizFormDialog(
{
fieldName: F_ProjectSchedule_tasks,
},
{
title: '任务详情',
size: 'largest',
dataId: dataId,
mode: mode,
options: {
...options,
projectScheduleAPI: this.presenter.options.projectScheduleAPI,
displayOptions: {
suppressDetailAttachmentUpload: [F_ProjectTask_deliverables]
},
onSave: (dialogOptions) => {
setTimeout(() => {
this.updateGanttData(true, dialogOptions.dataId, dialogOptions.options.taskContext.followTaskIndex);
}, 300);
}
},
customizeButtons: options.customizeButtons,
presenterClass: TaskFormPresenter,
presenterOptions: {
passParams: {
// showDialog: this.showDialog,
mediatorKey: this.mediatorKey,
passOptions: {
mainPresenterAttachment: (task, newFiles) => {
this.setTaskAttachmentAppendRow(task, newFiles);
},
mainPresenterSave: () => {
this.presenter.api.save()
},
mainPresenterIsEdit: () => {
return this.presenter.api.stateController.isEditing;
}
}
},
},
}
)
}
}
showNestBizFormDialog的参数是一个大型的option,而此方法中用到了其中一部分参数
…装饰器
原代码
class Spread {
suspendPaint = () => { }
resumePaint = () => { }
}
function spreadPerformance() {
return function (target: A, propertyName: string, propertyDescriptor: PropertyDescriptor) {
const method = propertyDescriptor.value;
console.log('target', target)
console.log('propertyName', propertyName)
console.log('propertyDescriptor', propertyDescriptor)
propertyDescriptor.value = function (...args: any[]) {
console.log(1, this.name)
method.call(this, ...args)
console.log(2, this.name)
};
return propertyDescriptor;
};
}
class A {
constructor(public name: string) {
A.age = 22
}
public static age = 18
@spreadPerformance()
fillDataToSpanCell(spread: Spread) {
console.log(`run ${this.name}`, spread)
}
}
const a = new A("hello")
a.fillDataToSpanCell(new Spread())
编译后
__decorate([
spreadPerformance(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Spread]),
__metadata("design:returntype", void 0)
], A.prototype, "fillDataToSpanCell", null);
- 装饰器只能作用与class,不能适用于纯函数。
- 各个参数是什么,target 是 A.prototype 原型,propertyName 是方法或属性名,propertyDescriptor.value 是方法本体
- 需要call或apply绑定 this 不然方法内有问题
- 装饰器函数内可以通过 this 拿到 A 类的实例
Jest
模拟模块
先看代码,isEntity是我们要测试的目标,其中引用了 metadata 模块。
import { metadata } from '@metadata';
function isEntity (item) {
return !!metadata.getEntity(item.id)
}
如果 metadata 模块比较复杂,在 jest 的环境中不能直接初始化出来,这时候最好的办法就是通过 mock 模拟这个模块,使测试代码可以正常运行。
import { metadata } from '@metadata';
const entity = {
custom1Entity: {}
}
jest.mock('@metadata', () => {
return {
metadata: {
getEntity: jest.fn().mockImplementation(id => {
return entity[id];
}),
}
};
});
describe('模拟模块', () => {
test('isEntity', () => {
const item = { id: 'customEntity2' };
expect(isEntity(item)).toBe(false)
expect(metadata.getEntity.mock.calls.pop()[0]).toEqual(item.id);
expect(metadata.getEntity.mock.calls.length).toBe(1);
})
})
jest.mock 会模拟 @metadata 模块,无论是测试文件中还是原代码中的 @metadata 模块都会被模拟。 jest.fn() 只接受空参数的函数作为参数,并返回一个值。如果想有参数,就要在调用 mockImplementation 方法,来提供有逻辑的实现过程。
…