refactor: 整合财务系统到主应用并重构后端架构
主要变更: - 将独立的 web-finance 应用整合到 web-antd 主应用中 - 重命名 backend-mock 为 backend,增强后端功能 - 新增财务模块 API 端点(账户、预算、类别、交易) - 增强财务仪表板和报表功能 - 添加 SQLite 数据存储支持和财务数据导入脚本 - 优化路由结构,删除冗余的 finance-system 模块 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
14
apps/backend/api/auth/codes.ts
Normal file
14
apps/backend/api/auth/codes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const codes =
|
||||
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
|
||||
|
||||
return useResponseSuccess(codes);
|
||||
});
|
||||
36
apps/backend/api/auth/login.post.ts
Normal file
36
apps/backend/api/auth/login.post.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { password, username } = await readBody(event);
|
||||
if (!password || !username) {
|
||||
setResponseStatus(event, 400);
|
||||
return useResponseError(
|
||||
'BadRequestException',
|
||||
'Username and password are required',
|
||||
);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === username && item.password === password,
|
||||
);
|
||||
|
||||
if (!findUser) {
|
||||
clearRefreshTokenCookie(event);
|
||||
return forbiddenResponse(event, 'Username or password is incorrect.');
|
||||
}
|
||||
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
const refreshToken = generateRefreshToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return useResponseSuccess({
|
||||
...findUser,
|
||||
accessToken,
|
||||
});
|
||||
});
|
||||
15
apps/backend/api/auth/logout.post.ts
Normal file
15
apps/backend/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return useResponseSuccess('');
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
return useResponseSuccess('');
|
||||
});
|
||||
33
apps/backend/api/auth/refresh.post.ts
Normal file
33
apps/backend/api/auth/refresh.post.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { verifyRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
const userinfo = verifyRefreshToken(refreshToken);
|
||||
if (!userinfo) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === userinfo.username,
|
||||
);
|
||||
if (!findUser) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return accessToken;
|
||||
});
|
||||
28
apps/backend/api/demo/bigint.ts
Normal file
28
apps/backend/api/demo/bigint.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const data = `
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 123456789012345678901234567890123456789012345678901234567890,
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john-doe@demo.com"
|
||||
},
|
||||
{
|
||||
"id": 987654321098765432109876543210987654321098765432109876543210,
|
||||
"name": "Jane Smith",
|
||||
"age": 25,
|
||||
"email": "jane@demo.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
`;
|
||||
setHeader(event, 'Content-Type', 'application/json');
|
||||
return data;
|
||||
});
|
||||
17
apps/backend/api/finance/accounts.get.ts
Normal file
17
apps/backend/api/finance/accounts.get.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { getQuery } from 'h3';
|
||||
|
||||
import { listAccounts } from '~/utils/finance-metadata';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event);
|
||||
const currency = query.currency as string | undefined;
|
||||
|
||||
let accounts = listAccounts();
|
||||
|
||||
if (currency) {
|
||||
accounts = accounts.filter((account) => account.currency === currency);
|
||||
}
|
||||
|
||||
return useResponseSuccess(accounts);
|
||||
});
|
||||
10
apps/backend/api/finance/budgets.get.ts
Normal file
10
apps/backend/api/finance/budgets.get.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineEventHandler } from '#nitro';
|
||||
|
||||
import { MOCK_BUDGETS } from '../../utils/mock-data';
|
||||
import { useResponseSuccess } from '../../utils/response';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
// 返回未删除的预算
|
||||
const budgets = MOCK_BUDGETS.filter((b) => !b.isDeleted);
|
||||
return useResponseSuccess(budgets);
|
||||
});
|
||||
33
apps/backend/api/finance/budgets.post.ts
Normal file
33
apps/backend/api/finance/budgets.post.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { defineEventHandler, readBody } from '#nitro';
|
||||
|
||||
import { MOCK_BUDGETS } from '../../utils/mock-data';
|
||||
import { useResponseSuccess } from '../../utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
const newBudget = {
|
||||
id: Date.now(),
|
||||
userId: 1,
|
||||
category: body.category,
|
||||
categoryId: body.categoryId,
|
||||
emoji: body.emoji,
|
||||
limit: body.limit,
|
||||
spent: body.spent || 0,
|
||||
remaining: body.remaining || body.limit,
|
||||
percentage: body.percentage || 0,
|
||||
currency: body.currency,
|
||||
period: body.period,
|
||||
alertThreshold: body.alertThreshold,
|
||||
description: body.description,
|
||||
autoRenew: body.autoRenew,
|
||||
overspendAlert: body.overspendAlert,
|
||||
dailyReminder: body.dailyReminder,
|
||||
monthlyTrend: body.monthlyTrend || 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
isDeleted: false,
|
||||
};
|
||||
|
||||
MOCK_BUDGETS.push(newBudget);
|
||||
return useResponseSuccess(newBudget);
|
||||
});
|
||||
22
apps/backend/api/finance/budgets/[id].delete.ts
Normal file
22
apps/backend/api/finance/budgets/[id].delete.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineEventHandler, getRouterParam } from '#nitro';
|
||||
|
||||
import { MOCK_BUDGETS } from '../../../utils/mock-data';
|
||||
import { useResponseError, useResponseSuccess } from '../../../utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
const index = MOCK_BUDGETS.findIndex((b) => b.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return useResponseError('预算不存在', -1);
|
||||
}
|
||||
|
||||
// 软删除
|
||||
MOCK_BUDGETS[index] = {
|
||||
...MOCK_BUDGETS[index],
|
||||
isDeleted: true,
|
||||
deletedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return useResponseSuccess({ message: '删除成功' });
|
||||
});
|
||||
48
apps/backend/api/finance/budgets/[id].put.ts
Normal file
48
apps/backend/api/finance/budgets/[id].put.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { defineEventHandler, getRouterParam, readBody } from '#nitro';
|
||||
|
||||
import { MOCK_BUDGETS } from '../../../utils/mock-data';
|
||||
import { useResponseError, useResponseSuccess } from '../../../utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
const body = await readBody(event);
|
||||
|
||||
const index = MOCK_BUDGETS.findIndex((b) => b.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return useResponseError('预算不存在', -1);
|
||||
}
|
||||
|
||||
// 如果是恢复操作
|
||||
if (body.isDeleted === false) {
|
||||
MOCK_BUDGETS[index] = {
|
||||
...MOCK_BUDGETS[index],
|
||||
isDeleted: false,
|
||||
deletedAt: undefined,
|
||||
};
|
||||
return useResponseSuccess(MOCK_BUDGETS[index]);
|
||||
}
|
||||
|
||||
// 普通更新
|
||||
const updatedBudget = {
|
||||
...MOCK_BUDGETS[index],
|
||||
category: body.category ?? MOCK_BUDGETS[index].category,
|
||||
categoryId: body.categoryId ?? MOCK_BUDGETS[index].categoryId,
|
||||
emoji: body.emoji ?? MOCK_BUDGETS[index].emoji,
|
||||
limit: body.limit ?? MOCK_BUDGETS[index].limit,
|
||||
spent: body.spent ?? MOCK_BUDGETS[index].spent,
|
||||
remaining: body.remaining ?? MOCK_BUDGETS[index].remaining,
|
||||
percentage: body.percentage ?? MOCK_BUDGETS[index].percentage,
|
||||
currency: body.currency ?? MOCK_BUDGETS[index].currency,
|
||||
period: body.period ?? MOCK_BUDGETS[index].period,
|
||||
alertThreshold: body.alertThreshold ?? MOCK_BUDGETS[index].alertThreshold,
|
||||
description: body.description ?? MOCK_BUDGETS[index].description,
|
||||
autoRenew: body.autoRenew ?? MOCK_BUDGETS[index].autoRenew,
|
||||
overspendAlert: body.overspendAlert ?? MOCK_BUDGETS[index].overspendAlert,
|
||||
dailyReminder: body.dailyReminder ?? MOCK_BUDGETS[index].dailyReminder,
|
||||
monthlyTrend: body.monthlyTrend ?? MOCK_BUDGETS[index].monthlyTrend,
|
||||
};
|
||||
|
||||
MOCK_BUDGETS[index] = updatedBudget;
|
||||
return useResponseSuccess(updatedBudget);
|
||||
});
|
||||
13
apps/backend/api/finance/categories.get.ts
Normal file
13
apps/backend/api/finance/categories.get.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { getQuery } from 'h3';
|
||||
|
||||
import { fetchCategories } from '~/utils/finance-repository';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event);
|
||||
const type = query.type as 'income' | 'expense' | undefined;
|
||||
|
||||
const categories = fetchCategories({ type });
|
||||
|
||||
return useResponseSuccess(categories);
|
||||
});
|
||||
23
apps/backend/api/finance/categories.post.ts
Normal file
23
apps/backend/api/finance/categories.post.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { readBody } from 'h3';
|
||||
|
||||
import { createCategoryRecord } from '~/utils/finance-metadata';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
if (!body?.name || !body?.type) {
|
||||
return useResponseError('分类名称和类型为必填项', -1);
|
||||
}
|
||||
|
||||
const category = createCategoryRecord({
|
||||
name: body.name,
|
||||
type: body.type,
|
||||
icon: body.icon,
|
||||
color: body.color,
|
||||
userId: 1,
|
||||
isActive: body.isActive ?? true,
|
||||
});
|
||||
|
||||
return useResponseSuccess(category);
|
||||
});
|
||||
18
apps/backend/api/finance/categories/[id].delete.ts
Normal file
18
apps/backend/api/finance/categories/[id].delete.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getRouterParam } from 'h3';
|
||||
|
||||
import { deleteCategoryRecord } from '~/utils/finance-metadata';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (Number.isNaN(id)) {
|
||||
return useResponseError('参数错误', -1);
|
||||
}
|
||||
|
||||
const deleted = deleteCategoryRecord(id);
|
||||
if (!deleted) {
|
||||
return useResponseError('分类不存在', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess({ message: '删除成功' });
|
||||
});
|
||||
27
apps/backend/api/finance/categories/[id].put.ts
Normal file
27
apps/backend/api/finance/categories/[id].put.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getRouterParam, readBody } from 'h3';
|
||||
|
||||
import { updateCategoryRecord } from '~/utils/finance-metadata';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (Number.isNaN(id)) {
|
||||
return useResponseError('参数错误', -1);
|
||||
}
|
||||
|
||||
const body = await readBody(event);
|
||||
|
||||
const updated = updateCategoryRecord(id, {
|
||||
name: body?.name,
|
||||
icon: body?.icon,
|
||||
color: body?.color,
|
||||
userId: body?.userId,
|
||||
isActive: body?.isActive,
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
return useResponseError('分类不存在', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess(updated);
|
||||
});
|
||||
6
apps/backend/api/finance/currencies.get.ts
Normal file
6
apps/backend/api/finance/currencies.get.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { listCurrencies } from '~/utils/finance-metadata';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
return useResponseSuccess(listCurrencies());
|
||||
});
|
||||
30
apps/backend/api/finance/exchange-rates.get.ts
Normal file
30
apps/backend/api/finance/exchange-rates.get.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { getQuery } from 'h3';
|
||||
|
||||
import { listExchangeRates } from '~/utils/finance-metadata';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event);
|
||||
const fromCurrency = query.from as string | undefined;
|
||||
const toCurrency = query.to as string | undefined;
|
||||
const date = query.date as string | undefined;
|
||||
|
||||
let rates = listExchangeRates();
|
||||
|
||||
if (fromCurrency) {
|
||||
rates = rates.filter((rate) => rate.fromCurrency === fromCurrency);
|
||||
}
|
||||
|
||||
if (toCurrency) {
|
||||
rates = rates.filter((rate) => rate.toCurrency === toCurrency);
|
||||
}
|
||||
|
||||
if (date) {
|
||||
rates = rates.filter((rate) => rate.date === date);
|
||||
} else if (rates.length > 0) {
|
||||
const latestDate = rates.reduce((max, rate) => (rate.date > max ? rate.date : max), rates[0].date);
|
||||
rates = rates.filter((rate) => rate.date === latestDate);
|
||||
}
|
||||
|
||||
return useResponseSuccess(rates);
|
||||
});
|
||||
12
apps/backend/api/finance/transactions.get.ts
Normal file
12
apps/backend/api/finance/transactions.get.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getQuery } from 'h3';
|
||||
|
||||
import { fetchTransactions } from '~/utils/finance-repository';
|
||||
import { useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event);
|
||||
const type = query.type as string | undefined;
|
||||
const transactions = fetchTransactions({ type });
|
||||
|
||||
return useResponseSuccess(transactions);
|
||||
});
|
||||
33
apps/backend/api/finance/transactions.post.ts
Normal file
33
apps/backend/api/finance/transactions.post.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { readBody } from 'h3';
|
||||
|
||||
import { createTransaction } from '~/utils/finance-repository';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
const DEFAULT_CURRENCY = 'CNY';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
if (!body?.type || !body?.amount || !body?.transactionDate) {
|
||||
return useResponseError('缺少必填字段', -1);
|
||||
}
|
||||
|
||||
const amount = Number(body.amount);
|
||||
if (Number.isNaN(amount)) {
|
||||
return useResponseError('金额格式不正确', -1);
|
||||
}
|
||||
|
||||
const transaction = createTransaction({
|
||||
type: body.type,
|
||||
amount,
|
||||
currency: body.currency ?? DEFAULT_CURRENCY,
|
||||
categoryId: body.categoryId ?? null,
|
||||
accountId: body.accountId ?? null,
|
||||
transactionDate: body.transactionDate,
|
||||
description: body.description ?? '',
|
||||
project: body.project ?? null,
|
||||
memo: body.memo ?? null,
|
||||
});
|
||||
|
||||
return useResponseSuccess(transaction);
|
||||
});
|
||||
19
apps/backend/api/finance/transactions/[id].delete.ts
Normal file
19
apps/backend/api/finance/transactions/[id].delete.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getRouterParam } from 'h3';
|
||||
|
||||
import { softDeleteTransaction } from '~/utils/finance-repository';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
|
||||
if (Number.isNaN(id)) {
|
||||
return useResponseError('参数错误', -1);
|
||||
}
|
||||
|
||||
const updated = softDeleteTransaction(id);
|
||||
if (!updated) {
|
||||
return useResponseError('交易不存在', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess({ message: '删除成功' });
|
||||
});
|
||||
47
apps/backend/api/finance/transactions/[id].put.ts
Normal file
47
apps/backend/api/finance/transactions/[id].put.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { getRouterParam, readBody } from 'h3';
|
||||
|
||||
import { restoreTransaction, updateTransaction } from '~/utils/finance-repository';
|
||||
import { useResponseError, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (Number.isNaN(id)) {
|
||||
return useResponseError('参数错误', -1);
|
||||
}
|
||||
|
||||
const body = await readBody(event);
|
||||
|
||||
if (body?.isDeleted === false) {
|
||||
const restored = restoreTransaction(id);
|
||||
if (!restored) {
|
||||
return useResponseError('交易不存在', -1);
|
||||
}
|
||||
return useResponseSuccess(restored);
|
||||
}
|
||||
|
||||
const payload: Record<string, unknown> = {};
|
||||
|
||||
if (body?.type) payload.type = body.type;
|
||||
if (body?.amount !== undefined) {
|
||||
const amount = Number(body.amount);
|
||||
if (Number.isNaN(amount)) {
|
||||
return useResponseError('金额格式不正确', -1);
|
||||
}
|
||||
payload.amount = amount;
|
||||
}
|
||||
if (body?.currency) payload.currency = body.currency;
|
||||
if (body?.categoryId !== undefined) payload.categoryId = body.categoryId ?? null;
|
||||
if (body?.accountId !== undefined) payload.accountId = body.accountId ?? null;
|
||||
if (body?.transactionDate) payload.transactionDate = body.transactionDate;
|
||||
if (body?.description !== undefined) payload.description = body.description ?? '';
|
||||
if (body?.project !== undefined) payload.project = body.project ?? null;
|
||||
if (body?.memo !== undefined) payload.memo = body.memo ?? null;
|
||||
if (body?.isDeleted !== undefined) payload.isDeleted = body.isDeleted;
|
||||
|
||||
const updated = updateTransaction(id, payload);
|
||||
if (!updated) {
|
||||
return useResponseError('交易不存在', -1);
|
||||
}
|
||||
|
||||
return useResponseSuccess(updated);
|
||||
});
|
||||
13
apps/backend/api/menu/all.ts
Normal file
13
apps/backend/api/menu/all.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const menus =
|
||||
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
|
||||
return useResponseSuccess(menus);
|
||||
});
|
||||
5
apps/backend/api/status.ts
Normal file
5
apps/backend/api/status.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default eventHandler((event) => {
|
||||
const { status } = getQuery(event);
|
||||
setResponseStatus(event, Number(status));
|
||||
return useResponseError(`${status}`);
|
||||
});
|
||||
15
apps/backend/api/system/dept/.post.ts
Normal file
15
apps/backend/api/system/dept/.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(600);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
15
apps/backend/api/system/dept/[id].delete.ts
Normal file
15
apps/backend/api/system/dept/[id].delete.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(1000);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
15
apps/backend/api/system/dept/[id].put.ts
Normal file
15
apps/backend/api/system/dept/[id].put.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(2000);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
61
apps/backend/api/system/dept/list.ts
Normal file
61
apps/backend/api/system/dept/list.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem: Record<string, any> = {
|
||||
id: faker.string.uuid(),
|
||||
pid: 0,
|
||||
name: faker.commerce.department(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
|
||||
),
|
||||
remark: faker.lorem.sentence(),
|
||||
};
|
||||
if (faker.datatype.boolean()) {
|
||||
dataItem.children = Array.from(
|
||||
{ length: faker.number.int({ min: 1, max: 5 }) },
|
||||
() => ({
|
||||
id: faker.string.uuid(),
|
||||
pid: dataItem.id,
|
||||
name: faker.commerce.department(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
|
||||
),
|
||||
remark: faker.lorem.sentence(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(10);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const listData = structuredClone(mockData);
|
||||
|
||||
return useResponseSuccess(listData);
|
||||
});
|
||||
12
apps/backend/api/system/menu/list.ts
Normal file
12
apps/backend/api/system/menu/list.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
return useResponseSuccess(MOCK_MENU_LIST);
|
||||
});
|
||||
28
apps/backend/api/system/menu/name-exists.ts
Normal file
28
apps/backend/api/system/menu/name-exists.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
const namesMap: Record<string, any> = {};
|
||||
|
||||
function getNames(menus: any[]) {
|
||||
menus.forEach((menu) => {
|
||||
namesMap[menu.name] = String(menu.id);
|
||||
if (menu.children) {
|
||||
getNames(menu.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
getNames(MOCK_MENU_LIST);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const { id, name } = getQuery(event);
|
||||
|
||||
return (name as string) in namesMap &&
|
||||
(!id || namesMap[name as string] !== String(id))
|
||||
? useResponseSuccess(true)
|
||||
: useResponseSuccess(false);
|
||||
});
|
||||
28
apps/backend/api/system/menu/path-exists.ts
Normal file
28
apps/backend/api/system/menu/path-exists.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
const pathMap: Record<string, any> = { '/': 0 };
|
||||
|
||||
function getPaths(menus: any[]) {
|
||||
menus.forEach((menu) => {
|
||||
pathMap[menu.path] = String(menu.id);
|
||||
if (menu.children) {
|
||||
getPaths(menu.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
getPaths(MOCK_MENU_LIST);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const { id, path } = getQuery(event);
|
||||
|
||||
return (path as string) in pathMap &&
|
||||
(!id || pathMap[path as string] !== String(id))
|
||||
? useResponseSuccess(true)
|
||||
: useResponseSuccess(false);
|
||||
});
|
||||
83
apps/backend/api/system/role/list.ts
Normal file
83
apps/backend/api/system/role/list.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||
|
||||
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
const menuIds = getMenuIds(MOCK_MENU_LIST);
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem: Record<string, any> = {
|
||||
id: faker.string.uuid(),
|
||||
name: faker.commerce.product(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
|
||||
),
|
||||
permissions: faker.helpers.arrayElements(menuIds),
|
||||
remark: faker.lorem.sentence(),
|
||||
};
|
||||
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(100);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
name,
|
||||
id,
|
||||
remark,
|
||||
startTime,
|
||||
endTime,
|
||||
status,
|
||||
} = getQuery(event);
|
||||
let listData = structuredClone(mockData);
|
||||
if (name) {
|
||||
listData = listData.filter((item) =>
|
||||
item.name.toLowerCase().includes(String(name).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (id) {
|
||||
listData = listData.filter((item) =>
|
||||
item.id.toLowerCase().includes(String(id).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (remark) {
|
||||
listData = listData.filter((item) =>
|
||||
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (startTime) {
|
||||
listData = listData.filter((item) => item.createTime >= startTime);
|
||||
}
|
||||
if (endTime) {
|
||||
listData = listData.filter((item) => item.createTime <= endTime);
|
||||
}
|
||||
if (['0', '1'].includes(status as string)) {
|
||||
listData = listData.filter((item) => item.status === Number(status));
|
||||
}
|
||||
return usePageResponseSuccess(page as string, pageSize as string, listData);
|
||||
});
|
||||
73
apps/backend/api/table/list.ts
Normal file
73
apps/backend/api/table/list.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem = {
|
||||
id: faker.string.uuid(),
|
||||
imageUrl: faker.image.avatar(),
|
||||
imageUrl2: faker.image.avatar(),
|
||||
open: faker.datatype.boolean(),
|
||||
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
|
||||
productName: faker.commerce.productName(),
|
||||
price: faker.commerce.price(),
|
||||
currency: faker.finance.currencyCode(),
|
||||
quantity: faker.number.int({ min: 1, max: 100 }),
|
||||
available: faker.datatype.boolean(),
|
||||
category: faker.commerce.department(),
|
||||
releaseDate: faker.date.past(),
|
||||
rating: faker.number.float({ min: 1, max: 5 }),
|
||||
description: faker.commerce.productDescription(),
|
||||
weight: faker.number.float({ min: 0.1, max: 10 }),
|
||||
color: faker.color.human(),
|
||||
inProduction: faker.datatype.boolean(),
|
||||
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
|
||||
};
|
||||
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(100);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
await sleep(600);
|
||||
|
||||
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
|
||||
const listData = structuredClone(mockData);
|
||||
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
|
||||
listData.sort((a, b) => {
|
||||
if (sortOrder === 'asc') {
|
||||
if (sortBy === 'price') {
|
||||
return (
|
||||
Number.parseFloat(a[sortBy as string]) -
|
||||
Number.parseFloat(b[sortBy as string])
|
||||
);
|
||||
} else {
|
||||
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
if (sortBy === 'price') {
|
||||
return (
|
||||
Number.parseFloat(b[sortBy as string]) -
|
||||
Number.parseFloat(a[sortBy as string])
|
||||
);
|
||||
} else {
|
||||
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return usePageResponseSuccess(page as string, pageSize as string, listData);
|
||||
});
|
||||
1
apps/backend/api/test.get.ts
Normal file
1
apps/backend/api/test.get.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test get handler');
|
||||
1
apps/backend/api/test.post.ts
Normal file
1
apps/backend/api/test.post.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test post handler');
|
||||
13
apps/backend/api/upload.ts
Normal file
13
apps/backend/api/upload.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
return useResponseSuccess({
|
||||
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
});
|
||||
// return useResponseError("test")
|
||||
});
|
||||
10
apps/backend/api/user/info.ts
Normal file
10
apps/backend/api/user/info.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
return useResponseSuccess(userinfo);
|
||||
});
|
||||
Reference in New Issue
Block a user