同步数据库工具

背景

之前开发时就发现,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

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

如何修改上传按钮组件样式

背景

系统自带的上传按钮有点丑,并且大的样式无法修改,想了个方法用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 函数,传输给外部调用者

PostgreSQL Windows Docker Setup

背景

项目需要使用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

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...

请我喝杯咖啡吧~

支付宝
微信