基本界面

前端基本架构
main
LiRen-qiu 5 months ago
parent 1018773b14
commit b15d3f1428

@ -0,0 +1,159 @@
Vue 2 前端界面设计构想 (SVG/Excel 数据处理)
一、 核心设计理念
清晰分步: 将复杂的 SVG/Excel 数据处理流程分解为四个明确、独立的步骤(格式合并、单词纠错、大模型分析、统计结果),降低用户的认知负荷。
用户引导: 通过可视化的步骤指示器 (Stepper) 和明确的导航按钮,引导用户按顺序完成任务。
关注点分离: 将文件上传、数据查看/编辑、逐步处理这几个核心功能在界面布局上进行区分。
大数据友好: 考虑到可能处理大数据量,将数据查看和编辑功能与主处理流程适当分离(强烈推荐使用模态框),避免主界面卡顿。
简洁直观: 界面元素和交互设计力求简洁明了,符合初学者的使用习惯。
二、 界面整体布局与结构
采用上下两层的主体布局:
1. 上层区域:文件上传与准备
目的: 负责接收用户输入的文件。
内容:
一个醒目的文件上传控件:
支持拖拽和点击选择文件。
明确标示支持的文件类型 (SVG, Excel)。
上传过程中的视觉反馈:
进度条。
成功/失败提示。
上传成功后显示文件基本信息:
文件名、大小、类型等。
一个关键的 “查看/编辑文件内容” 按钮 (文件上传成功后激活)。
2. 下层区域:多步骤处理流程
目的: 引导用户完成四个核心处理步骤,并展示每步结果。
内容:
步骤指示器 (Stepper):
位于该区域的最上方。
可视化展示四个步骤(例如:① 格式合并 → ② 单词纠错 → ③ 大模型分析 → ④ 统计结果)。
高亮显示当前活动步骤和已完成步骤。
当前步骤内容区:
下层区域的主体。
每次只显示一个步骤的相关内容。
根据 Stepper 的状态动态切换。
导航按钮:
通常在当前步骤内容区的底部。
包含“下一步”按钮。
最后一步为“完成”或“导出结果”按钮。
三、 关键组件与交互细节
1. 文件上传组件
提供清晰的拖拽区域和“选择文件”按钮。
上传中、上传成功、上传失败都要有明确的视觉提示。
2. 数据查看/编辑组件 (核心交互点)
触发方式: 点击上层区域文件上传成功后的 “查看/编辑文件内容” 按钮。
实现方式: 强烈推荐使用“模态框 (Modal Dialog)”。
优点: 保持用户在主流程页面内,提供连贯体验,技术上状态管理相对简单。
备选方案: 在当前页面内展开一个区域。
不推荐方案: 跳转到全新的页面(打断流程,增加状态管理复杂度)。
模态框内容:
Excel 数据: 使用交互式表格组件(如 Element UI 的 Table 配合输入框),允许基本的单元格查看/编辑。
SVG 数据: 渲染 SVG 图像供查看。(编辑功能按需添加)。
操作: 模态框内需要有 “保存修改并关闭” 或 “关闭” 按钮。“保存修改”操作会将处理后的数据准备好,作为处理流程第一步的输入。
3. 步骤指示器 (Stepper)
使用 UI 库(如 Element UI 的 Steps 组件)实现。
清晰标示步骤编号和名称。
视觉上区分已完成、当前、未开始状态。
(可选) 允许点击已完成步骤返回查看。
4. 单个处理步骤内容区 (动态切换)
结构: 每个步骤都应包含:
明确的标题: 如 “步骤 1格式合并”。
简要说明 (可选): 解释该步骤的作用。
(可选) 配置项: 若步骤需要用户输入参数,提供表单控件。
处理结果展示区: 显示该步骤的处理结果。
格式合并: 合并后的数据预览或摘要。
单词纠错: 修改的单词列表/数量,或高亮修改处。
大模型分析: 分析摘要、关键标签或评分。(此步骤预计耗时长,必须显示加载/处理中状态)
统计结果: 图表或关键统计数据。
导航按钮: “下一步”按钮(触发进入下一处理步骤并更新 Stepper最后一个步骤显示“完成”或“导出结果”。
四、 整体工作流程 (User Journey)
用户进入界面,看到上层文件上传区和下层待激活的步骤区。
用户上传 SVG 或 Excel 文件,界面反馈上传状态。
上传成功,显示文件信息,“查看/编辑文件内容” 按钮可用。
用户点击 “查看/编辑文件内容”,弹出模态框。
用户在模态框中查看/编辑数据,点击 “保存修改并关闭”。
模态框关闭下层步骤区自动激活并显示第一步格式合并内容Stepper 指向第一步。
用户查看结果,点击 “下一步:单词纠错”。
界面更新为第二步单词纠错内容Stepper 指向第二步。
用户查看结果,点击 “下一步:大模型分析”。
界面显示明确的加载/处理中提示。完成后更新为第三步内容Stepper 指向第三步。
用户查看结果,点击 “下一步:查看统计结果”。
界面更新为第四步内容Stepper 指向第四步。
用户查看最终结果,点击 “完成” 或 “导出结果”。
五、 技术栈提示 (Vue 2)
初始化: 使用 vue create 并手动选择 Vue 2 版本。
核心 API: 使用 Options API (data, methods, computed, watch, 生命周期钩子)。
UI 库: 推荐使用成熟的 Vue 2 UI 库 (如 Element UI, Vuetify, iView) 获取开箱即用的组件(上传、模态框、步骤条、表格等)。
状态管理: 可能需要 Vuex (v3) 来管理跨组件/步骤的状态(尤其数据从编辑模态框传递到处理流程)。
路由: 若选择页面跳转方式实现查看/编辑,则需要 Vue Router (v3)。

Binary file not shown.

1
src/.gitignore vendored

@ -1 +0,0 @@
node_modules

@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/民航NLP.iml" filepath="$PROJECT_DIR$/.idea/民航NLP.iml" />
</modules>
</component>
</project>

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Varun Shenoy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,65 +0,0 @@
The License
一、定义
“许可方”是指分发其软件的CoLa Lab模型团队。
“软件”是指根据本许可提供的软件。
2. 许可授予
根据本许可的条款和条件,许可方特此授予您非排他性、全球性、不可转让、不可再许可、可撤销、免版税的版权许可,仅用于您的非商业研究目的。
上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中。
3.限制
您不得出于任何商业、军事或非法目的使用、复制、修改、合并、发布、分发、复制或创建本软件的全部或部分衍生作品。
您不得利用本软件从事任何危害国家安全和国家统一、危害社会公共利益、侵犯人身权益的行为。
4.免责声明
本软件“按原样”提供,不提供任何明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和非侵权性的保证。 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权行为还是其他方面,由软件或软件的使用或其他交易引起、由软件引起或与之相关 软件。
5. 责任限制
除适用法律禁止的范围外,在任何情况下且根据任何法律理论,无论是基于侵权行为、疏忽、合同、责任或其他原因,任何许可方均不对您承担任何直接、间接、特殊、偶然、示范性、 或间接损害,或任何其他商业损失,即使许可人已被告知此类损害的可能性。
6.争议解决
本许可受中华人民共和国法律管辖并按其解释。 因本许可引起的或与本许可有关的任何争议应提交北京市海淀区人民法院。
请注意,许可证可能会更新到更全面的版本。 有关许可和版权的任何问题,请通过 lab.cognition.and.language@gmail.com 与我们联系。
1. Definitions
“Licensor” means the CoLa Lab Team that distributes its Software.
“Software” means the CoLa Lab prompts made available under this license.
2. License Grant
Subject to the terms and conditions of this License, the Licensor hereby grants to you a non-exclusive, worldwide, non-transferable, non-sublicensable, revocable, royalty-free copyright license to use the Software solely for your non-commercial research purposes.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
3. Restriction
You will not use, copy, modify, merge, publish, distribute, reproduce, or create derivative works of the Software, in whole or in part, for any commercial, military, or illegal purposes.
You will not use the Software for any act that may undermine China's national security and national unity, harm the public interest of society, or infringe upon the rights and interests of human beings.
4. Disclaimer
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5. Limitation of Liability
EXCEPT TO THE EXTENT PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER BASED IN TORT, NEGLIGENCE, CONTRACT, LIABILITY, OR OTHERWISE WILL ANY LICENSOR BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES, OR ANY OTHER COMMERCIAL LOSSES, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Dispute Resolution
This license shall be governed and construed in accordance with the laws of Peoples Republic of China. Any dispute arising from or in connection with this License shall be submitted to Haidian District People's Court in Beijing.
Note that the license is subject to update to a more comprehensive version. For any questions related to the license and copyright, please contact us at lab.cognition.and.language@gmail.com.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

@ -1 +0,0 @@
# some run examples

20510
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,45 +0,0 @@
{
"name": "GPT4IE",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"npm": "^9.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-graph-vis": "^1.0.7",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"gh-pages": "^5.0.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Making IE from textual data using GPT4IE." />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>基于有噪声民航数据的NLP处理系统</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

@ -1,25 +0,0 @@
{
"short_name": "GPT4IE",
"name": "GPT4IE",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -1,4 +0,0 @@
已知事件类型和论元角色列表的对应关系如下:$prompt1
给定一句话:"$prompt", 请提取出这句话的事件论元,并以字典的形式回答,字典的形式为:{事件类型: {论元角色: 论元内容}}。直接给出答案即可。如果没有论元角色没有相应的论元内容,论元内容回答:无。

@ -1,3 +0,0 @@
The correspondence between known event types and argument role lists is as follows: $prompt1
Give a sentence:"$prompt", Please extract the event argument or arguments of this sentence, and answer it or them in the form of a dictionary. The dictionary is in the form of: {event type: {argument role: argument content}}. Just give the answer. If the argument role does not have the corresponding argument content, the argument content answers: None.

@ -1,5 +0,0 @@
已知实体类型列表:$prompt1。
给定一个句子:"$prompt"
在给定的句子中可能包含哪些实体,请按照形式 (实体类型, 实体内容) 回答:

@ -1,5 +0,0 @@
List of known entity types: $prompt1.
Given a sentence: "$prompt"
Which entities may be included in a given sentence, please answer in the form (entity type, entity content):

@ -1,11 +0,0 @@
假设你是一个实体关系三元组抽取模型。我会给你头实体类型列表subject_types尾实体类型列表object_types关系列表relations再给你一个句子请你根据这三个列表抽出句子中的subject和object并组成三元组且形式为(subject, relation, object)。
给定的句子为:"$prompt"
relations$prompt1
subject_types$prompt2
object_types$prompt3
在给定的句子中,可能包含了哪些三元组?请按照形式(subject, relation, object)回答:

@ -1,11 +0,0 @@
Suppose you are an entity-relationship triple extraction model. I'll give you list of head entity types: subject_types, list of tail entity types: object_types, list of relations: relations. Give you a sentence, please extract the subject and object in the sentence based on these three lists, and form a triplet in the form of (subject, relation, object).
The given sentence is "$prompt".
relations: $prompt1
subject_types: $prompt2
object_types: $prompt3
In the given sentence, what triples might be contained? Please answer in the form (subject, relation, object):

@ -1,38 +0,0 @@
The aid group Doctors Without Borders said that since Saturday , more than 275 wounded people had been admitted and treated at Donka Hospital in the capital of Guinea , Conakry .
['location-located_in', 'administrative_division-country', 'person-place_lived', 'person-company', 'person-nationality', 'company-founders', 'country-administrative_divisions', 'person-children', 'country-capital', 'deceased_person-place_of_death', 'neighborhood-neighborhood_of', 'person-place_of_birth']
['organization', 'person', 'location', 'country']
['person', 'location', 'country', 'organization', 'city']
_____________________________________________________
第五部:《如懿传》《如懿传》是一部古装宫廷情感电视剧,由汪俊执导,周迅、霍建华、张钧甯、董洁、辛芷蕾、童瑶、李纯、邬君梅等主演
['所属专辑', '成立日期', '海拔', '官方语言', '占地面积', '父亲', '歌手', '制片人', '导演', '首都', '主演', '董事长', '祖籍', '妻子', '母亲', '气候', '面积', '主角', '邮政编码', '简称', '出品公司', '注册资本', '编剧', '创始人', '毕业院校', '国籍', '专业代码', '朝代', '作者', '作词', '所在城市', '嘉宾', '总部地点', '人口数量', '代言人', '改编自', '校长', '丈夫', '主持人', '主题曲', '修业年限', '作曲', '号', '上映时间', '票房', '饰演', '配音', '获奖']
['国家', '行政区', '文学作品', '人物', '影视作品', '学校', '图书作品', '地点', '历史人物', '景点', '歌曲', '学科专业', '企业', '电视综艺', '机构', '企业/品牌', '娱乐人物']
['国家', '人物', 'Text', 'Date', '地点', '气候', '城市', '歌曲', '企业', 'Number', '音乐专辑', '学校', '作品', '语言']
_______________________________________________________
比如本轮的比赛中就有,中甲榜首之争,再次惨败的西北狼回到主场能否止住颓势,闹剧频出的川足事态如何发展,永昌客场是否能迎来首胜。
{'组织行为-罢工': ['时间', '所属组织', '罢工人数', '罢工人员'], '竞赛行为-晋级': ['时间', '晋级方', '晋级赛事'], '财经/交易-涨停':['时间', '涨停股票'] , '组织关系-解雇': ['时间', '解雇方', '被解雇人员']}
_____________________________________________________
He lost an election to a dead man.
{'Personnel:Elect': ['Person', 'Entity', 'Position', 'Time', 'Place'], 'Business:Declare-Bankruptcy': ['Org', 'Time', 'Place'], 'Justice:Arrest-Jail': ['Person', 'Agent', 'Crime', 'Time', 'Place'], 'Life:Divorce': ['Person', 'Time', 'Place'], 'Life:Injure': ['Agent', 'Victim', 'Instrument', 'Time', 'Place']}
————————————————————————————————————————————
在过去的五年中,致公党在邓小平理论指引下,遵循社会主义初级阶段的基本路线,努力实践致公党十大提出的发挥参政党职能、加强自身建设的基本任务。
['组织机构', '地点', '人物']
_____________________________________________________
Japan then laid siege to the Syrian penalty area and had a goal disallowed for offside in the 16th minute.
['LOC', 'MISC', 'ORG', 'PER']
_____________________________________________________
"homepage": "https://cocacola-lab.github.io/GPT4IE",

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -1,210 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Noticia+Text:wght@400;700&display=swap');
.headerText {
font-family: 'Noticia Text';
font-weight: 900;
font-size: 60px;
text-align: center;
padding-top: 40px;
}
.subheaderText {
font-family: 'Noticia Text';
font-weight: 400;
font-size: 20px;
text-align: center;
width: 60%;
margin: 0 auto;
}
.subsubheaderText {
font-family: 'Noticia Text';
font-weight: 400;
font-size: 18px;
text-align: center;
width: 60%;
margin: 0 auto;
}
table{
font-family: 'Oswald', sans-serif;
border-collapse:collapse;
margin-left:auto;
margin-right:auto;
margin-top: 5px;
}
td{
background-color:#ffffff;
width:50%;
height:50px;
text-align:center;
}
tr{
border-bottom: 1px solid #dddddd;
}
tr:first-of-type{
border-top: 2px solid gray;
}
tr:last-of-type{
border-bottom: 2px solid gray;
}
.opensourceText {
font-family: 'Noticia Text';
font-weight: 400;
font-size: 14px;
text-align: center;
width: 60%;
margin: 10px auto;
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 768px) {
.container {
width: 750px;
}
}
@media (min-width: 992px) {
.container {
width: 970px;
}
}
@media (min-width: 1200px) {
.container {
width: 1170px;
}
}
.graphContainer {
width: 80%;
height: 80%;
background-color: rgb(255, 255, 255);
border-radius: 10px;
overflow: hidden;
border: 1px solid rgb(0, 0, 0);
box-shadow: 0 0 5px rgb(0 0 0 / 10%);
margin: 0 auto;
margin-bottom: 10px;
}
.ulC {
text-align: center
}
.footer {
font-family: 'Noticia Text';
font-weight: 400;
font-size: 13px;
text-align: center;
margin: 0 auto;
margin-bottom: 100px;
}
.inputContainer {
width: 60%;
margin: 0 auto;
margin-top: 50px;
margin-bottom: 50px;
}
.searchBar {
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
box-shadow: 0 0 5px rgb(0 0 0 / 10%);
display: block;
font-size: 16px;
height: 50px;
outline: none;
padding: 0 10px;
width: 90%;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
.typeList {
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
box-shadow: 0 0 5px rgb(0 0 0 / 10%);
display: block;
font-size: 16px;
height: 50px;
outline: none;
padding: 0 10px;
width: 90%;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
.apiKeyTextField {
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
box-shadow: 0 0 5px rgb(0 0 0 / 10%);
display: block;
font-size: 16px;
height: 50px;
outline: none;
padding: 0 10px;
width: 90%;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
.generateButton {
background-color: rgb(0, 0, 0);
border: none;
border-radius: 5px;
color: rgb(255, 255, 255);
cursor: pointer;
font-size: 16px;
height: 50px;
outline: none;
width: 40%;
display: inline-block;
margin-right: 5px;
}
.generateButton:hover {
background-color: rgb(0, 0, 0, 0.8);
}
.generateButton:disabled {
background-color: rgb(0, 0, 0, 0.5);
cursor: wait;
}
.clearButton {
background-color: rgb(255, 255, 255);
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
color: rgb(0, 0, 0);
cursor: pointer;
font-size: 16px;
height: 50px;
outline: none;
width: 40%;
margin-left: auto;
margin-right: auto;
display: inline-block;
margin-left: 5px;
}
.clearButton:hover {
background-color: rgba(239, 239, 239, 0.8);
}

@ -1,446 +0,0 @@
import './App.css';
import React, { useState } from "react";
const DEFAULT_PARAMS = {
"model": "text-davinci-003",
"temperature": 0,
"max_tokens": 256,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0
}
//const SELECTED_PROMPT = "STATELESS"
function App() {
const [structureState, setStructureState] = useState(
[] //[{subject: '七里香', relation: '歌手', object: '人物'}]
);
const clearState = () => {
setStructureState([])
};
const updateStructure = (update) =>{
//setStructureState([]); //清理状态值
//console.log(JSON.parse(JSON.stringify(structureState)));
var current_structure = JSON.parse(JSON.stringify(structureState));
if (update.length === 0) {
console.log("0 output")
return;
}
update.forEach(ut => {
current_structure.push(ut);
});
setStructureState(current_structure);
}
const queryREPrompt = (prompt, prompt1, prompt2, prompt3, apiKey) => {
console.log(lanState);
var file = '';
if (lanState.lanValue === 'english') {
file ='prompts/RE-eng.prompt'
} else {
file ='prompts/RE-chi.prompt'
};
console.log(file);
fetch(file)
.then(response => response.text()) //response转文本->
.then(curprompt => {
console.log(curprompt);
curprompt = curprompt.replace("$prompt", prompt);
if(prompt1.length === 0){
console.log("default relation type list");
if (lanState.lanValue === 'english') {
curprompt = curprompt.replace("$prompt1", "['location-located_in', 'administrative_division-country', 'person-place_lived', 'person-company', 'person-nationality', 'company-founders', 'country-administrative_divisions', 'person-children', 'country-capital', 'deceased_person-place_of_death', 'neighborhood-neighborhood_of', 'person-place_of_birth']");
} else {
curprompt = curprompt.replace("$prompt1", "['所属专辑', '成立日期', '海拔', '官方语言', '占地面积', '父亲', '歌手', '制片人', '导演', '首都', '主演', '董事长', '祖籍', '妻子', '母亲', '气候', '面积', '主角', '邮政编码', '简称', '出品公司', '注册资本', '编剧', '创始人', '毕业院校', '国籍', '专业代码', '朝代', '作者', '作词', '所在城市', '嘉宾', '总部地点', '人口数量', '代言人', '改编自', '校长', '丈夫', '主持人', '主题曲', '修业年限', '作曲', '号', '上映时间', '票房', '饰演', '配音', '获奖']");
};
} else{
curprompt = curprompt.replace("$prompt1", prompt1);
}
if(prompt2.length === 0){
console.log("default subject type list");
if (lanState.lanValue === 'english') {
curprompt = curprompt.replace("$prompt2", "['organization', 'person', 'location', 'country']");
} else {
curprompt = curprompt.replace("$prompt2", "['国家', '行政区', '文学作品', '人物', '影视作品', '学校', '图书作品', '地点', '历史人物', '景点', '歌曲', '学科专业', '企业', '电视综艺', '机构', '企业/品牌', '娱乐人物']");
};
} else{
curprompt = curprompt.replace("$prompt2", prompt1);
}
if(prompt3.length === 0){
console.log("default object type list");
if (lanState.lanValue === 'english') {
curprompt = curprompt.replace("$prompt3", "['person', 'location', 'country', 'organization', 'city']");
} else {
curprompt = curprompt.replace("$prompt3", "['国家', '人物', 'Text', 'Date', '地点', '气候', '城市', '歌曲', '企业', 'Number', '音乐专辑', '学校', '作品', '语言']");
};
} else{
curprompt = curprompt.replace("$prompt3", prompt1);
}
console.log(curprompt);
const params = { ...DEFAULT_PARAMS, prompt: curprompt };
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + String(apiKey)
},
body: JSON.stringify(params)
};
fetch('https://api.openai.com/v1/completions', requestOptions)
.then(response => {
if (!response.ok) {
switch (response.status) {
case 401: // 401: Unauthorized: API key is wrong
throw new Error('Please double-check your API key.');
case 429: // 429: Too Many Requests: Need to pay
throw new Error('You exceeded your current quota, please check your plan and billing details.');
default:
throw new Error('Something went wrong with the request, please check the Network log');
}
}
//console.log(response)
return response.json();
})
.then((response) => {
const { choices } = response;
//console.log(choices)
const text = choices[0].text;
console.log(text);
//const updates = JSON.parse(text);
//console.log(updates);
//updateGraph(updates);
const reg = /\(.*?\)/g; //g表示全局搜索会进行多次match
var update = text.match(reg);
if(! update){
update = [text];
}
console.log(update);
console.log(update[0])
updateStructure(update);
//document.getElementsByClassName("searchBar")[0].value = "";
//document.getElementsByClassName("typeList")[0].value = "";
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}).catch((error) => {
console.log(error);
alert(error);
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false; //出错则释放,为了能再次使用
});
})
};
const queryNERPrompt = (prompt, prompt1, prompt2, prompt3, apiKey) => {
console.log(lanState);
var file = '';
if (lanState.lanValue === 'english') {
file ='prompts/NER-eng.prompt'
} else {
file ='prompts/NER-chi.prompt'
};
console.log(file);
fetch(file)
.then(response => response.text()) //response转文本-> curprompt
.then(curprompt => {
console.log(curprompt);
if(prompt1.length === 0){
console.log("default ner type list");
if (lanState.lanValue === 'english') {
curprompt = curprompt.replace("$prompt1", "['LOC', 'MISC', 'ORG', 'PER']");
} else {
curprompt = curprompt.replace("$prompt1", "['组织机构', '地点', '人物']");
};
} else{
curprompt = curprompt.replace("$prompt1", prompt1);
}
curprompt = curprompt.replace("$prompt", prompt);
console.log(curprompt);
const params = { ...DEFAULT_PARAMS, prompt: curprompt };
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + String(apiKey)
},
body: JSON.stringify(params)
};
fetch('https://api.openai.com/v1/completions', requestOptions)
.then(response => {
if (!response.ok) {
switch (response.status) {
case 401: // 401: Unauthorized: API key is wrong
throw new Error('Please double-check your API key.');
case 429: // 429: Too Many Requests: Need to pay
throw new Error('You exceeded your current quota, please check your plan and billing details.');
default:
throw new Error('Something went wrong with the request, please check the Network log');
}
}
//console.log(response)
return response.json();
})
.then((response) => {
const { choices } = response;
//console.log(choices)
const text = choices[0].text;
console.log(text);
//const updates = JSON.parse(text);
//console.log(updates);
//updateGraph(updates);
const reg = /\(.*?\)/g; //g表示全局搜索会进行多次match
var update = text.match(reg);
if(! update){
update = [text];
}
console.log(update);
console.log(update[0])
updateStructure(update);
//document.getElementsByClassName("searchBar")[0].value = "";
//document.getElementsByClassName("typeList")[0].value = "";
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}).catch((error) => {
console.log(error);
alert(error);
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false; //出错则释放,为了能再次使用
});
})
};
const queryEEPrompt = (prompt, prompt1, prompt2, prompt3, apiKey) => {
console.log(lanState);
var file = '';
if (lanState.lanValue === 'english') {
file ='prompts/EE-eng.prompt'
} else {
file ='prompts/EE-chi.prompt'
};
console.log(file);
fetch(file)
.then(response => response.text()) //response转文本-> curprompt
.then(curprompt => {
console.log(curprompt);
if(prompt1.length === 0){
console.log("default event type list");
if (lanState.lanValue === 'english') {
curprompt = curprompt.replace("$prompt1", "{'Personnel:Elect': ['Person', 'Entity', 'Position', 'Time', 'Place'], 'Business:Declare-Bankruptcy': ['Org', 'Time', 'Place'], 'Justice:Arrest-Jail': ['Person', 'Agent', 'Crime', 'Time', 'Place'], 'Life:Divorce': ['Person', 'Time', 'Place'], 'Life:Injure': ['Agent', 'Victim', 'Instrument', 'Time', 'Place']}");
} else {
curprompt = curprompt.replace("$prompt1", "{'组织行为-罢工': ['时间', '所属组织', '罢工人数', '罢工人员'], '竞赛行为-晋级': ['时间', '晋级方', '晋级赛事'], '财经/交易-涨停':['时间', '涨停股票'] , '组织关系-解雇': ['时间', '解雇方', '被解雇人员']}");
};
} else{
curprompt = curprompt.replace("$prompt1", prompt1);
}
curprompt = curprompt.replace("$prompt", prompt);
console.log(curprompt);
const params = { ...DEFAULT_PARAMS, prompt: curprompt };
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + String(apiKey)
},
body: JSON.stringify(params)
};
fetch('https://api.openai.com/v1/completions', requestOptions)
.then(response => {
if (!response.ok) {
switch (response.status) {
case 401: // 401: Unauthorized: API key is wrong
throw new Error('Please double-check your API key.');
case 429: // 429: Too Many Requests: Need to pay
throw new Error('You exceeded your current quota, please check your plan and billing details.');
default:
throw new Error('Something went wrong with the request, please check the Network log');
}
}
//console.log(response)
return response.json();
})
.then((response) => {
const { choices } = response;
//console.log(choices)
const text = choices[0].text;
console.log(text);
//const updates = JSON.parse(text);
//console.log(updates);
//updateGraph(updates);
const reg = /[^,{]+?:[\x20]*\{.*?\}/g; //g表示全局搜索会进行多次match
var update = text.match(reg);
if(! update){
update = [text];
}
console.log(update);
console.log(update[0])
updateStructure(update);
//document.getElementsByClassName("searchBar")[0].value = "";
//document.getElementsByClassName("typeList")[0].value = "";
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}).catch((error) => {
console.log(error);
alert(error);
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false; //出错则释放,为了能再次使用
});
})
};
const queryPrompt = (prompt, prompt1, prompt2, prompt3, apiKey) => {
console.log(taskState.taskValue);
if (taskState.taskValue === 'RE') {
queryREPrompt(prompt, prompt1, prompt2, prompt3, apiKey);
} else if (taskState.taskValue === 'NER') {
queryNERPrompt(prompt, prompt1, prompt2, prompt3, apiKey);
} else if (taskState.taskValue === 'EE') {
queryEEPrompt(prompt, prompt1, prompt2, prompt3, apiKey);
} else {
alert("Please select a task");
document.body.style.cursor = 'default';
document.getElementsByClassName("generateButton")[0].disabled = false;
}
}
const createIE = () => {
document.body.style.cursor = 'wait';
document.getElementsByClassName("generateButton")[0].disabled = true;
const prompt = document.getElementsByClassName("searchBar")[0].value;
const prompt1 = document.getElementById("prompt1").value;
const prompt2 = document.getElementById("prompt2").value;
const prompt3 = document.getElementById("prompt3").value;
var apiKey = document.getElementsByClassName("apiKeyTextField")[0].value;
//console.log(apiKey);
if (apiKey.length === 0){
console.log("default open AI key");
const temp_k = "@WITy9ios86qOmgLcFT3BlbkFJi68ofPwe9IVifL3NYgZ5";
apiKey = "sk-nd9" + temp_k.substring(1);
//console.log(apiKey);
}
queryPrompt(prompt, prompt1, prompt2, prompt3, apiKey);
}
const lists = structureState.map((triplet) =>
<li key={triplet.toString()}> {triplet}</li>
);
// 语言选项
const [lanState, setLanState] = useState(
{lanValue: "english"}
);
const handleChange = (event) => {
console.log(event);
console.log(event.target.value);
//setLanState({lanValue: event.target.value},
// () => {console.log(lanState)});
setLanState({lanValue: event.target.value}); // 坑set状态后lanState值不会立即改变因为react是渲染周期结束后才更新值。
};
// 任务选项
const [taskState, setTaskState] = useState(
{taskValue: "NER"}
);
const handleChange2 = (event) => {
console.log(event);
console.log(event.target.value);
//setLanState({lanValue: event.target.value},
// () => {console.log(lanState)});
setTaskState({taskValue: event.target.value});
};
// <form>能控制radio为一组。
return (
<div className='container'>
<h1 className="headerText">基于民航有噪声数据的NLP处理工具 </h1>
<p className='subheaderText'>基于民航有噪声数据的NLP处理工具 </p>
<p className='subheaderText'>工具简介输入非结构化文本->消除噪声数据预处理->无噪声的标准化文本->利用手动枚举的论元关系字典样例如上和提示词工程样例如上使大模型根据知识图谱里的内容neo4j和langchain输出主体飞机和要素行为地点json格式</p>
<p className='subheaderText'>我们支持以下功能:</p>
<div>
<table>
<tbody>
<tr>
<td>RE</td>
<td>entity-relation joint extraction</td>
</tr>
<tr>
<td>NER</td>
<td>named entity recoginzation</td>
</tr>
<tr>
<td>EE</td>
<td>event extraction</td>
</tr>
</tbody>
</table>
</div>
<center>
<div>
<form>
<input type="radio" id="zh" value="chinese" checked={lanState.lanValue === 'chinese'}
onChange={handleChange} /> Chinese
<input type="radio" id="en" value="english" checked={lanState.lanValue === 'english'}
onChange={handleChange} /> English
</form>
</div>
<div>
<form>
<input type="radio" id="re" value="RE" checked={taskState.taskValue === 'RE'}
onChange={handleChange2} /> RE
<input type="radio" id="ner" value="NER" checked={taskState.taskValue === 'NER'}
onChange={handleChange2} /> NER
<input type="radio" id="ee" value="EE" checked={taskState.taskValue === 'EE'}
onChange={handleChange2} /> EE
</form>
</div>
<div className='inputContainer'>
<input className="searchBar" placeholder="Input sentence..."></input>
<input className="typeList" id="prompt1" placeholder="relation/ner/event type list;
like ['nationality']/['LOC']/{'Life:Divorce':['Person','Time','Place']}"></input>
<input className="typeList" id="prompt2" placeholder="Subject type list... like ['person','location']"></input>
<input className="typeList" id="prompt3" placeholder="Object type list... like ['country','location']"></input>
<input className="apiKeyTextField" type="password" placeholder="Enter your OpenAI API key..."></input>
<button className="generateButton" onClick={createIE}>Generate</button>
<button className="clearButton" onClick={clearState}>Clear</button>
</div>
</center>
<div className='graphContainer'>
<ul className='ulC'>{lists}</ul>
</div>
<p className='footer'>Tip: you can clear output by clicking Clear button for aesthetics
<br></br>Note: Except for the mandatory "Input sentence" and "OpenAI API key" fields, other items can be optional.
<br></br>We set the default relation/entity/event type list; subject type list; object type list. Change the default setting and extract specific information by reset the type lists.</p>
</div>
);
}
export default App;

@ -1,9 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: black;
}

@ -1,9 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')); // 渲染到这个index.html中的root id处
root.render(
<App />
);

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,24 @@
# vue_project
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
{
"name": "vue_project",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"vue": "^2.6.14",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,32 @@
<template>
<div id="app">
<MainPage />
</div>
</template>
<script>
import MainPage from './components/MainPage.vue'
export default {
name: 'App',
components: {
MainPage
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 20px;
}
body {
margin: 0;
padding: 0;
background-color: #f5f7fa;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1,405 @@
<template>
<div class="upload-container">
<el-upload
class="upload-area"
drag
action="#"
:http-request="handleUpload"
:before-upload="beforeUpload"
:on-progress="onProgress"
:on-success="onSuccess"
:on-error="onError"
accept=".svg,.xlsx,.xls"
:file-list="fileList"
:auto-upload="true"
:limit="1"
:disabled="uploading">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">支持上传 SVG Excel 文件</div>
</el-upload>
<div v-if="uploading" class="upload-progress">
<el-progress :percentage="uploadPercentage" :format="format"></el-progress>
</div>
<div v-if="uploadSuccess" class="operation-buttons">
<el-button type="primary" @click="viewFileContent" :disabled="!uploadSuccess">查看/编辑文件内容</el-button>
</div>
<!-- 文件查看/编辑模态框 -->
<el-dialog
title="文件内容查看/编辑"
:visible.sync="dialogVisible"
width="80%"
:before-close="handleClose">
<div v-if="fileType === 'svg'" class="svg-preview">
<div v-html="fileContent" class="svg-container"></div>
</div>
<div v-else-if="fileType === 'excel'" class="excel-preview">
<!-- Excel数据表格展示 -->
<el-table
:data="tableData"
border
style="width: 100%"
max-height="500px">
<el-table-column
v-for="(column, index) in tableColumns"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width">
<template slot-scope="scope">
<el-input v-model="scope.row[column.prop]" size="small"></el-input>
</template>
</el-table-column>
</el-table>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveFileContent"></el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as XLSX from 'xlsx';
export default {
name: 'FileUpload',
data() {
return {
fileList: [],
uploading: false,
uploadPercentage: 0,
uploadSuccess: false,
fileType: '', // 'svg' 'excel'
fileContent: '',
dialogVisible: false,
tableData: [],
tableColumns: []
};
},
methods: {
//
beforeUpload(file) {
const isSVG = file.type === 'image/svg+xml';
const isExcel = file.type === 'application/vnd.ms-excel' ||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
if (!isSVG && !isExcel) {
this.$message.error('只能上传SVG或Excel文件!');
return false;
}
return true;
},
//
handleUpload(options) {
const { file } = options;
this.uploading = true;
this.uploadPercentage = 0;
//
const interval = setInterval(() => {
if (this.uploadPercentage < 99) {
this.uploadPercentage += 10;
}
}, 300);
//
setTimeout(() => {
clearInterval(interval);
this.uploadPercentage = 100;
//
if (file.type === 'image/svg+xml') {
this.fileType = 'svg';
} else {
this.fileType = 'excel';
}
//
this.readFileContent(file);
this.uploading = false;
this.uploadSuccess = true;
//
options.onSuccess();
}, 3000);
},
//
readFileContent(file) {
const reader = new FileReader();
if (this.fileType === 'svg') {
reader.onload = (e) => {
// SVG
this.fileContent = this.sanitizeSvgContent(e.target.result);
};
reader.readAsText(file);
} else if (this.fileType === 'excel') {
reader.onload = (e) => {
try {
// XLSX
if (!XLSX || typeof XLSX.read !== 'function') {
throw new Error('XLSX库未正确加载');
}
// Excel
const data = new Uint8Array(e.target.result);
//
const workbook = XLSX.read(data, { type: 'array', cellDates: true, dateNF: 'yyyy-mm-dd' });
if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
throw new Error('Excel文件没有工作表');
}
//
const firstSheetName = workbook.SheetNames[0];
//
const worksheet = workbook.Sheets[firstSheetName];
if (!worksheet) {
throw new Error('无法读取工作表数据');
}
// JSON
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
defval: '', //
blankrows: true //
});
//
this.processExcelData(jsonData);
} catch (error) {
console.error('解析Excel文件错误:', error);
this.$message.error(`无法读取Excel文件内容: ${error.message || '未知错误'}`);
// 使
this.simulateExcelData();
}
};
//
reader.onerror = (event) => {
console.error('文件读取错误:', event);
this.$message.error('读取文件时发生错误');
this.simulateExcelData();
};
try {
reader.readAsArrayBuffer(file);
} catch (error) {
console.error('读取文件缓冲区错误:', error);
this.$message.error('无法读取文件内容');
this.simulateExcelData();
}
}
},
// SVG
sanitizeSvgContent(content) {
// SVG
if (!content || typeof content !== 'string' || !content.includes('<svg')) {
this.$message.warning('SVG文件内容不符合预期格式');
return '<div class="svg-error">无效的SVG内容</div>';
}
// SVG
// 使DOMPurify
return content;
},
// Excel
processExcelData(jsonData) {
if (!jsonData || jsonData.length === 0) {
this.$message.warning('Excel文件为空');
this.tableData = [];
this.tableColumns = [];
return;
}
//
const headers = jsonData[0];
//
this.tableColumns = headers.map((header, index) => {
return {
prop: `col${index}`,
label: header || `${index + 1}`,
width: '180'
};
});
//
this.tableData = jsonData.slice(1).map(row => {
const rowData = {};
headers.forEach((header, index) => {
rowData[`col${index}`] = row[index] || '';
});
return rowData;
});
},
// Excel (Excel使)
simulateExcelData() {
//
this.tableColumns = [
{ prop: 'col0', label: '列1', width: '180' },
{ prop: 'col1', label: '列2', width: '180' },
{ prop: 'col2', label: '列3', width: '180' }
];
//
this.tableData = [];
for (let i = 0; i < 20; i++) {
this.tableData.push({
col0: `数据${i}-1`,
col1: `数据${i}-2`,
col2: `数据${i}-3`
});
}
},
//
format(percentage) {
return percentage === 100 ? '完成' : `${percentage}%`;
},
//
onProgress(/* event, file */) {
// 使
},
//
onSuccess(/* response, file */) {
this.$message.success('文件上传成功');
},
//
onError(err/* , file */) {
this.uploading = false;
this.$message.error('文件上传失败');
console.error(err);
},
// /
viewFileContent() {
this.dialogVisible = true;
},
//
handleClose(done) {
this.$confirm('确认关闭?未保存的修改将会丢失')
.then(() => {
done();
})
.catch(() => {});
},
//
saveFileContent() {
this.$message.success('修改已保存');
this.dialogVisible = false;
if (this.fileType === 'excel' && this.tableData.length > 0) {
//
const exportData = this.convertTableDataToExportFormat();
//
this.$emit('file-ready', {
fileType: this.fileType,
fileData: exportData
});
} else {
//
this.$emit('file-ready', {
fileType: this.fileType,
fileData: this.fileType === 'excel' ? this.tableData : this.fileContent
});
}
},
//
convertTableDataToExportFormat() {
if (!this.tableData || this.tableData.length === 0) {
return [];
}
//
const headers = this.tableColumns.map(col => col.label);
//
const exportData = [headers];
//
this.tableData.forEach(row => {
const rowData = [];
this.tableColumns.forEach(col => {
rowData.push(row[col.prop] || '');
});
exportData.push(rowData);
});
return exportData;
}
}
};
</script>
<style scoped>
.upload-container {
margin: 20px;
padding: 20px;
border-radius: 4px;
border: 1px solid #ebeef5;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.upload-area {
width: 100%;
}
.upload-progress {
margin-top: 20px;
}
.operation-buttons {
margin-top: 20px;
text-align: center;
}
.svg-preview {
width: 100%;
min-height: 400px;
border: 1px solid #dcdfe6;
padding: 10px;
border-radius: 4px;
background-color: #f5f7fa;
overflow: auto;
text-align: center;
}
.svg-container {
display: inline-block;
max-width: 100%;
max-height: 100%;
}
.excel-preview {
width: 100%;
overflow: auto;
}
/* 控制SVG内容的大小 */
.svg-container svg {
max-width: 100%;
max-height: 500px;
}
</style>

@ -0,0 +1,58 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

@ -0,0 +1,84 @@
<template>
<div class="main-container">
<h1 class="app-title">SVG/Excel 数据处理应用</h1>
<!-- 上层区域文件上传 -->
<el-card class="upload-section">
<div slot="header" class="section-header">
<span>文件上传与准备</span>
</div>
<file-upload @file-ready="onFileReady" />
</el-card>
<!-- 下层区域多步骤处理流程将在后续实现 -->
<el-card class="process-section" v-if="fileReady">
<div slot="header" class="section-header">
<span>数据处理流程</span>
</div>
<div class="placeholder-text">
<p>文件处理准备就绪后续步骤将在下一阶段实现</p>
</div>
</el-card>
</div>
</template>
<script>
import FileUpload from './FileUpload.vue';
export default {
name: 'MainPage',
components: {
FileUpload
},
data() {
return {
fileReady: false,
fileData: null,
fileType: null
};
},
methods: {
onFileReady(data) {
this.fileReady = true;
this.fileData = data.fileData;
this.fileType = data.fileType;
this.$message.success('文件已准备好,可以开始处理');
}
}
};
</script>
<style scoped>
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.app-title {
text-align: center;
margin-bottom: 30px;
color: #409EFF;
}
.upload-section {
margin-bottom: 30px;
}
.process-section {
transition: all 0.3s ease;
}
.section-header {
font-size: 18px;
font-weight: bold;
color: #303133;
}
.placeholder-text {
padding: 20px;
text-align: center;
color: #909399;
font-style: italic;
}
</style>

@ -0,0 +1,12 @@
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')

@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})
Loading…
Cancel
Save