132 lines
7.2 KiB
TypeScript
132 lines
7.2 KiB
TypeScript
// @ts-nocheck
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { imUserAPI } from '@/api';
|
|
|
|
const PRIMARY = '#5B5FED';
|
|
|
|
const AppServices: React.FC = () => {
|
|
const [loading, setLoading] = useState(true);
|
|
const [items, setItems] = useState<any[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(1);
|
|
const [search, setSearch] = useState('');
|
|
const [category, setCategory] = useState<string>('');
|
|
const [toast, setToast] = useState<{ msg: string; type: 'success' | 'error' } | null>(null);
|
|
|
|
const showToast = (msg: string, type: 'success' | 'error' = 'success') => {
|
|
setToast({ msg, type });
|
|
setTimeout(() => setToast(null), 2500);
|
|
};
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await imUserAPI.appServices({ category: category || undefined, search: search || undefined, page, page_size: 12 });
|
|
if (res?.success) {
|
|
setItems(res.data.items || []);
|
|
setTotal(res.data.total || 0);
|
|
} else {
|
|
showToast('加载应用服务失败', 'error');
|
|
}
|
|
} catch (e) {
|
|
showToast('加载应用服务失败', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [category, search, page]);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
const categories = [
|
|
{ key: '', name: '全部' },
|
|
{ key: 'customer_service', name: '客服助手' },
|
|
{ key: 'data_analysis', name: '数据分析' },
|
|
{ key: 'coding', name: '编程辅助' },
|
|
{ key: 'writing', name: '内容创作' },
|
|
{ key: 'education', name: '教育培训' },
|
|
{ key: 'marketing', name: '营销推广' },
|
|
{ key: 'hr', name: '人力资源' },
|
|
{ key: 'finance', name: '财务法务' },
|
|
];
|
|
|
|
return (
|
|
<div style={{ minHeight: '100%', background: '#FFFFFF', color: '#1A1F36', padding: '32px' }}>
|
|
<div style={{ maxWidth: '1080px', margin: '0 auto' }}>
|
|
<div style={{ marginBottom: '20px' }}>
|
|
<h1 style={{ fontSize: '24px', fontWeight: 700, margin: 0, color: '#1A1F36' }}>应用服务</h1>
|
|
<p style={{ color: '#6B7280', fontSize: '14px', margin: '6px 0 0 0' }}>
|
|
浏览平台已上架的 AI 应用服务,按需接入你的工作流。由七虎AI 大脑统一驱动,覆盖客服、分析、创作等多类场景。
|
|
</p>
|
|
</div>
|
|
|
|
{toast && (
|
|
<div style={{ position: 'fixed', top: '20px', right: '20px', zIndex: 9999, padding: '12px 24px', borderRadius: '8px', background: toast.type === 'success' ? '#10b981' : '#ef4444', color: '#fff', fontSize: '14px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)' }}>{toast.msg}</div>
|
|
)}
|
|
|
|
{/* 搜索 + 分类筛选 */}
|
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '20px' }}>
|
|
<input
|
|
value={search}
|
|
onChange={(e) => { setSearch(e.target.value); setPage(1); }}
|
|
placeholder="搜索应用名称 / 描述…"
|
|
style={{ flex: '1 1 260px', minWidth: '200px', padding: '9px 14px', border: '1px solid #E5E7EB', borderRadius: '8px', fontSize: '14px', outline: 'none' }}
|
|
/>
|
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
|
{categories.map((c) => (
|
|
<button
|
|
key={c.key}
|
|
onClick={() => { setCategory(c.key); setPage(1); }}
|
|
style={{
|
|
padding: '7px 14px', borderRadius: '20px', fontSize: '13px', cursor: 'pointer',
|
|
border: '1px solid', borderColor: category === c.key ? PRIMARY : '#E5E7EB',
|
|
background: category === c.key ? PRIMARY : '#fff', color: category === c.key ? '#fff' : '#6B7280',
|
|
}}
|
|
>{c.name}</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div style={{ padding: '40px', textAlign: 'center', color: '#9CA3AF' }}>加载中…</div>
|
|
) : items.length === 0 ? (
|
|
<div style={{ padding: '50px', textAlign: 'center', color: '#9CA3AF', background: '#F8F9FC', borderRadius: '12px' }}>暂无已发布的应用服务</div>
|
|
) : (
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '16px' }}>
|
|
{items.map((app) => (
|
|
<div key={app.id} style={{ border: '1px solid #E5E7EB', borderRadius: '14px', padding: '20px', background: '#fff', boxShadow: '0 1px 3px rgba(0,0,0,0.04)', display: 'flex', flexDirection: 'column' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px' }}>
|
|
<div style={{ width: '44px', height: '44px', borderRadius: '10px', background: PRIMARY + '18', color: PRIMARY, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>{app.icon ? <img src={app.icon} alt="" style={{ width: 28, height: 28, borderRadius: 6 }} /> : '🤖'}</div>
|
|
<div style={{ flex: 1 }}>
|
|
<div style={{ fontSize: '16px', fontWeight: 600, color: '#1A1F36' }}>{app.name}</div>
|
|
<div style={{ display: 'flex', gap: '6px', marginTop: '4px' }}>
|
|
<span style={{ fontSize: '11px', padding: '1px 8px', borderRadius: '4px', background: '#EEF0FF', color: PRIMARY }}>{app.type_name}</span>
|
|
<span style={{ fontSize: '11px', padding: '1px 8px', borderRadius: '4px', background: '#F1F5F9', color: '#64748B' }}>{app.category_name}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p style={{ fontSize: '13px', color: '#6B7280', lineHeight: 1.7, flex: 1, margin: '0 0 14px 0' }}>{app.description || '暂无描述'}</p>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span style={{ fontSize: '12px', color: '#9CA3AF' }}>{app.published_at ? '上架于 ' + new Date(app.published_at).toLocaleDateString('zh-CN') : ''}</span>
|
|
<button onClick={() => showToast('已加入「我的应用」,可在 AI 聊天中调用')} style={{ padding: '7px 16px', background: PRIMARY, border: 'none', borderRadius: '8px', color: '#fff', fontSize: '13px', fontWeight: 500, cursor: 'pointer' }}>使用</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{total > 12 && (
|
|
<div style={{ display: 'flex', justifyContent: 'center', gap: '12px', marginTop: '24px', alignItems: 'center' }}>
|
|
<button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))} style={{ ...pageBtn, opacity: page <= 1 ? 0.4 : 1 }}>上一页</button>
|
|
<span style={{ fontSize: '13px', color: '#6B7280' }}>第 {page} 页 / 共 {Math.ceil(total / 12)} 页({total} 个)</span>
|
|
<button disabled={page >= Math.ceil(total / 12)} onClick={() => setPage((p) => p + 1)} style={{ ...pageBtn, opacity: page >= Math.ceil(total / 12) ? 0.4 : 1 }}>下一页</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const pageBtn: React.CSSProperties = { padding: '8px 16px', background: '#F0F2F8', border: '1px solid #E5E7EB', borderRadius: '8px', color: '#1A1F36', fontSize: '14px', cursor: 'pointer' };
|
|
|
|
export default AppServices;
|