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

Read more ⟶

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 中

Read more ⟶

模板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.
Read more ⟶

策略模式


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() {
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
abstract class Duck {
    quack(){
        console.log('呱呱叫')
    }
    swim(){
        console.log('游泳')
    }
    abstract display() // 各种鸭子的外观不同,此为抽象方法
    fly(){
        console.log('飞')
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class RubberDuck {
    quack(){
        // 覆盖成吱吱叫
    }

    display(){
        // 外观是橡皮鸭
    }
    fly(){
        console.log('飞')
    }
}
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()
Read more ⟶

异步组件


利用 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之前就可以做一些异步请求数据或初始化之类的操作。

Read more ⟶

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 }}
Read more ⟶

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 就行了, 处理函数内部可以按数据接口或者网页来处理请求。

Read more ⟶

设计模式实践


策略模式

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,而此方法中用到了其中一部分参数

Read more ⟶

装饰器


原代码

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);
  1. 装饰器只能作用与class,不能适用于纯函数。
  2. 各个参数是什么,target 是 A.prototype 原型,propertyName 是方法或属性名,propertyDescriptor.value 是方法本体
  3. 需要call或apply绑定 this 不然方法内有问题
  4. 装饰器函数内可以通过 this 拿到 A 类的实例
Read more ⟶

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 方法,来提供有逻辑的实现过程。

Read more ⟶