feat: 基础迁移完成了
This commit is contained in:
16
nkebao/craco.config.js
Normal file
16
nkebao/craco.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
webpack: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
'@/components': path.resolve(__dirname, 'src/components'),
|
||||||
|
'@/api': path.resolve(__dirname, 'src/api'),
|
||||||
|
'@/types': path.resolve(__dirname, 'src/types'),
|
||||||
|
'@/hooks': path.resolve(__dirname, 'src/hooks'),
|
||||||
|
'@/utils': path.resolve(__dirname, 'src/utils'),
|
||||||
|
'@/styles': path.resolve(__dirname, 'src/styles'),
|
||||||
|
'@/pages': path.resolve(__dirname, 'src/pages'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,24 +3,85 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/plots": "latest",
|
||||||
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@radix-ui/react-accordion": "latest",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
|
"@radix-ui/react-avatar": "latest",
|
||||||
|
"@radix-ui/react-checkbox": "latest",
|
||||||
|
"@radix-ui/react-collapsible": "latest",
|
||||||
|
"@radix-ui/react-context-menu": "^2.2.4",
|
||||||
|
"@radix-ui/react-dialog": "latest",
|
||||||
|
"@radix-ui/react-dropdown-menu": "latest",
|
||||||
|
"@radix-ui/react-hover-card": "^1.1.4",
|
||||||
|
"@radix-ui/react-icons": "latest",
|
||||||
|
"@radix-ui/react-label": "latest",
|
||||||
|
"@radix-ui/react-menubar": "^1.1.4",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||||
|
"@radix-ui/react-popover": "latest",
|
||||||
|
"@radix-ui/react-progress": "latest",
|
||||||
|
"@radix-ui/react-radio-group": "latest",
|
||||||
|
"@radix-ui/react-scroll-area": "latest",
|
||||||
|
"@radix-ui/react-select": "latest",
|
||||||
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
|
"@radix-ui/react-slider": "latest",
|
||||||
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"@radix-ui/react-switch": "latest",
|
||||||
|
"@radix-ui/react-tabs": "latest",
|
||||||
|
"@radix-ui/react-toast": "latest",
|
||||||
|
"@radix-ui/react-toggle": "^1.1.1",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.1",
|
||||||
|
"@radix-ui/react-tooltip": "latest",
|
||||||
|
"@tanstack/react-table": "latest",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^16.18.126",
|
"@types/node": "^16.18.126",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"chart.js": "latest",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "1.0.4",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"date-fns": "latest",
|
||||||
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"input-otp": "1.4.1",
|
||||||
|
"lucide-react": "^0.454.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-day-picker": "latest",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-hook-form": "^7.54.1",
|
||||||
|
"react-hot-toast": "^2.5.2",
|
||||||
|
"react-resizable-panels": "^2.1.7",
|
||||||
|
"react-router-dom": "^6.20.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"typescript": "^4.9.5",
|
"recharts": "latest",
|
||||||
"web-vitals": "^2.1.4"
|
"regenerator-runtime": "latest",
|
||||||
|
"sonner": "^1.7.4",
|
||||||
|
"tailwind-merge": "^2.5.5",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vaul": "^0.9.6",
|
||||||
|
"web-vitals": "^2.1.4",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@craco/craco": "^7.1.0",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "craco start",
|
||||||
"build": "react-scripts build",
|
"build": "craco build",
|
||||||
"test": "react-scripts test",
|
"test": "craco test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
@@ -1,23 +1,87 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import logo from './logo.svg';
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { fetchDeviceList } from '@/api/devices';
|
||||||
|
import { fetchWechatAccountList } from '@/api/wechat-accounts';
|
||||||
|
import { fetchScenes } from '@/api/scenarios';
|
||||||
|
import TestComponent from '@/components/TestComponent';
|
||||||
|
import type { Device } from '@/types/device';
|
||||||
|
import type { WechatFriend } from '@/types/wechat';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [devices, setDevices] = useState<any[]>([]);
|
||||||
|
const [wechatAccounts, setWechatAccounts] = useState<any[]>([]);
|
||||||
|
const [scenes, setScenes] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const testAPIs = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 测试设备API
|
||||||
|
const deviceResponse = await fetchDeviceList(1, 5);
|
||||||
|
console.log('设备API响应:', deviceResponse);
|
||||||
|
setDevices(deviceResponse.data?.list || []);
|
||||||
|
|
||||||
|
// 测试微信账号API
|
||||||
|
const wechatResponse = await fetchWechatAccountList({ page: 1, limit: 5 });
|
||||||
|
console.log('微信账号API响应:', wechatResponse);
|
||||||
|
setWechatAccounts(wechatResponse.data?.list || []);
|
||||||
|
|
||||||
|
// 测试场景API
|
||||||
|
const sceneResponse = await fetchScenes({ page: 1, limit: 5 });
|
||||||
|
console.log('场景API响应:', sceneResponse);
|
||||||
|
setScenes(sceneResponse.data || []);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API测试失败:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 页面加载时自动测试API
|
||||||
|
testAPIs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<h1>nkebao2 - 基础文件迁移测试</h1>
|
||||||
<p>
|
<p>这是一个测试页面,用于验证从Cunkebao迁移过来的基础文件是否正常工作</p>
|
||||||
Edit <code>src/App.tsx</code> and save to reload.
|
|
||||||
</p>
|
<TestComponent
|
||||||
<a
|
title="路径别名测试"
|
||||||
className="App-link"
|
className="mb-4 bg-blue-50"
|
||||||
href="https://reactjs.org"
|
/>
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
<button onClick={testAPIs} disabled={loading}>
|
||||||
>
|
{loading ? '测试中...' : '重新测试API'}
|
||||||
Learn React
|
</button>
|
||||||
</a>
|
|
||||||
|
<div style={{ marginTop: '20px', textAlign: 'left', maxWidth: '800px' }}>
|
||||||
|
<h3>测试结果:</h3>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<h4>设备API (共 {devices.length} 条):</h4>
|
||||||
|
<pre style={{ fontSize: '12px', background: '#f5f5f5', padding: '10px', borderRadius: '4px' }}>
|
||||||
|
{JSON.stringify(devices.slice(0, 2), null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<h4>微信账号API (共 {wechatAccounts.length} 条):</h4>
|
||||||
|
<pre style={{ fontSize: '12px', background: '#f5f5f5', padding: '10px', borderRadius: '4px' }}>
|
||||||
|
{JSON.stringify(wechatAccounts.slice(0, 2), null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<h4>场景API (共 {scenes.length} 条):</h4>
|
||||||
|
<pre style={{ fontSize: '12px', background: '#f5f5f5', padding: '10px', borderRadius: '4px' }}>
|
||||||
|
{JSON.stringify(scenes.slice(0, 2), null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
147
nkebao/src/api/devices.ts
Normal file
147
nkebao/src/api/devices.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { get, post, put, del } from './request';
|
||||||
|
import type { ApiResponse, PaginatedResponse } from '@/types/common';
|
||||||
|
import type {
|
||||||
|
Device,
|
||||||
|
DeviceStats,
|
||||||
|
DeviceTaskRecord,
|
||||||
|
QueryDeviceParams,
|
||||||
|
CreateDeviceParams,
|
||||||
|
UpdateDeviceParams,
|
||||||
|
DeviceStatus,
|
||||||
|
ServerDevice,
|
||||||
|
ServerDevicesResponse
|
||||||
|
} from '@/types/device';
|
||||||
|
|
||||||
|
const API_BASE = "/devices";
|
||||||
|
|
||||||
|
// 获取设备列表 - 连接到服务器/v1/devices接口
|
||||||
|
export const fetchDeviceList = async (page: number = 1, limit: number = 20, keyword?: string): Promise<ServerDevicesResponse> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', page.toString());
|
||||||
|
params.append('limit', limit.toString());
|
||||||
|
|
||||||
|
if (keyword) {
|
||||||
|
params.append('keyword', keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return get<ServerDevicesResponse>(`/v1/devices?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取设备详情 - 连接到服务器/v1/devices/:id接口
|
||||||
|
export const fetchDeviceDetail = async (id: string | number): Promise<ApiResponse<any>> => {
|
||||||
|
return get<ApiResponse<any>>(`/v1/devices/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取设备关联的微信账号
|
||||||
|
export const fetchDeviceRelatedAccounts = async (id: string | number): Promise<ApiResponse<any>> => {
|
||||||
|
return get<ApiResponse<any>>(`/v1/wechats/related-device/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取设备操作记录
|
||||||
|
export const fetchDeviceHandleLogs = async (id: string | number, page: number = 1, limit: number = 10): Promise<ApiResponse<any>> => {
|
||||||
|
return get<ApiResponse<any>>(`/v1/devices/${id}/handle-logs?page=${page}&limit=${limit}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新设备任务配置
|
||||||
|
export const updateDeviceTaskConfig = async (
|
||||||
|
config: {
|
||||||
|
deviceId: string | number;
|
||||||
|
autoAddFriend?: boolean;
|
||||||
|
autoReply?: boolean;
|
||||||
|
momentsSync?: boolean;
|
||||||
|
aiChat?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<ApiResponse<any>> => {
|
||||||
|
return post<ApiResponse<any>>(`/v1/devices/task-config`, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除设备
|
||||||
|
export const deleteDevice = async (id: number): Promise<ApiResponse<any>> => {
|
||||||
|
return del<ApiResponse<any>>(`/v1/devices/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设备管理API
|
||||||
|
export const deviceApi = {
|
||||||
|
// 创建设备
|
||||||
|
async create(params: CreateDeviceParams): Promise<ApiResponse<Device>> {
|
||||||
|
return post<ApiResponse<Device>>(`${API_BASE}`, params);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新设备
|
||||||
|
async update(params: UpdateDeviceParams): Promise<ApiResponse<Device>> {
|
||||||
|
return put<ApiResponse<Device>>(`${API_BASE}/${params.id}`, params);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取设备详情
|
||||||
|
async getById(id: string): Promise<ApiResponse<Device>> {
|
||||||
|
return get<ApiResponse<Device>>(`${API_BASE}/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询设备列表
|
||||||
|
async query(params: QueryDeviceParams): Promise<ApiResponse<PaginatedResponse<Device>>> {
|
||||||
|
// 创建一个新对象,用于构建URLSearchParams
|
||||||
|
const queryParams: Record<string, string> = {};
|
||||||
|
|
||||||
|
// 按需将params中的属性添加到queryParams
|
||||||
|
if (params.keyword) queryParams.keyword = params.keyword;
|
||||||
|
if (params.status) queryParams.status = params.status;
|
||||||
|
if (params.type) queryParams.type = params.type;
|
||||||
|
if (params.page) queryParams.page = params.page.toString();
|
||||||
|
if (params.pageSize) queryParams.pageSize = params.pageSize.toString();
|
||||||
|
|
||||||
|
// 特殊处理需要JSON序列化的属性
|
||||||
|
if (params.tags) queryParams.tags = JSON.stringify(params.tags);
|
||||||
|
if (params.dateRange) queryParams.dateRange = JSON.stringify(params.dateRange);
|
||||||
|
|
||||||
|
// 构建查询字符串
|
||||||
|
const queryString = new URLSearchParams(queryParams).toString();
|
||||||
|
return get<ApiResponse<PaginatedResponse<Device>>>(`${API_BASE}?${queryString}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除设备
|
||||||
|
async delete(id: string): Promise<ApiResponse<void>> {
|
||||||
|
return del<ApiResponse<void>>(`${API_BASE}/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重启设备
|
||||||
|
async restart(id: string): Promise<ApiResponse<void>> {
|
||||||
|
return post<ApiResponse<void>>(`${API_BASE}/${id}/restart`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 解绑设备
|
||||||
|
async unbind(id: string): Promise<ApiResponse<void>> {
|
||||||
|
return post<ApiResponse<void>>(`${API_BASE}/${id}/unbind`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取设备统计数据
|
||||||
|
async getStats(id: string): Promise<ApiResponse<DeviceStats>> {
|
||||||
|
return get<ApiResponse<DeviceStats>>(`${API_BASE}/${id}/stats`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取设备任务记录
|
||||||
|
async getTaskRecords(id: string, page = 1, pageSize = 20): Promise<ApiResponse<PaginatedResponse<DeviceTaskRecord>>> {
|
||||||
|
return get<ApiResponse<PaginatedResponse<DeviceTaskRecord>>>(`${API_BASE}/${id}/tasks?page=${page}&pageSize=${pageSize}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量更新设备标签
|
||||||
|
async updateTags(ids: string[], tags: string[]): Promise<ApiResponse<void>> {
|
||||||
|
return post<ApiResponse<void>>(`${API_BASE}/tags`, { deviceIds: ids, tags });
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量导出设备数据
|
||||||
|
async exportDevices(ids: string[]): Promise<Blob> {
|
||||||
|
const response = await fetch(`${process.env.REACT_APP_API_BASE || 'http://localhost:3000/api'}${API_BASE}/export`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ deviceIds: ids }),
|
||||||
|
});
|
||||||
|
return response.blob();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查设备在线状态
|
||||||
|
async checkStatus(ids: string[]): Promise<ApiResponse<Record<string, DeviceStatus>>> {
|
||||||
|
return post<ApiResponse<Record<string, DeviceStatus>>>(`${API_BASE}/status`, { deviceIds: ids });
|
||||||
|
},
|
||||||
|
};
|
||||||
61
nkebao/src/api/request.ts
Normal file
61
nkebao/src/api/request.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
// 创建axios实例
|
||||||
|
const request: AxiosInstance = axios.create({
|
||||||
|
baseURL: process.env.REACT_APP_API_BASE || 'http://localhost:3000/api',
|
||||||
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
request.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// 可以在这里添加token等认证信息
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// 统一错误处理
|
||||||
|
console.error('API请求错误:', error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 封装GET请求
|
||||||
|
export const get = <T = any>(url: string, config?: AxiosRequestConfig): Promise<T> => {
|
||||||
|
return request.get(url, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 封装POST请求
|
||||||
|
export const post = <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
|
||||||
|
return request.post(url, data, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 封装PUT请求
|
||||||
|
export const put = <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
|
||||||
|
return request.put(url, data, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 封装DELETE请求
|
||||||
|
export const del = <T = any>(url: string, config?: AxiosRequestConfig): Promise<T> => {
|
||||||
|
return request.delete(url, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出request实例
|
||||||
|
export { request };
|
||||||
|
export default request;
|
||||||
120
nkebao/src/api/scenarios.ts
Normal file
120
nkebao/src/api/scenarios.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { get } from './request';
|
||||||
|
import type { ApiResponse } from '@/types/common';
|
||||||
|
|
||||||
|
// 服务器返回的场景数据类型
|
||||||
|
export interface SceneItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
status: number;
|
||||||
|
createTime: number;
|
||||||
|
updateTime: number | null;
|
||||||
|
deleteTime: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器返回的场景列表响应类型
|
||||||
|
export interface ScenesResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: SceneItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前端使用的场景数据类型
|
||||||
|
export interface Channel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
stats: {
|
||||||
|
daily: number;
|
||||||
|
growth: number;
|
||||||
|
};
|
||||||
|
link?: string;
|
||||||
|
plans?: Plan[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计划类型
|
||||||
|
export interface Plan {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isNew?: boolean;
|
||||||
|
status: "active" | "paused" | "completed";
|
||||||
|
acquisitionCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取获客场景列表
|
||||||
|
*
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 获客场景列表
|
||||||
|
*/
|
||||||
|
export const fetchScenes = async (params: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
keyword?: string;
|
||||||
|
} = {}): Promise<ScenesResponse> => {
|
||||||
|
const { page = 1, limit = 10, keyword = "" } = params;
|
||||||
|
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
queryParams.append("page", String(page));
|
||||||
|
queryParams.append("limit", String(limit));
|
||||||
|
|
||||||
|
if (keyword) {
|
||||||
|
queryParams.append("keyword", keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await get<ScenesResponse>(`/v1/plan/scenes?${queryParams.toString()}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching scenes:", error);
|
||||||
|
// 返回一个错误响应
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
msg: "获取场景列表失败",
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将服务器返回的场景数据转换为前端展示需要的格式
|
||||||
|
*
|
||||||
|
* @param item 服务器返回的场景数据
|
||||||
|
* @returns 前端展示的场景数据
|
||||||
|
*/
|
||||||
|
export const transformSceneItem = (item: SceneItem): Channel => {
|
||||||
|
// 为每个场景生成随机的"今日"数据和"增长百分比"
|
||||||
|
const dailyCount = Math.floor(Math.random() * 100);
|
||||||
|
const growthPercent = Math.floor(Math.random() * 40) - 10; // -10% 到 30% 的随机值
|
||||||
|
|
||||||
|
// 默认图标(如果服务器没有返回)
|
||||||
|
const defaultIcon = "/assets/icons/poster-icon.svg";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: String(item.id),
|
||||||
|
name: item.name,
|
||||||
|
icon: item.image || defaultIcon,
|
||||||
|
stats: {
|
||||||
|
daily: dailyCount,
|
||||||
|
growth: growthPercent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场景详情
|
||||||
|
*
|
||||||
|
* @param id 场景ID
|
||||||
|
* @returns 场景详情
|
||||||
|
*/
|
||||||
|
export const fetchSceneDetail = async (id: string | number): Promise<ApiResponse<SceneItem>> => {
|
||||||
|
try {
|
||||||
|
return await get<ApiResponse<SceneItem>>(`/v1/plan/scenes/${id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching scene detail:", error);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
msg: "获取场景详情失败",
|
||||||
|
data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
219
nkebao/src/api/wechat-accounts.ts
Normal file
219
nkebao/src/api/wechat-accounts.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { get, post, put } from './request';
|
||||||
|
import type { ApiResponse } from '@/types/common';
|
||||||
|
|
||||||
|
// 添加接口返回数据类型定义
|
||||||
|
interface WechatAccountSummary {
|
||||||
|
accountAge: string;
|
||||||
|
activityLevel: {
|
||||||
|
allTimes: number;
|
||||||
|
dayTimes: number;
|
||||||
|
};
|
||||||
|
accountWeight: {
|
||||||
|
scope: number;
|
||||||
|
ageWeight: number;
|
||||||
|
activityWeigth: number;
|
||||||
|
restrictWeight: number;
|
||||||
|
realNameWeight: number;
|
||||||
|
};
|
||||||
|
statistics: {
|
||||||
|
todayAdded: number;
|
||||||
|
addLimit: number;
|
||||||
|
};
|
||||||
|
restrictions: {
|
||||||
|
id: number;
|
||||||
|
level: string;
|
||||||
|
reason: string;
|
||||||
|
date: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WechatAccountSummaryResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: WechatAccountSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServerWechatAccountsResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: {
|
||||||
|
list: any[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryWechatAccountParams {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
keyword?: string;
|
||||||
|
sort?: string;
|
||||||
|
order?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信账号列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 微信账号列表响应
|
||||||
|
*/
|
||||||
|
export const fetchWechatAccountList = async (params: QueryWechatAccountParams = {}): Promise<ServerWechatAccountsResponse> => {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
|
// 添加查询参数
|
||||||
|
if (params.page) queryParams.append('page', params.page.toString());
|
||||||
|
if (params.limit) queryParams.append('limit', params.limit.toString());
|
||||||
|
if (params.keyword) queryParams.append('nickname', params.keyword); // 使用nickname作为关键词搜索参数
|
||||||
|
if (params.sort) queryParams.append('sort', params.sort);
|
||||||
|
if (params.order) queryParams.append('order', params.order);
|
||||||
|
|
||||||
|
// 发起API请求
|
||||||
|
return get<ServerWechatAccountsResponse>(`/v1/wechats?${queryParams.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新微信账号状态
|
||||||
|
* @returns 刷新结果
|
||||||
|
*/
|
||||||
|
export const refreshWechatAccounts = async (): Promise<{ code: number; msg: string; data: any }> => {
|
||||||
|
return put<{ code: number; msg: string; data: any }>('/v1/wechats/refresh', {});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行微信好友转移
|
||||||
|
* @param sourceId 源微信账号ID
|
||||||
|
* @param targetId 目标微信账号ID
|
||||||
|
* @returns 转移结果
|
||||||
|
*/
|
||||||
|
export const transferWechatFriends = async (sourceId: string | number, targetId: string | number): Promise<{ code: number; msg: string; data: any }> => {
|
||||||
|
return post<{ code: number; msg: string; data: any }>('/v1/wechats/transfer-friends', {
|
||||||
|
source_id: sourceId,
|
||||||
|
target_id: targetId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将服务器返回的微信账号数据转换为前端使用的格式
|
||||||
|
* @param serverAccount 服务器返回的微信账号数据
|
||||||
|
* @returns 前端使用的微信账号数据
|
||||||
|
*/
|
||||||
|
export const transformWechatAccount = (serverAccount: any): any => {
|
||||||
|
// 从deviceInfo中提取设备信息
|
||||||
|
let deviceId = '';
|
||||||
|
let deviceName = '';
|
||||||
|
|
||||||
|
if (serverAccount.deviceInfo) {
|
||||||
|
// 尝试解析设备信息字符串
|
||||||
|
const deviceInfo = serverAccount.deviceInfo.split(' ');
|
||||||
|
if (deviceInfo.length > 0) {
|
||||||
|
// 提取数字部分作为设备ID,确保是整数
|
||||||
|
const possibleId = deviceInfo[0].trim();
|
||||||
|
// 验证是否为数字
|
||||||
|
deviceId = /^\d+$/.test(possibleId) ? possibleId : '';
|
||||||
|
|
||||||
|
// 提取设备名称
|
||||||
|
if (deviceInfo.length > 1) {
|
||||||
|
deviceName = deviceInfo[1] ? deviceInfo[1].replace(/[()]/g, '').trim() : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果从deviceInfo无法获取有效的设备ID,使用imei作为备选
|
||||||
|
if (!deviceId && serverAccount.imei) {
|
||||||
|
deviceId = serverAccount.imei;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然没有设备ID,使用微信账号的ID作为最后的备选
|
||||||
|
if (!deviceId && serverAccount.id) {
|
||||||
|
deviceId = serverAccount.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有设备名称,使用备用名称
|
||||||
|
if (!deviceName) {
|
||||||
|
deviceName = serverAccount.deviceMemo || '未命名设备';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 假设每天最多可添加20个好友
|
||||||
|
const maxDailyAdds = 20;
|
||||||
|
const todayAdded = serverAccount.todayNewFriendCount || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: serverAccount.id.toString(),
|
||||||
|
avatar: serverAccount.avatar || '',
|
||||||
|
nickname: serverAccount.nickname || serverAccount.accountNickname || '未命名',
|
||||||
|
wechatId: serverAccount.wechatId || '',
|
||||||
|
deviceId,
|
||||||
|
deviceName,
|
||||||
|
friendCount: serverAccount.totalFriend || 0,
|
||||||
|
todayAdded,
|
||||||
|
remainingAdds: serverAccount.canAddFriendCount || (maxDailyAdds - todayAdded),
|
||||||
|
maxDailyAdds,
|
||||||
|
status: serverAccount.wechatAlive === 1 ? "normal" : "abnormal" as "normal" | "abnormal",
|
||||||
|
lastActive: new Date().toLocaleString() // 服务端未提供,使用当前时间
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信好友列表
|
||||||
|
* @param wechatId 微信账号ID
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @param searchQuery 搜索关键词
|
||||||
|
* @returns 好友列表数据
|
||||||
|
*/
|
||||||
|
export const fetchWechatFriends = async (wechatId: string, page: number = 1, pageSize: number = 20, searchQuery: string = '') => {
|
||||||
|
try {
|
||||||
|
return get(`/v1/wechats/${wechatId}/friends?page=${page}&limit=${pageSize}${searchQuery ? `&search=${searchQuery}` : ''}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取好友列表失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信账号概览信息
|
||||||
|
* @param id 微信账号ID
|
||||||
|
* @returns 微信账号概览信息
|
||||||
|
*/
|
||||||
|
export const fetchWechatAccountSummary = async (wechatIdid: string): Promise<WechatAccountSummaryResponse> => {
|
||||||
|
try {
|
||||||
|
return get<WechatAccountSummaryResponse>(`/v1/wechats/${wechatIdid}/summary`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取账号概览失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取好友详情信息
|
||||||
|
* @param wechatId 微信账号ID
|
||||||
|
* @param friendId 好友ID
|
||||||
|
* @returns 好友详情信息
|
||||||
|
*/
|
||||||
|
export interface WechatFriendDetail {
|
||||||
|
id: number;
|
||||||
|
avatar: string;
|
||||||
|
nickname: string;
|
||||||
|
region: string;
|
||||||
|
wechatId: string;
|
||||||
|
addDate: string;
|
||||||
|
tags: string[];
|
||||||
|
playDate: string;
|
||||||
|
memo: string;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WechatFriendDetailResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: WechatFriendDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchWechatFriendDetail = async (wechatId: string): Promise<WechatFriendDetailResponse> => {
|
||||||
|
try {
|
||||||
|
return get<WechatFriendDetailResponse>(`/v1/wechats/${wechatId}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取好友详情失败:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
23
nkebao/src/components/TestComponent.tsx
Normal file
23
nkebao/src/components/TestComponent.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { cn } from '@/utils';
|
||||||
|
|
||||||
|
interface TestComponentProps {
|
||||||
|
title: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestComponent: React.FC<TestComponentProps> = ({ title, className }) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('p-4 border rounded-lg', className)}>
|
||||||
|
<h3 className="text-lg font-semibold mb-2">{title}</h3>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
这是一个测试组件,用于验证路径别名 @/ 是否正常工作。
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 text-sm text-blue-600">
|
||||||
|
✓ 成功使用 @/utils 导入工具函数
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestComponent;
|
||||||
43
nkebao/src/types/common.ts
Normal file
43
nkebao/src/types/common.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// API响应格式
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: T | null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应格式
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
items: T[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用查询参数
|
||||||
|
export interface QueryParams {
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
|
keyword?: string
|
||||||
|
dateRange?: {
|
||||||
|
start: string
|
||||||
|
end: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用状态枚举
|
||||||
|
export enum Status {
|
||||||
|
ACTIVE = "active",
|
||||||
|
INACTIVE = "inactive",
|
||||||
|
PENDING = "pending",
|
||||||
|
COMPLETED = "completed",
|
||||||
|
FAILED = "failed",
|
||||||
|
DRAFT = "draft",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用基础实体
|
||||||
|
export interface BaseEntity {
|
||||||
|
id: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
123
nkebao/src/types/device.ts
Normal file
123
nkebao/src/types/device.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 设备状态枚举
|
||||||
|
export enum DeviceStatus {
|
||||||
|
ONLINE = "online",
|
||||||
|
OFFLINE = "offline",
|
||||||
|
BUSY = "busy",
|
||||||
|
ERROR = "error",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备类型枚举
|
||||||
|
export enum DeviceType {
|
||||||
|
ANDROID = "android",
|
||||||
|
IOS = "ios",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务端API返回的设备类型
|
||||||
|
export interface ServerDevice {
|
||||||
|
id: number;
|
||||||
|
imei: string;
|
||||||
|
memo: string;
|
||||||
|
wechatId: string;
|
||||||
|
alive: number;
|
||||||
|
totalFriend: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务端API返回的设备列表响应
|
||||||
|
export interface ServerDevicesResponse {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: {
|
||||||
|
list: ServerDevice[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备基础信息
|
||||||
|
export interface Device {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
imei: string
|
||||||
|
type: DeviceType
|
||||||
|
status: DeviceStatus
|
||||||
|
wechatId: string
|
||||||
|
friendCount: number
|
||||||
|
battery: number
|
||||||
|
lastActive: string
|
||||||
|
addFriendStatus: "normal" | "abnormal"
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备统计信息
|
||||||
|
export interface DeviceStats {
|
||||||
|
totalTasks: number
|
||||||
|
completedTasks: number
|
||||||
|
failedTasks: number
|
||||||
|
todayNewFriends: number
|
||||||
|
totalNewFriends: number
|
||||||
|
onlineTime: number // 在线时长(分钟)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备任务记录
|
||||||
|
export interface DeviceTaskRecord {
|
||||||
|
id: string
|
||||||
|
deviceId: string
|
||||||
|
taskType: string
|
||||||
|
status: "pending" | "running" | "completed" | "failed"
|
||||||
|
startTime: string
|
||||||
|
endTime?: string
|
||||||
|
result?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备查询参数
|
||||||
|
export interface QueryDeviceParams {
|
||||||
|
keyword?: string
|
||||||
|
status?: DeviceStatus
|
||||||
|
type?: DeviceType
|
||||||
|
tags?: string[]
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
|
dateRange?: {
|
||||||
|
start: string
|
||||||
|
end: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建设备参数
|
||||||
|
export interface CreateDeviceParams {
|
||||||
|
name: string
|
||||||
|
imei: string
|
||||||
|
type: DeviceType
|
||||||
|
wechatId?: string
|
||||||
|
remark?: string
|
||||||
|
tags?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设备参数
|
||||||
|
export interface UpdateDeviceParams {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
wechatId?: string
|
||||||
|
remark?: string
|
||||||
|
tags?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceResponse {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: {
|
||||||
|
devices: Device[]
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceSelectResponse {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: {
|
||||||
|
success: boolean
|
||||||
|
deviceIds: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
35
nkebao/src/types/wechat.ts
Normal file
35
nkebao/src/types/wechat.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// 微信好友类型定义
|
||||||
|
export interface WechatFriend {
|
||||||
|
id: string
|
||||||
|
nickname: string
|
||||||
|
wechatId: string
|
||||||
|
avatar: string
|
||||||
|
gender?: "male" | "female"
|
||||||
|
customer?: string
|
||||||
|
alias?: string
|
||||||
|
ownerNickname?: string
|
||||||
|
ownerAlias?: string
|
||||||
|
createTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信群组类型定义
|
||||||
|
export interface WechatGroup {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
memberCount: number
|
||||||
|
avatar: string
|
||||||
|
owner: string
|
||||||
|
customer: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信群成员类型定义
|
||||||
|
export interface WechatGroupMember {
|
||||||
|
id: string
|
||||||
|
nickname: string
|
||||||
|
wechatId: string
|
||||||
|
avatar: string
|
||||||
|
gender?: "male" | "female"
|
||||||
|
role?: "owner" | "admin" | "member"
|
||||||
|
joinTime?: string
|
||||||
|
groupId?: string
|
||||||
|
}
|
||||||
46
nkebao/src/utils/index.ts
Normal file
46
nkebao/src/utils/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// 通用工具函数
|
||||||
|
export const cn = (...classes: (string | undefined | null | false)[]) => {
|
||||||
|
return classes.filter(Boolean).join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
export const formatDate = (date: string | Date) => {
|
||||||
|
return new Date(date).toLocaleDateString('zh-CN');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
export const formatTime = (date: string | Date) => {
|
||||||
|
return new Date(date).toLocaleTimeString('zh-CN');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
export const formatDateTime = (date: string | Date) => {
|
||||||
|
return new Date(date).toLocaleString('zh-CN');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 防抖函数
|
||||||
|
export const debounce = <T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
wait: number
|
||||||
|
): ((...args: Parameters<T>) => void) => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func(...args), wait);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 节流函数
|
||||||
|
export const throttle = <T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
wait: number
|
||||||
|
): ((...args: Parameters<T>) => void) => {
|
||||||
|
let inThrottle: boolean;
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
if (!inThrottle) {
|
||||||
|
func(...args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), wait);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -18,7 +18,18 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx",
|
||||||
|
"baseUrl": "src",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"],
|
||||||
|
"@/components/*": ["components/*"],
|
||||||
|
"@/api/*": ["api/*"],
|
||||||
|
"@/types/*": ["types/*"],
|
||||||
|
"@/hooks/*": ["hooks/*"],
|
||||||
|
"@/utils/*": ["utils/*"],
|
||||||
|
"@/styles/*": ["styles/*"],
|
||||||
|
"@/pages/*": ["pages/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|||||||
Reference in New Issue
Block a user