Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "rsbuild preview"
},
"dependencies": {
"@cap.js/widget": "^0.1.41",
"@hookform/resolvers": "^5.2.2",
"@tabler/icons-react": "^3.36.1",
"@tanstack/react-query": "^5.90.17",
Expand Down
12 changes: 11 additions & 1 deletion src/api/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@ export const UpdatePasswordApiBody = z.object({
newPassword: z.string().min(8, "密码长度至少为8位"),
});
export class AuthAPI {
static async login(data: { email: string; password: string }) {
static async login(data: { email: string; password: string; captchaToken: string }) {
const res = await Axios.post<{ token: string; user: User }>("/auth/login", data);
return res.data;
}

static async register(data: {
name: string;
password: string;
email: string;
captchaToken: string;
}) {
const res = await Axios.post<{ token: string; user: User }>("/auth/register", data);
return res.data;
}

static async getCurrentUser() {
const res = await Axios.get<User>("/auth/me");
return res.data;
Expand Down
39 changes: 35 additions & 4 deletions src/api/developer/application.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
import Axios from "@/api";
import { Application } from "@/types/application";
import { InferZodType } from "@/types/common";
import { List } from "@/types/Request";
import { Application } from "@/types/application";
import { z } from "zod";

export const EditApplicationApiBody = z.object({
name: z.string().min(1),
description: z.string().nullish(),
});

export class ApplicationApi {
static async getApplicationList(params?: { pageSize?: number; current?: number; search?: string; orgSearch?: string }) {
const res = await Axios.get<List<Application>>("/developer/application", {
params,
});
return res.data;
}

static async getApplicationDetail(id: string) {
const res = await Axios.get<Application>(`/developer/application/${id}`);
return res.data;
}

static async createApplication(application: InferZodType<typeof EditApplicationApiBody>) {
const res = await Axios.post<Application>("/developer/application", application);
return res.data;
}

static async updateApplication(id: string, application: InferZodType<typeof EditApplicationApiBody>) {
const res = await Axios.post<Application>(`/developer/application/${id}`, application);
return res.data;
}

export async function getApplicationList() {
const res = await Axios.get<List<Application>>("/developer/application");
return res.data;
static async deleteApplication(id: string) {
const res = await Axios.delete<{ success: boolean }>(`/developer/application/${id}`);
return res.data;
}
}
95 changes: 95 additions & 0 deletions src/components/Application/ApplicationEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { ApplicationApi, EditApplicationApiBody } from "@/api/developer/application";
import { InferZodType } from "@/types/common";
import { Application } from "@/types/application";
import { useZodValidateData } from "@/utils/form";
import { pickBy } from "es-toolkit";
import { App, Button, Flex, Form, Input, Modal } from "antd";

const { TextArea } = Input;

export default function ApplicationEditor({
opened,
onClose,
editingApplication,
}: {
opened: boolean;
onClose: () => void;
editingApplication: Application | null;
}) {
return (
<Modal
open={opened}
onCancel={onClose}
title={editingApplication?.id ? "编辑应用" : "添加应用"}
centered
destroyOnHidden
footer={null}
>
<EditorContent editingApplication={editingApplication} onClose={onClose} />
</Modal>
);
}

function EditorContent({
editingApplication,
onClose,
}: {
editingApplication: Application | null;
onClose: () => void;
}) {
const { message, modal } = App.useApp();
const cleanedApplication = editingApplication ? pickBy(editingApplication, (v) => v !== "" && v != null) : {};
const [form] = Form.useForm();

const initialValues = {
name: cleanedApplication.name,
description: cleanedApplication.description,
};

const onSubmit = async (value: InferZodType<typeof EditApplicationApiBody>) => {
try {
if (editingApplication?.id) {
await ApplicationApi.updateApplication(editingApplication.id, value);
message.success("更新应用成功");
return onClose();
}
await ApplicationApi.createApplication(value);
message.success("创建应用成功");
return onClose();
} catch (error) {
message.error(`有错误发生: ${JSON.stringify(error)}`);
}
};

const handleFinish = (value: typeof initialValues) => {
const processedValues = useZodValidateData(value, EditApplicationApiBody);
if (processedValues.errors.length > 0) {
return modal.warning({
title: "接口数据校验失败☹️",
content: processedValues.prettyErrors,
});
}
if (processedValues.values) {
return onSubmit(processedValues.values);
}
return;
};

return (
<Form form={form} onFinish={handleFinish} layout="vertical" initialValues={initialValues}>
<Form.Item label="应用名称" required name="name" rules={[{ required: true, message: "请输入应用名称" }]}>
<Input placeholder="请输入应用名称" />
</Form.Item>

<Form.Item label="应用描述" name="description">
<TextArea placeholder="请输入应用描述" autoSize={{ minRows: 4 }} />
</Form.Item>

<Flex justify="flex-end" style={{ marginTop: 16 }}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Flex>
</Form>
);
}
68 changes: 68 additions & 0 deletions src/components/CapWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import "@cap.js/widget";
import type { CapWidget as CapWidgetType } from "@cap.js/widget";
import { forwardRef, useImperativeHandle, useRef } from "react";

export interface CapWidgetProps {
apiEndpoint: string;
onSolve?: (token: string) => void;
onProgress?: (progress: number) => void;
onCapError?: (message: string) => void;
className?: string;
style?: React.CSSProperties;
}

export interface CapWidgetRef {
reset: () => void;
}

declare module "react" {
namespace JSX {
interface IntrinsicElements {
"cap-widget": React.DetailedHTMLProps<
React.HTMLAttributes<CapWidgetType> & {
"data-cap-api-endpoint"?: string;
onsolve?: (e: CustomEvent<{ token: string }>) => void;
onprogress?: (e: CustomEvent<{ progress: number }>) => void;
onerror?: (e: CustomEvent<{ message: string }>) => void;
},
CapWidgetType
>;
}
}
}

export const CapWidget = forwardRef<CapWidgetRef, CapWidgetProps>(
({ apiEndpoint, onSolve, onProgress, onCapError, className, style }, ref) => {
const widgetRef = useRef<CapWidgetType>(null);

useImperativeHandle(ref, () => ({
reset: () => {
widgetRef.current?.reset();
},
}));

return (
<cap-widget
ref={widgetRef}
className={className}
style={style}
data-cap-api-endpoint={apiEndpoint}
onsolve={(e) => onSolve?.(e.detail.token)}
onprogress={(e) => onProgress?.(e.detail.progress)}
onerror={(e) => onCapError?.(e.detail.message)}
data-cap-i18n-initial-state="验证您是人类"
data-cap-i18n-verifying-label="验证中..."
data-cap-i18n-solved-label="验证成功"
data-cap-i18n-error-label="验证失败"
data-cap-i18n-troubleshooting-label="故障排除"
data-cap-i18n-wasm-disabled="启用 WASM 以显著加快验证速度"
data-cap-i18n-verify-aria-label="点击验证您是人类"
data-cap-i18n-verifying-aria-label="验证中,请稍候"
data-cap-i18n-verified-aria-label="验证成功"
data-cap-i18n-error-aria-label="验证失败,请重试"
/>
);
}
);

CapWidget.displayName = "CapWidget";
62 changes: 37 additions & 25 deletions src/components/EventFeature/FeatureEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@ export default function FeatureEditor({
opened: boolean;
onClose: () => void;
editingFeature: Feature | null;
}) {
return (
<Modal open={opened} onCancel={onClose} title="标签编辑" centered destroyOnHidden footer={null}>
<EditorContent editingFeature={editingFeature} onClose={onClose} />
</Modal>
);
}

function EditorContent({
editingFeature,
onClose,
}: {
editingFeature: Feature | null;
onClose: () => void;
}) {
const { message, modal } = App.useApp();
const cleanedFeature = editingFeature ? pickBy(editingFeature, (v) => v !== "" && v != null) : {};

const [form] = Form.useForm();

const initialValues = {
Expand Down Expand Up @@ -55,33 +68,32 @@ export default function FeatureEditor({
}
return;
};

return (
<Modal open={opened} onCancel={onClose} title="标签编辑" centered destroyOnHidden footer={null}>
<Form form={form} onFinish={handleFinish} layout="vertical" initialValues={initialValues}>
<Form.Item label="标签名称" required name="name" rules={[{ required: true, message: "请输入标签名称" }]}>
<Input placeholder="请输入标签名称" />
</Form.Item>
<Form form={form} onFinish={handleFinish} layout="vertical" initialValues={initialValues}>
<Form.Item label="标签名称" required name="name" rules={[{ required: true, message: "请输入标签名称" }]}>
<Input placeholder="请输入标签名称" />
</Form.Item>

<Form.Item label="标签分类" required name="category" rules={[{ required: true, message: "请选择标签分类" }]}>
<Select
placeholder="请选择标签分类"
options={Object.values(FeatureCategory).map((item) => ({
label: FeatureCategoryLabel[item],
value: item,
}))}
/>
</Form.Item>
<Form.Item label="标签分类" required name="category" rules={[{ required: true, message: "请选择标签分类" }]}>
<Select
placeholder="请选择标签分类"
options={Object.values(FeatureCategory).map((item) => ({
label: FeatureCategoryLabel[item],
value: item,
}))}
/>
</Form.Item>

<Form.Item label="标签简述" name="description" extra="标签简述可能会在未来展示于筛选设置中。">
<TextArea placeholder="请输入标签简述" />
</Form.Item>
<Form.Item label="标签简述" name="description" extra="标签简述可能会在未来展示于筛选设置中。">
<TextArea placeholder="请输入标签简述" />
</Form.Item>

<Flex justify="flex-end" style={{ marginTop: 16 }}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Flex>
</Form>
</Modal>
<Flex justify="flex-end" style={{ marginTop: 16 }}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Flex>
</Form>
);
}
Loading
Loading