diff --git a/.gitignore b/.gitignore index 54c883c5..41d080fe 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ mvnw /react-ui/types/tsconfig.tsbuildinfo /react-ui/storybook-static /react-ui/.storybook/scripts +/react-ui/dist.zip diff --git a/react-ui/config/config.ts b/react-ui/config/config.ts index 04ab330d..ca78981f 100644 --- a/react-ui/config/config.ts +++ b/react-ui/config/config.ts @@ -127,6 +127,8 @@ export default defineConfig({ headScripts: [ // 解决首次加载时白屏的问题 { src: '/scripts/loading.js', async: true }, + { src: '/scripts/resize.js', async: true }, + // { src: '/scripts/resize-breakpoint.js', async: true }, ], // links: [ // { @@ -148,6 +150,7 @@ export default defineConfig({ // mfsu: { // strategy: 'normal', // }, + esbuildMinifyIIFE: true, requestRecord: {}, icons: {}, lessLoader: { @@ -157,4 +160,19 @@ export default defineConfig({ javascriptEnabled: true, }, valtio: {}, + qiankun: { + master: { + sandbox: true, + apps: [ + { + name: 'app1', + entry: '//localhost:7001', + }, + { + name: 'app2', + entry: '//localhost:3000', + }, + ], + }, + }, }); diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts index c4c59d2f..8129ba1e 100644 --- a/react-ui/config/defaultSettings.ts +++ b/react-ui/config/defaultSettings.ts @@ -9,9 +9,8 @@ const Settings: ProLayoutProps & { } = { locale: 'zh-CN', navTheme: 'light', - // 拂晓蓝 - colorPrimary: '#1664ff', - // layout: 'mix', + colorPrimary: '#514cf9', + layout: 'mix', contentWidth: 'Fluid', fixedHeader: false, fixSiderbar: false, diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts index 3b7a8381..eadf6991 100644 --- a/react-ui/config/proxy.ts +++ b/react-ui/config/proxy.ts @@ -22,17 +22,37 @@ export default { // 要代理的地址 // target: 'http://172.20.32.197:31213', // 开发环境 // target: 'http://172.20.32.235:31213', // 测试环境 - // target: 'http://172.20.32.44:8082', - target: 'http://172.20.32.164:8082', + target: 'http://36.103.199.74:31213', // 公网环境 + // target: 'http://172.20.32.164:8082', // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, - pathRewrite: { '^/api': '' }, + // pathRewrite: { '^/api': '' }, }, '/profile/avatar/': { target: 'http://172.20.32.235:31213', changeOrigin: true, }, + '/proxyApi/skybox-flames/': { + target: 'http://36.103.199.74:30009/', + // target: 'http://172.31.164.103:30009/', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/proxyApi\/skybox-flames/, ''), + }, + '/proxyApi/skybox-flames/flames/api/v1': { + target: 'http://36.103.199.74:30009/', + // target: 'http://172.31.164.103:30009/', + changeOrigin: true, + rewrite: (path) => + path.replace( + /^\/proxyApi\/skybox-flames\/flames\/api\/v1/, + '/flames/api/v1' + ), + }, + '/proxyApi/': { + target: 'http://36.103.199.74:30009/', + // target: 'http://172.31.164.103:30009/', + }, }, /** diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 199769fe..133cbbc1 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -13,7 +13,20 @@ export default [ { path: '/', - redirect: '/workspace', + redirect: '/home', + }, + { + name: '首页', + path: '/home', + layout: false, + routes: [ + { + name: '首页', + path: '', + key: 'home', + component: './Home/index', + }, + ], }, { name: '工作空间', @@ -25,6 +38,12 @@ export default [ key: 'workspace', component: './Workspace/index', }, + { + name: '消息中心', + path: 'message', + key: 'message', + component: './Message/index', + }, ], }, { @@ -569,6 +588,11 @@ export default [ }, ], }, + { + name: '审核管理', + path: 'approval', + component: './System/Approval', + }, ], }, { @@ -583,6 +607,18 @@ export default [ }, ], }, + { + name: 'mixed', + path: '/mixed', + routes: [ + { + name: '父子页面混合', + path: '', + key: 'mixed', + component: './Mixed/index', + }, + ], + }, { name: '算力积分', path: '/points', @@ -595,6 +631,18 @@ export default [ }, ], }, + { + path: '/app1/*', + name: '子应用1', + microApp: 'app1', + layout: true, + }, + { + path: '/app2/*', + name: '子应用2', + microApp: 'app2', + layout: true, + }, { path: '*', layout: false, diff --git a/react-ui/mock/home.ts b/react-ui/mock/home.ts new file mode 100644 index 00000000..25039391 --- /dev/null +++ b/react-ui/mock/home.ts @@ -0,0 +1,600 @@ +import { defineMock } from 'umi'; + +export default defineMock({ + 'GET /api/mmp/workspace/getPublicDatasets': { + msg: '操作成功', + code: 200, + data: { + content: [ + { + name: 'R1蒸馏模型数学推理能力测试集', + identifier: 'public_dataset_20250519163052', + description: + '共728道数学推理题目,包括:\nMATH-500:一组具有挑战性的高中数学竞赛问题数据集,涵盖七个科目(如初等代数、代数、数论)共500道题。\nGPQA-Diamond:该数据集包含物理、化学和生物学子领域的硕士水平多项选择题,共198道题。\nAIME-2024:美国邀请数学竞赛的数据集,包含30道数学题。', + is_public: true, + time_ago: '2个月前', + full_last_update_time: '2025-06-23T14:36:48.000+08:00', + id: 91, + visits: 1, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '有机化学VLM', + identifier: 'public_dataset_20250527113008', + description: 'Dataset Card for "Chemistry_text_to_image"', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:06:32.000+08:00', + id: 134, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'OQMD 开源量子材料数据集', + identifier: 'public_dataset_20250527141950', + description: + 'QMD 包含了通过密度泛函理论 (DFT) 计算得到的超过 1,226,781 种材料的热力学和结构性质。数据库中的数据来源于无机晶体结构数据库 (ICSD),包括了近 300,000 种化合物的 DFT 总能量计算以及常见晶体结构的修饰', + is_public: true, + time_ago: '2个月前', + full_last_update_time: '2025-06-23T14:38:43.000+08:00', + id: 136, + visits: 5, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '不可降解和可生物降解的材料数据集', + identifier: 'public_dataset_20250527142930', + description: + '此数据集包含大约 256K 图像(156K 原始数据),代表两类:可生物降解和不可生物降解。\n可生物降解,包含可被微生物自然分解的材料,如食物、植物、水果等。这种材料的废物可以加工成堆肥。\n不可生物降解,包含无法自然分解的材料,例如塑料、金属、无机元素等。这种材料的废料将被回收成新材料。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:06:11.000+08:00', + id: 137, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '金属有机框架材料预测', + identifier: 'public_dataset_20250527143028', + description: + '金属有机框架 (MOF) 是一类通过金属离子(或金属簇)和有机配体之间的配位键连接的结晶材料。MOF 材料具有多孔结构、高度可调和巨大的比表面积,使其在吸附、储气、分离、催化等领域具有广泛的应用潜力。预测合成是指通过计算机模拟和机器学习方法对新型 MOF 材料的合成路线和条件进行预测和设计。\n', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:06:01.000+08:00', + id: 138, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '纤维增强复合材料的弹性特性', + identifier: 'public_dataset_20250527144649', + description: + '纤维增强复合材料弹性特性数据集主要包含其力学性能参数,如弹性模量(纵向、横向)、剪切模量、泊松比以及应力-应变关系等。数据通常通过实验测试(拉伸、压缩、弯曲试验)或计算模拟(有限元分析、细观力学模型)获得,涵盖不同纤维类型(碳纤维、玻璃纤维、芳纶等)、基体材料(环氧树脂、热塑性塑料等)及铺层方式(单向、编织、多轴向)的组合。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:05:24.000+08:00', + id: 142, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'OCR 合成材料', + identifier: 'public_dataset_20250527144821', + description: + 'OCR(光学字符识别)合成材料数据集是用于训练和评估文本识别模型的专用数据集,主要包含人工生成的文本图像,模拟真实场景中的材料标签、说明书、包装文字等。这类数据集通常涵盖多种字体、背景、光照条件、扭曲变形及噪声干扰,以提高模型鲁棒性。数据可能包含金属、塑料、复合材料等工业材料的名称、参数(如成分、规格、批次号)及安全标识', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:05:18.000+08:00', + id: 143, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '钙钛矿稳定性', + identifier: 'public_dataset_20250527145953', + description: + '这个钙钛矿稳定性数据集给出了潜在钙钛矿材料成分相对于用 DFT 计算的凸包的能量。钙钛矿数据集还包括包含钙钛矿结构中 A 位点、B 位点和 X 位点信息的列,以便对数据进行更高级的分组。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:04:30.000+08:00', + id: 148, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '纳米颗粒毒性数据集', + identifier: 'public_dataset_20250527150856', + description: + '该数据集是一个毒性数据集,由几列组成,捕获了纳米颗粒 (NPs) 的各种属性及其毒理学影响。该数据集包含与纳米颗粒 (NPs) 及其特性相关的各种特征,这些特征可能与毒性分类有关', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:04:41.000+08:00', + id: 149, + visits: 2, + praises_count: 1, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '3D多模态医疗数据集-分割-fanshuai', + identifier: 'public_dataset_20250519151852', + description: '大规模通用 3D 医疗图像分割数据集 (M3D-Seg)', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-22T10:20:52.000+08:00', + id: 82, + visits: 0, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '中文基于满血DeepSeek-R1蒸馏数据集', + identifier: 'public_dataset_20250519161406', + description: + '注意:提供了直接SFT使用的版本。将数据中的思考和答案整合成output字段,大部分SFT代码框架均可直接直接加载训练。\n本数据集为中文开源蒸馏满血R1的数据集,数据集中不仅包含math数据,还包括大量的通用类型数据,总数量为110K。\n为什么开源这个数据?\nR1的效果十分强大,并且基于R1蒸馏数据SFT的小模型也展现出了强大的效果,但检索发现,大部分开源的R1蒸馏数据集均为英文数', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-19T16:14:06.000+08:00', + id: 88, + visits: 0, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: '中文Text2SQL数据集', + identifier: 'public_dataset_20250519165142', + description: + '同时包含用于训练和测试表格问答预训练模型的数据,数据集包含500条训练数据和100条测试数据。\n表格问答预训练模型的训练和测试数据,支持中文,支持通用领域的表格问答。另外,也可以从本model card中,点击数据集文件panel,然后点击数据文件选项,即可下载trian.zip和test.zip文件', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-19T16:51:43.000+08:00', + id: 93, + visits: 0, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'MatPES', + identifier: 'public_dataset_20250521090336', + description: + '使用元素周期表几乎完全覆盖的势能面数据集来训练基础 电位 (FP),即机器学习原子间电位 (MLIP),几乎完全覆盖了周期性 桌子。MatPES 是材料虚拟实验室和材料项目的一项倡议,旨在解决此类材料 PES 数据集中的关键缺陷。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:10:21.000+08:00', + id: 100, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'innovation_contest/innov202305100905418', + identifier: 'public_dataset_20250526093119', + description: + '1. 赛题解读PPT;2.根据流动状态分开的数据集,方便选手测试自己模型的变状态泛化性能\n新的数据中输入输出与均在一个文件夹中\n新的数据集中,模型文件简称对应的状态如下:\nCBFS 曲线后台阶 雷诺数Re=13700\nCDN 收缩扩张管道 Re=12600\nduct 方管 Re在文件名中包含,比如duct_Re1100.csv代表Re=1100\nperhill 周期山 Re=5600,文件名后面', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:08:27.000+08:00', + id: 122, + visits: 3, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'WIDERFace', + identifier: 'public_dataset_20250526094839', + description: + '32,203张图像,并对393,703张像样本图像中所描述的在尺度、姿势和遮挡方面具有高度可变性的面孔进行标记。较宽的人脸数据集基于61个事件类进行组织。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:08:18.000+08:00', + id: 123, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'StanfordSentimentTreebank', + identifier: 'public_dataset_20250526095521', + description: + '用于情感分析的数据集,其中包含11855个句子的语法分析树中215154个短语的细粒度情感标签,并为情感组成提出了新挑战。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:08:12.000+08:00', + id: 124, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'COCO', + identifier: 'public_dataset_20250526100341', + description: + 'COCO是大规模的对象检测,分割和字幕数据集。 它包含:330K图像(标为> 200K),150万个对象实例,80个对象类别。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:07:56.000+08:00', + id: 125, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'BillionWords', + identifier: 'public_dataset_20250526101006', + description: '该项目的目的是为语言建模实验提供标准的培训和测试设置,包含10亿字。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:07:47.000+08:00', + id: 126, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'car_ims', + identifier: 'public_dataset_20250527084401', + description: + '斯坦福汽车数据集包含196类汽车的16,185张图像。数据被分为8,144个训练图像和8,041个测试图像,其中每个类别已大致分为50-50个分割。', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:07:20.000+08:00', + id: 128, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + { + name: 'IU-xrays', + identifier: 'public_dataset_20250527093542', + description: '放射图像', + is_public: true, + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:07:12.000+08:00', + id: 129, + visits: 2, + praises_count: 0, + create_by: 'fanshuai', + owner: 'fanshuai', + }, + ], + pageable: { + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + pageNumber: 0, + pageSize: 20, + offset: 0, + unpaged: false, + paged: true, + }, + last: false, + totalElements: 39, + totalPages: 2, + first: true, + number: 0, + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + numberOfElements: 20, + size: 20, + empty: false, + }, + }, + 'GET /api/mmp/workspace/getPublicModels': { + msg: '操作成功', + code: 200, + data: { + content: [ + { + id: 109, + name: '介电', + create_by: 'ceshi', + description: '介电材料模型', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:09:54.000+08:00', + owner: 'ceshi', + identifier: 'public_model_20250522110231', + is_public: true, + praises_count: 2, + }, + { + id: 156, + name: 'ChatGLM2-6B', + create_by: 'fanshuai', + description: + 'ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上', + time_ago: '2个月前', + full_last_update_time: '2025-06-20T16:09:02.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528093916', + is_public: true, + praises_count: 1, + }, + { + id: 155, + name: '鹏城·脑海(原鹏城·盘古)α-2.6B-CPU', + create_by: 'fanshuai', + description: + '「鹏城·盘古α」由以鹏城实验室为首的技术团队联合攻关,首次基于“鹏城云脑Ⅱ”和国产MindSpore框架的自动混合并行模式实现在2048卡算力集群上的大规模分布式训练,训练出业界首个2000亿参数以中文为核心的预训练生成语言模型。鹏城·盘古α预训练模型支持丰富的场景应用,在知识问答、知识检索、知识推理、阅读理解等文本生成领域表现突出,具备很强的小样本学习能力。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:03:36.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528093254', + is_public: true, + praises_count: 0, + }, + { + id: 157, + name: 'ernie-3.0-base-zh', + create_by: 'fanshuai', + description: '大规模知识增强预训练,用于语言理解和生成', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:03:08.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528094825', + is_public: true, + praises_count: 0, + }, + { + id: 158, + name: 'FastChat-T5', + create_by: 'fanshuai', + description: + 'FastChat-T5是一款开源聊天机器人,通过微调Flan-t5-xl (3B参数)并基于从ShareGPT.收集的用户共享对话进行训练。它基于编码器-解码器变压器架构,能够自回归生成对用户输入的响应。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:03:19.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528101831', + is_public: true, + praises_count: 0, + }, + { + id: 159, + name: 'Kolors-IP-Adapter-Plus', + create_by: 'fanshuai', + description: '基于Kolors-Basemodel提供了IP-Adapter-Plus的权重和推理代码', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:03:01.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528102217', + is_public: true, + praises_count: 0, + }, + { + id: 160, + name: 'Florence-2-base', + create_by: 'fanshuai', + description: + 'Florence-2是一款先进的视觉基础模型,采用提示式方法处理广泛的视觉和视觉-语言任务。Florence-2能够通过简单的文本提示来执行诸如字幕生成、物体检测和分割等任务。该模型利用了包含54亿个注释的FLD-5B数据集,这些注释覆盖了1.26亿张图像,从而掌握了多任务学习。模型的序列到序列架构使其在零样本和微调设置中表现出色,证明了其作为竞争性视觉基础模型的实力。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:55.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528102750', + is_public: true, + praises_count: 0, + }, + { + id: 161, + name: 'E5-base', + create_by: 'fanshuai', + description: '弱监督对比预训练的文本嵌入。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:48.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528103059', + is_public: true, + praises_count: 0, + }, + { + id: 162, + name: 'Mini-InternVL-Chat', + create_by: 'fanshuai', + description: + '使用了与InternVL 1相同的数据来训练这个较小的模型。此外,由于较小模型的训练成本较低,我们在训练时采用了8K的上下文长度。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:39.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528105945', + is_public: true, + praises_count: 0, + }, + { + id: 163, + name: 'Verdict-Classifier', + create_by: 'fanshuai', + description: + '该模型是基于xlm-roberta-base的微调版本,基于谷歌事实核查工具API提供的2,500条去重多语言判决,并通过谷歌云翻译API转换成65种语言', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:27.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528110545', + is_public: true, + praises_count: 0, + }, + { + id: 164, + name: 'Text2Vec-Base-Multilingual', + create_by: 'fanshuai', + description: + '这是一个CoSENT(余弦句子)模型,它将句子映射到一个384维的密集向量空间,并可用于任务,例如句子嵌入、文本匹配或语义搜索。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:19.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528110858', + is_public: true, + praises_count: 0, + }, + { + id: 167, + name: 'Latex-OCR', + create_by: 'fanshuai', + description: '识别图像中的数学公式并转换为Latex源码。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T18:02:09.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528112153', + is_public: true, + praises_count: 0, + }, + { + id: 169, + name: 'XLNet', + create_by: 'fanshuai', + description: + 'XLNet是一种基于新型广义置换语言建模目标的新型无监督语言表示学习方法。此外,XLNet采用Transformer-XL作为骨干模型,在处理长上下文的语言任务中表现出色。总体而言,XLNet在包括问答、自然语言推理、情感分析和文档排序在内的多种下游语言任务中取得了最先进的(SOTA)成果。', + time_ago: '3个月前', + full_last_update_time: '2025-05-28T17:29:05.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250528172905', + is_public: true, + praises_count: 0, + }, + { + id: 173, + name: ' GTE-base', + create_by: 'fanshuai', + description: + 'GTE模型由阿里巴巴达摩学院训练。这些模型主要基于BERT框架,目前提供三种不同规模的版本,分别是GTE-large、GTE-base和GTE-small。GTE模型在大规模的相关文本对语料库上进行训练,涵盖了广泛的领域和场景。这使得GTE模型能够应用于文本嵌入的多种下游任务,如信息检索、语义文本相似性分析、文本重排序等。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T09:14:15.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529091415', + is_public: true, + praises_count: 0, + }, + { + id: 174, + name: 'Tiny-lm', + create_by: 'fanshuai', + description: + '此仓库提供了一个小型的1600万参数语言模型,该模型基于英文和日文维基百科数据训练。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T09:18:57.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529091857', + is_public: true, + praises_count: 0, + }, + { + id: 175, + name: "Snowflake's Arctic-embed-s", + create_by: 'fanshuai', + description: + 'snowflake-arctic-embed是一套文本嵌入模型,专注于创建高性能的高质量检索模型。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T09:23:58.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529092358', + is_public: true, + praises_count: 0, + }, + { + id: 176, + name: 'ViTMatte model', + create_by: 'fanshuai', + description: + 'ViTMatte是一种简单的图像抠图方法,旨在准确估计图像中的前景物体。该模型由一个Vision Transformer(ViT)和一个轻量级头部组成。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T09:41:07.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529094107', + is_public: true, + praises_count: 0, + }, + { + id: 177, + name: 'Wartortle', + create_by: 'fanshuai', + description: '此模型专为语义自动补全功能而设计。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T09:44:43.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529094443', + is_public: true, + praises_count: 0, + }, + { + id: 179, + name: 'Cerebras-GPT', + create_by: 'fanshuai', + description: + 'Cerebras-GPT系列的发布旨在通过开放架构和数据集促进对大型语言模型(LLM)扩展规律的研究,并展示在Cerebras软硬件栈上训练LLM的简便性和可扩展性。所有Cerebras-GPT模型均可在Hugging Face.上获取。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T10:01:52.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529100152', + is_public: true, + praises_count: 0, + }, + { + id: 180, + name: 'Qwen2-1.5B-Instruct-AWQ', + create_by: 'fanshuai', + description: + 'Qwen2是Qwen大语言模型系列的最新成员。我们为Qwen2推出了多个基础语言模型和指令调优语言模型,参数规模从0.5亿到72亿不等,其中包括一个专家混合模型。本仓库包含1.5亿参数的指令调优Qwen2模型。', + time_ago: '3个月前', + full_last_update_time: '2025-05-29T10:13:53.000+08:00', + owner: 'fanshuai', + identifier: 'public_model_20250529101353', + is_public: true, + praises_count: 0, + }, + ], + pageable: { + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + pageNumber: 0, + pageSize: 20, + offset: 0, + unpaged: false, + paged: true, + }, + last: false, + totalElements: 28, + totalPages: 2, + first: true, + number: 0, + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + numberOfElements: 20, + size: 20, + empty: false, + }, + }, +}); diff --git a/react-ui/mock/notices.ts b/react-ui/mock/notices.ts deleted file mode 100644 index 616c9218..00000000 --- a/react-ui/mock/notices.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Request, Response } from 'express'; - -const getNotices = (req: Request, res: Response) => { - res.json({ - data: [ - { - id: '000000001', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr', - title: '你收到了 14 份新周报', - datetime: '2017-08-09', - type: 'notification', - }, - { - id: '000000002', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr', - title: '你推荐的 曲妮妮 已通过第三轮面试', - datetime: '2017-08-08', - type: 'notification', - }, - { - id: '000000003', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr', - title: '这种模板可以区分多种通知类型', - datetime: '2017-08-07', - read: true, - type: 'notification', - }, - { - id: '000000004', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr', - title: '左侧图标用于区分不同的类型', - datetime: '2017-08-07', - type: 'notification', - }, - { - id: '000000005', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr', - title: '内容不要超过两行字,超出时自动截断', - datetime: '2017-08-07', - type: 'notification', - }, - { - id: '000000006', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr', - title: '曲丽丽 评论了你', - description: '描述信息描述信息描述信息', - datetime: '2017-08-07', - type: 'message', - clickClose: true, - }, - { - id: '000000007', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr', - title: '朱偏右 回复了你', - description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', - datetime: '2017-08-07', - type: 'message', - clickClose: true, - }, - { - id: '000000008', - avatar: - 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr', - title: '标题', - description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', - datetime: '2017-08-07', - type: 'message', - clickClose: true, - }, - { - id: '000000009', - title: '任务名称', - description: '任务需要在 2017-01-12 20:00 前启动', - extra: '未开始', - status: 'todo', - type: 'event', - }, - { - id: '000000010', - title: '第三方紧急代码变更', - description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', - extra: '马上到期', - status: 'urgent', - type: 'event', - }, - { - id: '000000011', - title: '信息安全考试', - description: '指派竹尔于 2017-01-09 前完成更新并发布', - extra: '已耗时 8 天', - status: 'doing', - type: 'event', - }, - { - id: '000000012', - title: 'ABCD 版本发布', - description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', - extra: '进行中', - status: 'processing', - type: 'event', - }, - ], - }); -}; - -export default { - 'GET /api/notices': getNotices, -}; diff --git a/react-ui/mock/requestRecord.mock.js b/react-ui/mock/requestRecord.mock.js deleted file mode 100644 index 6c59e198..00000000 --- a/react-ui/mock/requestRecord.mock.js +++ /dev/null @@ -1,324 +0,0 @@ -module.exports = { - 'GET /api/currentUser': { - data: { - name: 'Serati Ma', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', - userid: '00000001', - email: 'antdesign@alipay.com', - signature: '海纳百川,有容乃大', - title: '交互专家', - group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', - tags: [ - { key: '0', label: '很有想法的' }, - { key: '1', label: '专注设计' }, - { key: '2', label: '辣~' }, - { key: '3', label: '大长腿' }, - { key: '4', label: '川妹子' }, - { key: '5', label: '海纳百川' }, - ], - notifyCount: 12, - unreadCount: 11, - country: 'China', - geographic: { - province: { label: '浙江省', key: '330000' }, - city: { label: '杭州市', key: '330100' }, - }, - address: '西湖区工专路 77 号', - phone: '0752-268888888', - }, - }, - 'GET /api/rule': { - data: [ - { - key: 99, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 99', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 503, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 81, - }, - { - key: 98, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 98', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 164, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 12, - }, - { - key: 97, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 97', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 174, - status: '1', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 81, - }, - { - key: 96, - disabled: true, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 96', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 914, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 7, - }, - { - key: 95, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 95', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 698, - status: '2', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 82, - }, - { - key: 94, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 94', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 488, - status: '1', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 14, - }, - { - key: 93, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 93', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 580, - status: '2', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 77, - }, - { - key: 92, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 92', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 244, - status: '3', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 58, - }, - { - key: 91, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 91', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 959, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 66, - }, - { - key: 90, - disabled: true, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 90', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 958, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 72, - }, - { - key: 89, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 89', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 301, - status: '2', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 2, - }, - { - key: 88, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 88', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 277, - status: '1', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 12, - }, - { - key: 87, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 87', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 810, - status: '1', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 82, - }, - { - key: 86, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 86', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 780, - status: '3', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 22, - }, - { - key: 85, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 85', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 705, - status: '3', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 12, - }, - { - key: 84, - disabled: true, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 84', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 203, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 79, - }, - { - key: 83, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 83', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 491, - status: '2', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 59, - }, - { - key: 82, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 82', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 73, - status: '0', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 100, - }, - { - key: 81, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - name: 'TradeCode 81', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 406, - status: '3', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 61, - }, - { - key: 80, - disabled: false, - href: 'https://ant.design', - avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - name: 'TradeCode 80', - owner: '曲丽丽', - desc: '这是一段描述', - callNo: 112, - status: '2', - updatedAt: '2022-12-06T05:00:57.040Z', - createdAt: '2022-12-06T05:00:57.040Z', - progress: 20, - }, - ], - total: 100, - success: true, - pageSize: 20, - current: 1, - }, - 'POST /api/login/outLogin': { data: {}, success: true }, - 'POST /api/login/account': { - status: 'ok', - type: 'account', - currentAuthority: 'admin', - }, -}; diff --git a/react-ui/mock/user.ts b/react-ui/mock/user.ts deleted file mode 100644 index 75edd340..00000000 --- a/react-ui/mock/user.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { Request, Response } from 'express'; - -const waitTime = (time: number = 100) => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, time); - }); -}; - -async function getFakeCaptcha(req: Request, res: Response) { - await waitTime(2000); - return res.json('captcha-xxx'); -} - -const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; - -/** - * 当前用户的权限,如果为空代表没登录 - * current user access, if is '', user need login - * 如果是 pro 的预览,默认是有权限的 - */ -let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; - -const getAccess = () => { - return access; -}; - -// 代码中会兼容本地 service mock 以及部署站点的静态数据 -export default { - // 支持值为 Object 和 Array - 'GET /api/currentUser': (req: Request, res: Response) => { - if (!getAccess()) { - res.status(401).send({ - data: { - isLogin: false, - }, - errorCode: '401', - errorMessage: '请先登录!', - success: true, - }); - return; - } - res.send({ - success: true, - data: { - name: 'Serati Ma', - avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', - userid: '00000001', - email: 'antdesign@alipay.com', - signature: '海纳百川,有容乃大', - title: '交互专家', - group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', - tags: [ - { - key: '0', - label: '很有想法的', - }, - { - key: '1', - label: '专注设计', - }, - { - key: '2', - label: '辣~', - }, - { - key: '3', - label: '大长腿', - }, - { - key: '4', - label: '川妹子', - }, - { - key: '5', - label: '海纳百川', - }, - ], - notifyCount: 12, - unreadCount: 11, - country: 'China', - access: getAccess(), - geographic: { - province: { - label: '浙江省', - key: '330000', - }, - city: { - label: '杭州市', - key: '330100', - }, - }, - address: '西湖区工专路 77 号', - phone: '0752-268888888', - }, - }); - }, - // GET POST 可省略 - 'GET /api/users': [ - { - key: '1', - name: 'John Brown', - age: 32, - address: 'New York No. 1 Lake Park', - }, - { - key: '2', - name: 'Jim Green', - age: 42, - address: 'London No. 1 Lake Park', - }, - { - key: '3', - name: 'Joe Black', - age: 32, - address: 'Sidney No. 1 Lake Park', - }, - ], - 'POST /api/login/account': async (req: Request, res: Response) => { - const { password, username, type } = req.body; - await waitTime(2000); - if (password === 'ant.design' && username === 'admin') { - res.send({ - status: 'ok', - type, - currentAuthority: 'admin', - }); - access = 'admin'; - return; - } - if (password === 'ant.design' && username === 'user') { - res.send({ - status: 'ok', - type, - currentAuthority: 'user', - }); - access = 'user'; - return; - } - if (type === 'mobile') { - res.send({ - status: 'ok', - type, - currentAuthority: 'admin', - }); - access = 'admin'; - return; - } - - res.send({ - status: 'error', - type, - currentAuthority: 'guest', - }); - access = 'guest'; - }, - 'POST /api/login/outLogin': (req: Request, res: Response) => { - access = ''; - res.send({ data: {}, success: true }); - }, - 'POST /api/register': (req: Request, res: Response) => { - res.send({ status: 'ok', currentAuthority: 'user', success: true }); - }, - 'GET /api/500': (req: Request, res: Response) => { - res.status(500).send({ - timestamp: 1513932555104, - status: 500, - error: 'error', - message: 'error', - path: '/base/category/list', - }); - }, - 'GET /api/404': (req: Request, res: Response) => { - res.status(404).send({ - timestamp: 1513932643431, - status: 404, - error: 'Not Found', - message: 'No message available', - path: '/base/category/list/2121212', - }); - }, - 'GET /api/403': (req: Request, res: Response) => { - res.status(403).send({ - timestamp: 1513932555104, - status: 403, - error: 'Forbidden', - message: 'Forbidden', - path: '/base/category/list', - }); - }, - 'GET /api/401': (req: Request, res: Response) => { - res.status(401).send({ - timestamp: 1513932555104, - status: 401, - error: 'Unauthorized', - message: 'Unauthorized', - path: '/base/category/list', - }); - }, - - 'GET /api/login/captcha': getFakeCaptcha, -}; diff --git a/react-ui/package.json b/react-ui/package.json index 10086d10..4b28d7cf 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -1,5 +1,5 @@ { - "name": "cl-model", + "name": "ci4s", "version": "1.0.0", "private": true, "description": "", @@ -60,14 +60,17 @@ "not ie <= 10" ], "dependencies": { + "@ant-design/colors": "~7.2.1", "@ant-design/icons": "^5.0.0", "@ant-design/pro-components": "^2.4.4", "@ant-design/use-emotion-css": "1.0.4", "@antv/g6": "^4.8.24", "@antv/hierarchy": "^0.6.12", + "@ctrl/tinycolor": "~4.1.0", "@types/crypto-js": "^4.2.2", "@umijs/route-utils": "^4.0.1", "antd": "~5.21.4", + "antd-style": "~3.7.1", "caniuse-lite": "~1.0.30001707", "classnames": "^2.3.2", "crypto-js": "^4.2.0", @@ -75,6 +78,7 @@ "fabric": "^5.3.0", "highlight.js": "^11.7.0", "lodash": "^4.17.21", + "motion": "~12.23.12", "omit.js": "^2.0.2", "pnpm": "^8.9.0", "query-string": "^8.1.0", @@ -82,6 +86,7 @@ "rc-util": "^5.30.0", "react": "^18.2.0", "react-activation": "^0.12.4", + "react-countup": "~6.5.3", "react-cropper": "^2.3.3", "react-dev-inspector": "^1.8.1", "react-dom": "^18.2.0", diff --git a/react-ui/public/fonts/WenYiHei.ttf b/react-ui/public/fonts/WenYiHei.ttf new file mode 100644 index 00000000..5e392798 Binary files /dev/null and b/react-ui/public/fonts/WenYiHei.ttf differ diff --git a/react-ui/public/fonts/YouSheBiaoTiHei.ttf b/react-ui/public/fonts/YouSheBiaoTiHei.ttf new file mode 100644 index 00000000..3729151a Binary files /dev/null and b/react-ui/public/fonts/YouSheBiaoTiHei.ttf differ diff --git a/react-ui/public/fonts/font.css b/react-ui/public/fonts/font.css index 012b9f59..cd3d6f12 100644 --- a/react-ui/public/fonts/font.css +++ b/react-ui/public/fonts/font.css @@ -1,6 +1,6 @@ @font-face { font-family: Alibaba; - src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); + src: url('./ALIBABA-PUHUITI-REGULAR.TTF'); font-display: swap; } @@ -10,4 +10,17 @@ url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */ url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */ font-display: swap; /* 优化页面加载时的字体显示 */ +} + +@font-face { + font-family: 'WenYiHei'; + src: url('./WenYiHei.ttf'); + font-display: swap; /* 优化页面加载时的字体显示 */ +} + + +@font-face { + font-family: 'YouSheBiaoTiHei'; + src: url('./YouSheBiaoTiHei.ttf'); + font-display: swap; /* 优化页面加载时的字体显示 */ } \ No newline at end of file diff --git a/react-ui/public/mockServiceWorker.js b/react-ui/public/mockServiceWorker.js index ec47a9a5..7f2f4b72 100644 --- a/react-ui/public/mockServiceWorker.js +++ b/react-ui/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.7.0' +const PACKAGE_VERSION = '2.7.1' const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/react-ui/public/scripts/resize-breakpoint.js b/react-ui/public/scripts/resize-breakpoint.js new file mode 100644 index 00000000..f2c5f45e --- /dev/null +++ b/react-ui/public/scripts/resize-breakpoint.js @@ -0,0 +1,52 @@ +(function (doc, win) { + 'use strict'; + + // 配置项 + const config = { + // 断点设置(单位:px) + // 1440 1560 1680 1800 1920 2040 2160 2280 2400 2520 + breakpoints: [ + { minWidth: 2520, fontSize: 22 }, // 21 + { minWidth: 2280, fontSize: 20 }, // 19 + { minWidth: 2040, fontSize: 18 }, // 17 + { minWidth: 1800, fontSize: 16 }, // 15 + { minWidth: 1560, fontSize: 14 }, // 13 + { minWidth: 0, fontSize: 12 }, + ], + delay: 300 // 防抖延迟(ms) + }; + + const docEl = doc.documentElement; + const resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'; + let resizeTimeout; + + // 计算当前宽度对应的字体大小 + function calculateFontSize() { + const clientWidth = docEl.clientWidth || win.innerWidth; + if (!clientWidth) return; + + // 从大到小匹配断点 + const targetBreakpoint = config.breakpoints.find( + bp => clientWidth >= bp.minWidth + ); + + // 设置字体大小 + docEl.style.fontSize = targetBreakpoint.fontSize + 'px'; + + // 调试输出(可选) + console.debug('[REM-Resize]', + 'Width:', clientWidth + 'px', + 'Font-Size:', targetBreakpoint.fontSize + 'px'); + } + + // 防抖处理 + function handleResize() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(calculateFontSize, config.delay); + } + + calculateFontSize(); + + // 初始化监听 + win.addEventListener(resizeEvt, handleResize, false); +})(document, window); \ No newline at end of file diff --git a/react-ui/public/scripts/resize.js b/react-ui/public/scripts/resize.js new file mode 100644 index 00000000..8434654d --- /dev/null +++ b/react-ui/public/scripts/resize.js @@ -0,0 +1,44 @@ +// rem-resize.js +(function (doc, win) { + 'use strict'; + + // 配置项 + const config = { + designWidth: 1920, // 设计稿宽度 + baseFontSize: 16, // 基础字体大小(设计稿下1rem = 16px) + minFontSize: 12, // 最小字体限制 + maxFontSize: 24, // 最大字体限制 + delay: 300, // 窗口变化时的延迟执行(ms) + }; + + const docEl = doc.documentElement; + const resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'; + let resizeTimeout; + + function calculateFontSize() { + const clientWidth = docEl.clientWidth || win.innerWidth; + if (!clientWidth) return; + + const fontSize = Math.min( + Math.max((clientWidth / config.designWidth) * config.baseFontSize, config.minFontSize), + config.maxFontSize, + ); + + docEl.style.fontSize = fontSize + 'px'; + + // 可选:调试输出 + if (win.console) { + console.debug('[REM-Resize]', 'width:', clientWidth, 'font-size:', fontSize + 'px'); + } + } + + function resizeHandler() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(calculateFontSize, config.delay); + } + + calculateFontSize(); + + // 初始化监听 + win.addEventListener(resizeEvt, resizeHandler, false); +})(document, window); diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index c0ea08c5..6de3d781 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -4,11 +4,13 @@ import { type GlobalInitialState } from '@/types'; import { menuItemRender } from '@/utils/menuRender'; import type { Settings as LayoutSettings } from '@ant-design/pro-components'; import { RuntimeConfig, history } from '@umijs/max'; +import { useState } from 'react'; import { RuntimeAntdConfig } from 'umi'; import defaultSettings from '../config/defaultSettings'; import '../public/fonts/font.css'; import { getAccessToken } from './access'; import ErrorBoundary from './components/ErrorBoundary'; +import PageContainer from './components/PageContainer'; import './dayjsConfig'; import { removeAllPageCacheState } from './hooks/useCacheState'; import { globalGetSeverTime } from './hooks/useServerTime'; @@ -20,9 +22,10 @@ import { setRemoteMenu, } from './services/session'; import './styles/menu.less'; -import { needAuth } from './utils'; +import { isLoginPage, needAuth } from './utils'; +import { HomeUrl } from './utils/constant'; import { closeAllModals } from './utils/modal'; -import { gotoLoginPage } from './utils/ui'; +import { gotoHomePage } from './utils/ui'; export { requestConfig as request } from './requestConfig'; /** @@ -41,7 +44,8 @@ export async function getInitialState(): Promise { } as API.CurrentUser; } catch (error) { console.error('getInitialState', error); - gotoLoginPage(); + // gotoLoginPage(true); + gotoHomePage(); } return undefined; }; @@ -67,7 +71,7 @@ export async function getInitialState(): Promise { export const layout: RuntimeConfig['layout'] = ({ initialState }) => { return { ErrorBoundary: ErrorBoundary, - rightContentRender: false, + rightContentRender: () => , menu: { locale: false, // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request @@ -84,12 +88,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { childrenRender: (children) => { // 增加一个 loading 的状态 // if (initialState?.loading) return ; - return ( -
- -
{children}
-
- ); + return {children}; }, collapsedButtonRender: false, collapsed: initialState?.collapsed, @@ -110,6 +109,11 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', colorMenuBackground: themes['siderBGColor'], }, + header: { + colorBgHeader: '#3a3da5', + heightLayoutHeader: 60, + colorHeaderTitle: 'white', + }, }, menuItemRender: menuItemRender(false), subMenuItemRender: menuItemRender(true), @@ -123,21 +127,22 @@ export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { closeAllModals(); const { location } = e; + const pathname = location.pathname; const token = getAccessToken(); // 没有 token,跳转到登录页面 - if (!token && needAuth(location.pathname)) { - gotoLoginPage(); + if (!token && needAuth(pathname)) { + gotoHomePage(); return; } // 有 token, 登录页面直接跳转到首页 - if (token && !needAuth(location.pathname)) { - history.push('/'); + if (token && isLoginPage(pathname)) { + history.push(HomeUrl); } const menus = getRemoteMenu(); // 没有菜单,刷新页面 - if (menus === null && needAuth(location.pathname)) { + if (menus === null && needAuth(pathname)) { history.go(0); } }; @@ -170,16 +175,34 @@ export function render(oldRender: () => void) { }); } +export const useQiankunStateForSlave = () => { + const [globalState, setGlobalState] = useState({ + slogan: 'Hello MicroFrontend', + }); + + return { + globalState, + setGlobalState, + }; +}; + // 主题修改 export const antd: RuntimeAntdConfig = (memo) => { memo.theme ??= {}; memo.theme.token = { colorPrimary: themes['primaryColor'], + colorPrimaryHover: themes['primaryHoverColor'], + colorPrimaryActive: themes['primaryActiveColor'], + colorPrimaryText: themes['primaryColor'], + colorPrimaryTextHover: themes['primaryHoverColor'], + colorPrimaryTextActive: themes['primaryActiveColor'], + // colorPrimaryBg: 'rgba(81, 76, 249, 0.07)', colorSuccess: themes['successColor'], colorError: themes['errorColor'], colorWarning: themes['warningColor'], colorLink: themes['primaryColor'], colorText: themes['textColor'], + fontSize: parseInt(themes['fontSize']), controlHeightLG: 46, }; memo.theme.components ??= {}; diff --git a/react-ui/src/assets/img/home/app-logo-dark.png b/react-ui/src/assets/img/home/app-logo-dark.png new file mode 100644 index 00000000..ff9f95da Binary files /dev/null and b/react-ui/src/assets/img/home/app-logo-dark.png differ diff --git a/react-ui/src/assets/img/home/app-logo.png b/react-ui/src/assets/img/home/app-logo.png new file mode 100644 index 00000000..e5342fcb Binary files /dev/null and b/react-ui/src/assets/img/home/app-logo.png differ diff --git a/react-ui/src/assets/img/home/code-arrow.png b/react-ui/src/assets/img/home/code-arrow.png new file mode 100644 index 00000000..7446812d Binary files /dev/null and b/react-ui/src/assets/img/home/code-arrow.png differ diff --git a/react-ui/src/assets/img/home/code-bg.png b/react-ui/src/assets/img/home/code-bg.png new file mode 100644 index 00000000..194c033a Binary files /dev/null and b/react-ui/src/assets/img/home/code-bg.png differ diff --git a/react-ui/src/assets/img/home/code-item-bg-hover.png b/react-ui/src/assets/img/home/code-item-bg-hover.png new file mode 100644 index 00000000..d0b2fc65 Binary files /dev/null and b/react-ui/src/assets/img/home/code-item-bg-hover.png differ diff --git a/react-ui/src/assets/img/home/code-item-bg.png b/react-ui/src/assets/img/home/code-item-bg.png new file mode 100644 index 00000000..325500e9 Binary files /dev/null and b/react-ui/src/assets/img/home/code-item-bg.png differ diff --git a/react-ui/src/assets/img/home/code.png b/react-ui/src/assets/img/home/code.png new file mode 100644 index 00000000..604107f9 Binary files /dev/null and b/react-ui/src/assets/img/home/code.png differ diff --git a/react-ui/src/assets/img/home/dataset-arrow-right.png b/react-ui/src/assets/img/home/dataset-arrow-right.png new file mode 100644 index 00000000..6a4d1a35 Binary files /dev/null and b/react-ui/src/assets/img/home/dataset-arrow-right.png differ diff --git a/react-ui/src/assets/img/home/dataset-bg.png b/react-ui/src/assets/img/home/dataset-bg.png new file mode 100644 index 00000000..c822ebf4 Binary files /dev/null and b/react-ui/src/assets/img/home/dataset-bg.png differ diff --git a/react-ui/src/assets/img/home/dataset-item-bg.png b/react-ui/src/assets/img/home/dataset-item-bg.png new file mode 100644 index 00000000..241b4f4b Binary files /dev/null and b/react-ui/src/assets/img/home/dataset-item-bg.png differ diff --git a/react-ui/src/assets/img/home/dataset.png b/react-ui/src/assets/img/home/dataset.png new file mode 100644 index 00000000..fcd669ca Binary files /dev/null and b/react-ui/src/assets/img/home/dataset.png differ diff --git a/react-ui/src/assets/img/home/default-avatar.png b/react-ui/src/assets/img/home/default-avatar.png new file mode 100644 index 00000000..66dc36ca Binary files /dev/null and b/react-ui/src/assets/img/home/default-avatar.png differ diff --git a/react-ui/src/assets/img/home/footer-bg.png b/react-ui/src/assets/img/home/footer-bg.png new file mode 100644 index 00000000..618a77b5 Binary files /dev/null and b/react-ui/src/assets/img/home/footer-bg.png differ diff --git a/react-ui/src/assets/img/home/header-bg-mini.png b/react-ui/src/assets/img/home/header-bg-mini.png new file mode 100644 index 00000000..22632022 Binary files /dev/null and b/react-ui/src/assets/img/home/header-bg-mini.png differ diff --git a/react-ui/src/assets/img/home/header-bg.png b/react-ui/src/assets/img/home/header-bg.png new file mode 100644 index 00000000..db0d3c17 Binary files /dev/null and b/react-ui/src/assets/img/home/header-bg.png differ diff --git a/react-ui/src/assets/img/home/image.png b/react-ui/src/assets/img/home/image.png new file mode 100644 index 00000000..fac6397a Binary files /dev/null and b/react-ui/src/assets/img/home/image.png differ diff --git a/react-ui/src/assets/img/home/mirror-arrow.png b/react-ui/src/assets/img/home/mirror-arrow.png new file mode 100644 index 00000000..0cd71d90 Binary files /dev/null and b/react-ui/src/assets/img/home/mirror-arrow.png differ diff --git a/react-ui/src/assets/img/home/mirror-version.png b/react-ui/src/assets/img/home/mirror-version.png new file mode 100644 index 00000000..b3d33cfc Binary files /dev/null and b/react-ui/src/assets/img/home/mirror-version.png differ diff --git a/react-ui/src/assets/img/home/model-between-dataset.png b/react-ui/src/assets/img/home/model-between-dataset.png new file mode 100644 index 00000000..d0384a4e Binary files /dev/null and b/react-ui/src/assets/img/home/model-between-dataset.png differ diff --git a/react-ui/src/assets/img/home/model-bg.png b/react-ui/src/assets/img/home/model-bg.png new file mode 100644 index 00000000..2238efa0 Binary files /dev/null and b/react-ui/src/assets/img/home/model-bg.png differ diff --git a/react-ui/src/assets/img/home/model-item-bg-hover.png b/react-ui/src/assets/img/home/model-item-bg-hover.png new file mode 100644 index 00000000..e428905c Binary files /dev/null and b/react-ui/src/assets/img/home/model-item-bg-hover.png differ diff --git a/react-ui/src/assets/img/home/model-item-bg-hover2.png b/react-ui/src/assets/img/home/model-item-bg-hover2.png new file mode 100644 index 00000000..70f48227 Binary files /dev/null and b/react-ui/src/assets/img/home/model-item-bg-hover2.png differ diff --git a/react-ui/src/assets/img/home/model-item-bg.png b/react-ui/src/assets/img/home/model-item-bg.png new file mode 100644 index 00000000..61e96565 Binary files /dev/null and b/react-ui/src/assets/img/home/model-item-bg.png differ diff --git a/react-ui/src/assets/img/home/model-item-hot-hover.png b/react-ui/src/assets/img/home/model-item-hot-hover.png new file mode 100644 index 00000000..4fcc6c27 Binary files /dev/null and b/react-ui/src/assets/img/home/model-item-hot-hover.png differ diff --git a/react-ui/src/assets/img/home/model-item-hot.png b/react-ui/src/assets/img/home/model-item-hot.png new file mode 100644 index 00000000..bda601b1 Binary files /dev/null and b/react-ui/src/assets/img/home/model-item-hot.png differ diff --git a/react-ui/src/assets/img/home/model.png b/react-ui/src/assets/img/home/model.png new file mode 100644 index 00000000..7383692e Binary files /dev/null and b/react-ui/src/assets/img/home/model.png differ diff --git a/react-ui/src/assets/img/home/right-arrow-hover.png b/react-ui/src/assets/img/home/right-arrow-hover.png new file mode 100644 index 00000000..31f968fd Binary files /dev/null and b/react-ui/src/assets/img/home/right-arrow-hover.png differ diff --git a/react-ui/src/assets/img/home/right-arrow.png b/react-ui/src/assets/img/home/right-arrow.png new file mode 100644 index 00000000..d89934dd Binary files /dev/null and b/react-ui/src/assets/img/home/right-arrow.png differ diff --git a/react-ui/src/assets/img/home/service-bg.png b/react-ui/src/assets/img/home/service-bg.png new file mode 100644 index 00000000..be9bdede Binary files /dev/null and b/react-ui/src/assets/img/home/service-bg.png differ diff --git a/react-ui/src/assets/img/home/service.png b/react-ui/src/assets/img/home/service.png new file mode 100644 index 00000000..a62fb0be Binary files /dev/null and b/react-ui/src/assets/img/home/service.png differ diff --git a/react-ui/src/assets/img/home/service1.png b/react-ui/src/assets/img/home/service1.png new file mode 100644 index 00000000..9009197d Binary files /dev/null and b/react-ui/src/assets/img/home/service1.png differ diff --git a/react-ui/src/assets/img/home/service2.png b/react-ui/src/assets/img/home/service2.png new file mode 100644 index 00000000..07bc8eab Binary files /dev/null and b/react-ui/src/assets/img/home/service2.png differ diff --git a/react-ui/src/assets/img/home/service3.png b/react-ui/src/assets/img/home/service3.png new file mode 100644 index 00000000..17bce241 Binary files /dev/null and b/react-ui/src/assets/img/home/service3.png differ diff --git a/react-ui/src/assets/img/home/service4.png b/react-ui/src/assets/img/home/service4.png new file mode 100644 index 00000000..471cf98a Binary files /dev/null and b/react-ui/src/assets/img/home/service4.png differ diff --git a/react-ui/src/assets/img/home/statistics-bg.png b/react-ui/src/assets/img/home/statistics-bg.png new file mode 100644 index 00000000..0386de44 Binary files /dev/null and b/react-ui/src/assets/img/home/statistics-bg.png differ diff --git a/react-ui/src/assets/img/home/timestamp.png b/react-ui/src/assets/img/home/timestamp.png new file mode 100644 index 00000000..7087131a Binary files /dev/null and b/react-ui/src/assets/img/home/timestamp.png differ diff --git a/react-ui/src/assets/img/home/user-avatar-big.png b/react-ui/src/assets/img/home/user-avatar-big.png new file mode 100644 index 00000000..ed548da3 Binary files /dev/null and b/react-ui/src/assets/img/home/user-avatar-big.png differ diff --git a/react-ui/src/assets/img/home/user-avatar.png b/react-ui/src/assets/img/home/user-avatar.png new file mode 100644 index 00000000..067aa252 Binary files /dev/null and b/react-ui/src/assets/img/home/user-avatar.png differ diff --git a/react-ui/src/assets/img/home/路径 17816@2x (1).png b/react-ui/src/assets/img/home/路径 17816@2x (1).png new file mode 100644 index 00000000..a57d2466 Binary files /dev/null and b/react-ui/src/assets/img/home/路径 17816@2x (1).png differ diff --git a/react-ui/src/assets/img/home/路径 17816@2x.png b/react-ui/src/assets/img/home/路径 17816@2x.png new file mode 100644 index 00000000..f4db7070 Binary files /dev/null and b/react-ui/src/assets/img/home/路径 17816@2x.png differ diff --git a/react-ui/src/assets/img/message/at-hover.png b/react-ui/src/assets/img/message/at-hover.png new file mode 100644 index 00000000..57aa81db Binary files /dev/null and b/react-ui/src/assets/img/message/at-hover.png differ diff --git a/react-ui/src/assets/img/message/at.png b/react-ui/src/assets/img/message/at.png new file mode 100644 index 00000000..e1cd2875 Binary files /dev/null and b/react-ui/src/assets/img/message/at.png differ diff --git a/react-ui/src/assets/img/message/content-bg.png b/react-ui/src/assets/img/message/content-bg.png new file mode 100644 index 00000000..dc31d537 Binary files /dev/null and b/react-ui/src/assets/img/message/content-bg.png differ diff --git a/react-ui/src/assets/img/message/menu-bg.png b/react-ui/src/assets/img/message/menu-bg.png new file mode 100644 index 00000000..c762354b Binary files /dev/null and b/react-ui/src/assets/img/message/menu-bg.png differ diff --git a/react-ui/src/assets/img/message/message-bg.png b/react-ui/src/assets/img/message/message-bg.png new file mode 100644 index 00000000..6a54cd26 Binary files /dev/null and b/react-ui/src/assets/img/message/message-bg.png differ diff --git a/react-ui/src/assets/img/message/red-point.png b/react-ui/src/assets/img/message/red-point.png new file mode 100644 index 00000000..bfd49b2e Binary files /dev/null and b/react-ui/src/assets/img/message/red-point.png differ diff --git a/react-ui/src/assets/img/message/system-hover.png b/react-ui/src/assets/img/message/system-hover.png new file mode 100644 index 00000000..31efbf60 Binary files /dev/null and b/react-ui/src/assets/img/message/system-hover.png differ diff --git a/react-ui/src/assets/img/message/system.png b/react-ui/src/assets/img/message/system.png new file mode 100644 index 00000000..4e23e3ec Binary files /dev/null and b/react-ui/src/assets/img/message/system.png differ diff --git a/react-ui/src/assets/img/message/trumpet-hover.png b/react-ui/src/assets/img/message/trumpet-hover.png new file mode 100644 index 00000000..82439bd6 Binary files /dev/null and b/react-ui/src/assets/img/message/trumpet-hover.png differ diff --git a/react-ui/src/assets/img/message/trumpet.png b/react-ui/src/assets/img/message/trumpet.png new file mode 100644 index 00000000..720aeb04 Binary files /dev/null and b/react-ui/src/assets/img/message/trumpet.png differ diff --git a/react-ui/src/components/ErrorBoundary/index.tsx b/react-ui/src/components/ErrorBoundary/index.tsx index 66bc8d94..2483c9e9 100644 --- a/react-ui/src/components/ErrorBoundary/index.tsx +++ b/react-ui/src/components/ErrorBoundary/index.tsx @@ -1,4 +1,5 @@ import KFEmpty, { EmptyType } from '@/components/KFEmpty'; +import { HomeUrl } from '@/utils/constant'; import { Button } from 'antd'; import { Component, ReactNode } from 'react'; @@ -55,7 +56,7 @@ function ErrorBoundaryFallback({ error }: { error: Error | null }) { + ); +} + +export default KFButton; diff --git a/react-ui/src/components/MessageBroadcast/index.less b/react-ui/src/components/MessageBroadcast/index.less new file mode 100644 index 00000000..1935c56b --- /dev/null +++ b/react-ui/src/components/MessageBroadcast/index.less @@ -0,0 +1,18 @@ +.message-broadcast { + position: relative; + width: 32px; + height: 32px; + cursor: pointer; + .backgroundFullImage(url(@/assets/img/message/trumpet.png)); + + &:hover { + background-image: url(@/assets/img/message/trumpet-hover.png); + } + + &__red-point { + position: absolute; + top: 8px; + left: 18px; + width: 6px; + } +} diff --git a/react-ui/src/components/MessageBroadcast/index.tsx b/react-ui/src/components/MessageBroadcast/index.tsx new file mode 100644 index 00000000..71d03d53 --- /dev/null +++ b/react-ui/src/components/MessageBroadcast/index.tsx @@ -0,0 +1,47 @@ +import RedPointImg from '@/assets/img/message/red-point.png'; +import { getMessageCountReq } from '@/services/message'; +import { to } from '@/utils/promise'; +import { useModel, useNavigate } from '@umijs/max'; +import { useCallback, useEffect, useState } from 'react'; +import styles from './index.less'; + +function MessageBroadcast() { + const { initialState } = useModel('@@initialState'); + const { currentUser } = initialState || {}; + const { userId } = currentUser || {}; + const [total, setTotal] = useState(0); + const navigate = useNavigate(); + + const getMessageCount = useCallback(async () => { + if (!userId) return; + const params: Record = { + receiver: userId, + type: -1, + }; + const [res] = await to(getMessageCountReq(params)); + if (res && res.data) { + const { unread_total } = res.data; + setTotal(unread_total); + } + }, [userId]); + + useEffect(() => { + const interval = setInterval(() => { + getMessageCount(); + }, 60 * 1000); + getMessageCount(); + return () => { + clearInterval(interval); + }; + }, [getMessageCount]); + + return ( +
navigate('/workspace/message')}> + {total > 0 && ( + + )} +
+ ); +} + +export default MessageBroadcast; diff --git a/react-ui/src/components/PageContainer/index.less b/react-ui/src/components/PageContainer/index.less new file mode 100644 index 00000000..6f055f71 --- /dev/null +++ b/react-ui/src/components/PageContainer/index.less @@ -0,0 +1,14 @@ +.kf-page-container { + height: 100%; + + &__breadcrumb { + display: flex; + align-items: center; + height: 70px; + padding-left: 30px; + } + + &__content { + height: calc(100% - 70px); + } +} diff --git a/react-ui/src/components/PageContainer/index.tsx b/react-ui/src/components/PageContainer/index.tsx new file mode 100644 index 00000000..c8a35416 --- /dev/null +++ b/react-ui/src/components/PageContainer/index.tsx @@ -0,0 +1,16 @@ +import { ProBreadcrumb } from '@ant-design/pro-components'; +import React from 'react'; +import './index.less'; + +function PageContainer({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
+
{children}
+
+ ); +} + +export default PageContainer; diff --git a/react-ui/src/components/ParameterSelect/config.tsx b/react-ui/src/components/ParameterSelect/config.tsx index 59d984b0..71b1ff6b 100644 --- a/react-ui/src/components/ParameterSelect/config.tsx +++ b/react-ui/src/components/ParameterSelect/config.tsx @@ -16,14 +16,17 @@ export type SelectPropsConfig = { getLabel?: (value: any) => string; // 对象类型时,获取其 label }; -export type ParameterSelectDataType = - | 'dataset' - | 'model' - | 'service' - | 'resource' - | 'remote-image' - | 'remote-resource-type' - | 'remote-resource'; +export const ParameterSelectTypeList = [ + 'dataset', + 'model', + 'service', + 'resource', + 'remote-image', + 'remote-resource-type', + 'remote-resource', +] as const; + +export type ParameterSelectDataType = (typeof ParameterSelectTypeList)[number]; export const paramSelectConfig: Record = { dataset: { diff --git a/react-ui/src/components/ParameterSelect/index.tsx b/react-ui/src/components/ParameterSelect/index.tsx index 4beddf7f..bb23bc06 100644 --- a/react-ui/src/components/ParameterSelect/index.tsx +++ b/react-ui/src/components/ParameterSelect/index.tsx @@ -4,7 +4,7 @@ * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 */ -import jccResourceState from '@/state/jcdResource'; +import jccResourceState, { getResourceTypes } from '@/state/jcdResource'; import systemResourceState, { getSystemResources } from '@/state/systemResource'; import { to } from '@/utils/promise'; import { useSnapshot } from '@umijs/max'; @@ -13,7 +13,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import FormInfo from '../FormInfo'; import { paramSelectConfig, type ParameterSelectDataType } from './config'; -export { type ParameterSelectDataType }; +export { ParameterSelectTypeList, type ParameterSelectDataType } from './config'; export type ParameterSelectObject = { value: any; @@ -59,7 +59,6 @@ function ParameterSelect({ const valueText = typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; const jccResourceSnap = useSnapshot(jccResourceState); - const { getResourceTypes } = jccResourceSnap; const systemResourceSnap = useSnapshot(systemResourceState); const objectOptions = useMemo(() => { @@ -107,13 +106,15 @@ function ParameterSelect({ setOptions(res); } } else if (dataType === 'remote-resource-type') { - getResourceTypes(); + if (jccResourceSnap.types.length === 0) { + getResourceTypes(); + } } else if (dataType === 'resource') { getSystemResources(); } }; getSelectOptions(); - }, [getOptions, dataType, getResourceTypes]); + }, [getOptions, dataType, getResourceTypes, jccResourceSnap.types]); const selectOptions = ( dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions @@ -122,7 +123,7 @@ function ParameterSelect({ const handleChange = (text: string) => { // 数据集、模型、服务,转换成对象 if (isObjectValue) { - // 设置为 null 是因为 antv g6 的 bug + // 设置为 null 是因为 ant g6 bug // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 const selectValue = text ? valueMap.get(text) : null; if (typeof value === 'object' && value !== null) { diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx index 55e11bf1..43cc54bc 100644 --- a/react-ui/src/components/RightContent/AvatarDropdown.tsx +++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx @@ -13,12 +13,13 @@ import { useEmotionCss } from '@ant-design/use-emotion-css'; import { useModel, useNavigate } from '@umijs/max'; import { Avatar, Spin } from 'antd'; import type { MenuInfo } from 'rc-menu/lib/interface'; -import React, { useCallback } from 'react'; +import React from 'react'; import { flushSync } from 'react-dom'; import HeaderDropdown from '../HeaderDropdown'; export type GlobalHeaderRightProps = { menu?: boolean; + isHome?: boolean; }; const Name = () => { @@ -33,6 +34,7 @@ const Name = () => { lineHeight: '48px', whiteSpace: 'nowrap', textOverflow: 'ellipsis', + color: 'white', [`@media only screen and (max-width: ${token.screenMD}px)`]: { display: 'none', }, @@ -67,12 +69,13 @@ const AvatarLogo = () => { ); }; -const AvatarDropdown: React.FC = ({ menu }) => { +const AvatarDropdown: React.FC = ({ menu, isHome = false }) => { const navigate = useNavigate(); /** * 退出登录,并且将当前的 url 保存 */ const loginOut = async () => { + // const { origin } = location; const [res] = await to(getLabelStudioUrl()); if (res && res.data) { oauthLogout(`${res.data}/oauth/logout`); @@ -86,8 +89,17 @@ const AvatarDropdown: React.FC = ({ menu }) => { if (clientInfo) { const { logoutUri } = clientInfo; location.replace(logoutUri); + // if (isHome) { + // setTimeout(() => { + // location.replace(origin); + // }, 1); + // } } else { - gotoLoginPage(); + if (isHome) { + location.reload(); + } else { + gotoLoginPage(true); + } } }; const actionClassName = useEmotionCss(({ token }) => { @@ -107,20 +119,17 @@ const AvatarDropdown: React.FC = ({ menu }) => { }); const { initialState, setInitialState } = useModel('@@initialState'); - const onMenuClick = useCallback( - (event: MenuInfo) => { - const { key } = event; - if (key === 'logout') { - flushSync(() => { - setInitialState((s) => ({ ...s, currentUser: undefined })); - }); - loginOut(); - return; - } - navigate(`/account/${key}`); - }, - [setInitialState, navigate], - ); + const onMenuClick = (event: MenuInfo) => { + const { key } = event; + if (key === 'logout') { + flushSync(() => { + setInitialState((s) => ({ ...s, currentUser: undefined })); + }); + loginOut(); + return; + } + navigate(`/account/${key}`); + }; const loading = ( diff --git a/react-ui/src/components/RightContent/index.less b/react-ui/src/components/RightContent/index.less index df84cdfa..096e4da4 100644 --- a/react-ui/src/components/RightContent/index.less +++ b/react-ui/src/components/RightContent/index.less @@ -1,10 +1,9 @@ .right-content { display: flex; - gap: 8px; + gap: 10px; align-items: center; - height: 55px; - margin-right: -10px; - padding: 0 16px; - background-color: white; - border-bottom: 1px solid #e9edf0; + height: 60px; + padding: 0 14px; + // background-color: white; + // border-bottom: 1px solid #e9edf0; } diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx index 9b84950a..ede172a5 100644 --- a/react-ui/src/components/RightContent/index.tsx +++ b/react-ui/src/components/RightContent/index.tsx @@ -1,8 +1,8 @@ -import KFIcon from '@/components/KFIcon'; -import { ProBreadcrumb } from '@ant-design/pro-components'; +// import KFIcon from '@/components/KFIcon'; +// import { ProBreadcrumb } from '@ant-design/pro-components'; import { useModel } from '@umijs/max'; -import { Button } from 'antd'; import React from 'react'; +import MessageBroadcast from '../MessageBroadcast'; import Avatar from './AvatarDropdown'; import styles from './index.less'; // import { SelectLang } from '@umijs/max'; @@ -16,12 +16,12 @@ const GlobalHeaderRight: React.FC = () => { return null; } - const handleMenuCollapse = () => { - setInitialState((preInitialState) => ({ - ...preInitialState, - collapsed: !preInitialState?.collapsed, - })); - }; + // const handleMenuCollapse = () => { + // setInitialState((preInitialState) => ({ + // ...preInitialState, + // collapsed: !preInitialState?.collapsed, + // })); + // }; return (
@@ -34,16 +34,19 @@ const GlobalHeaderRight: React.FC = () => { */} - + > */} - + {/* */} + + + {/* */}
); diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 848016f0..1b077b81 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -171,3 +171,29 @@ export enum ComponentType { Map = 'map', Str = 'str', } + +// 消息类型 +export enum MessageType { + System = 1, + Mine = 2, +} + +// 消息状态 +export enum MessageStatus { + All = -1, + UnRead = 1, + Readed = 2, +} + +// 审核状态 +export enum ApprovalStatus { + Pending = 0, + Agree = 1, + Reject = 2, +} + +export const approvalStatusOptions = [ + { label: '待审核', value: ApprovalStatus.Pending }, + { label: '通过', value: ApprovalStatus.Agree }, + { label: '拒绝', value: ApprovalStatus.Reject }, +]; diff --git a/react-ui/src/enums/pagesEnums.ts b/react-ui/src/enums/pagesEnums.ts index d11eb8b5..c8073a8c 100644 --- a/react-ui/src/enums/pagesEnums.ts +++ b/react-ui/src/enums/pagesEnums.ts @@ -1,4 +1,6 @@ export enum PageEnum { + Root = '/', LOGIN = '/user/login', Authorize = '/authorize', + Home = '/home', } diff --git a/react-ui/src/global.less b/react-ui/src/global.less index df79e9f7..c7b3c868 100644 --- a/react-ui/src/global.less +++ b/react-ui/src/global.less @@ -76,7 +76,7 @@ body { } .ant-pro-layout .ant-layout-sider.ant-pro-sider { height: 100vh; - // padding-top: 56px; + padding-top: 60px; } .ant-pro-layout .ant-pro-layout-container { height: 100vh; @@ -137,14 +137,6 @@ ol { } } -.kf-page-container { - height: 100%; - - &__content { - height: calc(100% - 55px); - } -} - .kf-menu-collapsed { position: fixed; top: 0; diff --git a/react-ui/src/global.tsx b/react-ui/src/global.tsx index 2e26173b..8213cbf5 100644 --- a/react-ui/src/global.tsx +++ b/react-ui/src/global.tsx @@ -19,6 +19,30 @@ const clearCache = () => { } }; +const doubleText = () => { + if (process.env.NODE_ENV === 'development') { + document.body.addEventListener( + 'click', + (e) => { + const target = e.target; + if ( + // e.altKey && + e.ctrlKey && + target && + target.innerText && + target.nodeType === Node.ELEMENT_NODE + ) { + e.stopPropagation(); + e.preventDefault(); + const times = 2; + target.innerText = target.innerText.repeat(times); + } + }, + true, + ); + } +}; + // if pwa is true if (pwa) { // Notify user if offline now @@ -89,3 +113,5 @@ if (pwa) { clearCache(); } + +doubleText(); diff --git a/react-ui/src/hooks/useServerTime.ts b/react-ui/src/hooks/useServerTime.ts index a5c6f229..fcf8469d 100644 --- a/react-ui/src/hooks/useServerTime.ts +++ b/react-ui/src/hooks/useServerTime.ts @@ -10,6 +10,7 @@ import { useCallback, useEffect, useState } from 'react'; let globalTimeOffset: number | undefined = undefined; +// 获取服务器时间偏移 export const globalGetSeverTime = async () => { const requestStartTime = Date.now(); const [res] = await to(getSeverTimeReq()); @@ -23,7 +24,8 @@ export const globalGetSeverTime = async () => { } }; -export const now = () => { +// 服务器的当前时间 +export const serverNow = () => { return new Date(Date.now() + (globalTimeOffset ?? 0)); }; diff --git a/react-ui/src/overrides.less b/react-ui/src/overrides.less index e129b4a7..bfdc938d 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -220,35 +220,32 @@ height: 100%; .ant-pro-grid-content-children { height: 100%; - .ant-pro-layout-watermark-wrapper { + .ant-pro-page-container-children-container { height: 100%; - .ant-pro-page-container-children-container { + padding: 0; + .ant-pro-table { + display: flex; + flex-direction: column; height: 100%; - padding: 0; - .ant-pro-table { - display: flex; - flex-direction: column; - height: 100%; - .ant-pro-card.ant-pro-table-search { - flex: none; - height: auto; - } - .ant-pro-card { - flex: 1; - min-height: 0; - .ant-pro-card-body { - height: 100%; - .ant-table-wrapper { - height: calc(100% - 64px); - .ant-spin-nested-loading { + .ant-pro-card.ant-pro-table-search { + flex: none; + height: auto; + } + .ant-pro-card { + flex: 1; + min-height: 0; + .ant-pro-card-body { + height: 100%; + .ant-table-wrapper { + height: calc(100% - 64px - 64px); + .ant-spin-nested-loading { + height: 100%; + .ant-spin-container { height: 100%; - .ant-spin-container { + .ant-table-fixed-header { height: 100%; - .ant-table-fixed-header { + .ant-table-container { height: 100%; - .ant-table-container { - height: 100%; - } } } } diff --git a/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx index d6f45451..4166c245 100644 --- a/react-ui/src/pages/404.tsx +++ b/react-ui/src/pages/404.tsx @@ -1,4 +1,5 @@ import KFEmpty, { EmptyType } from '@/components/KFEmpty'; +import { HomeUrl } from '@/utils/constant'; import { useNavigate } from '@umijs/max'; const NoFoundPage = () => { @@ -12,7 +13,7 @@ const NoFoundPage = () => { content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} hasFooter={true} buttonTitle="返回首页" - onButtonClick={() => navigate('/')} + onButtonClick={() => navigate(HomeUrl)} > ); }; diff --git a/react-ui/src/pages/Authorize/index.tsx b/react-ui/src/pages/Authorize/index.tsx index 9a035ac9..7c5f9fb9 100644 --- a/react-ui/src/pages/Authorize/index.tsx +++ b/react-ui/src/pages/Authorize/index.tsx @@ -1,6 +1,7 @@ import { setSessionToken } from '@/access'; import { loginByOauth2Req } from '@/services/auth'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; import { history, useModel, useSearchParams } from '@umijs/max'; import { message } from 'antd'; import { useCallback, useEffect } from 'react'; @@ -36,7 +37,11 @@ function Authorize() { setSessionToken(access_token, access_token, expires_in); message.success('登录成功!'); await fetchUserInfo(); - history.replace(redirect || '/'); + const redierctUrl = SessionStorage.getItem(SessionStorage.redirectUrl); + console.log('redirect', redirect); + console.log('redierctUrl', redierctUrl); + history.replace(redirect || redierctUrl || '/workspace'); + // SessionStorage.removeItem(SessionStorage.redirectUrl); } }, [fetchUserInfo, redirect, code]); diff --git a/react-ui/src/pages/AutoML/components/CreateForm/utils.ts b/react-ui/src/pages/AutoML/components/CreateForm/utils.ts index 16553178..fa556e2a 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/utils.ts +++ b/react-ui/src/pages/AutoML/components/CreateForm/utils.ts @@ -42,37 +42,44 @@ export const regressorAlgorithms = [ // 特征预处理算法 export const featureAlgorithms = [ - { label: 'densifier (数据增稠)', value: 'densifier' }, + { label: 'densifier (特征变换-数据增稠)', value: 'densifier' }, { - label: 'extra_trees_preproc_for_classification (分类任务极端随机树)', + label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)', value: 'extra_trees_preproc_for_classification', }, { - label: 'extra_trees_preproc_for_regression (回归任务极端随机树)', + label: 'extra_trees_preproc_for_regression (特征选择-回归任务极端随机树)', value: 'extra_trees_preproc_for_regression', }, - { label: 'fast_ica (快速独立成分分析)', value: 'fast_ica' }, - { label: 'feature_agglomeration (特征聚合)', value: 'feature_agglomeration' }, - { label: 'kernel_pca (核主成分分析)', value: 'kernel_pca' }, - { label: 'kitchen_sinks (随机特征映射)', value: 'kitchen_sinks' }, - { label: 'liblinear_svc_preprocessor (线性svc预处理器)', value: 'liblinear_svc_preprocessor' }, + { label: 'fast_ica (特征选择-快速独立成分分析)', value: 'fast_ica' }, + { label: 'feature_agglomeration (特征变换-特征聚合)', value: 'feature_agglomeration' }, + { label: 'kernel_pca (特征选择-核主成分分析)', value: 'kernel_pca' }, + { label: 'kitchen_sinks (特征变换-随机特征映射)', value: 'kitchen_sinks' }, + { + label: 'liblinear_svc_preprocessor (特征选择-线性svc预处理器)', + value: 'liblinear_svc_preprocessor', + }, + { label: 'miss_value_impute (缺失值填充)', value: 'miss_value_impute' }, { label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' }, - { label: 'nystroem_sampler (尼斯特罗姆采样器)', value: 'nystroem_sampler' }, - { label: 'pca (主成分分析)', value: 'pca' }, - { label: 'polynomial (多项式特征扩展)', value: 'polynomial' }, - { label: 'random_trees_embedding (随机森林特征嵌入)', value: 'random_trees_embedding' }, + { label: 'nystroem_sampler (特征变换-尼斯特罗姆采样器)', value: 'nystroem_sampler' }, + { label: 'pca (特征选择-主成分分析)', value: 'pca' }, + { label: 'polynomial (特征变换-多项式特征扩展)', value: 'polynomial' }, + { label: 'random_trees_embedding (特征变换-随机森林特征嵌入)', value: 'random_trees_embedding' }, { - label: 'select_percentile_classification (基于百分位的分类特征选择)', + label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)', value: 'select_percentile_classification', }, { - label: 'select_percentile_regression (基于百分位的回归特征选择)', + label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)', value: 'select_percentile_regression', }, { - label: 'select_rates_classification (基于比率的分类特征选择)', + label: 'select_rates_classification (特征选择-基于比率的分类特征选择)', value: 'select_rates_classification', }, - { label: 'select_rates_regression (基于比率的回归特征选择)', value: 'select_rates_regression' }, - { label: 'truncatedSVD (截断奇异值分解)', value: 'truncatedSVD' }, + { + label: 'select_rates_regression (特征选择-基于比率的回归特征选择)', + value: 'select_rates_regression', + }, + { label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' }, ]; diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx index 8bb68cf9..ea8f9765 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -1,30 +1,8 @@ -import { getAccessToken } from '@/access'; -import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; -import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; +import { CategoryData, DataSource } from '@/pages/Dataset/config'; import { addDataset } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; -import { - getFileListFromEvent, - limitUploadFileType, - removeUploadedFile, - validateUploadFiles, -} from '@/utils/ui'; -import { - Button, - Form, - Input, - Radio, - Select, - Upload, - UploadFile, - message, - type ModalProps, - type UploadProps, -} from 'antd'; -import { omit } from 'lodash'; -import { useState } from 'react'; -import styles from './index.less'; +import { Form, Input, Radio, Select, message, type ModalProps } from 'antd'; interface AddDatasetModalProps extends Omit { typeList: CategoryData[]; @@ -33,20 +11,6 @@ interface AddDatasetModalProps extends Omit { } function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { - const [uuid] = useState(Date.now()); - - // 上传组件参数 - const uploadProps: UploadProps = { - action: resourceConfig[ResourceType.Dataset].uploadAction, - headers: { - Authorization: getAccessToken() || '', - }, - defaultFileList: [], - accept: '.zip,.tgz', - beforeUpload: limitUploadFileType('zip,tgz'), - onRemove: removeUploadedFile, - }; - // 上传请求 const createDataset = async (params: any) => { const [res] = await to(addDataset(params)); @@ -58,22 +22,11 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr // 提交 const onFinish = (formData: any) => { - const fileList: UploadFile[] = formData['fileList'] ?? []; - if (validateUploadFiles(fileList)) { - const params = { - ...omit(formData, ['fileList']), - dataset_source: DataSource.Create, - dataset_version_vos: fileList.map((item) => { - const data = item.response?.data?.[0] ?? {}; - return { - file_name: data.fileName, - file_size: data.fileSize, - url: data.url, - }; - }), - }; - createDataset(params); - } + const params = { + ...formData, + dataset_source: DataSource.Create, + }; + createDataset(params); }; return ( @@ -108,32 +61,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr > - { - if (value === 'master') { - return Promise.reject(`数据集版本不能为 master`); - } else if (value === 'origin') { - return Promise.reject(`数据集版本不能为 origin`); - } - return Promise.resolve(); - }, - }, - ]} - > - - - { - if (value === 'master') { - return Promise.reject(`模型版本不能为 master`); - } else if (value === 'origin') { - return Promise.reject(`模型版本不能为 origin`); - } - return Promise.resolve(); - }, - }, - ]} - > - - + { + resourceType: ResourceType; + resourceVersion: ResourceData; + onOk: () => void; +} + +function EditVersionModal({ resourceType, resourceVersion, onOk, ...rest }: EditVersionModalProps) { + const config = resourceConfig[resourceType]; + const { name: resoureName, version, version_desc } = resourceVersion; + + // 修改请求 + const editDatasetVersion = async (params: any) => { + const request = config.editVersion; + const [res] = await to(request(params)); + if (res) { + message.success('编辑成功'); + onOk?.(); + } + }; + + // 提交 + const onFinish = (formData: any) => { + const params = { + ...resourceVersion, + ...formData, + [config.sourceParamKey]: DataSource.Create, + }; + editDatasetVersion(params); + }; + + const name = config.name; + + return ( + +
+ + + + { + if (value === 'master') { + return Promise.reject(`${name}版本不能为 master`); + } else if (value === 'origin') { + return Promise.reject(`${name}版本不能为 origin`); + } + return Promise.resolve(); + }, + }, + ]} + > + + + + + +
+
+ ); +} + +export default EditVersionModal; diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less index 5bd8b25e..9f85e8f0 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less @@ -28,8 +28,15 @@ border-radius: 4px; } + &__desc { + margin-bottom: 0 !important; + color: @text-color; + font-size: @font-size; + } + &__praise { display: flex; + flex: none; align-items: center; justify-content: center; width: 70px; diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx index 2f162f9b..774fb30d 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -4,6 +4,7 @@ * @Description: 数据集、模型详情 */ +import KFEmpty, { EmptyType } from '@/components/KFEmpty'; import KFIcon from '@/components/KFIcon'; import { ResourceData, @@ -19,10 +20,11 @@ import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { useParams, useSearchParams } from '@umijs/max'; -import { App, Button, Flex, Select, Tabs } from 'antd'; +import { App, Button, Flex, Select, Tabs, Typography } from 'antd'; import classNames from 'classnames'; import { useCallback, useEffect, useState } from 'react'; import AddVersionModal from '../AddVersionModal'; +import EditVersionModal from '../EditVersionModal'; import ResourceIntro from '../ResourceIntro'; import ResourceVersion from '../ResourceVersion'; import VersionCompareModal from '../VersionCompareModal'; @@ -61,21 +63,24 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { const { message } = App.useApp(); // 获取详情 - const getResourceDetail = useCallback(async () => { - const params = { - id: resourceId, - owner, - name, - identifier, - version, - is_public, - }; - const request = config.getInfo; - const [res] = await to(request(params)); - if (res && res.data) { - setInfo(res.data); - } - }, [config, resourceId, owner, name, identifier, version, is_public]); + const getResourceDetail = useCallback( + async (version?: string) => { + const params = { + id: resourceId, + owner, + name, + identifier, + version, + is_public, + }; + const request = config.getInfo; + const [res] = await to(request(params)); + if (res && res.data) { + setInfo(res.data); + } + }, + [config, resourceId, owner, name, identifier, is_public], + ); // 获取版本列表 const getVersionList = useCallback( @@ -100,14 +105,15 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { } } else { setVersion(undefined); + getResourceDetail(undefined); } }, - [config, owner, identifier, versionParam], + [config, owner, identifier, versionParam, getResourceDetail], ); useEffect(() => { if (version) { - getResourceDetail(); + getResourceDetail(version); } }, [version, getResourceDetail]); @@ -116,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { }, [getVersionList]); // 新建版本 - const showModal = () => { + const showAddVersionModal = () => { const { close } = openAntdModal(AddVersionModal, { resourceType: resourceType, resourceId: resourceId, @@ -132,6 +138,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { }); }; + // 版本编辑 + const showEditVersionModal = () => { + const { close } = openAntdModal(EditVersionModal, { + resourceType: resourceType, + resourceVersion: info, + onOk: () => { + getResourceDetail(); + close(); + }, + }); + }; + // 选择版本 const showVersionSelector = () => { const { close } = openAntdModal(VersionSelectorModal, { @@ -205,6 +223,21 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { } }; + // 处理发布 + const handlePublish = async () => { + const request = config.publish; + const params = { + id: resourceId, + owner, + name, + identifier, + }; + const [res] = await to(request(params)); + if (res) { + message.success('操作成功'); + } + }; + const items = [ { key: ResourceInfoTabKeys.Introduction, @@ -264,6 +297,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { {(info[tagPropertyName] as string) || '--'} )} +
{ /> {info.praises_count}
- - - 版本号: - + + + + + ) : ( + + {info.description ?? '暂无描述'} + + )}
- setActiveTab(key)}> -
- {activeTab === ResourceInfoTabKeys.Evolution && } -
+ {version ? ( + <> + setActiveTab(key)}> +
+ {activeTab === ResourceInfoTabKeys.Evolution && } +
+ + ) : ( + + )}
); diff --git a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx index 18fbc3ee..a20c3065 100644 --- a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx @@ -34,6 +34,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { id: info.id, version: info.version, identifier: info.identifier, + owner: info.owner, is_public: info.is_public, }); }; diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index 4799c75c..04e9ee7b 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -9,12 +9,18 @@ import { deleteDatasetVersion, deleteModel, deleteModelVersion, + editDatasetVersion, + editModelVersion, getDatasetInfo, getDatasetList, + getDatasetNextVersionReq, getDatasetVersionList, getModelInfo, getModelList, + getModelNextVersionReq, getModelVersionList, + publishDatasetReq, + publishModelReq, } from '@/services/dataset/index.js'; import { limitUploadFileType } from '@/utils/ui'; import type { TabsProps, UploadFile } from 'antd'; @@ -36,9 +42,12 @@ type ResourceTypeInfo = { getVersions: (params: any) => Promise; // 获取版本列表 deleteRecord: (params: any) => Promise; // 删除 addVersion: (params: any) => Promise; // 新增版本 + editVersion: (params: any) => Promise; // 编辑版本 deleteVersion: (params: any) => Promise; // 删除版本 getInfo: (params: any) => Promise; // 获取详情 compareVersion: (params: any) => Promise; // 版本对比 + getNextVersion: (params: any) => Promise; // 获取下一个版本 + publish: (params: any) => Promise; // 发布 name: string; // 名称 typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 @@ -65,9 +74,12 @@ export const resourceConfig: Record = { getVersions: getDatasetVersionList, deleteRecord: deleteDataset, addVersion: addDatasetVersion, + editVersion: editDatasetVersion, deleteVersion: deleteDatasetVersion, getInfo: getDatasetInfo, compareVersion: compareDatasetVersion, + getNextVersion: getDatasetNextVersionReq, + publish: publishDatasetReq, name: '数据集', typeParamKey: 'data_type', tagParamKey: 'data_tag', @@ -103,9 +115,12 @@ export const resourceConfig: Record = { getVersions: getModelVersionList, deleteRecord: deleteModel, addVersion: addModelVersion, + editVersion: editModelVersion, deleteVersion: deleteModelVersion, getInfo: getModelInfo, compareVersion: compareModelVersion, + getNextVersion: getModelNextVersionReq, + publish: publishModelReq, name: '模型', typeParamKey: 'model_type', tagParamKey: 'model_tag', diff --git a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx index bb9c1b63..80c6d25d 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx @@ -1,7 +1,11 @@ import FormInfo from '@/components/FormInfo'; -import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; +import ParameterSelect, { + type ParameterSelectDataType, + ParameterSelectTypeList, +} from '@/components/ParameterSelect'; import SubAreaTitle from '@/components/SubAreaTitle'; import { ComponentType } from '@/enums'; +import { setCurrentType } from '@/state/jcdResource'; import type { PipelineGlobalParam, PipelineNodeModelParameter, @@ -16,6 +20,12 @@ type ExperimentParameterProps = { }; function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProps) { + // 云际组件,设置 store 当前资源类型 + if (nodeData.id.startsWith('remote-task')) { + const resourceType = nodeData.in_parameters['--resource_type'].value; + setCurrentType(resourceType); + } + // 表单组件 const getFormComponent = ( item: { key: string; value: PipelineNodeModelParameter }, @@ -65,7 +75,7 @@ function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProp )} {item.value.type === ComponentType.Select && - (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( + (ParameterSelectTypeList.includes(item.value.item_type as ParameterSelectDataType) ? ( ) : null)} {item.value.type !== ComponentType.Map && item.value.type !== ComponentType.Select && ( diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx index 60f0555b..a49bf6a6 100644 --- a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx @@ -3,12 +3,10 @@ import KFModal from '@/components/KFModal'; import { DataSource, ResourceType, - ResourceVersionData, resourceConfig, type ResourceData, } from '@/pages/Dataset/config'; import { to } from '@/utils/promise'; -import { InfoCircleOutlined } from '@ant-design/icons'; import { Form, Input, ModalProps, Select } from 'antd'; import { pick } from 'lodash'; import { useEffect, useState } from 'react'; @@ -44,7 +42,6 @@ function ExportModelModal({ }: ExportModelModalProps) { const [form] = Form.useForm(); const [resources, setResources] = useState([]); - const [versions, setVersions] = useState([]); const config = resourceConfig[resourceType]; const layout = { @@ -77,35 +74,24 @@ function ExportModelModal({ return undefined; }; - // 版本 tooltip - const getTooltip = () => { - const id = form.getFieldValue('id'); - const resource = getSelectedResource(id); - const name = resource?.name ?? ''; - const versionNames = versions.map((item: ResourceVersionData) => item.name).join('、'); - const tooltip = - versions.length > 0 ? `${name}有以下版本:\n${versionNames}\n注意不能重复` : undefined; - return tooltip; - }; - // 处理数据集、模型选择变化 const handleResourceChange = (id: number | undefined) => { if (id) { - getRecourceVersions(id); + getRecourceNextVersion(id); } else { - setVersions([]); + form.setFieldValue('version', ''); } }; - // 获取数据集、模型版本列表 - const getRecourceVersions = async (id: number) => { + // 获取数据集、模型下一个版本 + const getRecourceNextVersion = async (id: number) => { const resource = getSelectedResource(id); if (!resource) { return; } - const [res] = await to(config.getVersions(pick(resource, ['identifier', 'owner']))); + const [res] = await to(config.getNextVersion(pick(resource, ['identifier', 'owner']))); if (res && res.data) { - setVersions(res.data); + form.setFieldValue('version', res.data); } }; @@ -184,15 +170,6 @@ function ExportModelModal({ , - } - : undefined - } rules={[ { required: true, message: `请输入${config.name}版本` }, { @@ -205,8 +182,6 @@ function ExportModelModal({ return Promise.reject(`${config.name}版本不能为 master`); } else if (value === 'origin') { return Promise.reject(`${config.name}版本不能为 origin`); - } else if (value && versions.map((item) => item.name).includes(value)) { - return Promise.reject(`${config.name}版本已存在`); } else { return Promise.resolve(); } @@ -214,7 +189,13 @@ function ExportModelModal({ }, ]} > - + void; +}; + +function BlockTitle({ title, onClick, className, style }: BlockTitleProps) { + return ( +
+
{title}
+ +
+ ); +} + +export default BlockTitle; diff --git a/react-ui/src/pages/Home/components/CodeConfig/index.less b/react-ui/src/pages/Home/components/CodeConfig/index.less new file mode 100644 index 00000000..89ee9468 --- /dev/null +++ b/react-ui/src/pages/Home/components/CodeConfig/index.less @@ -0,0 +1,151 @@ +.code { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 4.375rem @home-padding-x 9.375rem; + .backgroundFullImage(url(@/assets/img/home/code-bg.png)); + + &__item { + position: relative; + width: calc((100% - 2 * 1.25rem) / 3); + padding: 1.625rem; + color: @home-text-color-tertiary; + font-size: 0.8125rem; + cursor: pointer; + + &__title { + flex: 1; + color: @home-text-color; + font-size: 1rem; + .singleLine(); + } + + &__arrow { + display: none; + width: 0.875rem; + margin-left: 0.5rem; + } + + &:hover &__arrow { + display: block; + } + + // &__type { + // width: 3.25rem; + // height: 1.375rem; + // color: white; + // font-size: 0.875rem; + // line-height: 1.375rem; + // text-align: center; + // border-radius: 0.75rem; + + // &--public { + // background: linear-gradient(120.77deg, @primary-color 0%, #79ffa7 100%); + // } + + // &--private { + // background: linear-gradient(127.67deg, #ffb716 0%, #e079ff 100%); + // } + // } + + // &:hover &__type--public { + // color: @primary-color; + // background: linear-gradient(120.77deg, #ffffff 0%, #d0ffe0 100%); + // } + + // &:hover &__type--private { + // color: @primary-color; + // background: linear-gradient(127.67deg, #e079ff 0%, #ffb716 100%); + // } + + &__desc { + height: 2.75rem; + margin-bottom: 0.875rem; + color: @home-text-color-secondary; + font-size: 0.875rem; + line-height: 1.375rem; + .multiLine(2); + } + + &__user-avatar { + flex: none; + width: 1.5rem; + height: 1.5rem; + margin-right: 0.875rem; + } + + &__user { + .singleLine(); + } + + &__user-divider { + flex: none; + height: 0.625rem; + margin-right: 0.75rem; + margin-left: 0.75rem; + background-color: @home-divider-color; + } + + &__timestamp-icon { + flex: none; + width: 1rem; + height: 1rem; + margin-right: 0.375rem; + } + + &__timestamp { + flex: none; + } + } + + &__item--first { + background-color: transparent; + border-radius: 1rem; + box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); + .backgroundFullImage(url(@/assets/img/home/code-item-bg.png)); + + &:hover { + .backgroundFullImage(url(@/assets/img/home/code-item-bg-hover.png)); + color: white; + box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); + } + } + + &__item--first &__item__arrow { + display: none !important; + } + + &__second-line { + position: relative; + margin-top: 1.625rem; + background-color: white; + border-radius: 1rem; + box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); + + &__divider { + position: absolute; + top: 1.625rem; + bottom: 1.625rem; + left: calc((100% - 2 * 1.25rem) / 3 + 0.625rem); + border-left: 1px dashed rgba(146, 164, 201, 0.56); + + &&--second { + right: calc((100% - 2 * 1.25rem) / 3 + 0.625rem); + left: auto; + } + } + } + + &__item--first:hover &__item__title { + color: white; + } + + &__item--first:hover &__item__desc { + color: white; + } + + &__item--second:hover &__item__title { + color: @primary-color; + } +} diff --git a/react-ui/src/pages/Home/components/CodeConfig/index.tsx b/react-ui/src/pages/Home/components/CodeConfig/index.tsx new file mode 100644 index 00000000..cf0b6ea2 --- /dev/null +++ b/react-ui/src/pages/Home/components/CodeConfig/index.tsx @@ -0,0 +1,94 @@ +import { CodeConfigData } from '@/components/CodeSelectorModal'; +import { getPublicCodeConfigsReq } from '@/services/home'; +import { getGitUrl } from '@/utils'; +import { formatDate } from '@/utils/date'; +import { to } from '@/utils/promise'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { Divider, Flex } from 'antd'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +import BlockTitle from '../BlockTitle'; +import styles from './index.less'; + +function CodeConfig() { + const [codeCofigs, setCodeConfigs] = useState([]); + + useEffect(() => { + const getPublicCodeConfigs = async () => { + const [res] = await to(getPublicCodeConfigsReq()); + if (res && res.data) { + const { content = [] } = res.data; + setCodeConfigs(content.slice(0, 9)); + } + }; + + getPublicCodeConfigs(); + }, []); + + const createItem = (item: CodeConfigData, className: string) => { + return ( +
{ + const url = getGitUrl(item.git_url, item.git_branch); + window.open(url, '_blank'); + }} + > + +
{item.code_repo_name}
+ +
+
{item.git_url}
+ + +
{item.create_by}
+ + +
{formatDate(item.create_time)}
+
+
+
+ ); + }; + + return ( +
+ gotoPageIfLogin('/dataset/codeConfig')} + > + + {codeCofigs.slice(0, 3).map((item) => createItem(item, styles['code__item--first']))} + + + {codeCofigs.slice(3).map((item) => createItem(item, styles['code__item--second']))} +
+
+
+
+ ); +} + +export default CodeConfig; diff --git a/react-ui/src/pages/Home/components/Dataset/index.less b/react-ui/src/pages/Home/components/Dataset/index.less new file mode 100644 index 00000000..686dfa96 --- /dev/null +++ b/react-ui/src/pages/Home/components/Dataset/index.less @@ -0,0 +1,92 @@ +.dataset { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 @home-padding-x 11.125rem; + + &__item { + position: relative; + width: calc((100% - 3 * 1.25rem) / 4); + padding: 1.625rem; + color: @home-text-color-tertiary; + font-size: 0.8125rem; + background-color: transparent; + border-radius: 1rem; + box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.06); + cursor: pointer; + .backgroundFullImage(url(@/assets/img/home/dataset-item-bg.png)); + + &:nth-child(1), + &:nth-child(2), + &:nth-child(3) { + width: calc((100% - 2 * 1.25rem) / 3); + } + &:hover { + outline: 2px solid @primary-color; + box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); + } + + &__title { + flex: 1; + color: @home-text-color; + font-size: 1rem; + .singleLine(); + } + + &:hover &__title { + color: @primary-color; + } + + &__hot { + width: 3.25rem; + height: 1.375rem; + margin-left: 0.5rem; + color: white; + font-size: 0.875rem; + line-height: 1.375rem; + text-align: center; + background: linear-gradient(127.67deg, @primary-color 0%, #e079ff 100%); + border-radius: 0.6875rem; + } + + &__desc { + height: 2.75rem; + margin-bottom: 0.875rem; + color: @home-text-color-secondary; + font-size: 0.875rem; + line-height: 1.375rem; + .multiLine(2); + } + + &__user-avatar { + flex: none; + width: 1.5rem; + height: 1.5rem; + margin-right: 0.875rem; + } + + &__user { + .singleLine(); + } + + &__user-divider { + flex: none; + height: 0.625rem; + margin-right: 0.75rem; + margin-left: 0.75rem; + background-color: @home-divider-color; + } + + &__timestamp-icon { + flex: none; + width: 1rem; + height: 1rem; + margin-right: 0.375rem; + } + + &__timestamp { + flex: none; + } + } +} diff --git a/react-ui/src/pages/Home/components/Dataset/index.tsx b/react-ui/src/pages/Home/components/Dataset/index.tsx new file mode 100644 index 00000000..9bdd4852 --- /dev/null +++ b/react-ui/src/pages/Home/components/Dataset/index.tsx @@ -0,0 +1,73 @@ +import { DatasetData } from '@/pages/Dataset/config'; +import { getPublicDatasetsReq } from '@/services/home'; +import { to } from '@/utils/promise'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { Divider, Flex } from 'antd'; +import { useEffect, useState } from 'react'; +import BlockTitle from '../BlockTitle'; +import styles from './index.less'; + +function DatasetBlock() { + const [datasetData, setDatasetData] = useState([]); + + useEffect(() => { + const getPublicDatasets = async () => { + const [res] = await to(getPublicDatasetsReq()); + if (res && res.data) { + const { content = [] } = res.data; + setDatasetData(content.slice(0, 7)); + } + }; + + getPublicDatasets(); + }, []); + + return ( +
+ gotoPageIfLogin('/dataset/dataset')} + > + + {datasetData.map((item, index) => { + return ( +
{ + gotoPageIfLogin( + `/dataset/dataset/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`, + ); + }} + > + +
{item.name}
+ {index < 3 &&
HOT
} +
+
{item.description}
+ + +
{item.create_by}
+ + +
{item.time_ago}更新
+
+
+ ); + })} +
+
+ ); +} + +export default DatasetBlock; diff --git a/react-ui/src/pages/Home/components/Footer/index.less b/react-ui/src/pages/Home/components/Footer/index.less new file mode 100644 index 00000000..efb201f8 --- /dev/null +++ b/react-ui/src/pages/Home/components/Footer/index.less @@ -0,0 +1,44 @@ +.footer { + width: 100%; + padding: 4.375rem 15.625rem 1.25rem; + color: .addAlpha(@home-text-color, 0.6) []; + font-size: 0.9375rem; + font-size: 0.75rem; + .backgroundFullImage(url(@/assets/img/home/footer-bg.png)); + + &__app-logo { + width: 1.875rem; + margin-right: 0.625rem; + } + + &__app-name { + margin-right: 5rem; + color: @home-text-color; + font-size: 1.5rem; + font-family: WenYiHei; + } + + &__about-us { + width: 24.75rem; + } + + &__contact { + align-self: start; + } + + &__title { + margin-bottom: 1rem; + color: @home-text-color; + font-size: 0.875rem; + } + + &__desc { + line-height: 1.5rem; + } + + &__copyright { + width: 100%; + color: @home-text-color-secondary; + text-align: center; + } +} diff --git a/react-ui/src/pages/Home/components/Footer/index.tsx b/react-ui/src/pages/Home/components/Footer/index.tsx new file mode 100644 index 00000000..367e253d --- /dev/null +++ b/react-ui/src/pages/Home/components/Footer/index.tsx @@ -0,0 +1,33 @@ +import { Divider, Flex } from 'antd'; +import styles from './index.less'; + +function Footer() { + return ( +
+ + + + 智能材料科研平台 + +
+
关于我们
+
+ 我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容 +
+
+
+
联系我们
+
邮箱:xxxx@163.com
+
地址:湖南省长沙市岳麓区中电软件园
+
+
+ +
© 2025 国防科技大学所有
+
+ ); +} + +export default Footer; diff --git a/react-ui/src/pages/Home/components/Intro/index.less b/react-ui/src/pages/Home/components/Intro/index.less new file mode 100644 index 00000000..6adf597c --- /dev/null +++ b/react-ui/src/pages/Home/components/Intro/index.less @@ -0,0 +1,69 @@ +.intro { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 100; + height: @home-info-height; + overflow: hidden; + background-color: transparent; + background-repeat: no-repeat; + background-position: left top; + background-size: 100% 100%; + + &__content { + position: relative; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + padding: 1.25rem @home-padding-x 0; + // background-image: url(@/assets/img/home/header-bg.png); + background-repeat: no-repeat; + background-position: left top; + background-size: 100% 100%; + } + + &__title { + margin-top: 1.25rem; + margin-bottom: 1.125rem; + color: #ffffff; + font-weight: 400; + font-size: 2.375rem; + font-family: WenYiHei; + } + + &__desc { + width: 54.5rem; + margin-bottom: 1.875rem; + color: #ffffff; + font-size: 1rem; + line-height: 1.75rem; + text-align: center; + } + + &__button { + margin-bottom: 4.375rem; + padding: 0.75rem 2.375rem; + color: #ffffff; + font-size: 1rem; + text-align: center; + background: linear-gradient( + 136.87deg, + rgba(57, 217, 255, 0.51) 0%, + rgba(255, 255, 255, 0.01) 48.54%, + rgba(255, 149, 247, 0.33) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.38); + border-radius: 0.5rem; + cursor: pointer; + + &:hover { + background: linear-gradient( + 108.54deg, + rgba(183, 131, 255, 0.81) 3.72%, + rgba(119, 208, 255, 0.31) 98.01% + ); + } + } +} diff --git a/react-ui/src/pages/Home/components/Intro/index.tsx b/react-ui/src/pages/Home/components/Intro/index.tsx new file mode 100644 index 00000000..7ea944c1 --- /dev/null +++ b/react-ui/src/pages/Home/components/Intro/index.tsx @@ -0,0 +1,81 @@ +import miniHeaderImage from '@/assets/img/home/header-bg-mini.png'; +import headerImage from '@/assets/img/home/header-bg.png'; +import { convertRemToPx } from '@/utils'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { + motion, + useMotionTemplate, + useMotionValueEvent, + useScroll, + useSpring, + useTransform, +} from 'motion/react'; +import { useState } from 'react'; +import NavBar from '../NavBar'; +import StatisticsBlock from '../Statistics'; +import styles from './index.less'; + +function IntroBlock() { + const [backgroundImage1, setBackgroundImage1] = useState(undefined); + const [backgroundImage2, setBackgroundImage2] = useState(headerImage); + const { scrollY } = useScroll(); + const springValue = useSpring(scrollY, { + stiffness: 100, + damping: 30, + restDelta: 0.001, + }); + + const initialHeight = convertRemToPx(35); + const minHeight = convertRemToPx(4.7); + const height = useTransform(() => `max(calc(35rem - ${springValue.get()}px), 4.7rem)`); + const left = useTransform(springValue, [0, initialHeight], [0.0, 16.25]); + const leftRem = useMotionTemplate`${left}rem`; + const radius = useTransform(springValue, [0, initialHeight], [0.0, 2.5]); + const radiusRem = useMotionTemplate`${radius}rem`; + const top = useTransform(springValue, [0, initialHeight], [0, 10]); + const paddingX = useTransform(springValue, [0, initialHeight], [16.25, 10]); + const paddingXRem = useMotionTemplate`1.25rem ${paddingX}rem 0`; + useMotionValueEvent(scrollY, 'change', (value) => { + setBackgroundImage1(value > initialHeight - minHeight ? miniHeaderImage : undefined); + setBackgroundImage2(value > initialHeight - minHeight ? undefined : headerImage); + }); + + return ( + + + +
智能材料科研平台
+
+ 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, + 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 +
+
gotoPageIfLogin('/workspace')}> + 开始使用 +
+ +
+
+ ); +} + +export default IntroBlock; diff --git a/react-ui/src/pages/Home/components/Mirror/index.less b/react-ui/src/pages/Home/components/Mirror/index.less new file mode 100644 index 00000000..a88a2393 --- /dev/null +++ b/react-ui/src/pages/Home/components/Mirror/index.less @@ -0,0 +1,110 @@ +.mirror { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 @home-padding-x 11.125rem; + + &__item { + position: relative; + width: calc((100% - 2 * 1.25rem) / 4); + padding: 1.625rem; + color: @home-text-color-tertiary; + font-size: 0.8125rem; + background-color: transparent; + border-radius: 1.125rem; + box-shadow: 0px 0.1875rem 0.75rem rgba(41, 50, 225, 0.09); + cursor: pointer; + .backgroundFullImage(url(@/assets/img/home/dataset-item-bg.png)); + + &:nth-child(2), + &:nth-child(4) { + width: calc((100% - 2 * 1.25rem) / 2); + } + &:hover { + outline: 2px solid @primary-color; + box-shadow: 0px 0.1875rem 0.75rem rgba(41, 50, 225, 0.09); + } + + &__title { + flex: 1; + margin-right: 0.5rem; + color: @home-text-color; + font-size: 1rem; + .singleLine(); + } + + &:hover &__title { + color: @primary-color; + } + + &__arrow { + display: none; + width: 0.75rem; + height: 0.75rem; + } + + &:hover &__arrow { + display: block; + } + + &__desc { + height: 2.75rem; + margin-bottom: 0.875rem; + color: @home-text-color-secondary; + font-size: 0.875rem; + line-height: 1.375rem; + .multiLine(2); + } + + &__version { + width: fit-content; + margin-bottom: 1.625rem; + padding: 0.375rem 0.625rem; + color: @primary-color; + font-size: 0.875rem; + background: linear-gradient( + 90deg, + .addAlpha(@primary-color, 0.1) [] 0%, + .addAlpha(#c7daff, 0.1) [] 100% + ); + border-radius: 0.25rem; + + &__img { + width: 0.875rem; + height: 0.875rem; + margin-right: 0.375rem; + } + } + + &__user-avatar { + flex: none; + width: 1.5rem; + height: 1.5rem; + margin-right: 0.875rem; + } + + &__user { + .singleLine(); + } + + &__user-divider { + flex: none; + height: 0.625rem; + margin-right: 0.75rem; + margin-left: 0.75rem; + background-color: @home-divider-color; + } + + &__timestamp-icon { + flex: none; + width: 1rem; + height: 1rem; + margin-right: 0.375rem; + } + + &__timestamp { + flex: none; + } + } +} diff --git a/react-ui/src/pages/Home/components/Mirror/index.tsx b/react-ui/src/pages/Home/components/Mirror/index.tsx new file mode 100644 index 00000000..b7fcc7c2 --- /dev/null +++ b/react-ui/src/pages/Home/components/Mirror/index.tsx @@ -0,0 +1,82 @@ +import { MirrorData } from '@/pages/Mirror/List'; +import { getPublicImagesReq } from '@/services/home'; +import { formatDate } from '@/utils/date'; +import { to } from '@/utils/promise'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { Divider, Flex } from 'antd'; +import { useEffect, useState } from 'react'; +import BlockTitle from '../BlockTitle'; +import styles from './index.less'; + +function MirrorBlock() { + const [mirrorData, setMirrirData] = useState([]); + + useEffect(() => { + const getPublicImages = async () => { + const [res] = await to(getPublicImagesReq()); + if (res && res.data) { + const { content = [] } = res.data; + setMirrirData(content.slice(0, 6)); + } + }; + + getPublicImages(); + }, []); + + return ( +
+ gotoPageIfLogin('/dataset/mirror')} + > + + {mirrorData.map((item) => { + return ( +
{ + gotoPageIfLogin(`/dataset/mirror/info/${item.id}`); + }} + > + + +
{item.name}
+ +
+
{item.description}
+ + + {`版本数:${item.version_count}`} + + +
{item.create_by}
+ + +
+ {formatDate(item.create_time)} +
+
+
+
+ ); + })} +
+
+ ); +} + +export default MirrorBlock; diff --git a/react-ui/src/pages/Home/components/Model/index.less b/react-ui/src/pages/Home/components/Model/index.less new file mode 100644 index 00000000..bf2e8ba4 --- /dev/null +++ b/react-ui/src/pages/Home/components/Model/index.less @@ -0,0 +1,113 @@ +.model { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 4.125rem @home-padding-x 14.75rem; + .backgroundFullImage(url(@/assets/img/home/model-bg.png)); + + &__content { + display: flex; + flex-wrap: wrap; + gap: 1.625rem 1.25rem; + align-items: center; + width: 100%; + } + + &__item { + position: relative; + display: flex; + align-items: center; + width: calc((100% - 2 * 1.25rem) / 3); + padding: 1.875rem 1.25rem; + color: @home-text-color-tertiary; + font-size: 0.8125rem; + background-color: transparent; + border-radius: 1rem; + box-shadow: 0px 0.0625rem 0.75rem rgba(33, 73, 212, 0.09); + cursor: pointer; + .backgroundFullImage(url(@/assets/img/home/model-item-bg.png)); + + &:hover { + color: white; + .backgroundFullImage(url(@/assets/img/home/model-item-bg-hover2.png)); + } + + &__hot { + .backgroundFullImage(url(@/assets/img/home/model-item-hot.png)); + position: absolute; + top: 0; + right: 0; + width: 4.625rem; + height: 2rem; + } + + &:hover &__hot { + .backgroundFullImage(url(@/assets/img/home/model-item-hot-hover.png)); + } + + &__user-avatar { + flex: none; + width: 2.75rem; + height: 2.75rem; + margin-right: 0.875rem; + } + + &__title { + margin-bottom: 0.625rem; + color: @home-text-color; + font-size: 1rem; + .singleLine(); + } + + &:hover &__title { + color: white; + } + + &__desc { + height: 2.75rem; + margin-bottom: 0.875rem; + font-size: 0.875rem; + line-height: 1.375rem; + .multiLine(2); + } + + &__user { + .singleLine(); + } + + &__user-divider { + flex: none; + height: 0.625rem; + margin-right: 0.75rem; + margin-left: 0.75rem; + background-color: @home-divider-color; + } + + &__timestamp { + flex: none; + } + + &:hover &__user-divider { + background-color: white; + } + + // &__category { + // padding: 0.25rem 0.625rem; + // color: @primary-color; + // font-size: 0.8125rem; + // background-color: .addAlpha(@primary-color, 0.07) []; + // border-radius: 0.25rem; + + // &:nth-child(2) { + // color: rgba(28, 153, 7, 1); + // background-color: rgba(28, 153, 7, 0.07); + // } + // } + + // &:hover &__category { + // color: @home-text-color; + // background-color: white; + // } + } +} diff --git a/react-ui/src/pages/Home/components/Model/index.tsx b/react-ui/src/pages/Home/components/Model/index.tsx new file mode 100644 index 00000000..1d7c8c6a --- /dev/null +++ b/react-ui/src/pages/Home/components/Model/index.tsx @@ -0,0 +1,108 @@ +import { ModelData } from '@/pages/Dataset/config'; +import { getPublicModelsReq } from '@/services/home'; +import { to } from '@/utils/promise'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { Divider, Flex } from 'antd'; +import { motion, useMotionValueEvent, useScroll, type Variants } from 'motion/react'; +import { useEffect, useState } from 'react'; +import BlockTitle from '../BlockTitle'; +import styles from './index.less'; + +const modelVariants: Variants = { + offscreen: (down: boolean) => ({ + y: 100, + opacity: 0, + transition: { + ease: 'linear', + duration: 0.5, + }, + }), + onscreen: { + y: 0, + opacity: 1, + transition: { + ease: 'easeOut', + duration: 0.5, + // times: [0, 0, 1], + }, + }, +}; + +function ModelBlock() { + const [modelData, setModelData] = useState([]); + const [isDowning, setIsDowning] = useState(true); + const { scrollYProgress } = useScroll(); + useMotionValueEvent(scrollYProgress, 'change', (value) => { + setIsDowning((scrollYProgress.getPrevious() ?? 0) - value < 0); + }); + + useEffect(() => { + console.log(isDowning); + }, [isDowning]); + + useEffect(() => { + const getPublicModels = async () => { + const [res] = await to(getPublicModelsReq()); + if (res && res.data) { + const { content = [] } = res.data; + setModelData(content.slice(0, 6)); + } + }; + + getPublicModels(); + }, []); + + return ( +
+ gotoPageIfLogin('/dataset/model')} + > +
+ {modelData.map((item, index) => { + return ( + { + gotoPageIfLogin( + `/dataset/model/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`, + ); + }} + > + + {index < 3 &&
} +
+
{item.name}
+
{item.description}
+ +
{item.create_by}
+ +
{item.time_ago}更新
+
+
+ {/* +
电池开发
+
材料研发
+
*/} +
+
+ ); + })} +
+
+ ); +} + +export default ModelBlock; diff --git a/react-ui/src/pages/Home/components/NavBar/index.less b/react-ui/src/pages/Home/components/NavBar/index.less new file mode 100644 index 00000000..6641b46a --- /dev/null +++ b/react-ui/src/pages/Home/components/NavBar/index.less @@ -0,0 +1,52 @@ +.nav-bar { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-bottom: 4.375rem; + color: white; + font-size: 0.9375rem; + + &__app-logo { + width: 1.75rem; + margin-right: 0.625rem; + } + + &__app-name { + margin-right: 6.625rem; + font-size: 1.375rem; + font-family: WenYiHei; + line-height: 2.2rem; + } + + &__menu-item { + margin-right: 3.125rem; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; + cursor: pointer; + + &:hover { + background-color: rgba(0, 0, 0, 0.06); + } + + &:last-of-type { + margin-right: 0; + } + } + + :global { + .ant-dropdown-trigger { + height: 2.15rem; + + .ant-avatar { + width: 1.875rem; + height: 1.875rem; + margin-right: 0 !important; + } + } + + .ant-dropdown-trigger > .anticon { + display: none; + } + } +} diff --git a/react-ui/src/pages/Home/components/NavBar/index.tsx b/react-ui/src/pages/Home/components/NavBar/index.tsx new file mode 100644 index 00000000..62a71c0b --- /dev/null +++ b/react-ui/src/pages/Home/components/NavBar/index.tsx @@ -0,0 +1,89 @@ +import { getAccessToken } from '@/access'; +import Avatar from '@/components/RightContent/AvatarDropdown'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { Flex } from 'antd'; +import classNames from 'classnames'; +import styles from './index.less'; + +function NavBar() { + const navigate = useNavigate(); + const token = getAccessToken(); + + const gotoPage = (page: string) => { + if (page === 'login') { + navigate('/user/login'); + return; + } + + let pathname = ''; + switch (page) { + case 'service': + pathname = '/dataset/modelDeployment'; + break; + + case 'model': + pathname = '/dataset/model'; + break; + + case 'dataset': + pathname = '/dataset/dataset'; + break; + + case 'mirror': + pathname = '/dataset/mirror'; + break; + + case 'codeConfig': + pathname = '/dataset/codeConfig'; + break; + + default: + break; + } + + if (pathname) { + gotoPageIfLogin(pathname); + } + }; + + return ( +
+ + + 智能材料科研平台 +
gotoPage('service')}> + 服务 +
+
gotoPage('model')}> + 模型 +
+
gotoPage('dataset')}> + 数据集 +
+
gotoPage('mirror')}> + 镜像 +
+
gotoPage('codeConfig')}> + 代码配置 +
+
+ + {token ? ( + + ) : ( +
gotoPage('login')} + > + 登录 +
+ )} +
+ ); +} + +export default NavBar; diff --git a/react-ui/src/pages/Home/components/Service/index.less b/react-ui/src/pages/Home/components/Service/index.less new file mode 100644 index 00000000..dd83e60b --- /dev/null +++ b/react-ui/src/pages/Home/components/Service/index.less @@ -0,0 +1,75 @@ +.service { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 5.625rem @home-padding-x 6.25rem; + .backgroundFullImage(url(@/assets/img/home/service-bg.png)); + + &__item { + width: 25%; + padding: 1.25rem 1.25rem 1.5625rem; + background: #ffffff; + border-radius: 1.25rem; + box-shadow: 0rem 0rem 0.75rem rgba(33, 73, 212, 0.06); + cursor: pointer; + + &:hover { + box-shadow: 0rem 0rem 0.75rem rgba(33, 73, 212, 0.12); + } + + &__image-container { + width: 18.4375rem; + height: 10.6875rem; + margin-bottom: 0.75rem; + overflow: hidden; + border-radius: 1.875rem; + } + + &__image { + width: 100%; + height: 100%; + transform: scale(1); + transition: transform 0.3s linear; + } + + &:hover &__image { + transform: scale(1.1); + } + + &__title { + height: 2.625rem; + margin-bottom: 0.875rem; + color: @home-text-color; + font-size: 0.9375rem; + .multiLine(2); + } + + &:hover &__title { + color: @primary-color; + } + + &__user-avatar { + flex: none; + width: 1.3125rem; + height: 1.3125rem; + margin-right: 0.5rem; + } + + &__user { + margin-right: 0.5rem; + color: #191919; + font-size: 0.875rem; + .singleLine(); + } + + &__date { + flex: none; + margin-right: 0; + margin-left: auto; + color: @text-color-tertiary; + font-size: 0.8125rem; + } + } +} diff --git a/react-ui/src/pages/Home/components/Service/index.tsx b/react-ui/src/pages/Home/components/Service/index.tsx new file mode 100644 index 00000000..828d0635 --- /dev/null +++ b/react-ui/src/pages/Home/components/Service/index.tsx @@ -0,0 +1,93 @@ +import ServiceImg1 from '@/assets/img/home/service1.png'; +import ServiceImg2 from '@/assets/img/home/service2.png'; +import ServiceImg3 from '@/assets/img/home/service3.png'; +import ServiceImg4 from '@/assets/img/home/service4.png'; +import { type ServiceData } from '@/pages/ModelDeployment/types'; +import { getPublicServicesReq } from '@/services/home'; +import { formatDate } from '@/utils/date'; +import { to } from '@/utils/promise'; +import { gotoPageIfLogin } from '@/utils/ui'; +import { Flex } from 'antd'; +import { motion, type Variants } from 'motion/react'; +import { useEffect, useState } from 'react'; +import BlockTitle from '../BlockTitle'; +import styles from './index.less'; + +const serviceVariants: Variants = { + offscreen: { + y: -200, + opacity: 0, + transition: { + ease: 'linear', + duration: 0, + }, + }, + onscreen: { + y: 0, + opacity: 1, + transition: { + type: 'spring', + duration: 1, + }, + }, +}; + +function ServiceBlock() { + const [serviceData, setServiceData] = useState([]); + const images = [ServiceImg1, ServiceImg2, ServiceImg3, ServiceImg4]; + + useEffect(() => { + const getPublicServices = async () => { + const [res] = await to(getPublicServicesReq()); + if (res && res.data) { + const { content = [] } = res.data; + setServiceData(content.slice(0, 4)); + } + }; + + getPublicServices(); + }, []); + return ( +
+ gotoPageIfLogin('/dataset/modelDeployment')} + > + + {serviceData.map((item, index) => { + return ( + gotoPageIfLogin(`/dataset/modelDeployment/serviceInfo/${item.id}`)} + initial="offscreen" + whileInView="onscreen" + custom={index} + > +
+ +
+
{item.service_name}
+ + +
{item.create_by}
+
{formatDate(item.create_time)}
+
+
+ ); + })} +
+
+ ); +} + +export default ServiceBlock; diff --git a/react-ui/src/pages/Home/components/Statistics/index.less b/react-ui/src/pages/Home/components/Statistics/index.less new file mode 100644 index 00000000..8c6b3369 --- /dev/null +++ b/react-ui/src/pages/Home/components/Statistics/index.less @@ -0,0 +1,32 @@ +.statistics { + display: flex; + align-items: center; + justify-content: space-evenly; + width: 87.5rem; + padding: 2.125rem 0 1.625rem; + .backgroundFullImage(url(@/assets/img/home/statistics-bg.png)); + + &__item { + display: flex; + align-items: center; + width: 9rem; + + &__icon { + width: 3.75rem; + height: 3.75rem; + margin-right: 1rem; + } + + &__count { + color: @home-text-color; + font-size: 2.25rem; + font-family: YouSheBiaoTiHei; + line-height: 3rem; + } + + &__name { + color: @home-text-color-secondary; + font-size: 0.875rem; + } + } +} diff --git a/react-ui/src/pages/Home/components/Statistics/index.tsx b/react-ui/src/pages/Home/components/Statistics/index.tsx new file mode 100644 index 00000000..1357532a --- /dev/null +++ b/react-ui/src/pages/Home/components/Statistics/index.tsx @@ -0,0 +1,105 @@ +import CodeIcon from '@/assets/img/home/code.png'; +import DatasetIcon from '@/assets/img/home/dataset.png'; +import ImageIcon from '@/assets/img/home/image.png'; +import ModelIcon from '@/assets/img/home/model.png'; +import ServiceIcon from '@/assets/img/home/service.png'; +import { getAssetPublicCountReq } from '@/services/home'; +import { to } from '@/utils/promise'; +import { useEffect, useState } from 'react'; +import CountUp from 'react-countup'; +import styles from './index.less'; + +function StatisticsBlock() { + const [assetCounts, setAssetCounts] = useState< + { title: string; value: number | undefined; icon: string }[] + >([ + { + title: '数据集', + value: undefined, + icon: DatasetIcon, + }, + { + title: '模型', + value: undefined, + icon: ModelIcon, + }, + { + title: '镜像', + value: undefined, + icon: ImageIcon, + }, + { + title: '代码配置', + value: undefined, + icon: CodeIcon, + }, + { + title: '服务', + value: undefined, + icon: ServiceIcon, + }, + ]); + useEffect(() => { + const getAssetPublicCount = async () => { + const [res] = await to(getAssetPublicCountReq()); + if (res && res.data) { + const { dataset, image, model, codeConfig, service } = res.data; + const items = [ + { + title: '数据集', + value: dataset, + icon: DatasetIcon, + }, + { + title: '模型', + value: model, + icon: ModelIcon, + }, + { + title: '镜像', + value: image, + icon: ImageIcon, + }, + { + title: '代码配置', + value: codeConfig, + icon: CodeIcon, + }, + { + title: '服务', + value: service, + icon: ServiceIcon, + }, + ]; + setAssetCounts(items); + } + }; + + getAssetPublicCount(); + }, []); + + return ( +
+ {assetCounts.map((item) => { + return ( +
+ +
+
+ {item.value ? ( + + ) : ( + '--' + )} +
+ +
{item.title}
+
+
+ ); + })} +
+ ); +} + +export default StatisticsBlock; diff --git a/react-ui/src/pages/Home/components/ViewMore/index.less b/react-ui/src/pages/Home/components/ViewMore/index.less new file mode 100644 index 00000000..302cec87 --- /dev/null +++ b/react-ui/src/pages/Home/components/ViewMore/index.less @@ -0,0 +1,50 @@ +.view-more { + position: absolute; + right: 16.25rem; + display: flex; + align-items: center; + color: .addAlpha(@home-text-color, 0.7) []; + font-size: 0.9375rem; + cursor: pointer; + transition: color 0.3s; + &:hover { + color: @home-text-color; + } + + &__img-container { + position: relative; + width: 1.125rem; + height: 1.125rem; + margin-left: 0.75rem; + transition: width 0.3s ease-in-out; + } + + &:hover &__img-container { + width: 1.625rem; + } + + &__img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + transition: opacity 0.3s ease-in-out; + + &--first { + opacity: 1; + } + + &--second { + opacity: 0; + } + } + + &:hover &__img--first { + opacity: 0; + } + + &:hover &__img--second { + opacity: 1; + } +} diff --git a/react-ui/src/pages/Home/components/ViewMore/index.tsx b/react-ui/src/pages/Home/components/ViewMore/index.tsx new file mode 100644 index 00000000..b85acafd --- /dev/null +++ b/react-ui/src/pages/Home/components/ViewMore/index.tsx @@ -0,0 +1,31 @@ +import classNames from 'classnames'; +import styles from './index.less'; + +type ViewMoreProps = { + /** 自定义类名 */ + className?: string; + /** 自定义样式 */ + style?: React.CSSProperties; + /** 自定义样式 */ + onClick?: () => void; +}; + +function ViewMore({ className, style, onClick }: ViewMoreProps) { + return ( +
+ 查看更多 +
+ + +
+
+ ); +} + +export default ViewMore; diff --git a/react-ui/src/pages/Home/index.less b/react-ui/src/pages/Home/index.less new file mode 100644 index 00000000..aed372c8 --- /dev/null +++ b/react-ui/src/pages/Home/index.less @@ -0,0 +1,20 @@ +.home { + padding-top: @home-info-height; + font-family: Alibaba; + + &__separator { + position: relative; + z-index: 10; + display: block; + width: 97.5rem; + height: 8.375rem; + margin: -10.75rem auto 0; + } + + &__dataset-mirror { + background-image: url(@/assets/img/home/dataset-bg.png); + background-repeat: no-repeat; + background-position: top 6.25rem left; + background-size: 100% 100%; + } +} diff --git a/react-ui/src/pages/Home/index.tsx b/react-ui/src/pages/Home/index.tsx new file mode 100644 index 00000000..4cfee84c --- /dev/null +++ b/react-ui/src/pages/Home/index.tsx @@ -0,0 +1,31 @@ +import CodeConfig from './components/CodeConfig'; +import DatasetBlock from './components/Dataset'; +import Footer from './components/Footer'; +import IntroBlock from './components/Intro'; +import MirrorBlock from './components/Mirror'; +import ModelBlock from './components/Model'; +import ServiceBlock from './components/Service'; +import styles from './index.less'; + +function Home() { + return ( +
+ + + + +
+ + +
+ +
+
+ ); +} + +export default Home; diff --git a/react-ui/src/pages/Message/components/Content/index.less b/react-ui/src/pages/Message/components/Content/index.less new file mode 100644 index 00000000..b99e753f --- /dev/null +++ b/react-ui/src/pages/Message/components/Content/index.less @@ -0,0 +1,138 @@ +.message-content { + display: flex; + flex: 1; + flex-direction: column; + min-width: 0; + height: 100%; + .backgroundFullImage(url(@/assets/img/message/content-bg.png)); + + &__tabs { + display: flex; + flex: none; + align-items: center; + height: 76px; + padding: 0 30px; + border-bottom: 1px dashed rgba(130, 132, 164, 0.18); + + &__item { + margin-right: 20px; + color: @text-color-secondary; + font-size: @font-size; + + &--selected, + &:hover { + color: @text-color; + font-weight: 500; + } + } + + :global { + .ant-btn:first-of-type { + margin-right: 10px; + margin-left: auto; + } + } + } + + &__check-container { + display: flex; + align-items: center; + margin: 16px 0 10px; + padding-left: 30px; + color: @text-color-secondary; + font-size: @font-size; + + &__count { + margin: 0 2px; + color: @primary-color; + } + } + + &__list { + flex: 1; + width: 100%; + overflow-y: auto; + + &__item { + display: flex; + align-items: center; + width: 100%; + height: 56px; + padding: 0 30px; + color: @text-color; + font-size: @font-size; + + &__status { + flex: none; + margin-right: 10px; + padding: 2px 4px; + font-size: 12px; + border-radius: 4px; + + &--unread { + color: #d7312a; + background-color: rgba(215, 49, 42, 0.07); + } + + &--readed { + color: #2a814b; + background-color: rgba(42, 129, 75, 0.07); + } + } + + &__content { + flex: 1; + margin-right: 10px; + } + + &__time { + display: block; + flex: none; + margin-left: auto; + color: @text-color-secondary; + } + + &__button { + display: none; + flex: none; + padding-right: 0; + padding-left: 0; + color: @primary-color-hover; + font-size: @font-size; + + &:hover { + color: @primary-color !important; + } + + &:first-of-type { + margin-right: 10px; + margin-left: auto; + } + } + + &:hover { + color: @primary-color; + background-color: .addAlpha(@primary-color, 0.05) []; + } + + &:hover &__button { + display: block; + } + + &:hover &__time { + display: none; + } + } + } + + :global { + .ant-pagination { + margin-right: 30px; + margin-bottom: 40px; + } + } + + &__empty { + flex: 1; + } +} diff --git a/react-ui/src/pages/Message/components/Content/index.tsx b/react-ui/src/pages/Message/components/Content/index.tsx new file mode 100644 index 00000000..8b157ff9 --- /dev/null +++ b/react-ui/src/pages/Message/components/Content/index.tsx @@ -0,0 +1,374 @@ +import KFButton from '@/components/KFButton'; +import KFEmpty, { EmptyType } from '@/components/KFEmpty'; +import { MessageStatus, MessageType } from '@/enums'; +import { useCacheState } from '@/hooks/useCacheState'; +import { useCheck } from '@/hooks/useCheck'; +import { Message, MessageResponse } from '@/pages/Message'; +import { deleteMessagesReq, getMessageListReq, readMessagesReq } from '@/services/message'; +import { ago } from '@/utils/date'; +import { to } from '@/utils/promise'; +import { modalConfirm } from '@/utils/ui'; +import { useModel, useNavigate } from '@umijs/max'; +import { + Button, + Checkbox, + Pagination, + PaginationProps, + Typography, + message, + type TablePaginationConfig, +} from 'antd'; +import classNames from 'classnames'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import styles from './index.less'; + +export type MessageContentProps = { + messageType: MessageType; + messageStatus: MessageStatus; + pagination: TablePaginationConfig; + onStatusChange: (status: MessageStatus) => void; + onPaginationChange: (pagination: TablePaginationConfig) => void; +}; + +function MessageContent({ + messageType, + messageStatus, + pagination, + onStatusChange, + onPaginationChange, +}: MessageContentProps) { + const { initialState } = useModel('@@initialState'); + const { currentUser } = initialState || {}; + const { userId } = currentUser || {}; + const setCacheState = useCacheState()[1]; + const [messages, setMessages] = useState(undefined); + const [allTotal, setAllTotal] = useState(0); + const [unreadTotal, setUnreadTotal] = useState(undefined); + const [isDelete, setIsDelete] = useState(false); + const messageIds = useMemo(() => messages?.map((v) => v.id), [messages]); + const [ + selectedMessages, + setSelectedMessages, + messagesAllChecked, + messagesIndeterminate, + checkAllMessages, + isSingleMessagesChecked, + checkSingleMessages, + ] = useCheck(messageIds ?? []); + const navigate = useNavigate(); + + const tabs = useMemo( + () => [ + { + title: '未读', + status: MessageStatus.UnRead, + total: unreadTotal, + }, + { + title: '全部', + status: MessageStatus.All, + }, + ], + [unreadTotal], + ); + + const getMessages = useCallback(async () => { + if (!userId) return; + + const params: Record = { + receiver: userId, + status: messageStatus, + type: messageType, + page: pagination.current, + size: pagination.pageSize, + }; + const [res] = await to(getMessageListReq(params)); + if (res && res.data) { + const { records, records_count, unread_notification, unread_atme } = + res.data as MessageResponse; + setMessages(records); + setAllTotal(records_count); + setUnreadTotal(messageType === MessageType.System ? unread_notification : unread_atme); + } + }, [pagination, userId, messageStatus, messageType]); + + // 标记为已读 + const readMessages = async ( + message?: Message, + skipLoading: boolean = false, + skipResult: boolean = false, + ) => { + const params: Record = message + ? { + notificationIds: message.id, + status: MessageStatus.Readed, + receiver: message.receiver, + type: message.type, + } + : { + notificationIds: -1, + status: MessageStatus.Readed, + receiver: userId, + type: messageType, + }; + const [res] = await to(readMessagesReq(params, skipLoading)); + + // 点击消息置为已读时,不需要修改数据 + if (!skipResult && res) { + // 如果当前是【未读】状态 + // 【一键已读】后,设置分页为第一页 + // 如果是一页的唯一数据,设置为前一页 + if (messageStatus === MessageStatus.UnRead) { + onPaginationChange({ + ...pagination, + current: message + ? messages?.length === 1 + ? Math.max(1, pagination.current! - 1) + : pagination.current + : 1, + }); + } + } else { + getMessages(); + } + }; + + // 删除 + const deleteMessages = async (ids: number[]) => { + if (ids.length <= 0) { + message.error('请选择要删除的消息'); + return; + } + + const params: Record = { + notificationIds: ids.join(','), + receiver: userId, + type: messageType, + }; + const [res] = await to(deleteMessagesReq(params)); + if (res) { + cancelBatchDelete(); + // 如果是一页的唯一数据,删除后,请求前一页的数据 + // 否则直接刷新这一页的数据 + onPaginationChange({ + ...pagination, + current: + ids.length === messages?.length + ? Math.max(1, pagination.current! - 1) + : pagination.current, + }); + } + }; + + // 取消批量删除 + const cancelBatchDelete = useCallback(() => { + setIsDelete(false); + setSelectedMessages([]); + }, [setSelectedMessages]); + + useEffect(() => { + getMessages(); + }, [getMessages]); + + // 重置批量删除状态、分页 + useEffect(() => { + cancelBatchDelete(); + }, [messageType, messageStatus, cancelBatchDelete]); + + // 批量删除 + const handleBatchDelete = () => { + if (selectedMessages.length <= 0) { + message.error('请选择要删除的消息'); + return; + } + + modalConfirm({ + title: '删除后,消息不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteMessages(selectedMessages); + }, + }); + }; + + // 点击消息 + const hanldeMessageClick = (message: Message) => { + if (message.status === MessageStatus.UnRead) { + readMessages(message, true, true); + } + + if (message.notification_url) { + navigate(message.notification_url); + setCacheState({ + messageType, + pagination, + messageStatus, + }); + } + }; + + // 分页切换 + const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { + onPaginationChange({ + current: page, + pageSize: pageSize, + }); + }; + + return ( +
+
+ {tabs.map((item) => ( +
{ + onStatusChange(item.status); + }} + > + {item.title + (item.total !== undefined ? `(${item.total})` : '')} +
+ ))} + + {isDelete ? ( + <> + + 取消 + + + 删除 + + + ) : ( + <> + {messageType === MessageType.Mine && allTotal > 0 && ( + setIsDelete(true)}> + 批量删除 + + )} + {allTotal > 0 && ( + readMessages()}> + 一键已读 + + )} + + )} +
+ + {isDelete && ( +
+ + 全选 + + + 已选 + + {selectedMessages.length} + + 项 + +
+ )} + + {messages && messages.length > 0 && ( + <> +
+ {messages.map((message) => ( +
hanldeMessageClick(message)} + > + {messageType === MessageType.Mine && isDelete && ( + { + checkSingleMessages(message.id); + }} + onClick={(e) => e.stopPropagation()} + > + )} + {messageStatus === MessageStatus.All && ( +
+ {message.status === MessageStatus.UnRead ? '未读' : '已读'} +
+ )} + /g, '') }} + > + + + +
+ {ago(message.created_at)} +
+ {message.status === MessageStatus.UnRead && ( + + )} + {messageType === MessageType.Mine && ( + + )} +
+ ))} +
+ + + )} + {messages && messages.length === 0 && ( + + )} +
+ ); +} + +export default MessageContent; diff --git a/react-ui/src/pages/Message/components/Menu/index.less b/react-ui/src/pages/Message/components/Menu/index.less new file mode 100644 index 00000000..dbfba954 --- /dev/null +++ b/react-ui/src/pages/Message/components/Menu/index.less @@ -0,0 +1,69 @@ +.message-menu { + flex: none; + width: 196px; + height: 100%; + .backgroundFullImage(url(@/assets/img/message/menu-bg.png)); + + &__title { + position: relative; + margin-bottom: 25px; + padding: 20px 20px 10px; + color: @text-color; + font-size: @font-size; + + &::after { + position: absolute; + right: 20px; + bottom: 0; + left: 20px; + border-bottom: 1px dashed rgba(130, 132, 164, 0.18); + content: ''; + } + } + + &__item { + display: flex; + align-items: center; + margin-bottom: 4px; + padding: 10px 0 10px 18px; + color: @text-color-secondary; + font-size: @font-size; + border-left: 2px solid transparent; + + &--selected, + &:hover { + color: @primary-color; + background-image: linear-gradient( + 101.08deg, + rgba(81, 76, 249, 0.09) 0%, + rgba(255, 255, 255, 0) 100% + ); + border-left: 2px solid @primary-color; + } + + &__icon, + &__icon--hover { + width: 18px; + height: 18px; + margin-right: 10px; + } + + &__icon { + display: block; + } + + &__icon--hover { + display: none; + } + + &--selected &__icon, + &__item:hover &__icon { + display: none; + } + + &--selected &__icon--hover, + &__item:hover &__icon--hover { + display: block; + } + } +} diff --git a/react-ui/src/pages/Message/components/Menu/index.tsx b/react-ui/src/pages/Message/components/Menu/index.tsx new file mode 100644 index 00000000..3f5b0513 --- /dev/null +++ b/react-ui/src/pages/Message/components/Menu/index.tsx @@ -0,0 +1,50 @@ +import AtHoverIcon from '@/assets/img/message/at-hover.png'; +import AtIcon from '@/assets/img/message/at.png'; +import SystemHoverIcon from '@/assets/img/message/system-hover.png'; +import SystemIcon from '@/assets/img/message/system.png'; +import { MessageType } from '@/enums'; +import classNames from 'classnames'; +import styles from './index.less'; + +const menus = [ + { + title: '系统消息', + icon: SystemIcon, + hoverIcon: SystemHoverIcon, + type: MessageType.System, + }, + { + title: '@我的', + icon: AtIcon, + hoverIcon: AtHoverIcon, + type: MessageType.Mine, + }, +]; + +export type MessageMenuProps = { + messageType: MessageType; + onTypeChange: (type: MessageType) => void; +}; + +function MessageMenu({ messageType: currentType, onTypeChange }: MessageMenuProps) { + return ( +
+
消息列表
+ {menus.map((item) => ( +
onTypeChange(item.type)} + > + + + {item.title} +
+ ))} +
+ ); +} + +export default MessageMenu; diff --git a/react-ui/src/pages/Message/index.less b/react-ui/src/pages/Message/index.less new file mode 100644 index 00000000..745c538c --- /dev/null +++ b/react-ui/src/pages/Message/index.less @@ -0,0 +1,8 @@ +.message { + display: flex; + flex-direction: row; + gap: 0 20px; + height: 100%; + padding: 30px 60px 30px; + .backgroundFullImage(url(@/assets/img/message/message-bg.png)); +} diff --git a/react-ui/src/pages/Message/index.tsx b/react-ui/src/pages/Message/index.tsx new file mode 100644 index 00000000..aba242cd --- /dev/null +++ b/react-ui/src/pages/Message/index.tsx @@ -0,0 +1,81 @@ +import { MessageStatus, MessageType } from '@/enums'; +import { useCacheState } from '@/hooks/useCacheState'; +import { type TablePaginationConfig } from 'antd'; +import { useState } from 'react'; +import MessageContent from './components/Content'; +import MessageMenu from './components/Menu'; +import styles from './index.less'; + +// 消息列表接口返回类型 +export interface MessageResponse { + receiver: number; + type: MessageType; + unread_total: number; + unread_notification: number; + unread_atme: number; + records: Message[]; + records_count: number; + page_num: number; + total_page_count: number; + page_size: number; +} + +// 消息数据 +export interface Message { + id: number; + sender: number; + receiver: number; + content: string; + status: MessageStatus; + type: MessageType; + source: string; + extra: string; + notification_url: string; + created_at: Date; +} + +function MessagePage() { + const [cacheState] = useCacheState(); + const [messageType, setMessageType] = useState(cacheState?.messageType ?? MessageType.System); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 20, + }, + ); + const [messageStatus, setMessageStatus] = useState( + cacheState?.messageStatus ?? MessageStatus.UnRead, + ); + + // 重置页面为第一页 + const resetToFirstPage = () => { + setPagination((prev) => ({ + ...prev, + current: 1, + })); + }; + + return ( +
+ { + setMessageType(type); + resetToFirstPage(); + }} + messageType={messageType} + > + { + setMessageStatus(status); + resetToFirstPage(); + }} + pagination={pagination} + onPaginationChange={setPagination} + > +
+ ); +} + +export default MessagePage; diff --git a/react-ui/src/pages/Mirror/List/index.tsx b/react-ui/src/pages/Mirror/List/index.tsx index 0671261b..7dd57234 100644 --- a/react-ui/src/pages/Mirror/List/index.tsx +++ b/react-ui/src/pages/Mirror/List/index.tsx @@ -47,6 +47,8 @@ export type MirrorData = { name: string; description: string; create_time: string; + create_by: string; + version_count: number; }; function MirrorList() { diff --git a/react-ui/src/pages/Mixed/index.less b/react-ui/src/pages/Mixed/index.less new file mode 100644 index 00000000..911ec33c --- /dev/null +++ b/react-ui/src/pages/Mixed/index.less @@ -0,0 +1,3 @@ +.mixed { + height: 100%; +} diff --git a/react-ui/src/pages/Mixed/index.tsx b/react-ui/src/pages/Mixed/index.tsx new file mode 100644 index 00000000..c9d657d9 --- /dev/null +++ b/react-ui/src/pages/Mixed/index.tsx @@ -0,0 +1,25 @@ +import { MicroAppWithMemoHistory } from '@umijs/max'; +import { Tabs } from 'antd'; +import styles from './index.less'; + +const Docs = () => { + const mirrorTabItems = [ + { + key: '1', + label: '父页面', + children:
Parent
, + }, + { + key: '2', + label: '子页面', + children: , + }, + ]; + + return ( +
+ +
+ ); +}; +export default Docs; diff --git a/react-ui/src/pages/ModelDeployment/types.ts b/react-ui/src/pages/ModelDeployment/types.ts index af1fdcc9..d333d9f2 100644 --- a/react-ui/src/pages/ModelDeployment/types.ts +++ b/react-ui/src/pages/ModelDeployment/types.ts @@ -8,7 +8,7 @@ export type ServiceData = { service_type_name: string; // 服务类型中文 description: string; // 描述 version_count: number; // 版本数量 - created_by: string; + create_by: string; create_time: string; update_by: string; update_time: string; diff --git a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx index 0ceee5d5..3b0baf90 100644 --- a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx @@ -4,6 +4,7 @@ import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; import ParameterSelect, { type ParameterSelectDataType, type ParameterSelectObject, + ParameterSelectTypeList, } from '@/components/ParameterSelect'; import ResourceSelectorModal, { ResourceSelectorType, @@ -12,19 +13,19 @@ import ResourceSelectorModal, { import SubAreaTitle from '@/components/SubAreaTitle'; import { CommonTabKeys, ComponentType } from '@/enums'; import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; -import state from '@/state/jcdResource'; +import { setCurrentType } from '@/state/jcdResource'; import { PipelineGlobalParam, PipelineNodeModel, PipelineNodeModelParameter, PipelineNodeModelSerialize, } from '@/types'; +import { undefinedToNull } from '@/utils'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { removeFormListItem } from '@/utils/ui'; import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { INode } from '@antv/g6'; -import { useSnapshot } from '@umijs/max'; import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd'; import { RuleObject } from 'antd/es/form'; import { NamePath } from 'antd/es/form/interface'; @@ -43,17 +44,6 @@ type PipelineNodeParameterProps = { onFormChange: (data: PipelineNodeModelSerialize) => void; }; -// 自定义的下拉组件类型 -const parameterSelectList = [ - 'dataset', - 'model', - 'service', - 'resource', - 'remote-resource-type', - 'remote-image', - 'remote-resource', -]; - const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParameterProps, ref) => { const [form] = Form.useForm(); const [stagingItem, setStagingItem] = useState( @@ -61,8 +51,6 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete ); const [open, setOpen] = useState(false); const [menuItems, setMenuItems] = useState([]); - const snap = useSnapshot(state); - const { setCurrentType } = snap; const afterOpenChange = async () => { if (!open) { @@ -102,8 +90,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete out_parameters: out_parameters, formError: !!error, }; - console.log('res', res); - onFormChange(res); + + // ant g6 bug + // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 + const convertRes = undefinedToNull(res); + console.log('res', convertRes); + onFormChange(convertRes as PipelineNodeModelSerialize); } }; const onClose = () => { @@ -160,7 +152,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete } }, }), - [form, open, setCurrentType], + [form, open], ); // ref 类型选择 @@ -364,7 +356,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete parentName: string, ) => { if (itemType === 'remote-resource-type') { - snap.setCurrentType(value.value); + setCurrentType(value.value); const remoteImage = form.getFieldValue([parentName, '--image']); form.setFieldValue([parentName, '--image'], { ...remoteImage, value: undefined }); const remoteResource = form.getFieldValue([parentName, '--resource']); @@ -402,7 +394,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete )} {item.value.type === ComponentType.Select && - (parameterSelectList.includes(item.value.item_type) ? ( + (ParameterSelectTypeList.includes(item.value.item_type as ParameterSelectDataType) ? ( { + record: ApprovalData; + onOk: () => void; +} + +function ApprovalModal({ record, onOk, ...rest }: ApprovalModalProps) { + const [form] = Form.useForm(); + const content = parseJsonText(record.content); + const recordTypeName = record.type === ApprovalType.Dataset ? '数据集' : '模型'; + + const items = + record.type === ApprovalType.Dataset + ? [ + { + label: '数据集名称', + value: content.name, + }, + { + label: '数据集分类', + value: content.dataType, + }, + { + label: '研究方向', + value: content.dataTag, + }, + { + label: '数据集描述', + value: content.description, + }, + ] + : [ + { + label: '模型名称', + value: content.name, + }, + { + label: '模型框架', + value: content.model_type, + }, + { + label: '模型能力', + value: content.model_tag, + }, + { + label: '模型描述', + value: content.description, + }, + ]; + + // 审批通过 + const agreeApproval = async (remark?: string) => { + const [res] = await to( + agreeApprovalReq({ + id: record.id, + result: remark, + }), + ); + if (res) { + onOk?.(); + } + }; + + // 审批拒绝 + const rejectApproval = async (remark: string) => { + const [res] = await to( + rejectApprovalReq({ + id: record.id, + result: remark, + }), + ); + if (res) { + onOk?.(); + } + }; + + const handleAgree = () => { + const remark = form.getFieldValue('remark') as string | undefined; + const remarkTrim = remark?.trim(); + modalConfirm({ + isDelete: false, + title: `审批通过后,将发布该${recordTypeName}`, + content: '是否确认通过?', + onOk: () => { + agreeApproval(remarkTrim); + }, + }); + }; + + const handleReject = () => { + const remark = form.getFieldValue('remark') as string | undefined; + const remarkTrim = remark?.trim(); + if (!remarkTrim) { + message.error('请输入审批意见'); + return; + } + + modalConfirm({ + isDelete: false, + title: `审批拒绝后,将不发布该${recordTypeName}`, + content: '是否确认拒绝?', + onOk: () => { + rejectApproval(remarkTrim); + }, + }); + }; + + return ( + + 审批通过 + , + , + ]} + > + + +
+ + + +
+
+ ); +} + +export default ApprovalModal; diff --git a/react-ui/src/pages/System/Approval/components/StatusCell/index.less b/react-ui/src/pages/System/Approval/components/StatusCell/index.less new file mode 100644 index 00000000..17a5e5ef --- /dev/null +++ b/react-ui/src/pages/System/Approval/components/StatusCell/index.less @@ -0,0 +1,15 @@ +.status-cell { + color: @text-color; + + &--agree { + color: @success-color; + } + + &--reject { + color: @error-color; + } + + &--pending { + color: @text-color; + } +} diff --git a/react-ui/src/pages/System/Approval/components/StatusCell/index.tsx b/react-ui/src/pages/System/Approval/components/StatusCell/index.tsx new file mode 100644 index 00000000..23adf66e --- /dev/null +++ b/react-ui/src/pages/System/Approval/components/StatusCell/index.tsx @@ -0,0 +1,36 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-18 18:35:41 + * @Description: 编辑器状态组件 + */ +import { ApprovalStatus } from '@/enums'; +import styles from './index.less'; + +export type DevEditorStatusInfo = { + text: string; + classname: string; +}; + +export const statusInfo: Record = { + [ApprovalStatus.Pending]: { + classname: styles['status-cell--pending'], + text: '待审核', + }, + [ApprovalStatus.Agree]: { + classname: styles['status-cell--agree'], + text: '通过', + }, + [ApprovalStatus.Reject]: { + classname: styles['status-cell--reject'], + text: '已拒绝', + }, +}; + +function StatusCell(status?: ApprovalStatus | null) { + if (status === null || status === undefined || !statusInfo[status]) { + return --; + } + return {statusInfo[status].text}; +} + +export default StatusCell; diff --git a/react-ui/src/pages/System/Approval/index.less b/react-ui/src/pages/System/Approval/index.less new file mode 100644 index 00000000..16adf456 --- /dev/null +++ b/react-ui/src/pages/System/Approval/index.less @@ -0,0 +1,29 @@ +.approval-list { + height: 100%; + &__header { + display: flex; + align-items: center; + justify-content: space-between; + height: 50px; + margin-bottom: 10px; + padding: 0 30px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + &__content { + height: calc(100% - 60px); + padding: 20px 30px 0; + background-color: white; + border-radius: 10px; + } + + &__table { + height: calc(100% - 32px - 28px); + margin-top: 28px; + background-color: white; + border-radius: 10px; + } +} diff --git a/react-ui/src/pages/System/Approval/index.tsx b/react-ui/src/pages/System/Approval/index.tsx new file mode 100644 index 00000000..47dcd88c --- /dev/null +++ b/react-ui/src/pages/System/Approval/index.tsx @@ -0,0 +1,210 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 审核列表 + */ + +import { ApprovalStatus, approvalStatusOptions } from '@/enums'; +import { getApprovalListReq } from '@/services/message'; +import { openAntdModal } from '@/utils/modal'; +import { to } from '@/utils/promise'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { + Button, + Select, + Table, + Typography, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import classNames from 'classnames'; +import { useCallback, useEffect, useState } from 'react'; +import ApprovalModal from './components/ApprovalModal'; +import StatusCell from './components/StatusCell'; +import styles from './index.less'; + +export interface ApprovalData { + id: number; + status: number; + result: null; + content: string; + applicant_id: number; + applicant_name: null; + applicant_time: Date; + approver_id: number; + approver_time: Date; + title: string; + type: ApprovalType; + url: string; +} + +export enum ApprovalType { + Dataset = 'DATASET', + Model = 'MODEL', +} + +const approvalTypeOptions = [ + { label: '数据集', value: ApprovalType.Dataset }, + { label: '模型', value: ApprovalType.Model }, +]; + +const statusOptions = [{ label: '全部', value: '' }, ...approvalStatusOptions]; + +function ApprovalList() { + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [status, setStatus] = useState(''); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + }); + + // 获取审核列表 + const getApprovalList = useCallback(async () => { + const params: Record = { + current: pagination.current, + pageSize: pagination.pageSize, + status: status, + }; + const [res] = await to(getApprovalListReq(params)); + if (res) { + const { rows = [], total = 0 } = res; + setTableData(rows); + setTotal(total); + } + }, [pagination, status]); + + useEffect(() => { + getApprovalList(); + }, [getApprovalList]); + + // 审核 + const approval = (record: ApprovalData) => { + const { close } = openAntdModal(ApprovalModal, { + record: record, + onOk: () => { + close(); + getApprovalList(); + }, + }); + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '内容', + dataIndex: 'title', + key: 'title', + render: (title) => ( + /g, '') }} + > + + + ), + }, + { + title: '类型', + dataIndex: 'type', + key: 'type', + width: 100, + render: tableCellRender(true, TableCellValueType.Enum, { + options: approvalTypeOptions, + }), + }, + { + title: '申请者', + dataIndex: 'applicant_name', + key: 'applicant_name', + width: 180, + render: tableCellRender(true), + }, + { + title: '申请时间', + dataIndex: 'applicant_time', + key: 'applicant_time', + width: 180, + render: tableCellRender(true, TableCellValueType.Date), + }, + { + title: '审核意见', + dataIndex: 'result', + key: 'result', + width: 200, + render: tableCellRender(true), + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: StatusCell, + }, + { + title: '操作', + dataIndex: 'operation', + width: 150, + key: 'operation', + render: (_: any, record: ApprovalData) => ( +
+ {record.status === ApprovalStatus.Pending ? ( + + ) : null} +
+ ), + }, + ]; + + return ( +
+
+
审核管理
+
+
+
+ 状态: + +
+
+ `共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + + ); +} + +export default ApprovalList; diff --git a/react-ui/src/pages/System/Menu/index.tsx b/react-ui/src/pages/System/Menu/index.tsx index 7f58fad0..de3bfeb4 100644 --- a/react-ui/src/pages/System/Menu/index.tsx +++ b/react-ui/src/pages/System/Menu/index.tsx @@ -294,7 +294,7 @@ const MenuTableList: React.FC = () => { setMenuTree(treeData); return { data: memuData, - total: res.data.length, + total: undefined, success: true, }; }) diff --git a/react-ui/src/pages/System/Role/index.tsx b/react-ui/src/pages/System/Role/index.tsx index c7a4e9cf..23215104 100644 --- a/react-ui/src/pages/System/Role/index.tsx +++ b/react-ui/src/pages/System/Role/index.tsx @@ -254,7 +254,7 @@ const RoleTableList: React.FC = () => { { title: , dataIndex: 'option', - width: '220px', + width: '240px', valueType: 'option', render: (_, record) => [