From 2564e3cfaca7a6c9d1866a98e5571c100d799117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=B0=B8=E5=B9=B3?= Date: Thu, 3 Jul 2025 18:09:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=BA=E7=A1=80=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/craco.config.js | 16 ++ nkebao/package.json | 71 +++++++- nkebao/src/App.tsx | 92 ++++++++-- nkebao/src/api/devices.ts | 147 ++++++++++++++++ nkebao/src/api/request.ts | 61 +++++++ nkebao/src/api/scenarios.ts | 120 +++++++++++++ nkebao/src/api/wechat-accounts.ts | 219 ++++++++++++++++++++++++ nkebao/src/components/TestComponent.tsx | 23 +++ nkebao/src/types/common.ts | 43 +++++ nkebao/src/types/device.ts | 123 +++++++++++++ nkebao/src/types/wechat.ts | 35 ++++ nkebao/src/utils/index.ts | 46 +++++ nkebao/tsconfig.json | 13 +- 13 files changed, 989 insertions(+), 20 deletions(-) create mode 100644 nkebao/craco.config.js create mode 100644 nkebao/src/api/devices.ts create mode 100644 nkebao/src/api/request.ts create mode 100644 nkebao/src/api/scenarios.ts create mode 100644 nkebao/src/api/wechat-accounts.ts create mode 100644 nkebao/src/components/TestComponent.tsx create mode 100644 nkebao/src/types/common.ts create mode 100644 nkebao/src/types/device.ts create mode 100644 nkebao/src/types/wechat.ts create mode 100644 nkebao/src/utils/index.ts diff --git a/nkebao/craco.config.js b/nkebao/craco.config.js new file mode 100644 index 000000000..7f4c82fea --- /dev/null +++ b/nkebao/craco.config.js @@ -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'), + }, + }, +}; \ No newline at end of file diff --git a/nkebao/package.json b/nkebao/package.json index b93309cc9..6918d7f9c 100644 --- a/nkebao/package.json +++ b/nkebao/package.json @@ -3,24 +3,85 @@ "version": "0.1.0", "private": true, "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/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "@types/crypto-js": "^4.2.2", "@types/jest": "^27.5.2", "@types/node": "^16.18.126", "@types/react": "^19.1.8", "@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-day-picker": "latest", "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", - "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "recharts": "latest", + "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": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "craco start", + "build": "craco build", + "test": "craco test", "eject": "react-scripts eject" }, "eslintConfig": { diff --git a/nkebao/src/App.tsx b/nkebao/src/App.tsx index a53698aab..07396f8e1 100644 --- a/nkebao/src/App.tsx +++ b/nkebao/src/App.tsx @@ -1,23 +1,87 @@ -import React from 'react'; -import logo from './logo.svg'; +import React, { useState, useEffect } from 'react'; 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() { + const [devices, setDevices] = useState([]); + const [wechatAccounts, setWechatAccounts] = useState([]); + const [scenes, setScenes] = useState([]); + 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 (
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - +

nkebao2 - 基础文件迁移测试

+

这是一个测试页面,用于验证从Cunkebao迁移过来的基础文件是否正常工作

+ + + + + +
+

测试结果:

+ +
+

设备API (共 {devices.length} 条):

+
+              {JSON.stringify(devices.slice(0, 2), null, 2)}
+            
+
+ +
+

微信账号API (共 {wechatAccounts.length} 条):

+
+              {JSON.stringify(wechatAccounts.slice(0, 2), null, 2)}
+            
+
+ +
+

场景API (共 {scenes.length} 条):

+
+              {JSON.stringify(scenes.slice(0, 2), null, 2)}
+            
+
+
); diff --git a/nkebao/src/api/devices.ts b/nkebao/src/api/devices.ts new file mode 100644 index 000000000..176041871 --- /dev/null +++ b/nkebao/src/api/devices.ts @@ -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 => { + const params = new URLSearchParams(); + params.append('page', page.toString()); + params.append('limit', limit.toString()); + + if (keyword) { + params.append('keyword', keyword); + } + + return get(`/v1/devices?${params.toString()}`); +}; + +// 获取设备详情 - 连接到服务器/v1/devices/:id接口 +export const fetchDeviceDetail = async (id: string | number): Promise> => { + return get>(`/v1/devices/${id}`); +}; + +// 获取设备关联的微信账号 +export const fetchDeviceRelatedAccounts = async (id: string | number): Promise> => { + return get>(`/v1/wechats/related-device/${id}`); +}; + +// 获取设备操作记录 +export const fetchDeviceHandleLogs = async (id: string | number, page: number = 1, limit: number = 10): Promise> => { + return get>(`/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> => { + return post>(`/v1/devices/task-config`, config); +}; + +// 删除设备 +export const deleteDevice = async (id: number): Promise> => { + return del>(`/v1/devices/${id}`); +}; + +// 设备管理API +export const deviceApi = { + // 创建设备 + async create(params: CreateDeviceParams): Promise> { + return post>(`${API_BASE}`, params); + }, + + // 更新设备 + async update(params: UpdateDeviceParams): Promise> { + return put>(`${API_BASE}/${params.id}`, params); + }, + + // 获取设备详情 + async getById(id: string): Promise> { + return get>(`${API_BASE}/${id}`); + }, + + // 查询设备列表 + async query(params: QueryDeviceParams): Promise>> { + // 创建一个新对象,用于构建URLSearchParams + const queryParams: Record = {}; + + // 按需将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>>(`${API_BASE}?${queryString}`); + }, + + // 删除设备 + async delete(id: string): Promise> { + return del>(`${API_BASE}/${id}`); + }, + + // 重启设备 + async restart(id: string): Promise> { + return post>(`${API_BASE}/${id}/restart`); + }, + + // 解绑设备 + async unbind(id: string): Promise> { + return post>(`${API_BASE}/${id}/unbind`); + }, + + // 获取设备统计数据 + async getStats(id: string): Promise> { + return get>(`${API_BASE}/${id}/stats`); + }, + + // 获取设备任务记录 + async getTaskRecords(id: string, page = 1, pageSize = 20): Promise>> { + return get>>(`${API_BASE}/${id}/tasks?page=${page}&pageSize=${pageSize}`); + }, + + // 批量更新设备标签 + async updateTags(ids: string[], tags: string[]): Promise> { + return post>(`${API_BASE}/tags`, { deviceIds: ids, tags }); + }, + + // 批量导出设备数据 + async exportDevices(ids: string[]): Promise { + 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>> { + return post>>(`${API_BASE}/status`, { deviceIds: ids }); + }, +}; \ No newline at end of file diff --git a/nkebao/src/api/request.ts b/nkebao/src/api/request.ts new file mode 100644 index 000000000..e0d3d9f87 --- /dev/null +++ b/nkebao/src/api/request.ts @@ -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 = (url: string, config?: AxiosRequestConfig): Promise => { + return request.get(url, config); +}; + +// 封装POST请求 +export const post = (url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return request.post(url, data, config); +}; + +// 封装PUT请求 +export const put = (url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return request.put(url, data, config); +}; + +// 封装DELETE请求 +export const del = (url: string, config?: AxiosRequestConfig): Promise => { + return request.delete(url, config); +}; + +// 导出request实例 +export { request }; +export default request; \ No newline at end of file diff --git a/nkebao/src/api/scenarios.ts b/nkebao/src/api/scenarios.ts new file mode 100644 index 000000000..c2b4955e5 --- /dev/null +++ b/nkebao/src/api/scenarios.ts @@ -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 => { + 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(`/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> => { + try { + return await get>(`/v1/plan/scenes/${id}`); + } catch (error) { + console.error("Error fetching scene detail:", error); + return { + code: 500, + msg: "获取场景详情失败", + data: null + }; + } +}; \ No newline at end of file diff --git a/nkebao/src/api/wechat-accounts.ts b/nkebao/src/api/wechat-accounts.ts new file mode 100644 index 000000000..48153c408 --- /dev/null +++ b/nkebao/src/api/wechat-accounts.ts @@ -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 => { + 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(`/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 => { + try { + return get(`/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 => { + try { + return get(`/v1/wechats/${wechatId}`); + } catch (error) { + console.error("获取好友详情失败:", error); + throw error; + } +}; \ No newline at end of file diff --git a/nkebao/src/components/TestComponent.tsx b/nkebao/src/components/TestComponent.tsx new file mode 100644 index 000000000..2591be216 --- /dev/null +++ b/nkebao/src/components/TestComponent.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { cn } from '@/utils'; + +interface TestComponentProps { + title: string; + className?: string; +} + +const TestComponent: React.FC = ({ title, className }) => { + return ( +
+

{title}

+

+ 这是一个测试组件,用于验证路径别名 @/ 是否正常工作。 +

+
+ ✓ 成功使用 @/utils 导入工具函数 +
+
+ ); +}; + +export default TestComponent; \ No newline at end of file diff --git a/nkebao/src/types/common.ts b/nkebao/src/types/common.ts new file mode 100644 index 000000000..078e2e1cc --- /dev/null +++ b/nkebao/src/types/common.ts @@ -0,0 +1,43 @@ +// API响应格式 +export interface ApiResponse { + code: number + message: string + data: T | null +} + +// 分页响应格式 +export interface PaginatedResponse { + 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 +} \ No newline at end of file diff --git a/nkebao/src/types/device.ts b/nkebao/src/types/device.ts new file mode 100644 index 000000000..e539b1e5e --- /dev/null +++ b/nkebao/src/types/device.ts @@ -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[] + } +} \ No newline at end of file diff --git a/nkebao/src/types/wechat.ts b/nkebao/src/types/wechat.ts new file mode 100644 index 000000000..5225583ef --- /dev/null +++ b/nkebao/src/types/wechat.ts @@ -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 +} \ No newline at end of file diff --git a/nkebao/src/utils/index.ts b/nkebao/src/utils/index.ts new file mode 100644 index 000000000..e4a5f67d6 --- /dev/null +++ b/nkebao/src/utils/index.ts @@ -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 = any>( + func: T, + wait: number +): ((...args: Parameters) => void) => { + let timeout: NodeJS.Timeout; + return (...args: Parameters) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +}; + +// 节流函数 +export const throttle = any>( + func: T, + wait: number +): ((...args: Parameters) => void) => { + let inThrottle: boolean; + return (...args: Parameters) => { + if (!inThrottle) { + func(...args); + inThrottle = true; + setTimeout(() => (inThrottle = false), wait); + } + }; +}; \ No newline at end of file diff --git a/nkebao/tsconfig.json b/nkebao/tsconfig.json index a273b0cfc..aa2f51d81 100644 --- a/nkebao/tsconfig.json +++ b/nkebao/tsconfig.json @@ -18,7 +18,18 @@ "resolveJsonModule": true, "isolatedModules": 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": [ "src"