feat: 补齐缺失页 Phase1 - 多平台IM Bot(企业微信/飞书/QQ/悟空IM)+应用服务页+路由导航
This commit is contained in:
1 parent
fa01b5df5b
commit
6525b4d994
7 files changed
+1163
No files matched your search
@@ -0,0 +1,131 @@
|
||||
// @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;
|
||||
Reference in new issue
Block a user