feat: 添加应用的样式

This commit is contained in:
lrhh123
2024-06-06 23:14:11 +08:00
parent 0751fbd8ad
commit fd371fc9c0
7 changed files with 119 additions and 405 deletions
+7 -24
View File
@@ -46,8 +46,6 @@ class BKServer {
private systemService: SystemService;
private ptfs: any[] = [];
constructor(port: number, mainWindow: BrowserWindow) {
this.app = express();
this.app.use(bodyParser.json());
@@ -150,26 +148,15 @@ class BKServer {
}),
);
// 使用平台
this.app.post(
'/api/v1/base/platform',
asyncHandler(async (req, res) => {
const ids = req.body;
console.log(`ids: ${ids}`);
const data = await this.strategyService.updateStrategies(ids);
res.json({ success: true, data });
}),
);
// 获取所有平台
this.app.get(
'/api/v1/base/platform/all',
asyncHandler(async (req, res) => {
if (this.ptfs.length === 0) {
this.ptfs = await this.strategyService.getAllPlatforms();
}
res.json({ success: true, data: this.ptfs });
const data = await this.strategyService.getAllPlatforms();
res.json({
success: data && data.length > 0,
data,
});
}),
);
@@ -199,7 +186,6 @@ class BKServer {
is_paused: isPaused,
is_keyword_match: isKeywordMatch,
is_use_gpt: isUseGptReply,
ids,
} = req.body;
try {
if (isPaused) {
@@ -213,7 +199,6 @@ class BKServer {
}
this.messageService.updateKeywordMatch(isKeywordMatch, isUseGptReply);
await this.strategyService.updateStrategies(ids);
res.json({ success: true });
} catch (error) {
@@ -299,11 +284,9 @@ class BKServer {
const { total, autoReplies } = await autoReplyController.list(query);
const data = autoReplies;
if (this.ptfs.length === 0) {
this.ptfs = await this.strategyService.getAllPlatforms();
}
const ptfs = await this.strategyService.getAllPlatforms();
const ptfMap = new Map(this.ptfs.map((ptf) => [ptf.id, ptf]));
const ptfMap = new Map(ptfs.map((ptf) => [ptf.id, ptf]));
const results: any[] = [];
data.forEach((item) => {
const ptfId = item.platform_id;
-330
View File
@@ -1,330 +0,0 @@
import { Platform } from '../types';
export const ALL_PLATFORMS: Platform[] = [
{
id: 'sannysoft',
type: 'OTHER',
name: 'Sannysoft',
avatar: '',
desc: '',
impl: true,
},
{
id: 'bilibili',
type: 'HOT',
name: '哔哩哔哩',
avatar: 'https://bilibili.com/favicon.ico',
desc: '',
impl: true,
},
{
id: 'zhihu',
type: 'ME_MEDIA',
name: '知乎',
avatar: 'https://www.zhihu.com/favicon.ico',
desc: '',
impl: true,
},
{
id: 'test',
type: 'OTHER',
name: 'Test',
avatar: '',
desc: '',
impl: true,
},
{
id: 'jinritemai',
type: 'E_COMMERCE',
name: '抖店',
avatar:
'https://lf3-static.bytednsdoc.com/obj/eden-cn/yvahlyj_upfbvk_zlp/ljhwZthlaukjlkulzlp/pc_creator/favicon_v2_7145ff0.ico',
desc: '',
impl: true,
},
{
id: 'douyin',
type: 'ME_MEDIA',
name: '抖音',
avatar:
'https://lf3-static.bytednsdoc.com/obj/eden-cn/yvahlyj_upfbvk_zlp/ljhwZthlaukjlkulzlp/pc_creator/favicon_v2_7145ff0.ico',
desc: '',
impl: true,
},
{
id: 'baijiahao',
type: 'ME_MEDIA',
name: '百家号',
avatar: 'https://baijiahao.baidu.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'buyin_jinritemai',
type: 'ME_MEDIA',
name: '百应巨量',
avatar: 'https://buyin.jinritemai.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'douyin_mp',
type: 'ME_MEDIA',
name: '抖音企业号',
avatar: 'https://buyin.jinritemai.com/favicon.ico',
desc: '',
impl: true,
},
{
id: 'ctrip_imvendor',
type: 'HOT',
name: '携程供应商',
avatar: 'https://ctrip.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'dianping',
type: 'E_COMMERCE',
name: '美团开店宝',
avatar: 'https://awp-assets.meituan.net/nibfe/epassport-html/favicon.ico',
desc: '',
impl: false,
},
{
id: 'findlaw',
type: 'LAW',
name: '找法网',
avatar: 'https://findlaw.cn/favicon.ico',
desc: '',
impl: false,
},
{
id: 'lawtime',
type: 'LAW',
name: '法律快车',
avatar: 'https://lawtime.cn/favicon.ico',
desc: '',
impl: false,
},
{
id: 'lvlin',
type: 'LAW',
name: '律临',
avatar: 'https://lvlin-base.cdn.bcebos.com/sp-saas/common/ae_logo.png',
desc: '',
impl: false,
},
{
id: 'meituan_mtm',
type: 'E_COMMERCE',
name: '美团电商',
avatar:
'https://p0.meituan.net/publiconline/62580fcd548dba6697eb64f2769cc9b21804.png',
desc: '',
impl: false,
},
{
id: 'meituan_my',
type: 'E_COMMERCE',
name: '美团买药',
avatar: 'https://epassport.meituan.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'meituan',
type: 'ME_MEDIA',
name: '美团直播助手',
avatar: 'https://live.meituan.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'oceanengine',
type: 'E_COMMERCE',
name: '小6客服',
avatar: 'https://sf1-cdn-tos.toutiaostatic.com/obj/ttfe/cg/fe/im/im.ico',
desc: '',
impl: false,
},
{
id: 'pinduoduo',
type: 'E_COMMERCE',
name: '拼多多',
avatar: 'https://mms.pinduoduo.com/login/favicon.ico',
desc: '',
impl: false,
},
{
id: 'toutiao',
type: 'ME_MEDIA',
name: '头条号',
avatar: 'https://sf3-cdn-tos.toutiaostatic.com/obj/mp/logo-toutiao.png',
desc: '',
impl: false,
},
{
id: '51job',
type: 'RECRUIT',
name: '前程无忧',
avatar: 'https://ehire.51job.com/favicon.ico',
desc: '',
impl: false,
},
{
id: '58',
type: 'RECRUIT',
name: '58同城',
avatar: 'https://passport.58.com/favicon.ico',
desc: '',
impl: false,
},
{
id: '66law',
type: 'LAW',
name: '华律',
avatar: 'https://login.66law.cn/favicon.ico',
desc: '',
impl: false,
},
{
id: '1688',
type: 'E_COMMERCE',
name: '1688 找工厂',
avatar: 'https://img.alicdn.com/tfs/TB1uh..zbj1gK0jSZFuXXcrHpXa-16-16.ico',
desc: '',
impl: false,
},
{
id: '64365',
type: 'LAW',
name: '律图律师',
avatar: 'https://login.64365.com/favicon.ico',
desc: '',
impl: false,
},
{
id: 'weibo',
type: 'HOT',
name: '微博私信',
avatar: 'https://conchfairy.sinajs.cn/chat/favicon.ico',
desc: '',
impl: true,
},
{
id: 'whatsapp',
type: 'HOT',
name: 'Whatsapp',
avatar: 'https://web.whatsapp.com/img/favicon/1x/favicon.png',
desc: '',
impl: false,
},
{
id: 'weixin_channels',
type: 'ME_MEDIA',
name: '微信视频号',
avatar:
'https://res.wx.qq.com/t/wx_fed/finder/helper/finder-helper-web/res/favicon.ico',
desc: '',
impl: false,
},
{
id: 'xiaohongshu_pro',
type: 'ME_MEDIA',
name: '小红书私信',
avatar: 'https://www.xiaohongshu.com/favicon.ico',
desc: '\n### 小红书平台支持\n\n发送关联链接(笔记、商品),可以使用以下规则编写回复:\n\n```\n【g-数字】:关联商品\n【n-数字】:关联笔记\n\n这个数字是商品或者笔记所在页面的排序\n```\n\n例如:【g-1】是指第一条关联商品,【n-2】是第二条关联笔记,\n\n如果要连发两条后再混着一条文案回复可以这样写: `【g-1】|【n-2】|你好`,这样就会先发送 【g-1】商品,然后发送【n-2】商品,最后发送“你好”。\n',
impl: true,
},
{
id: 'xiaohongshu',
type: 'ME_MEDIA',
name: '小红书评论',
avatar: 'https://www.xiaohongshu.com/favicon.ico',
desc: '',
impl: true,
},
{
id: 'zhuxiaobang',
type: 'HOT',
name: '住小帮',
avatar:
'https://lf3-static.bytednsdoc.com/obj/eden-cn/jpahaz_lm_kltvo/ljhwZthlaukjlkulzlp/homed_merchant_admin/favicon.ico',
desc: '',
impl: false,
},
{
id: 'nowsecure',
type: 'OTHER',
name: 'nowsecure',
avatar: '',
desc: '',
impl: false,
},
{
id: 'antoinevastel',
type: 'OTHER',
name: 'antoinevastel',
avatar: '',
desc: '',
impl: false,
},
{
id: 'fingerprintjs',
type: 'OTHER',
name: 'fingerprintjs',
avatar: '',
desc: '',
impl: false,
},
{
id: 'incolumitas',
type: 'OTHER',
name: 'incolumitas',
avatar: '',
desc: '',
impl: false,
},
{
id: 'recaptcha',
type: 'OTHER',
name: 'recaptcha',
avatar: '',
desc: '',
impl: false,
},
{
id: 'win_wechat',
type: 'HOT',
name: '微信',
avatar: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico',
desc: '',
impl: true,
},
{
id: 'win_wecom',
type: 'HOT',
name: '企业微信(Bate版)',
avatar:
'https://wwcdn.weixin.qq.com/node/wwnl/wwnl/style/images/independent/favicon/favicon_32h$a41a4426.png',
desc: '',
impl: true,
},
{
id: 'win_qianniu',
type: 'E_COMMERCE',
name: '千牛',
avatar: 'https://qianniu.1688.com/favicon.ico',
desc: '',
impl: true,
},
{
id: 'win_jinmai',
type: 'E_COMMERCE',
name: '京卖',
avatar: 'https://shop.jd.com/jdm/favicon.ico',
desc: '',
impl: true,
},
];
+20 -31
View File
@@ -1,15 +1,11 @@
import socketIo from 'socket.io';
import { Platform, StrategyServiceStatusEnum } from '../types';
import { ALL_PLATFORMS } from '../constants';
export class StrategyService {
private io: socketIo.Server;
private platformInfo: Platform[];
constructor(io: socketIo.Server) {
this.io = io;
this.platformInfo = [];
}
async emitAndWait<T>(
@@ -50,17 +46,6 @@ export class StrategyService {
return response;
}
async updateStrategies(ids: string[]): Promise<any> {
try {
return await this.emitAndWait('strategyService-updateStrategies', {
ids,
});
} catch (error) {
console.error('Failed to update strategies', error);
return null;
}
}
async updateStatus(status: StrategyServiceStatusEnum): Promise<any> {
try {
return await this.emitAndWait('strategyService-updateStatus', { status });
@@ -70,24 +55,28 @@ export class StrategyService {
}
}
public async getPlatformInfoById(id: string): Promise<Platform | null> {
const platforms = await this.getAllPlatforms();
return platforms.find((platform) => platform.id === id) || null;
}
public async getAllPlatforms(): Promise<Platform[]> {
if (this.platformInfo.length > 0) {
return this.platformInfo;
const maxRetries = 10;
let attempt = 0;
while (attempt < maxRetries) {
try {
// eslint-disable-next-line no-await-in-loop
const data = await this.emitAndWait<Platform[]>(
'strategyService-getAppsInfo',
);
return data;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => setTimeout(resolve, 1000));
attempt++;
console.error(`Attempt ${attempt} failed to update strategies`, error);
if (attempt >= maxRetries) {
return [];
}
}
}
try {
const data = await this.emitAndWait<Platform[]>(
'strategyService-getAllPlatforms',
);
return data.length > 0 ? data : ALL_PLATFORMS;
} catch (error) {
console.error('Failed to update strategies', error);
return ALL_PLATFORMS;
}
return [];
}
}
+1 -2
View File
@@ -38,9 +38,8 @@ export interface ReplyDTO {
export interface Platform {
id: string;
name: string;
impl: boolean;
type?: string;
urls?: string[];
avatar?: string;
desc?: string;
env?: string;
}
+89 -16
View File
@@ -3,23 +3,42 @@ import {
Box,
Flex,
Input,
Image,
InputGroup,
Tooltip,
InputRightElement,
Badge,
Text,
VStack,
HStack,
IconButton,
Spacer,
Stack,
Skeleton,
} from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons';
import { SearchIcon, SettingsIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
import defaultPlatformIcon from '../../../../../assets/base/default-platform-icon.png';
import windowsIcon from '../../../../../assets/base/windows.png';
import { getPlatformList } from '../../../services/platform/controller';
const AppManagerComponent = () => {
const { data, isLoading } = useQuery(['platformList'], getPlatformList);
const high = '42vh'; // 调整为合适的全局高度
if (isLoading || !data || !data.data) {
return (
<Stack>
<Skeleton height="20px" />
<Skeleton height="20px" />
<Skeleton height="20px" />
</Stack>
);
}
return (
<Flex h={high}>
<Box w="40%" bg="brand.50" display="flex" flexDirection="column">
<Box p={4} position="sticky" top="0" zIndex="1">
<Box p={2} position="sticky" top="0" zIndex="1">
<Flex align="center">
<InputGroup>
<Input placeholder="搜索" />
@@ -36,20 +55,74 @@ const AppManagerComponent = () => {
</Flex>
</Box>
<VStack spacing={3} align="stretch" overflowY="auto" flex="1" p={4}>
{[...Array(20)].map((_, i) => (
<Flex key={i} bg="red.100" borderRadius="md" p={3} align="center">
<Box w="100%" h="50px" bg="gray.200" borderRadius="md" />
<Spacer />
{i === 1 && (
<Badge ml="1" colorScheme="red">
2
</Badge>
)}
{i === 2 && (
<Badge ml="1" colorScheme="red">
5
</Badge>
{[...data.data].map((app, i) => (
<Flex
key={i}
bg="gray.100"
borderRadius="md"
p={3}
align="center"
position="relative"
>
{/* 红点标记 */}
{10 > 0 && (
<Box
position="absolute"
top="-5px"
right="-5px"
bg="red.500"
color="white"
borderRadius="full"
width="20px"
height="20px"
display="flex"
alignItems="center"
justifyContent="center"
fontSize="12px"
>
{10}
</Box>
)}
{/* Avatar 及其右上角的 windowsIcon */}
<Box position="relative" marginRight="12px">
<Image
src={app.avatar || defaultPlatformIcon}
fallbackSrc={defaultPlatformIcon}
boxSize="25px"
/>
{app.env === 'desktop' && (
<Tooltip label="客户端应用,需要先手动打开该应用">
<Image
src={windowsIcon}
boxSize="15px"
position="absolute"
top="-5px"
right="-5px"
alt="windows"
/>
</Tooltip>
)}
</Box>
<HStack align="center" flex="1">
<Badge colorScheme="gray">{app.name}</Badge>
</HStack>
{/* 设置按钮固定在右下角 */}
<Box position="absolute" bottom="5px" right="-5px">
<Tooltip label={`设置 ${app.name} 平台`}>
<IconButton
variant="borderless"
aria-label={`设置 ${app.name} 平台`}
fontSize="15px"
w={4}
h={4}
icon={<SettingsIcon />}
onClick={() => {}}
/>
</Tooltip>
</Box>
</Flex>
))}
</VStack>
@@ -2,6 +2,7 @@ import { Platform, Reply, Config, Message } from './platform';
import { GET, POST } from '../common/api/request';
export async function getPlatformList() {
console.log('getPlatformList');
const data = await GET<{
data: Platform[];
}>('/api/v1/base/platform/all');
+1 -2
View File
@@ -3,9 +3,8 @@ import { PlatformTypeEnum } from './constant';
export interface Platform {
id: string;
name: string;
impl: boolean;
env: string;
type?: PlatformTypeEnum;
urls?: string[];
avatar?: string;
desc?: string;
}