feat(customer): 添加客户详情和邀请好友功能
- 新增客户详情页面,包括公司信息、合同信息、企业信息和联系记录 - 添加邀请好友功能,包括二维码生成、邀请记录和统计图表 - 优化导航栏和首页网格组件,支持跳转到新页面- 调整 app.config.ts,添加新页面的路由配置
This commit is contained in:
381
src/pages/customer/trading.tsx
Normal file
381
src/pages/customer/trading.tsx
Normal file
@@ -0,0 +1,381 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Taro from '@tarojs/taro';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import {
|
||||
NavBar,
|
||||
SearchBar,
|
||||
Tabs,
|
||||
Button,
|
||||
Tag,
|
||||
Empty,
|
||||
PullToRefresh,
|
||||
InfiniteLoading
|
||||
} from '@nutui/nutui-react-taro';
|
||||
import {
|
||||
Filter,
|
||||
Calendar
|
||||
} from '@nutui/icons-react-taro';
|
||||
import './trading.scss';
|
||||
|
||||
// 交易记录数据类型
|
||||
interface TradingRecord {
|
||||
id: string;
|
||||
customerName: string;
|
||||
customerId: string;
|
||||
tradingType: 'buy' | 'sell';
|
||||
amount: string;
|
||||
price: string;
|
||||
quantity: string;
|
||||
tradingDate: string;
|
||||
status: 'pending' | 'completed' | 'cancelled';
|
||||
profit: string;
|
||||
profitRate: string;
|
||||
}
|
||||
|
||||
const CustomerTrading = () => {
|
||||
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
|
||||
const [activeTab, setActiveTab] = useState<string>('all');
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [tradingRecords, setTradingRecords] = useState<TradingRecord[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false);
|
||||
const [hasMore, setHasMore] = useState<boolean>(true);
|
||||
const [page, setPage] = useState<number>(1);
|
||||
|
||||
// 模拟交易数据
|
||||
const mockTradingRecords: TradingRecord[] = [
|
||||
{
|
||||
id: '1',
|
||||
customerName: '广州雅虎信息科技公司',
|
||||
customerId: '1',
|
||||
tradingType: 'buy',
|
||||
amount: '100,000',
|
||||
price: '15.50',
|
||||
quantity: '6,451',
|
||||
tradingDate: '2025-08-21 09:30:15',
|
||||
status: 'completed',
|
||||
profit: '+8,500',
|
||||
profitRate: '+8.5%'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
customerName: '深圳腾讯科技有限公司',
|
||||
customerId: '2',
|
||||
tradingType: 'sell',
|
||||
amount: '250,000',
|
||||
price: '28.80',
|
||||
quantity: '8,680',
|
||||
tradingDate: '2025-08-21 10:15:30',
|
||||
status: 'completed',
|
||||
profit: '+15,200',
|
||||
profitRate: '+6.1%'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
customerName: '阿里巴巴网络技术有限公司',
|
||||
customerId: '3',
|
||||
tradingType: 'buy',
|
||||
amount: '500,000',
|
||||
price: '42.30',
|
||||
quantity: '11,820',
|
||||
tradingDate: '2025-08-21 11:45:20',
|
||||
status: 'pending',
|
||||
profit: '0',
|
||||
profitRate: '0%'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
customerName: '百度在线网络技术公司',
|
||||
customerId: '4',
|
||||
tradingType: 'sell',
|
||||
amount: '180,000',
|
||||
price: '22.10',
|
||||
quantity: '8,144',
|
||||
tradingDate: '2025-08-21 14:20:45',
|
||||
status: 'cancelled',
|
||||
profit: '-2,800',
|
||||
profitRate: '-1.6%'
|
||||
}
|
||||
];
|
||||
|
||||
const tabList = [
|
||||
{ title: '全部', value: 'all' },
|
||||
{ title: '买入', value: 'buy' },
|
||||
{ title: '卖出', value: 'sell' },
|
||||
{ title: '待处理', value: 'pending' }
|
||||
];
|
||||
|
||||
const getFilteredRecords = () => {
|
||||
let filtered = tradingRecords;
|
||||
|
||||
// 按标签页筛选
|
||||
if (activeTab === 'buy') {
|
||||
filtered = filtered.filter(record => record.tradingType === 'buy');
|
||||
} else if (activeTab === 'sell') {
|
||||
filtered = filtered.filter(record => record.tradingType === 'sell');
|
||||
} else if (activeTab === 'pending') {
|
||||
filtered = filtered.filter(record => record.status === 'pending');
|
||||
}
|
||||
|
||||
// 按搜索关键词筛选
|
||||
if (searchValue) {
|
||||
filtered = filtered.filter(record =>
|
||||
record.customerName.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||
record.id.includes(searchValue)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return '待处理';
|
||||
case 'completed':
|
||||
return '已完成';
|
||||
case 'cancelled':
|
||||
return '已取消';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return '#ff6b35';
|
||||
case 'completed':
|
||||
return '#52c41a';
|
||||
case 'cancelled':
|
||||
return '#ff4d4f';
|
||||
default:
|
||||
return '#999';
|
||||
}
|
||||
};
|
||||
|
||||
const getTradingTypeText = (type: string) => {
|
||||
return type === 'buy' ? '买入' : '卖出';
|
||||
};
|
||||
|
||||
const getTradingTypeColor = (type: string) => {
|
||||
return type === 'buy' ? '#52c41a' : '#ff4d4f';
|
||||
};
|
||||
|
||||
const getProfitColor = (profit: string) => {
|
||||
if (profit.startsWith('+')) return '#52c41a';
|
||||
if (profit.startsWith('-')) return '#ff4d4f';
|
||||
return '#999';
|
||||
};
|
||||
|
||||
const loadTradingRecords = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setRefreshing(true);
|
||||
setPage(1);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
if (isRefresh) {
|
||||
setTradingRecords(mockTradingRecords);
|
||||
setHasMore(true);
|
||||
} else {
|
||||
// 模拟分页加载
|
||||
if (page === 1) {
|
||||
setTradingRecords(mockTradingRecords);
|
||||
} else {
|
||||
setHasMore(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = async () => {
|
||||
if (!hasMore || loading) return;
|
||||
|
||||
setPage(prev => prev + 1);
|
||||
await loadTradingRecords();
|
||||
};
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
const handleFilter = () => {
|
||||
Taro.showToast({
|
||||
title: '筛选功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecordClick = (_: TradingRecord) => {
|
||||
Taro.showToast({
|
||||
title: '查看交易详情功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddTrading = () => {
|
||||
Taro.showToast({
|
||||
title: '新增交易功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Taro.getSystemInfo({
|
||||
success: (res) => {
|
||||
setStatusBarHeight(Number(res.statusBarHeight));
|
||||
},
|
||||
});
|
||||
loadTradingRecords();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="customer-trading-page">
|
||||
{/* 头部背景 */}
|
||||
<View className="header-bg" style={{ height: '200px' }} />
|
||||
|
||||
{/* 搜索栏 */}
|
||||
<View className="search-container">
|
||||
<SearchBar
|
||||
placeholder="搜索客户名称或交易编号"
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 标签页 */}
|
||||
<View className="tabs-container">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(value) => setActiveTab(value as string)}
|
||||
activeColor="#ffffff"
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
color: '#ffffff',
|
||||
}}
|
||||
>
|
||||
{tabList.map(tab => (
|
||||
<Tabs.TabPane key={tab.value} title={tab.title} value={tab.value} />
|
||||
))}
|
||||
</Tabs>
|
||||
</View>
|
||||
|
||||
{/* 交易列表 */}
|
||||
<View className="trading-list">
|
||||
<PullToRefresh
|
||||
onRefresh={() => loadTradingRecords(true)}
|
||||
disabled={refreshing}
|
||||
>
|
||||
{loading && page === 1 ? (
|
||||
<View className="loading-container">
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
) : getFilteredRecords().length > 0 ? (
|
||||
<>
|
||||
{getFilteredRecords().map((record) => (
|
||||
<View
|
||||
key={record.id}
|
||||
className="trading-item"
|
||||
onClick={() => handleRecordClick(record)}
|
||||
>
|
||||
<View className="trading-header">
|
||||
<View className="customer-info">
|
||||
<Text className="customer-name">{record.customerName}</Text>
|
||||
<Tag
|
||||
color={getTradingTypeColor(record.tradingType)}
|
||||
plain
|
||||
>
|
||||
{getTradingTypeText(record.tradingType)}
|
||||
</Tag>
|
||||
</View>
|
||||
<View className="status-info">
|
||||
<Tag
|
||||
color={getStatusColor(record.status)}
|
||||
plain
|
||||
>
|
||||
{getStatusText(record.status)}
|
||||
</Tag>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="trading-details">
|
||||
<View className="detail-row">
|
||||
<View className="detail-item">
|
||||
<Text className="label">交易金额</Text>
|
||||
<Text className="value amount">¥{record.amount}</Text>
|
||||
</View>
|
||||
<View className="detail-item">
|
||||
<Text className="label">成交价格</Text>
|
||||
<Text className="value">¥{record.price}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="detail-row">
|
||||
<View className="detail-item">
|
||||
<Text className="label">交易数量</Text>
|
||||
<Text className="value">{record.quantity}</Text>
|
||||
</View>
|
||||
<View className="detail-item">
|
||||
<Text className="label">盈亏</Text>
|
||||
<Text
|
||||
className="value profit"
|
||||
style={{ color: getProfitColor(record.profit) }}
|
||||
>
|
||||
{record.profit} ({record.profitRate})
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="trading-footer">
|
||||
<View className="time-info">
|
||||
<Calendar size={12} color="#999" />
|
||||
<Text className="time-text">{record.tradingDate}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
<InfiniteLoading
|
||||
hasMore={hasMore}
|
||||
onLoadMore={loadMore}
|
||||
loadingText={loading && page > 1}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Empty description="暂无交易记录" />
|
||||
)}
|
||||
</PullToRefresh>
|
||||
</View>
|
||||
|
||||
{/* 底部新增交易按钮 */}
|
||||
<View className="fixed-bottom">
|
||||
<Button
|
||||
className="add-btn"
|
||||
onClick={handleAddTrading}
|
||||
>
|
||||
新增交易
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerTrading;
|
||||
Reference in New Issue
Block a user