- 支持试读
准备工作
本专题需要事先做的准备工作。专题完结之后,你可以通过 zliu.tech、liziqi.ggff.net、zlusi.me和reqwest.me 体验我们提供的域名分发服务。 Cloudflare 常用 API
本章我们讨论本专题需要的 Cloudflare API。- 支持试读
项目初始化及配置
本章将对我们的项目进行初始化,然后编写配置。 数据及模型定义
本章我们将对数据表和对应的数据模型进行定义。数据库操作模块
本章我们将简单实现一些数据库的操作。- 支持试读
注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中
本章将讨论注册 Cloudflare Turnstile 并将其集成到 AXUM 应用中。 - 支持试读
开发前端 SPA
为了更直观的进行 API 开发,我们先来编写前端 SPA。 用户注册及邮箱激活
本章将实现用户注册功能:包括数据库操作、邮箱激活账号等。本章包含后端和前端功能。用户登录、退出登录及鉴权中间件
本章将实现用户登录和退出登录功能:包括会话管理、鉴权中间件等。本章包含后端和前端功能。用户注册域名
本章将实现用户注册域名功能:包括数据库操作、CF API 封装调用等。本章包含后端和前端功能。用户管理域名
本章将实现用户管理域名功能:包括修改、删除域名的数据库操作、CF API 封装调用等。本章包含后端和前端功能。- 支持试读
用户积分变动记录
本章将实现用户查看本账号积分变动功能。本章包含后端和前端功能。 用户修改密码
本章将实现用户修改密码功能。本章包含后端和前端功能。后台管理
本章将实现后台管理功能。本章包含后端和前端功能。由于后台大部分功能和用户面板的功能重合,所以后台管理功能将集中在本章一个大章来进行全部讲解。把 React 开发的 SPA 嵌入 AXUM 应用中
本章将讨论把 React 开发的 SPA 嵌入 AXUM 应用中,并将其部署到生产环境。
用户积分变动记录
- 77
- 2025-01-09 15:40:55
本章将实现用户查看本账号积分变动功能。本章包含后端和前端功能。
// src/db/pointer_log.rs
pub async fn list_all<'a>(c: impl PgExecutor<'a>, user_id: &'a str) -> Result<Vec<Model>> {
let mut q = QueryBuilder::new(
r#"SELECT id, user_id, dateline, pointer_amount, before_pointer, after_pointer, "note" FROM pointer_logs WHERE user_id="#,
);
q.push_bind(user_id)
.push(" ORDER BY id DESC")
.push(" LIMIT 50");
q.build_query_as().fetch_all(c).await
}
- 根据用户ID列出所有积分变动记录
- 获取当前登录用户
- 从数据库中查找该用户积分变动记录
前端
// src/pages/user/Pointer.tsx
import UserPageTitle from "@/components/UserPageTitle";
import { Component as PointerIcon } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useEffect, useState } from "react";
import { $get, $ifApiErrorMsg, $isApiUnauthorizedError } from "@/lib/$fetch";
import use$status from "@/hooks/use$status";
import { useNavigate } from "react-router-dom";
import dayjs from "dayjs";
export default function UserPointerPage() {
const [list, setList] = useState<PointerLog[]>([]);
const { $setLoading, $setToast } = use$status();
const navigate = useNavigate();
const loadData = async () => {
try {
$setLoading(true);
const res = await $get<PointerLog[]>("/user/pointer");
if (res) {
setList(res);
}
} catch (e) {
if ($isApiUnauthorizedError(e)) {
$setToast(e.message);
return navigate("/login");
}
$setToast($ifApiErrorMsg(e));
} finally {
$setLoading(false);
}
};
useEffect(() => {
loadData();
}, []);
return (
<>
<div className="p-3 space-y-6">
<section className="flex justify-between items-center gap-x-2 ">
<UserPageTitle icon={<PointerIcon className="w-6" />}>
积分记录
</UserPageTitle>
</section>
<section className="my-6 bg-white rounded-md p-3">
<Table>
<TableHeader>
<TableRow>
<TableHead>#</TableHead>
<TableHead>类型</TableHead>
<TableHead>变动</TableHead>
<TableHead>可用积分</TableHead>
<TableHead>详情</TableHead>
<TableHead>时间</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{list.map((i) => (
<TableRow key={i.id}>
<TableCell>
<div className="text-xs font-mono">{i.id}</div>
</TableCell>
<TableCell>
{i.pointer_amount > 0 ? "收入" : "消费"}
</TableCell>
<TableCell>
{i.pointer_amount > 0 ? (
<div className="text-green-600">+{i.pointer_amount}</div>
) : (
<div className="text-red-600">{i.pointer_amount}</div>
)}
</TableCell>
<TableCell>{i.after_pointer}</TableCell>
<TableCell>{i.note}</TableCell>
<TableCell>
{dayjs(i.dateline).format("YYYY-MM-DD HH:mm:ss")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</div>
</>
);
}