如何利用Cloudflare搭建图床(+ Piclist快速上传)- Bearalise

背景

写博客就要有图床,之前搭了一个国内版的,今天尝试用Cloudflare搭建一个海外版的。

开通Cloudflare R2

R2 是 Cloudflare 推出的免费对象存储服务,需要免费注册一个Cloudflare 账号才能使用,注册登录后,点击左侧边栏的 R2 访问服务,但需要注意的是开通 R2 服务需要绑定信用卡(国内外主流信用卡皆可),但并不会扣费,主要是为了验证用户身份使用:

创建图床 Bucket

Bucket即为存储桶,所有的资源的是需要放在存储桶中。开通 R2 服务后,点击右上角「创建存储桶」按钮进行创建:

起一个名字,其他选项如图:

开通后显示以下信息:

More...

如何用pgloader同步数据库 - Bearalise

背景

之前开发时就发现,mysql和pg数据库 没有合适的数据库同步工具,只能用dump将数据库dump下来,再重新upload到新的库

解决方案

最近发现一个比较好的工具:Pgloader ,下面是具体步骤:

  1. 安装: sudo apt-get install pgloader

  2. 编辑同步配置 pg.load

    1
    2
    3
    load database
    from pgsql://localhost/pgloader
    into pgsql://localhost/copy;
  3. 运行

    1
    pgloader pg.load

运行成功,目前看起来还是十分简单好用的。

More...

利用AlertDialog实现二次确认按钮 - Bearalise

背景

在用户点击某个按钮后弹出 AlertDialog,如果用户确认,则执行原本的点击动作;如果用户取消,则不执行该动作。这是一个很常见的模式,用于防止用户误操作。这里给出用AlertDialog组件的一个实现。

关键点

  1. 我们定义了一个 performDangerousAction 函数,它代表您想要执行的实际操作。在这个例子中,它只是设置了一个结果状态,但在实际应用中,这里可能是一个 API 调用或其他重要操作。
  2. 当用户点击 “执行危险操作” 按钮时,我们不直接执行操作,而是打开 AlertDialog。
  3. 在 AlertDialog 中:
    1. 如果用户点击 “确认”,我们调用 handleConfirm 函数,它会执行危险操作并关闭对话框。
    2. 如果用户点击 “取消”,我们调用 handleCancel 函数,它会设置一个取消消息并关闭对话框。
  4. 我们使用 actionResult 状态来显示操作的结果,无论是执行了还是被取消。
  5. AlertDialog 的可见性由 isOpen 状态控制,这允许我们在需要时打开和关闭它。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
'use client'

import { useState } from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

export default function AlertDialogWithAction() {
const [isOpen, setIsOpen] = useState(false)
const [actionResult, setActionResult] = useState<string | null>(null)

// 模拟一个危险操作
const performDangerousAction = () => {
// 这里是实际的危险操作逻辑
setActionResult("危险操作已执行!")
}

const handleConfirm = () => {
performDangerousAction()
setIsOpen(false)
}

const handleCancel = () => {
setActionResult("操作已取消")
setIsOpen(false)
}

return (
<div className="space-y-4">
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
onClick={() => setIsOpen(true)}
>
执行危险操作
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>您确定要执行此操作吗?</AlertDialogTitle>
<AlertDialogDescription>
此操作可能会导致不可逆的结果。请确认您真的要执行此操作。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>取消</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}>确认</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{actionResult && (
<div className="mt-4 p-4 bg-muted rounded-md">
<p>操作结果: {actionResult}</p>
</div>
)}
</div>
)
}

作者:Bearalise
出处:https://blog.952005.xyz/2024/11/22/how-to-use-component-to-double-confirm/
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文链接。

如何修改HTML默认上传按钮组件样式 - Bearalis

背景

系统自带的上传按钮有点丑,并且大的样式无法修改,想了个方法用Tailwind的按钮包装了系统自带的按钮,实现了样式的统一

解决方案

建立Uploadbutton 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

import React, { ChangeEvent,useRef } from 'react';

import { Button } from '@/components/ui/button';
import { TreeDataItem } from '@/lib/utils';

interface Question {
...
}

interface Option {
...
}

type Props = {
disabled: boolean;
onUpload: ( result: any ) => void;
};

const UploadButton = ( { disabled, onUpload }: Props ) => {
const fileInputRef = useRef<HTMLInputElement | null>(null);

const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

const parseQuestions = (content: string): { questions: Question[] } => {
...
};

const generateTreeData = ( questions: Question[]) : { data: TreeDataItem[] } => {
...
};

const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {

const file = e.target.files?.[0];
if (!file) return;

try {
const content = await file.text();

const { questions } = parseQuestions(content);
const { data } = generateTreeData(questions);

const results = {
data: data,
jsondata: questions
};

onUpload( results );
} catch (error) {
console.error('Error processing file:', error);
}
}

return (
<div className="w-full lg:w-auto">
<input
type="file"
ref={fileInputRef}
accept=".txt"
onChange={handleFileChange}
className="hidden"
/>
<Button
disabled={disabled}
onClick={handleButtonClick}
size="sm"
className="w-full lg:w-auto"
>
Import TXT
</Button>
</div>
);
};

export default UploadButton;

分解来说,

  1. 定义组件,并定义onUpload 函数作为组件参数
  2. 定义 fileInputRef ,绑定给原始上传控件
  3. 再定义 handleButtonClick 函数,函数里调用上传控件的Click方法
  4. 定义Button控件,将点击事件绑定给handleButtonClick
  5. 定义handleFileChange函数,绑定原始上传控件的onChange事件
  6. 在handleFileChange函数,用 await file.text() 得到文件内容,并处理加工,把结果通过onUpload 函数,传输给外部调用者

作者:Bearalise
出处:如何修改HTML默认上传按钮组件样式 - Bearalis
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文链接。

PostgreSQL 在Windows环境下使用 Docker 安装 - Bearalise

背景

项目需要使用PG, 尝试安装一下

Create Volume

1
docker volume create --driver local --opt device=C:\10.VM\data\pg01 --opt type=none --opt o=bind pg01

Pull Image

1
docker pull postgres

Start Container

1
2
docker run --name pgsql --privileged -e POSTGRES_PASSWORD=xxxxx -p 15333:5432 -v pg01:/var/lib/postgresql/data -d postgres

连接数据库,创建用户

1
2
create user fintest01 with password 'xxxxxx';
create database fintest01 with owner fintest01;

切换数据库连接

1
DATABASE_URL=postgresql://fintest01:fin1234@192.xx.xx.xx:15333/fintest01
More...

Mac OS 下修改 Google Chrome 显示语言的方法

背景

由于调试程序需要,希望把Chrome的语言改成英文,发现在Mac下Chrome的语言是跟随系统语言的,通过菜单无法修改。

解决方案

英文 -> 简体中文

defaults write com.google.Chrome AppleLanguages '(zh-CN)'

简体中文 -> 英文

defaults write com.google.Chrome AppleLanguages '(en-US)'

英文优先,简体中文第二。反之改一下顺序

defaults write com.google.Chrome AppleLanguages "(en-US,zh-CN)"

Build a Finance SaaS Platform -26 (Account/Date Filter)(End)

Install Package

1
npm i query-string

Add Filter

Add components/filters.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import { AccountFilter } from '@/components/account-filter'
import { DateFilter } from '@/components/date-filter'

export const Filters = () => {
return (
<div className="flex flex-col lg:flex-row items-center gap-y-2 lg:gap-y-0 lg:gap-x-2">
<AccountFilter />
<DateFilter />
</div>
)
}

More...

Build a Finance SaaS Platform -25 (Spending Pie)

Add Spending Pie Component

Add components/spending-pie.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

import { useState } from "react";
import { FileSearch, Loader2, PieChart, Radar, Target } from "lucide-react";

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"

import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";

import { PieVariant } from "@/components/pie-variant";
import { RadialVariant } from "@/components/radial-variant";
import { RadarVariant } from "./radar-variant";
import { Skeleton } from "./ui/skeleton";


type Props = {
data?: {
name: string;
value: number;
}[];
};

export const SpendingPie = ( { data = []} : Props) => {
const [chartType, setChartType] = useState("pie");

const onTypeChange = ( type: string) => {
// TODO: Add paywall
setChartType(type);
}
return(
<Card className="border-none drop-shadow-sm">
<CardHeader className="flex space-y-2 lg:space-y-0 lg:flex-row lg:items-center justify-between">
<CardTitle className="text-xl line-clamp-1">
Categories
</CardTitle>
<Select
defaultValue={chartType}
onValueChange={onTypeChange}
>
<SelectTrigger className="lg:w-auto h-9 rounded-md px-3">
<SelectValue placeholder="Chart type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="pie">
<div className="flex items-center">
<PieChart className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Pie chart
</p>
</div>
</SelectItem>
<SelectItem value="radar">
<div className="flex items-center">
<Radar className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Radar chart
</p>
</div>
</SelectItem>
<SelectItem value="radial">
<div className="flex items-center">
<Target className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Radial chart
</p>
</div>
</SelectItem>
</SelectContent>
</Select>
</CardHeader>
<CardContent>
{data.length === 0 ? (
<div className="felx flex-col gap-y-4 items-center justify-center h-[350px] w-full">
<FileSearch className="size-6 text-muted-foreground" />
<p className="text-muted-foreground text-sm">
No data for this period
</p>
</div>
) : (
<>
{ chartType === "pie" && <PieVariant data={data} /> }
{ chartType === "radar" && <RadarVariant data={data} /> }
{ chartType === "radial" && <RadialVariant data={data} /> }
</>
)}
</CardContent>
</Card>
)
}

export const SpendingPieLoading =() => {
return (
<Card className="border-none drop-shadow-sm">
<CardHeader className="flex space-y-2 lg:space-y-0 lg:flex-row lg:items-center justify-between">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-8 lg:w-[120px] w-full" />
</CardHeader>
<CardContent>
<div className="h-[350px] w-full flex items-center justify-center">
<Loader2 className="h-6 w-6 text-slate-300 animate-spin"/>
</div>
</CardContent>
</Card>
);
};
More...

请我喝杯咖啡吧~

支付宝
微信