fix: npm lint errors
This commit is contained in:
@@ -6,16 +6,16 @@ import { API_CONFIG } from './config';
|
||||
export interface RequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
headers?: Record<string, string>;
|
||||
body?: any;
|
||||
body?: unknown;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
// 自定义错误类
|
||||
export class ApiError extends Error {
|
||||
status?: number;
|
||||
data?: any;
|
||||
data?: unknown;
|
||||
|
||||
constructor(message: string, status?: number, data?: any) {
|
||||
constructor(message: string, status?: number, data?: unknown) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.status = status;
|
||||
@@ -24,7 +24,7 @@ export class ApiError extends Error {
|
||||
}
|
||||
|
||||
// 基础请求函数
|
||||
export async function request<T = any>(
|
||||
export async function request<T = unknown>(
|
||||
url: string,
|
||||
options: RequestOptions = {}
|
||||
): Promise<T> {
|
||||
@@ -67,18 +67,19 @@ export async function request<T = any>(
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
let errorData: any;
|
||||
let errorData: unknown;
|
||||
try {
|
||||
errorData = await response.json();
|
||||
} catch {
|
||||
errorData = { message: response.statusText };
|
||||
}
|
||||
|
||||
throw new ApiError(
|
||||
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
|
||||
response.status,
|
||||
errorData
|
||||
);
|
||||
let messageText = `HTTP ${response.status}: ${response.statusText}`;
|
||||
if (errorData && typeof errorData === 'object') {
|
||||
const maybe = errorData as { message?: string };
|
||||
messageText = maybe.message ?? messageText;
|
||||
}
|
||||
throw new ApiError(messageText, response.status, errorData);
|
||||
}
|
||||
|
||||
// 检查响应是否有内容
|
||||
@@ -108,7 +109,8 @@ export async function request<T = any>(
|
||||
}
|
||||
|
||||
// GET 请求
|
||||
export function get<T = any>(url: string, params?: Record<string, any>): Promise<T> {
|
||||
type QueryParamValue = string | number | boolean | null | undefined;
|
||||
export function get<T = unknown>(url: string, params?: Record<string, QueryParamValue>): Promise<T> {
|
||||
let fullUrl = url;
|
||||
|
||||
if (params) {
|
||||
@@ -129,7 +131,7 @@ export function get<T = any>(url: string, params?: Record<string, any>): Promise
|
||||
}
|
||||
|
||||
// POST 请求
|
||||
export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
|
||||
export function post<T = unknown>(url: string, data?: unknown, options?: Partial<RequestOptions>): Promise<T> {
|
||||
return request<T>(url, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
@@ -138,7 +140,7 @@ export function post<T = any>(url: string, data?: any, options?: Partial<Request
|
||||
}
|
||||
|
||||
// PUT 请求
|
||||
export function put<T = any>(url: string, data?: any): Promise<T> {
|
||||
export function put<T = unknown>(url: string, data?: unknown): Promise<T> {
|
||||
return request<T>(url, {
|
||||
method: 'PUT',
|
||||
body: data,
|
||||
@@ -146,12 +148,12 @@ export function put<T = any>(url: string, data?: any): Promise<T> {
|
||||
}
|
||||
|
||||
// DELETE 请求
|
||||
export function del<T = any>(url: string): Promise<T> {
|
||||
export function del<T = unknown>(url: string): Promise<T> {
|
||||
return request<T>(url, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// 文件上传请求
|
||||
export function upload<T = any>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
|
||||
export function upload<T = unknown>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
|
||||
const formData = new FormData();
|
||||
formData.append(fieldName, file);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// API 请求和响应类型定义
|
||||
|
||||
// 基础响应类型
|
||||
export interface ApiResponse<T = any> {
|
||||
export interface ApiResponse<T = unknown> {
|
||||
data?: T;
|
||||
error_code: number;
|
||||
error_info?: string;
|
||||
@@ -25,7 +25,7 @@ export interface PostInputRequest {
|
||||
|
||||
// 人员信息请求类型
|
||||
export interface PostPeopleRequest {
|
||||
people: Record<string, any>;
|
||||
people: People;
|
||||
}
|
||||
|
||||
// 人员查询参数类型
|
||||
@@ -39,6 +39,7 @@ export interface GetPeoplesParams {
|
||||
offset?: number;
|
||||
search?: string;
|
||||
top_k?: number;
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
}
|
||||
|
||||
// 人员信息类型
|
||||
@@ -52,8 +53,9 @@ export interface People {
|
||||
marital_status?: string;
|
||||
created_at?: number;
|
||||
match_requirement?: string;
|
||||
[key: string]: any;
|
||||
cover?: string;
|
||||
introduction?: Record<string, string>;
|
||||
comments?: { remark?: { content: string; updated_at: number } };
|
||||
}
|
||||
|
||||
// 分页响应类型
|
||||
|
||||
@@ -56,8 +56,8 @@ export async function postInputImageWithProgress(
|
||||
try {
|
||||
const response = xhr.responseText ? JSON.parse(xhr.responseText) : {};
|
||||
resolve(response);
|
||||
} catch (error) {
|
||||
resolve({error_code: 1, error_info: '解析响应失败'});
|
||||
} catch {
|
||||
resolve({ error_code: 1, error_info: '解析响应失败' });
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
|
||||
|
||||
@@ -5,10 +5,10 @@ import PeopleForm from './PeopleForm.tsx'
|
||||
import InputDrawer from './InputDrawer.tsx'
|
||||
import { createPeoplesBatch, type People } from '../apis'
|
||||
|
||||
const { Panel } = Collapse as any
|
||||
const Panel = Collapse.Panel
|
||||
const { Content } = Layout
|
||||
|
||||
type FormItem = { id: string; initialData?: any }
|
||||
type FormItem = { id: string; initialData?: Partial<People> }
|
||||
|
||||
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null }
|
||||
|
||||
@@ -29,7 +29,18 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
||||
delete instancesRef.current[id]
|
||||
}
|
||||
|
||||
const buildPeople = (values: any, common: any): People => {
|
||||
type FormValues = {
|
||||
name: string;
|
||||
contact?: string;
|
||||
gender: string;
|
||||
age: number;
|
||||
height?: number;
|
||||
marital_status?: string;
|
||||
introduction?: Record<string, string>;
|
||||
match_requirement?: string;
|
||||
cover?: string;
|
||||
}
|
||||
const buildPeople = (values: FormValues, common: { contact?: string }): People => {
|
||||
return {
|
||||
name: values.name,
|
||||
contact: values.contact || common.contact || undefined,
|
||||
@@ -43,9 +54,9 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputResult = (list: any) => {
|
||||
const handleInputResult = (list: unknown) => {
|
||||
const arr = Array.isArray(list) ? list : [list]
|
||||
const next: FormItem[] = arr.map((data: any) => ({ id: `${Date.now()}-${Math.random()}`, initialData: data }))
|
||||
const next: FormItem[] = arr.map((data) => ({ id: `${Date.now()}-${Math.random()}`, initialData: data as Partial<People> }))
|
||||
setItems((prev) => [...prev, ...next])
|
||||
}
|
||||
|
||||
@@ -61,12 +72,12 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
||||
message.error('表单未就绪')
|
||||
return
|
||||
}
|
||||
const allValues: any[] = []
|
||||
const allValues: FormValues[] = []
|
||||
for (const f of forms) {
|
||||
try {
|
||||
const v = await f.validateFields()
|
||||
allValues.push(v)
|
||||
} catch (err: any) {
|
||||
} catch {
|
||||
setLoading(false)
|
||||
message.error('请完善全部表单后再提交')
|
||||
return
|
||||
@@ -87,7 +98,7 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
||||
setItems([{ id: `${Date.now()}-${Math.random()}` }])
|
||||
commonForm.resetFields()
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch {
|
||||
message.error('提交失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
||||
@@ -7,7 +7,7 @@ import './InputDrawer.css';
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onResult?: (data: any) => void;
|
||||
onResult?: (data: unknown) => void;
|
||||
containerEl?: HTMLElement | null; // 抽屉挂载容器(用于放在标题栏下方)
|
||||
showUpload?: boolean; // 透传到输入面板,控制图片上传按钮
|
||||
mode?: 'input' | 'search' | 'batch-image'; // 透传到输入面板,控制工作模式
|
||||
@@ -31,7 +31,7 @@ const InputDrawer: React.FC<Props> = ({ open, onClose, onResult, containerEl, sh
|
||||
|
||||
// 在输入处理成功(onResult 被调用)后,自动关闭抽屉
|
||||
const handleResult = React.useCallback(
|
||||
(data: any) => {
|
||||
(data: unknown) => {
|
||||
onResult?.(data);
|
||||
onClose();
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Input, Upload, message, Button, Spin, Tag } from 'antd';
|
||||
import type { UploadFile, RcFile } from 'antd/es/upload/interface';
|
||||
import { PictureOutlined, SendOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { postInput, postInputImage, getPeoples } from '../apis';
|
||||
import './InputPanel.css';
|
||||
@@ -7,14 +8,14 @@ import './InputPanel.css';
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface InputPanelProps {
|
||||
onResult?: (data: any) => void;
|
||||
onResult?: (data: unknown) => void;
|
||||
showUpload?: boolean; // 是否显示图片上传按钮,默认显示
|
||||
mode?: 'input' | 'search' | 'batch-image'; // 输入面板工作模式,新增批量图片模式
|
||||
}
|
||||
|
||||
const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mode = 'input' }) => {
|
||||
const [value, setValue] = React.useState('');
|
||||
const [fileList, setFileList] = React.useState<any[]>([]);
|
||||
const [fileList, setFileList] = React.useState<UploadFile[]>([]);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
// 批量模式不保留文本内容
|
||||
|
||||
@@ -63,9 +64,9 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const results: any[] = [];
|
||||
const results: unknown[] = [];
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const f = fileList[i].originFileObj || fileList[i];
|
||||
const f = fileList[i].originFileObj as RcFile | undefined;
|
||||
if (!f) continue;
|
||||
const resp = await postInputImage(f);
|
||||
if (resp && resp.error_code === 0 && resp.data) {
|
||||
@@ -95,7 +96,7 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
|
||||
// 如果有图片,优先处理图片上传
|
||||
if (hasImage) {
|
||||
const file = fileList[0].originFileObj || fileList[0];
|
||||
const file = fileList[0].originFileObj as RcFile | undefined;
|
||||
if (!file) {
|
||||
message.error('图片文件无效,请重新选择');
|
||||
return;
|
||||
@@ -152,18 +153,18 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
if (!items || items.length === 0) return;
|
||||
|
||||
if (mode === 'batch-image') {
|
||||
const newEntries: any[] = [];
|
||||
const newEntries: UploadFile[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item.kind === 'file') {
|
||||
const file = item.getAsFile();
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const entry = {
|
||||
const entry: UploadFile<RcFile> = {
|
||||
uid: `${Date.now()}-${Math.random()}`,
|
||||
name: 'image',
|
||||
status: 'done',
|
||||
originFileObj: file,
|
||||
} as any;
|
||||
originFileObj: file as unknown as RcFile,
|
||||
};
|
||||
newEntries.push(entry);
|
||||
}
|
||||
}
|
||||
@@ -190,14 +191,13 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
if (firstImage) {
|
||||
e.preventDefault();
|
||||
setValue('');
|
||||
setFileList([
|
||||
{
|
||||
uid: `${Date.now()}-${Math.random()}`,
|
||||
name: 'image',
|
||||
status: 'done',
|
||||
originFileObj: firstImage,
|
||||
} as any,
|
||||
]);
|
||||
const item: UploadFile<RcFile> = {
|
||||
uid: `${Date.now()}-${Math.random()}`,
|
||||
name: 'image',
|
||||
status: 'done',
|
||||
originFileObj: firstImage as unknown as RcFile,
|
||||
};
|
||||
setFileList([item]);
|
||||
message.success('已添加剪贴板图片');
|
||||
}
|
||||
}
|
||||
@@ -278,9 +278,9 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
fileList={fileList}
|
||||
onChange={({ fileList: nextFileList }) => {
|
||||
if (mode === 'batch-image') {
|
||||
const normalized = nextFileList.map((entry: any) => {
|
||||
const raw = entry.originFileObj || entry;
|
||||
return { ...entry, name: 'image', originFileObj: raw };
|
||||
const normalized: UploadFile<RcFile>[] = nextFileList.map((entry) => {
|
||||
const raw = (entry as UploadFile<RcFile>).originFileObj;
|
||||
return { ...(entry as UploadFile<RcFile>), name: 'image', originFileObj: raw };
|
||||
});
|
||||
setValue('');
|
||||
setFileList(normalized);
|
||||
@@ -290,15 +290,15 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
||||
return;
|
||||
}
|
||||
// 仅添加第一张
|
||||
const first = nextFileList[0] as any;
|
||||
const raw = first.originFileObj || first;
|
||||
const renamed = { ...first, name: 'image', originFileObj: raw };
|
||||
const first = nextFileList[0] as UploadFile<RcFile>;
|
||||
const raw = first.originFileObj;
|
||||
const renamed: UploadFile<RcFile> = { ...first, name: 'image', originFileObj: raw };
|
||||
setValue('');
|
||||
setFileList([renamed]);
|
||||
}
|
||||
}}
|
||||
onRemove={(file) => {
|
||||
setFileList((prev) => prev.filter((x) => x.uid !== (file as any).uid));
|
||||
setFileList((prev) => prev.filter((x) => x.uid !== (file as UploadFile<RcFile>).uid));
|
||||
return true;
|
||||
}}
|
||||
showUploadList={false}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Row, Col, Input, Button } from 'antd';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import './KeyValueList.css';
|
||||
@@ -15,11 +15,8 @@ type Props = {
|
||||
const KeyValueList: React.FC<Props> = ({ value, onChange }) => {
|
||||
const [rows, setRows] = useState<KeyValuePair[]>([]);
|
||||
|
||||
const initializedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
// 初始化时提供一行空输入;之后只合并父值,不再自动新增空行
|
||||
const initializedRef = (KeyValueList as any)._initializedRef || { current: false };
|
||||
(KeyValueList as any)._initializedRef = initializedRef;
|
||||
|
||||
setRows((prev) => {
|
||||
const existingIdByKey = new Map(prev.filter((r) => r.k).map((r) => [r.k, r.id]));
|
||||
const valuePairs: KeyValuePair[] = value
|
||||
|
||||
@@ -66,7 +66,7 @@ const LayoutWrapper: React.FC = () => {
|
||||
showInput={isHome || isList || isBatch}
|
||||
/>
|
||||
{/* 下方为主布局:左侧菜单 + 右侧内容 */}
|
||||
<Layout ref={layoutShellRef as any} className="layout-shell">
|
||||
<Layout ref={layoutShellRef} className="layout-shell">
|
||||
<SiderMenu
|
||||
onNavigate={handleNavigate}
|
||||
selectedKey={selectedKey}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Modal, Form, Input, Button, message } from 'antd';
|
||||
import type { LoginRequest } from '../apis/types';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onOk: (values: any) => Promise<void>;
|
||||
onOk: (values: LoginRequest) => Promise<void>;
|
||||
title: string;
|
||||
username?: string;
|
||||
usernameReadOnly?: boolean;
|
||||
@@ -30,12 +31,12 @@ const LoginModal: React.FC<Props> = ({ open, onCancel, onOk, title, username, us
|
||||
const payload = isEmail
|
||||
? { email: username, password }
|
||||
: { phone: username, password };
|
||||
await onOk(payload as any);
|
||||
await onOk(payload as LoginRequest);
|
||||
if (!hideSuccessMessage) {
|
||||
message.success('登录成功');
|
||||
}
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
message.error('登录失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,15 +3,16 @@ import { Layout, Typography } from 'antd';
|
||||
import PeopleForm from './PeopleForm.tsx';
|
||||
import InputDrawer from './InputDrawer.tsx';
|
||||
import './MainContent.css';
|
||||
import type { People } from '../apis';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null };
|
||||
const MainContent: React.FC<Props> = ({ inputOpen = false, onCloseInput, containerEl }) => {
|
||||
const [formData, setFormData] = React.useState<any>(null);
|
||||
const [formData, setFormData] = React.useState<Partial<People> | null>(null);
|
||||
|
||||
const handleInputResult = (data: any) => {
|
||||
setFormData(data);
|
||||
const handleInputResult = (data: unknown) => {
|
||||
setFormData(data as Partial<People>);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -24,7 +25,7 @@ const MainContent: React.FC<Props> = ({ inputOpen = false, onCloseInput, contain
|
||||
点击右上角可以直接输入描述或上传图片
|
||||
</Typography.Paragraph>
|
||||
|
||||
<PeopleForm initialData={formData} />
|
||||
<PeopleForm initialData={formData || undefined} />
|
||||
</div>
|
||||
|
||||
{/* 首页右侧输入抽屉,仅在顶栏点击后弹出;挂载到标题栏下方容器 */}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createPeople, type People } from '../apis';
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface PeopleFormProps {
|
||||
initialData?: any;
|
||||
initialData?: Partial<People>;
|
||||
// 编辑模式下由父组件控制提交,隐藏内部提交按钮
|
||||
hideSubmitButton?: boolean;
|
||||
// 暴露 AntD Form 实例给父组件,用于在外部触发校验与取值
|
||||
@@ -22,11 +22,7 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
||||
// 当 initialData 变化时,自动填充表单
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
console.log('收到API返回数据,自动填充表单:', initialData);
|
||||
|
||||
// 处理返回的数据,将其转换为表单需要的格式
|
||||
const formData: any = {};
|
||||
|
||||
const formData: Partial<People> = {};
|
||||
if (initialData.name) formData.name = initialData.name;
|
||||
if (initialData.contact) formData.contact = initialData.contact;
|
||||
if (initialData.cover) formData.cover = initialData.cover;
|
||||
@@ -35,13 +31,8 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
||||
if (initialData.height) formData.height = initialData.height;
|
||||
if (initialData.marital_status) formData.marital_status = initialData.marital_status;
|
||||
if (initialData.match_requirement) formData.match_requirement = initialData.match_requirement;
|
||||
if (initialData.introduction) formData.introduction = initialData.introduction;
|
||||
|
||||
// 设置表单字段值
|
||||
if (initialData.introduction) formData.introduction = initialData.introduction as Record<string, string>;
|
||||
form.setFieldsValue(formData);
|
||||
|
||||
// 显示成功消息
|
||||
// message.success('已自动填充表单,请检查并确认信息');
|
||||
}
|
||||
}, [initialData, form]);
|
||||
|
||||
@@ -52,7 +43,18 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
type FormValues = {
|
||||
name: string;
|
||||
contact?: string;
|
||||
gender: string;
|
||||
age: number;
|
||||
height?: number;
|
||||
marital_status?: string;
|
||||
introduction?: Record<string, string>;
|
||||
match_requirement?: string;
|
||||
cover?: string;
|
||||
};
|
||||
const onFinish = async (values: FormValues) => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
@@ -81,16 +83,14 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
||||
message.error(response.error_info || '提交失败,请重试');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('提交失败:', error);
|
||||
|
||||
// 根据错误类型显示不同的错误信息
|
||||
if (error.status === 422) {
|
||||
} catch (e) {
|
||||
const err = e as { status?: number; message?: string };
|
||||
if (err.status === 422) {
|
||||
message.error('表单数据格式有误,请检查输入内容');
|
||||
} else if (error.status >= 500) {
|
||||
} else if ((err.status ?? 0) >= 500) {
|
||||
message.error('服务器错误,请稍后重试');
|
||||
} else {
|
||||
message.error(error.message || '提交失败,请重试');
|
||||
message.error(err.message || '提交失败,请重试');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Form, Input, Button, message } from 'antd';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useAuth } from '../contexts/useAuth';
|
||||
import type { RegisterRequest } from '../apis/types';
|
||||
|
||||
interface Props {
|
||||
@@ -29,7 +29,7 @@ const RegisterModal: React.FC<Props> = ({ open, onCancel }) => {
|
||||
await sendCode({ target_type, target, scene: 'register' });
|
||||
message.success('验证码已发送');
|
||||
setStep('verify');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
message.error('发送验证码失败');
|
||||
}
|
||||
};
|
||||
@@ -40,7 +40,7 @@ const RegisterModal: React.FC<Props> = ({ open, onCancel }) => {
|
||||
await register({ ...registerPayload, ...values } as RegisterRequest);
|
||||
message.success('注册成功');
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
message.error('注册失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export type Resource = Omit<People, 'id'> & { id: string };
|
||||
|
||||
// 统一转换 API 返回的人员列表为表格需要的结构
|
||||
function transformPeoples(list: People[] = []): Resource[] {
|
||||
return (list || []).map((person: any) => ({
|
||||
return (list || []).map((person: Partial<People>) => ({
|
||||
id: person.id || `person-${Date.now()}-${Math.random()}`,
|
||||
name: person.name || '未知',
|
||||
gender: person.gender || '其他/保密',
|
||||
@@ -72,7 +72,7 @@ async function fetchResources(): Promise<Resource[]> {
|
||||
|
||||
return transformed;
|
||||
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('获取人员列表失败:', error);
|
||||
message.error('获取人员列表失败,使用模拟数据');
|
||||
|
||||
@@ -463,124 +463,70 @@ async function fetchResources(): Promise<Resource[]> {
|
||||
}
|
||||
|
||||
// 数字范围筛选下拉
|
||||
function NumberRangeFilterDropdown({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) {
|
||||
const [min, max] = String(selectedKeys?.[0] ?? ':').split(':');
|
||||
const [localMin, setLocalMin] = React.useState<number | undefined>(min ? Number(min) : undefined);
|
||||
const [localMax, setLocalMax] = React.useState<number | undefined>(max ? Number(max) : undefined);
|
||||
return (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Space direction="vertical" style={{ width: 200 }}>
|
||||
<InputNumber placeholder="最小值" value={localMin} onChange={(v) => setLocalMin(v ?? undefined)} style={{ width: '100%' }} />
|
||||
<InputNumber placeholder="最大值" value={localMax} onChange={(v) => setLocalMax(v ?? undefined)} style={{ width: '100%' }} />
|
||||
<Space>
|
||||
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={() => { const key = `${localMin ?? ''}:${localMax ?? ''}`; setSelectedKeys?.([key]); confirm?.({ closeDropdown: true }); }}>筛选</Button>
|
||||
<Button size="small" onClick={() => { setLocalMin(undefined); setLocalMax(undefined); setSelectedKeys?.([]); clearFilters?.(); confirm?.({ closeDropdown: true }); }}>重置</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function buildNumberRangeFilter(dataIndex: keyof Resource, label: string): ColumnType<Resource> {
|
||||
return {
|
||||
title: label,
|
||||
dataIndex,
|
||||
sorter: (a: Resource, b: Resource) => Number((a as any)[dataIndex] ?? 0) - Number((b as any)[dataIndex] ?? 0),
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) => {
|
||||
const [min, max] = String(selectedKeys?.[0] ?? ':').split(':');
|
||||
const [localMin, setLocalMin] = React.useState<number | undefined>(min ? Number(min) : undefined);
|
||||
const [localMax, setLocalMax] = React.useState<number | undefined>(max ? Number(max) : undefined);
|
||||
return (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Space direction="vertical" style={{ width: 200 }}>
|
||||
<InputNumber
|
||||
placeholder="最小值"
|
||||
value={localMin}
|
||||
onChange={(v) => setLocalMin(v ?? undefined)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<InputNumber
|
||||
placeholder="最大值"
|
||||
value={localMax}
|
||||
onChange={(v) => setLocalMax(v ?? undefined)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => {
|
||||
const key = `${localMin ?? ''}:${localMax ?? ''}`;
|
||||
setSelectedKeys?.([key]);
|
||||
confirm?.({ closeDropdown: true });
|
||||
}}
|
||||
>
|
||||
筛选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setLocalMin(undefined);
|
||||
setLocalMax(undefined);
|
||||
setSelectedKeys?.([]);
|
||||
clearFilters?.();
|
||||
confirm?.({ closeDropdown: true });
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
sorter: (a: Resource, b: Resource) => {
|
||||
const av = a[dataIndex] as number | undefined;
|
||||
const bv = b[dataIndex] as number | undefined;
|
||||
return Number(av ?? 0) - Number(bv ?? 0);
|
||||
},
|
||||
filterDropdown: (props) => <NumberRangeFilterDropdown {...props} />,
|
||||
onFilter: (filterValue: React.Key | boolean, record: Resource) => {
|
||||
const [minStr, maxStr] = String(filterValue).split(':');
|
||||
const min = minStr ? Number(minStr) : undefined;
|
||||
const max = maxStr ? Number(maxStr) : undefined;
|
||||
const val = Number((record as any)[dataIndex] ?? NaN);
|
||||
if (Number.isNaN(val)) return false;
|
||||
if (min !== undefined && val < min) return false;
|
||||
if (max !== undefined && val > max) return false;
|
||||
const val = record[dataIndex] as number | undefined;
|
||||
if (val === undefined || Number.isNaN(Number(val))) return false;
|
||||
if (min !== undefined && Number(val) < min) return false;
|
||||
if (max !== undefined && Number(val) > max) return false;
|
||||
return true;
|
||||
},
|
||||
} as ColumnType<Resource>;
|
||||
}
|
||||
|
||||
// 枚举筛选下拉(用于性别等枚举类列)
|
||||
function EnumFilterDropdown({ setSelectedKeys, selectedKeys, confirm, clearFilters, options, label }: FilterDropdownProps & { options: Array<{ label: string; value: string }>; label: string }) {
|
||||
const [val, setVal] = React.useState<string | undefined>(selectedKeys && selectedKeys[0] ? String(selectedKeys[0]) : undefined);
|
||||
return (
|
||||
<div className="byte-table-custom-filter" style={{ padding: 8 }}>
|
||||
<Space direction="vertical" style={{ width: 200 }}>
|
||||
<Select allowClear placeholder={`请选择${label}`} value={val} onChange={(v) => setVal(v)} options={options} style={{ width: '100%' }} />
|
||||
<Space>
|
||||
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={() => { setSelectedKeys?.(val ? [val] : []); confirm?.({ closeDropdown: true }); }}>筛选</Button>
|
||||
<Button size="small" onClick={() => { setVal(undefined); setSelectedKeys?.([]); clearFilters?.(); confirm?.({ closeDropdown: true }); }}>重置</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function buildEnumFilter(dataIndex: keyof Resource, label: string, options: Array<{ label: string; value: string }>): ColumnType<Resource> {
|
||||
return {
|
||||
title: label,
|
||||
dataIndex,
|
||||
key: String(dataIndex),
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) => {
|
||||
const [val, setVal] = React.useState<string | undefined>(
|
||||
(selectedKeys && selectedKeys[0] ? String(selectedKeys[0]) : undefined)
|
||||
);
|
||||
return (
|
||||
<div className="byte-table-custom-filter" style={{ padding: 8 }}>
|
||||
<Space direction="vertical" style={{ width: 200 }}>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={`请选择${label}`}
|
||||
value={val}
|
||||
onChange={(v) => setVal(v)}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => {
|
||||
setSelectedKeys?.(val ? [val] : []);
|
||||
confirm?.({ closeDropdown: true });
|
||||
}}
|
||||
>
|
||||
筛选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setVal(undefined);
|
||||
setSelectedKeys?.([]);
|
||||
clearFilters?.();
|
||||
confirm?.({ closeDropdown: true });
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
onFilter: (filterValue: React.Key | boolean, record: Resource) =>
|
||||
String((record as any)[dataIndex]) === String(filterValue),
|
||||
filterDropdown: (props) => <EnumFilterDropdown {...props} options={options} label={label} />,
|
||||
onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record[dataIndex] ?? '') === String(filterValue),
|
||||
render: (g: string) => {
|
||||
const color = g === '男' ? 'blue' : g === '女' ? 'magenta' : 'default';
|
||||
return <Tag color={color}>{g}</Tag>;
|
||||
@@ -654,7 +600,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
} else {
|
||||
message.error(res.error_info || '删除失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch {
|
||||
message.error('删除失败');
|
||||
} finally {
|
||||
await reloadResources();
|
||||
@@ -688,7 +634,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record.name).includes(String(filterValue)),
|
||||
render: (text: string, record: Resource) => {
|
||||
// 图片图标逻辑
|
||||
const hasCover = record.cover && record.cover.trim() !== '';
|
||||
const hasCover = typeof record.cover === 'string' && record.cover.trim() !== '';
|
||||
const pictureIcon = (
|
||||
<PictureOutlined
|
||||
style={{
|
||||
@@ -696,7 +642,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
cursor: hasCover ? 'pointer' : 'default'
|
||||
}}
|
||||
onClick={hasCover ? () => {
|
||||
setCurrentImageUrl(record.cover);
|
||||
setCurrentImageUrl(record.cover as string);
|
||||
setImageModalVisible(true);
|
||||
} : undefined}
|
||||
/>
|
||||
@@ -834,7 +780,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 80,
|
||||
render: (_: any, record: Resource) => (
|
||||
render: (_: unknown, record: Resource) => (
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
menu={{
|
||||
@@ -952,8 +898,9 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
} else {
|
||||
message.error(res.error_info || '更新失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err?.errorFields) {
|
||||
} catch (err) {
|
||||
const hasErrorFields = typeof err === 'object' && err !== null && 'errorFields' in err;
|
||||
if (hasErrorFields) {
|
||||
message.error('请完善表单后再保存');
|
||||
} else {
|
||||
message.error('更新失败');
|
||||
@@ -1003,7 +950,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
}
|
||||
},
|
||||
}
|
||||
: ({} as any)
|
||||
: {}
|
||||
}
|
||||
pagination={{
|
||||
...pagination,
|
||||
@@ -1055,7 +1002,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
)}
|
||||
{record.created_at && (
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '12px' }}>
|
||||
{record.created_at ? '录入于: ' + formatDate(record.created_at) : ''}
|
||||
{record.created_at ? '录入于: ' + formatDate(Number(record.created_at)) : ''}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1064,7 +1011,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
<span>择偶要求</span>
|
||||
</Typography.Title>
|
||||
{record.match_requirement ? (
|
||||
<div>{record.match_requirement}</div>
|
||||
<div>{String(record.match_requirement)}</div>
|
||||
) : (
|
||||
<div style={{ color: '#9ca3af' }}>暂无匹配要求</div>
|
||||
)}
|
||||
@@ -1078,7 +1025,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
{record.comments && record.comments.remark ? (
|
||||
<Space>
|
||||
<Button size="small" onClick={() => {
|
||||
setEditingRemark({ recordId: record.id, content: record.comments.remark.content });
|
||||
setEditingRemark({ recordId: record.id, content: record.comments?.remark?.content || '' });
|
||||
setRemarkModalVisible(true);
|
||||
}}>修改</Button>
|
||||
<Button size="small" danger onClick={async () => {
|
||||
@@ -1090,7 +1037,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
} else {
|
||||
message.error(res.error_info || '清空失败');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
message.error('清空失败');
|
||||
}
|
||||
}}>清空</Button>
|
||||
@@ -1121,7 +1068,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
<InputDrawer
|
||||
open={inputOpen}
|
||||
onClose={onCloseInput || (() => {})}
|
||||
onResult={(list: any) => {
|
||||
onResult={(list: unknown) => {
|
||||
// setInputResult(list);
|
||||
const mapped = transformPeoples(Array.isArray(list) ? list : []);
|
||||
setData(mapped);
|
||||
@@ -1186,13 +1133,14 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
} else {
|
||||
message.error(res.error_info || '更新失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err?.errorFields) {
|
||||
message.error('请完善表单后再确认');
|
||||
} else {
|
||||
message.error('更新失败');
|
||||
} catch (err) {
|
||||
const hasErrorFields = typeof err === 'object' && err !== null && 'errorFields' in err;
|
||||
if (hasErrorFields) {
|
||||
message.error('请完善表单后再确认');
|
||||
} else {
|
||||
message.error('更新失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
destroyOnHidden
|
||||
okText="确认"
|
||||
@@ -1225,7 +1173,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
||||
} else {
|
||||
message.error(res.error_info || '操作失败');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
message.error('操作失败');
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import LoginModal from './LoginModal';
|
||||
import RegisterModal from './RegisterModal';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useAuth } from '../contexts/useAuth';
|
||||
import React from 'react';
|
||||
import { Layout, Menu, Grid, Drawer, Button } from 'antd';
|
||||
import { FormOutlined, UnorderedListOutlined, MenuOutlined, CopyOutlined, UserOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { Form, Input, Button, message, Card, Space, Upload, Modal } from 'antd'
|
||||
import 'react-image-crop/dist/ReactCrop.css'
|
||||
import ReactCrop, { centerCrop, makeAspectCrop, type Crop } from 'react-image-crop'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { useAuth } from '../contexts/useAuth'
|
||||
import { updateMe, deleteUser, uploadAvatar, updatePhone, updateEmail } from '../apis'
|
||||
import { UserOutlined, EditOutlined } from '@ant-design/icons'
|
||||
import LoginModal from './LoginModal';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { createContext, useState, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { login as apiLogin, register as apiRegister, sendCode as apiSendCode, getMe as apiGetMe, logout as apiLogout } from '../apis';
|
||||
import type { LoginRequest, RegisterRequest, User, SendCodeRequest } from '../apis/types';
|
||||
@@ -28,9 +28,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
if (response.data) {
|
||||
setUser(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToken(null);
|
||||
localStorage.removeItem('token');
|
||||
void 0;
|
||||
}
|
||||
};
|
||||
validateSession();
|
||||
@@ -48,7 +49,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
if (me.data) {
|
||||
setUser(me.data);
|
||||
}
|
||||
} catch (e) { void e; }
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
@@ -79,7 +82,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
if (me.data) {
|
||||
setUser(me.data);
|
||||
}
|
||||
} catch (e) { void e; }
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
};
|
||||
|
||||
const value = {
|
||||
@@ -97,10 +102,4 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
export default AuthContext;
|
||||
12
src/contexts/useAuth.ts
Normal file
12
src/contexts/useAuth.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useContext } from 'react';
|
||||
import AuthContext from './AuthContext';
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default useAuth;
|
||||
Reference in New Issue
Block a user