重新构建代码

main
LiRen-qiu 4 months ago
parent f88eb58cc6
commit d87b029d88

@ -1,23 +0,0 @@
.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?

@ -1,24 +0,0 @@
# vue3_pro
## 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/).

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

@ -1,19 +0,0 @@
{
"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

@ -1,44 +0,0 @@
{
"name": "vue3_pro",
"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-plus": "^2.9.7",
"vue": "^3.2.13"
},
"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"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,17 +0,0 @@
<!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>

@ -1,57 +0,0 @@
<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;
}
body {
margin: 0;
padding: 0;
background-image: url('./assets/background.png');
background-size: cover;
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
min-height: 100vh;
}
/* 全局样式 */
.el-button--primary {
background-color: #0066FF;
border-color: #0066FF;
}
.el-button--primary:hover, .el-button--primary:focus {
background-color: #1473E6;
border-color: #1473E6;
}
.el-button--success {
background-color: #66CC33;
border-color: #66CC33;
}
.el-button--success:hover, .el-button--success:focus {
background-color: #5ab930;
border-color: #5ab930;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

@ -1,297 +0,0 @@
<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">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">支持上传 SVG Excel 文件</div>
</template>
</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
v-model="dialogVisible"
title="文件内容查看/编辑"
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 #default="scope">
<el-input v-model="scope.row[column.prop]" size="small"></el-input>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveFileContent"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
//
// eslint-disable-next-line no-undef
const emit = defineEmits(['file-ready'])
//
const fileList = ref([])
const uploading = ref(false)
const uploadPercentage = ref(0)
const uploadSuccess = ref(false)
const fileType = ref('') // 'svg' 'excel'
const fileContent = ref('')
const dialogVisible = ref(false)
const tableData = ref([])
const tableColumns = ref([])
//
const format = (percentage) => {
return percentage === 100 ? '完成' : `${percentage}%`
}
//
const 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) {
ElMessage.error('只能上传SVG或Excel文件!')
return false
}
return true
}
//
const onProgress = (event) => {
uploading.value = true
uploadPercentage.value = Math.ceil(event.percent)
}
//
const onSuccess = () => {
uploading.value = false
uploadSuccess.value = true
ElMessage.success('文件上传成功!')
}
//
const onError = (err) => {
uploading.value = false
ElMessage.error('文件上传失败!')
console.error('上传错误:', err)
}
//
const handleUpload = (options) => {
const { file } = options
uploading.value = true
uploadPercentage.value = 0
//
const interval = setInterval(() => {
if (uploadPercentage.value < 99) {
uploadPercentage.value += 10
}
}, 300)
//
setTimeout(() => {
clearInterval(interval)
uploadPercentage.value = 100
//
if (file.type === 'image/svg+xml') {
fileType.value = 'svg'
} else {
fileType.value = 'excel'
}
//
readFileContent(file)
uploading.value = false
uploadSuccess.value = true
//
if (options.onSuccess) {
options.onSuccess()
}
//
emitFileReady()
}, 3000)
}
//
const readFileContent = (file) => {
const reader = new FileReader()
if (fileType.value === 'svg') {
reader.onload = (e) => {
// SVG
fileContent.value = sanitizeSvgContent(e.target.result)
}
reader.readAsText(file)
} else if (fileType.value === 'excel') {
// Excel
simulateExcelData()
}
}
// SVG
const sanitizeSvgContent = (content) => {
//
return content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
}
// ExcelExcel
const simulateExcelData = () => {
//
tableColumns.value = [
{ prop: 'flightNo', label: '航班号', width: '120' },
{ prop: 'date', label: '日期', width: '120' },
{ prop: 'customerQuery', label: '客户问题', width: '250' },
{ prop: 'agentResponse', label: '客服回复', width: '250' },
{ prop: 'category', label: '问题类别', width: '120' }
]
//
tableData.value = [
{
flightNo: 'CA1234',
date: '2023-06-15',
customerQuery: '我的航班什么时候起飞?',
agentResponse: '您好CA1234航班计划于15:30起飞。',
category: '航班信息'
},
{
flightNo: 'MU5678',
date: '2023-06-15',
customerQuery: '我的行李还没到,怎么办?',
agentResponse: '很抱歉,我们会立即为您查询行李状态。',
category: '行李问题'
},
{
flightNo: 'CZ9012',
date: '2023-06-16',
customerQuery: '这个航班延误了,我能改签吗?',
agentResponse: '可以的,我们可以为您免费改签今天的其他航班。',
category: '航班延误'
}
]
}
//
const viewFileContent = () => {
dialogVisible.value = true
}
//
const handleClose = (done) => {
done()
}
//
const saveFileContent = () => {
ElMessage.success('修改已保存')
dialogVisible.value = false
}
//
const emitFileReady = () => {
emit('file-ready', {
fileType: fileType.value,
fileData: fileType.value === 'svg' ? fileContent.value : tableData.value
})
}
</script>
<style scoped>
.upload-container {
width: 100%;
background: rgba(255, 255, 255, 0.95);
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.upload-area {
width: 100%;
}
.upload-progress {
margin-top: 20px;
}
.operation-buttons {
margin-top: 20px;
display: flex;
justify-content: center;
}
.svg-preview, .excel-preview {
width: 100%;
max-height: 600px;
overflow: auto;
margin-bottom: 20px;
}
.svg-container {
background-color: #f5f7fa;
border: 1px dashed #dcdfe6;
border-radius: 4px;
min-height: 300px;
padding: 16px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
</style>

@ -1,58 +0,0 @@
<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>

@ -1,951 +0,0 @@
<template>
<div class="main-page">
<div class="header">
<h1>航班对话数据处理系统</h1>
<p class="subtitle">SVG/Excel数据预处理合并格式单词纠错大模型分析一站式解决方案</p>
</div>
<!-- 步骤内容区域 -->
<div class="step-content">
<!-- 步骤1上传文件 -->
<div v-if="activeStep === 0" class="upload-step">
<file-upload @file-ready="onFileReady"></file-upload>
<!-- 步骤导航按钮 - 添加在步骤内容底部 -->
<div class="step-navigation-buttons">
<el-button
v-if="stepResults[0]"
type="primary"
@click="nextStep">
下一步
<el-icon class="el-icon--right"><arrow-right /></el-icon>
</el-button>
</div>
</div>
<!-- 步骤2预处理 -->
<div v-if="activeStep === 1" class="process-step">
<div class="step-header">
<h2>文件预处理</h2>
<p>对上传的航班对话数据进行初步预处理确保数据格式正确</p>
</div>
<div class="step-body">
<div class="settings-panel">
<h3>预处理选项</h3>
<el-form label-position="top">
<el-form-item label="移除空行">
<el-switch v-model="preprocessSettings.removeEmptyLines" />
</el-form-item>
<el-form-item label="标准化日期格式">
<el-switch v-model="preprocessSettings.standardizeDates" />
</el-form-item>
<el-form-item label="移除特殊字符">
<el-switch v-model="preprocessSettings.removeSpecialChars" />
</el-form-item>
</el-form>
<!-- 新增处理按钮 -->
<div class="action-buttons">
<el-button
type="primary"
@click="processPreprocess"
:loading="processing">
处理数据
</el-button>
</div>
</div>
<div class="result-panel" v-if="stepResults[1]">
<h3>预处理结果</h3>
<el-alert
title="预处理完成"
type="success"
:closable="false"
show-icon>
<div class="result-summary">
<p>处理时间{{ new Date().toLocaleString() }}</p>
<p>修正的行数{{ Math.floor(Math.random() * 20) + 5 }}</p>
<p>移除的空行{{ Math.floor(Math.random() * 10) }}</p>
</div>
</el-alert>
<div class="data-preview">
<el-table
:data="sampleData"
border
style="width: 100%"
max-height="300px">
<el-table-column
v-for="(column, index) in sampleColumns"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width">
</el-table-column>
</el-table>
</div>
</div>
</div>
<!-- 步骤导航按钮 - 添加在步骤内容底部 -->
<div class="step-navigation-buttons">
<el-button @click="prevStep">
<el-icon class="el-icon--left"><arrow-left /></el-icon>
返回
</el-button>
<el-button
v-if="activeStep < 4 && stepResults[activeStep]"
type="primary"
@click="nextStep">
下一步
<el-icon class="el-icon--right"><arrow-right /></el-icon>
</el-button>
</div>
</div>
<!-- 步骤3合并格式 -->
<div v-if="activeStep === 2" class="process-step">
<div class="step-header">
<h2>合并格式</h2>
<p>将预处理后的数据合并为统一格式便于后续分析</p>
</div>
<div class="step-body">
<div class="settings-panel">
<h3>合并选项</h3>
<el-form label-position="top">
<el-form-item label="目标格式">
<el-radio-group v-model="mergeSettings.targetFormat">
<el-radio label="standard">标准格式</el-radio>
<el-radio label="extended">扩展格式</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="合并相似项">
<el-switch v-model="mergeSettings.mergeSimilarItems" />
</el-form-item>
<el-form-item label="生成唯一ID">
<el-switch v-model="mergeSettings.generateUniqueIds" />
</el-form-item>
</el-form>
<!-- 新增处理按钮 -->
<div class="action-buttons">
<el-button
type="primary"
@click="processMergeFormat"
:loading="processing">
处理数据
</el-button>
</div>
</div>
<div class="result-panel" v-if="stepResults[2]">
<h3>合并结果</h3>
<el-alert
title="格式合并完成"
type="success"
:closable="false"
show-icon>
<div class="result-summary">
<p>处理时间{{ new Date().toLocaleString() }}</p>
<p>合并条目数{{ Math.floor(Math.random() * 30) + 10 }}</p>
<p>匹配模式{{ mergeSettings.targetFormat === 'standard' ? '标准格式' : '扩展格式' }}</p>
</div>
</el-alert>
<div class="data-preview">
<el-table
:data="sampleData"
border
style="width: 100%"
max-height="300px">
<el-table-column
v-for="(column, index) in sampleColumns"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width">
</el-table-column>
</el-table>
</div>
</div>
</div>
<!-- 步骤导航按钮 - 添加在步骤内容底部 -->
<div class="step-navigation-buttons">
<el-button @click="prevStep">
<el-icon class="el-icon--left"><arrow-left /></el-icon>
返回
</el-button>
<el-button
v-if="activeStep < 4 && stepResults[activeStep]"
type="primary"
@click="nextStep">
下一步
<el-icon class="el-icon--right"><arrow-right /></el-icon>
</el-button>
</div>
</div>
<!-- 步骤4单词纠错 -->
<div v-if="activeStep === 3" class="process-step">
<div class="step-header">
<h2>单词纠错</h2>
<p>检测并修正数据中的拼写错误和语法问题</p>
</div>
<div class="step-body">
<div class="settings-panel">
<h3>纠错选项</h3>
<el-form label-position="top">
<el-form-item label="拼写检查">
<el-switch v-model="spellCheckSettings.enabled" />
</el-form-item>
<el-form-item label="语法检查">
<el-switch v-model="spellCheckSettings.grammarCheck" />
</el-form-item>
<el-form-item label="专业术语库">
<el-select v-model="spellCheckSettings.termBase" placeholder="选择术语库">
<el-option label="航空术语" value="aviation"></el-option>
<el-option label="客服对话" value="customer-service"></el-option>
<el-option label="通用术语" value="general"></el-option>
</el-select>
</el-form-item>
</el-form>
<!-- 新增处理按钮 -->
<div class="action-buttons">
<el-button
type="primary"
@click="processSpellCheck"
:loading="processing">
处理数据
</el-button>
</div>
</div>
<div class="result-panel" v-if="stepResults[3]">
<h3>纠错结果</h3>
<el-alert
title="单词纠错完成"
type="success"
:closable="false"
show-icon>
<div class="result-summary">
<p>处理时间{{ new Date().toLocaleString() }}</p>
<p>纠正错误{{ Math.floor(Math.random() * 40) + 15 }}</p>
<p>不确定项{{ Math.floor(Math.random() * 10) + 2 }}</p>
</div>
</el-alert>
<div class="correction-list">
<h4>错误修正列表</h4>
<el-table
:data="correctionSamples"
border
style="width: 100%">
<el-table-column prop="original" label="原文" width="180"></el-table-column>
<el-table-column prop="corrected" label="修正" width="180"></el-table-column>
<el-table-column prop="type" label="错误类型"></el-table-column>
<el-table-column prop="confidence" label="置信度"></el-table-column>
</el-table>
</div>
</div>
</div>
<!-- 步骤导航按钮 - 添加在步骤内容底部 -->
<div class="step-navigation-buttons">
<el-button @click="prevStep">
<el-icon class="el-icon--left"><arrow-left /></el-icon>
返回
</el-button>
<el-button
v-if="activeStep < 4 && stepResults[activeStep]"
type="primary"
@click="nextStep">
下一步
<el-icon class="el-icon--right"><arrow-right /></el-icon>
</el-button>
</div>
</div>
<!-- 步骤5大模型分析 -->
<div v-if="activeStep === 4" class="process-step">
<div class="step-header">
<h2>大模型分析</h2>
<p>利用大语言模型对对话数据进行深度分析提取关键信息并生成洞察</p>
</div>
<div class="step-body">
<div class="settings-panel">
<h3>分析选项</h3>
<el-form label-position="top">
<el-form-item label="分析模型">
<el-select v-model="analysisSettings.model" placeholder="选择模型">
<el-option label="基础分析模型" value="basic"></el-option>
<el-option label="高级分析模型" value="advanced"></el-option>
<el-option label="专业航空模型" value="aviation-pro"></el-option>
</el-select>
</el-form-item>
<el-form-item label="分析深度">
<el-slider v-model="analysisSettings.depth" :step="1" :min="1" :max="5" show-stops></el-slider>
</el-form-item>
<el-form-item label="分析维度">
<el-checkbox-group v-model="analysisSettings.dimensions">
<el-checkbox label="情感分析"></el-checkbox>
<el-checkbox label="关键信息提取"></el-checkbox>
<el-checkbox label="问题分类"></el-checkbox>
<el-checkbox label="客户满意度评估"></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<!-- 新增处理按钮 -->
<div class="action-buttons">
<el-button
type="primary"
@click="processAnalysis"
:loading="processing">
处理数据
</el-button>
</div>
</div>
<div class="result-panel" v-if="stepResults[4]">
<h3>分析结果</h3>
<el-alert
title="大模型分析完成"
type="success"
:closable="false"
show-icon>
<div class="result-summary">
<p>处理时间{{ new Date().toLocaleString() }}</p>
<p>分析条目{{ Math.floor(Math.random() * 50) + 30 }}</p>
<p>生成洞察{{ Math.floor(Math.random() * 15) + 5 }}</p>
</div>
</el-alert>
<div class="analysis-results">
<el-tabs type="border-card">
<el-tab-pane label="分析摘要">
<div class="analysis-summary">
<h4>主要发现</h4>
<p>通过对{{Math.floor(Math.random() * 50) + 30}}条航班对话数据的分析我们发现以下主要问题</p>
<ul>
<li>航班延误是客户投诉的主要原因占比约{{Math.floor(Math.random() * 30) + 20}}%</li>
<li>行李问题是第二大投诉点占比约{{Math.floor(Math.random() * 20) + 10}}%</li>
<li>客服回应速度和航班信息更新及时性是改进重点</li>
</ul>
<h4>建议改进方向</h4>
<ul>
<li>提高航班延误信息的透明度和及时性</li>
<li>改进行李追踪系统提供更精确的行李状态信息</li>
<li>优化客服培训提高问题解决效率</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label="情感分析">
<div class="sentiment-chart">
<h4>客户情感分布</h4>
<div class="chart-placeholder">
<!-- 这里将来放置图表组件 -->
<div class="mock-chart">
<div class="chart-bar positive" style="width: 30%">正面 30%</div>
<div class="chart-bar neutral" style="width: 40%">中性 40%</div>
<div class="chart-bar negative" style="width: 30%">负面 30%</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="关键词统计">
<div class="keyword-stats">
<h4>高频关键词</h4>
<div class="keyword-cloud">
<span class="keyword-item" style="font-size: 24px; color: #409EFF">延误</span>
<span class="keyword-item" style="font-size: 20px; color: #67C23A">行李</span>
<span class="keyword-item" style="font-size: 18px; color: #E6A23C">退票</span>
<span class="keyword-item" style="font-size: 22px; color: #F56C6C">航班</span>
<span class="keyword-item" style="font-size: 16px; color: #909399">补偿</span>
<span class="keyword-item" style="font-size: 19px; color: #409EFF">更改</span>
<span class="keyword-item" style="font-size: 15px; color: #67C23A">座位</span>
<span class="keyword-item" style="font-size: 21px; color: #E6A23C">客服</span>
<span class="keyword-item" style="font-size: 17px; color: #F56C6C">信息</span>
<span class="keyword-item" style="font-size: 23px; color: #909399">问题</span>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
<!-- 步骤导航按钮 - 添加在步骤内容底部 -->
<div class="step-navigation-buttons">
<el-button @click="prevStep">
<el-icon class="el-icon--left"><arrow-left /></el-icon>
返回
</el-button>
<el-button
v-if="activeStep === 4 && stepResults[4]"
type="success"
@click="exportFinalResults">
导出分析结果
<el-icon class="el-icon--right"><download /></el-icon>
</el-button>
</div>
</div>
</div>
<!-- 底部步骤指示器 -->
<div class="bottom-steps-container">
<div class="steps-background">
<div class="steps-wrapper">
<!-- 重新设计的步骤流程 -->
<div class="steps-flow-container">
<!-- 步骤1 -->
<div class="step-item-container">
<div class="step-circle" :class="{ 'active': activeStep === 0, 'completed': stepResults[0] }">
<div class="ripple-effect" v-if="activeStep === 0"></div>
1
</div>
<div class="step-button-box" :class="{ 'active': activeStep === 0, 'completed': stepResults[0] }" @click="handleFileUploadClick">
<el-icon><upload /></el-icon>
<div class="step-text">上传文件</div>
</div>
</div>
<!-- 连接线 -->
<div class="step-connector" :class="{ 'active-connector': stepResults[0] }"></div>
<!-- 步骤2 -->
<div class="step-item-container">
<div class="step-circle" :class="{ 'active': activeStep === 1, 'completed': stepResults[1] }">
<div class="ripple-effect" v-if="activeStep === 1"></div>
2
</div>
<div class="step-button-box" :class="{ 'active': activeStep === 1, 'completed': stepResults[1], 'disabled': !stepResults[0] }" @click="goToStep(1)">
<div class="step-text">预处理</div>
</div>
</div>
<!-- 连接线 -->
<div class="step-connector" :class="{ 'active-connector': stepResults[1] }"></div>
<!-- 步骤3 -->
<div class="step-item-container">
<div class="step-circle" :class="{ 'active': activeStep === 2, 'completed': stepResults[2] }">
<div class="ripple-effect" v-if="activeStep === 2"></div>
3
</div>
<div class="step-button-box" :class="{ 'active': activeStep === 2, 'completed': stepResults[2], 'disabled': !stepResults[1] }" @click="goToStep(2)">
<div class="step-text">合并格式</div>
</div>
</div>
<!-- 连接线 -->
<div class="step-connector" :class="{ 'active-connector': stepResults[2] }"></div>
<!-- 步骤4 -->
<div class="step-item-container">
<div class="step-circle" :class="{ 'active': activeStep === 3, 'completed': stepResults[3] }">
<div class="ripple-effect" v-if="activeStep === 3"></div>
4
</div>
<div class="step-button-box" :class="{ 'active': activeStep === 3, 'completed': stepResults[3], 'disabled': !stepResults[2] }" @click="goToStep(3)">
<div class="step-text">单词纠错</div>
</div>
</div>
<!-- 连接线 -->
<div class="step-connector" :class="{ 'active-connector': stepResults[3] }"></div>
<!-- 步骤5 -->
<div class="step-item-container">
<div class="step-circle" :class="{ 'active': activeStep === 4, 'completed': stepResults[4] }">
<div class="ripple-effect" v-if="activeStep === 4"></div>
5
</div>
<div class="step-button-box" :class="{ 'active': activeStep === 4, 'completed': stepResults[4], 'disabled': !stepResults[3] }" @click="goToStep(4)">
<div class="step-text">大模型分析</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElLoading } from 'element-plus'
import { ArrowLeft, ArrowRight, Upload, Download } from '@element-plus/icons-vue'
import FileUpload from './FileUpload.vue'
//
const activeStep = ref(0)
const fileData = ref(null)
const fileType = ref(null)
const processing = ref(false)
//
const stepResults = reactive({
0: false,
1: false,
2: false,
3: false,
4: false
})
//
const preprocessSettings = reactive({
removeEmptyLines: true,
standardizeDates: true,
removeSpecialChars: false
})
//
const mergeSettings = reactive({
targetFormat: 'standard',
mergeSimilarItems: true,
generateUniqueIds: true
})
//
const spellCheckSettings = reactive({
enabled: true,
grammarCheck: true,
termBase: 'aviation'
})
//
const analysisSettings = reactive({
model: 'basic',
depth: 3,
dimensions: ['情感分析', '关键信息提取']
})
//
const sampleColumns = [
{ prop: 'flightNo', label: '航班号', width: '120' },
{ prop: 'date', label: '日期', width: '120' },
{ prop: 'customerQuery', label: '客户问题', width: '250' },
{ prop: 'agentResponse', label: '客服回复', width: '250' },
{ prop: 'category', label: '问题类别', width: '120' }
]
const sampleData = [
{
flightNo: 'CA1234',
date: '2023-06-15',
customerQuery: '我的航班什么时候起飞?',
agentResponse: '您好CA1234航班计划于15:30起飞。',
category: '航班信息'
},
{
flightNo: 'MU5678',
date: '2023-06-15',
customerQuery: '我的行李还没到,怎么办?',
agentResponse: '很抱歉,我们会立即为您查询行李状态。',
category: '行李问题'
},
{
flightNo: 'CZ9012',
date: '2023-06-16',
customerQuery: '这个航班延误了,我能改签吗?',
agentResponse: '可以的,我们可以为您免费改签今天的其他航班。',
category: '航班延误'
}
]
const correctionSamples = [
{ original: 'filght', corrected: 'flight', type: '拼写错误', confidence: '98%' },
{ original: 'customar', corrected: 'customer', type: '拼写错误', confidence: '95%' },
{ original: 'dely', corrected: 'delay', type: '拼写错误', confidence: '97%' },
{ original: 'bagage', corrected: 'baggage', type: '拼写错误', confidence: '99%' },
{ original: 'tickit', corrected: 'ticket', type: '拼写错误', confidence: '96%' }
]
//
const onFileReady = (data) => {
fileData.value = data.fileData
fileType.value = data.fileType
stepResults[0] = true
ElMessage.success('文件准备就绪,可以进行下一步操作')
}
//
const nextStep = () => {
if (activeStep.value < 4 && stepResults[activeStep.value]) {
activeStep.value += 1
}
}
//
const prevStep = () => {
if (activeStep.value > 0) {
//
const currentStep = activeStep.value
//
activeStep.value -= 1
//
for (let i = currentStep; i <= 4; i++) {
stepResults[i] = false
}
}
}
//
const goToStep = (step) => {
//
if (step === 0 || (step > 0 && stepResults[step-1])) {
activeStep.value = step
} else {
ElMessage.warning('请先完成前一步骤')
}
}
//
const handleFileUploadClick = () => {
activeStep.value = 0
}
//
const processPreprocess = () => {
simulateProcessing('正在进行预处理...', () => {
stepResults[1] = true
ElMessage.success('预处理完成!')
})
}
//
const processMergeFormat = () => {
simulateProcessing('正在合并格式...', () => {
stepResults[2] = true
ElMessage.success('格式合并完成!')
})
}
//
const processSpellCheck = () => {
simulateProcessing('正在进行单词纠错...', () => {
stepResults[3] = true
ElMessage.success('单词纠错完成!')
})
}
//
const processAnalysis = () => {
simulateProcessing('正在进行大模型分析,这可能需要一些时间...', () => {
stepResults[4] = true
ElMessage.success('大模型分析完成!')
}, 3000)
}
//
const simulateProcessing = (loadingText, callback, duration = 2000) => {
processing.value = true
const loadingInstance = ElLoading.service({
lock: true,
text: loadingText,
background: 'rgba(0, 0, 0, 0.7)'
})
//
setTimeout(() => {
loadingInstance.close()
processing.value = false
if (callback) callback()
}, duration)
}
//
const exportFinalResults = () => {
ElMessage.success('分析结果已导出')
}
</script>
<style scoped>
.main-page {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
background-color: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.header h1 {
color: #0066FF;
margin-bottom: 8px;
}
.subtitle {
color: #666;
font-size: 16px;
}
.step-content {
background: rgba(255, 255, 255, 0.95);
border-radius: 8px;
padding: 30px;
margin-bottom: 40px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
min-height: 500px;
}
.step-header {
margin-bottom: 20px;
}
.step-header h2 {
color: #0066FF;
margin-bottom: 8px;
}
.step-body {
display: flex;
gap: 30px;
}
.settings-panel {
flex: 1;
max-width: 300px;
}
.result-panel {
flex: 2;
}
.action-buttons {
margin-top: 20px;
}
.result-summary {
margin: 10px 0;
}
.data-preview {
margin-top: 20px;
}
.step-navigation-buttons {
margin-top: 30px;
display: flex;
justify-content: space-between;
}
.next-button {
margin-left: auto;
}
/* 步骤流程样式 */
.bottom-steps-container {
margin-top: 30px;
}
.steps-background {
background-color: rgba(230, 236, 245, 0.95);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.steps-wrapper {
max-width: 1000px;
margin: 0 auto;
}
.steps-flow-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.step-item-container {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.step-circle {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #bbc6d6;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 20px;
position: relative;
z-index: 2;
margin-bottom: 10px;
transition: all 0.3s;
}
.step-circle.active {
background-color: #0055dd;
transform: scale(1.2);
}
.step-circle.completed {
background-color: #55aa22;
}
.ripple-effect {
position: absolute;
top: -8px;
left: -8px;
right: -8px;
bottom: -8px;
border-radius: 50%;
border: 2px solid #0066FF;
animation: ripple 2s infinite;
}
@keyframes ripple {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.3);
opacity: 0;
}
}
.step-button-box {
cursor: pointer;
text-align: center;
padding: 15px;
border-radius: 4px;
transition: all 0.3s;
width: 120px;
background-color: rgba(220, 230, 245, 0.8);
}
.step-button-box.active {
background-color: #daebff;
color: #0055dd;
font-weight: bold;
box-shadow: 0 2px 8px rgba(0, 100, 255, 0.2);
}
.step-button-box.completed {
color: #55aa22;
background-color: rgba(230, 245, 230, 0.9);
}
.step-button-box.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.step-text {
margin-top: 5px;
font-weight: 500;
}
.step-connector {
flex-grow: 1;
height: 3px;
background-color: #bbc6d6;
margin: 0 10px;
position: relative;
z-index: 1;
transition: background-color 0.3s;
}
.active-connector {
background-color: #55aa22;
}
/* 分析结果样式 */
.analysis-summary {
padding: 16px;
}
.analysis-summary h4 {
margin-top: 20px;
margin-bottom: 10px;
color: #0066FF;
}
.analysis-summary ul {
padding-left: 20px;
}
.chart-placeholder {
height: 100px;
margin: 20px 0;
}
.mock-chart {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
gap: 10px;
}
.chart-bar {
height: 24px;
display: flex;
align-items: center;
padding-left: 10px;
color: white;
border-radius: 4px;
font-size: 14px;
}
.positive {
background-color: #67C23A;
}
.neutral {
background-color: #909399;
}
.negative {
background-color: #F56C6C;
}
.keyword-cloud {
padding: 20px;
text-align: center;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.keyword-item {
display: inline-block;
padding: 5px 10px;
border-radius: 20px;
background-color: #f5f7fa;
}
.correction-list {
margin-top: 20px;
}
.correction-list h4 {
margin-bottom: 10px;
color: #0066FF;
}
</style>

@ -1,268 +0,0 @@
<template>
<div
class="file-upload"
@dragover.prevent="onDragover"
@dragleave.prevent="onDragleave"
@drop.prevent="onDrop"
>
<div
class="dropzone"
:class="{ 'dropzone-active': isDragging, 'dropzone-has-files': hasFiles }"
>
<template v-if="!hasFiles">
<div class="upload-icon">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 16L12 8" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 11L12 8 15 11" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 16.7428C21.2215 15.734 22 14.2079 22 12.5C22 9.46243 19.5376 7 16.5 7C16.2815 7 16.0771 6.886 15.9661 6.69774C14.6621 4.48484 12.2544 3 9.5 3C5.35786 3 2 6.35786 2 10.5C2 12.5661 2.83545 14.4371 4.18695 15.7935" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 16L12 20L16 16" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="upload-text">
<div class="upload-title">拖放文件到此处</div>
<div class="upload-subtitle">或者</div>
</div>
<div class="upload-actions">
<button class="upload-btn" @click="openFileDialog"></button>
<input
ref="fileInput"
type="file"
class="file-input"
@change="onFileSelected"
multiple
>
</div>
</template>
<template v-else>
<div class="file-list">
<div v-for="(file, index) in files" :key="index" class="file-item">
<div class="file-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V9L13 2Z" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 2V9H20" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="file-info">
<div class="file-name">{{ file.name }}</div>
<div class="file-size">{{ formatFileSize(file.size) }}</div>
</div>
<button class="file-remove" @click="removeFile(index)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18" stroke="#FF3B30" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6L18 18" stroke="#FF3B30" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
<div class="upload-actions">
<button class="add-more-btn" @click="openFileDialog"></button>
<button class="clear-btn" @click="clearFiles"></button>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'FileUpload',
data() {
return {
files: [],
isDragging: false
}
},
computed: {
hasFiles() {
return this.files.length > 0
}
},
methods: {
openFileDialog() {
this.$refs.fileInput.click()
},
onFileSelected(event) {
const newFiles = Array.from(event.target.files)
this.files = [...this.files, ...newFiles]
// input
event.target.value = null
},
onDragover() {
this.isDragging = true
},
onDragleave() {
this.isDragging = false
},
onDrop(event) {
this.isDragging = false
const newFiles = Array.from(event.dataTransfer.files)
this.files = [...this.files, ...newFiles]
},
removeFile(index) {
this.files.splice(index, 1)
},
clearFiles() {
this.files = []
},
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
}
}
</script>
<style scoped>
.file-upload {
width: 100%;
}
.dropzone {
border: 2px dashed #E6F0FF;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s ease;
background-color: #F7FAFF;
cursor: pointer;
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.dropzone-active {
border-color: #0066FF;
background-color: rgba(0, 102, 255, 0.05);
}
.dropzone-has-files {
border-style: solid;
}
.upload-icon {
margin-bottom: 16px;
}
.upload-text {
margin-bottom: 16px;
}
.upload-title {
font-size: 1.1rem;
font-weight: 500;
margin-bottom: 4px;
color: #333333;
}
.upload-subtitle {
font-size: 0.9rem;
color: #666666;
}
.upload-btn {
background-color: #66CC33;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.upload-btn:hover {
background-color: #5ab82e;
}
.file-input {
display: none;
}
.file-list {
width: 100%;
margin-bottom: 16px;
}
.file-item {
display: flex;
align-items: center;
padding: 8px;
margin-bottom: 8px;
background-color: #F7FAFF;
border-radius: 4px;
border: 1px solid #E6F0FF;
}
.file-icon {
margin-right: 12px;
}
.file-info {
flex: 1;
text-align: left;
}
.file-name {
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 2px;
color: #333333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
.file-size {
font-size: 0.8rem;
color: #666666;
}
.file-remove {
background: none;
border: none;
cursor: pointer;
padding: 4px;
}
.upload-actions {
display: flex;
gap: 10px;
}
.add-more-btn {
background-color: transparent;
border: 1px solid #0066FF;
color: #0066FF;
border-radius: 4px;
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s;
}
.add-more-btn:hover {
background-color: rgba(0, 102, 255, 0.1);
}
.clear-btn {
background-color: transparent;
border: 1px solid #FF3B30;
color: #FF3B30;
border-radius: 4px;
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s;
}
.clear-btn:hover {
background-color: rgba(255, 59, 48, 0.1);
}
</style>

@ -1,307 +0,0 @@
<template>
<div class="format-selector">
<div class="format-label">转换为</div>
<div class="format-dropdown" @click="toggleDropdown">
<div class="selected-format">
<span class="format-icon">{{ selectedFormatIcon }}</span>
<span class="format-name">{{ selectedFormat.name }}</span>
<span class="dropdown-arrow" :class="{ 'dropdown-arrow-open': isDropdownOpen }"></span>
</div>
<div class="format-options" v-if="isDropdownOpen">
<div class="search-box">
<input
type="text"
v-model="searchQuery"
placeholder="搜索格式..."
@click.stop
ref="searchInput"
>
</div>
<div class="format-groups">
<div
v-for="(group, groupIndex) in filteredFormatGroups"
:key="groupIndex"
class="format-group"
>
<div class="group-title">{{ group.category }}</div>
<div class="group-items">
<div
v-for="(format, formatIndex) in group.formats"
:key="formatIndex"
class="format-item"
:class="{ 'format-item-active': isSelectedFormat(format) }"
@click.stop="selectFormat(format)"
>
<span class="format-item-icon">{{ format.icon }}</span>
<span class="format-item-name">{{ format.name }}</span>
<span class="format-item-ext">{{ format.extension }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'FormatSelector',
data() {
return {
isDropdownOpen: false,
searchQuery: '',
selectedFormat: { name: 'PDF 文档', extension: '.pdf', icon: '📄' },
formatGroups: [
{
category: '文档',
formats: [
{ name: 'PDF 文档', extension: '.pdf', icon: '📄' },
{ name: 'Word 文档', extension: '.docx', icon: '📝' },
{ name: 'Excel 表格', extension: '.xlsx', icon: '📊' },
{ name: 'PowerPoint 演示文稿', extension: '.pptx', icon: '📊' },
{ name: 'TXT 文本', extension: '.txt', icon: '📃' }
]
},
{
category: '图像',
formats: [
{ name: 'JPEG 图像', extension: '.jpg', icon: '🖼️' },
{ name: 'PNG 图像', extension: '.png', icon: '🖼️' },
{ name: 'SVG 矢量图', extension: '.svg', icon: '🔍' },
{ name: 'GIF 动图', extension: '.gif', icon: '🎞️' },
{ name: 'WebP 图像', extension: '.webp', icon: '🖼️' }
]
},
{
category: '音频',
formats: [
{ name: 'MP3 音频', extension: '.mp3', icon: '🎵' },
{ name: 'WAV 音频', extension: '.wav', icon: '🎵' },
{ name: 'AAC 音频', extension: '.aac', icon: '🎵' },
{ name: 'FLAC 无损音频', extension: '.flac', icon: '🎵' }
]
},
{
category: '视频',
formats: [
{ name: 'MP4 视频', extension: '.mp4', icon: '🎬' },
{ name: 'AVI 视频', extension: '.avi', icon: '🎬' },
{ name: 'MOV 视频', extension: '.mov', icon: '🎬' },
{ name: 'WebM 视频', extension: '.webm', icon: '🎬' }
]
}
]
}
},
computed: {
selectedFormatIcon() {
return this.selectedFormat.icon || '📄'
},
filteredFormatGroups() {
if (!this.searchQuery.trim()) {
return this.formatGroups
}
const query = this.searchQuery.toLowerCase()
return this.formatGroups
.map(group => {
const filteredFormats = group.formats.filter(format => {
return format.name.toLowerCase().includes(query) ||
format.extension.toLowerCase().includes(query)
})
return {
category: group.category,
formats: filteredFormats
}
})
.filter(group => group.formats.length > 0)
}
},
mounted() {
//
document.addEventListener('click', this.closeDropdown)
},
beforeUnmount() {
//
document.removeEventListener('click', this.closeDropdown)
},
methods: {
toggleDropdown() {
this.isDropdownOpen = !this.isDropdownOpen
if (this.isDropdownOpen) {
this.$nextTick(() => {
this.$refs.searchInput.focus()
})
}
},
closeDropdown() {
this.isDropdownOpen = false
this.searchQuery = ''
},
selectFormat(format) {
this.selectedFormat = format
this.closeDropdown()
},
isSelectedFormat(format) {
return this.selectedFormat.name === format.name &&
this.selectedFormat.extension === format.extension
}
}
}
</script>
<style scoped>
.format-selector {
width: 100%;
}
.format-label {
margin-bottom: 8px;
font-weight: 500;
color: #333333;
text-align: left;
}
.format-dropdown {
position: relative;
width: 100%;
}
.selected-format {
width: 100%;
display: flex;
align-items: center;
padding: 12px 16px;
background-color: #F7FAFF;
border: 1px solid #E6F0FF;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.selected-format:hover {
border-color: #0066FF;
}
.format-icon {
margin-right: 8px;
font-size: 1.2rem;
}
.format-name {
flex: 1;
text-align: left;
font-size: 1rem;
color: #333333;
}
.dropdown-arrow {
font-size: 0.7rem;
color: #666666;
transition: transform 0.3s;
}
.dropdown-arrow-open {
transform: rotate(180deg);
}
.format-options {
position: absolute;
top: calc(100% + 4px);
left: 0;
width: 100%;
max-height: 350px;
overflow-y: auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 100;
}
.search-box {
padding: 12px;
border-bottom: 1px solid #E6F0FF;
position: sticky;
top: 0;
background-color: white;
z-index: 1;
}
.search-box input {
width: 100%;
padding: 8px 12px;
border: 1px solid #E6F0FF;
border-radius: 4px;
font-size: 0.9rem;
}
.search-box input:focus {
outline: none;
border-color: #0066FF;
}
.format-groups {
padding: 8px 0;
}
.format-group {
margin-bottom: 16px;
}
.group-title {
padding: 4px 16px;
font-size: 0.8rem;
font-weight: 600;
color: #666666;
text-align: left;
}
.group-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 4px;
padding: 0 8px;
}
.format-item {
display: flex;
align-items: center;
padding: 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.format-item:hover {
background-color: #F7FAFF;
}
.format-item-active {
background-color: #E6F0FF;
}
.format-item-icon {
margin-right: 8px;
font-size: 1.1rem;
}
.format-item-name {
flex: 1;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.format-item-ext {
font-size: 0.8rem;
color: #666666;
margin-left: 4px;
}
</style>

@ -1,181 +0,0 @@
<template>
<div class="conversion-widget">
<div class="conversion-steps">
<div class="step active">
<div class="step-number">1</div>
<div class="step-text">选择文件</div>
</div>
<div class="step-connector"></div>
<div class="step">
<div class="step-number">2</div>
<div class="step-text">选择格式</div>
</div>
<div class="step-connector"></div>
<div class="step">
<div class="step-number">3</div>
<div class="step-text">转换</div>
</div>
</div>
<div class="conversion-container">
<div class="file-upload-section">
<FileUpload />
</div>
<div class="format-selection">
<FormatSelector />
</div>
<div class="conversion-action">
<button class="convert-btn">立即转换</button>
<div class="additional-options">
<label class="notification-option">
<input type="checkbox" v-model="emailNotification">
<span>转换完成后通过电子邮件通知我</span>
</label>
<div class="terms-protection">
<a href="#" class="protection-link">了解文件保护</a>
<span class="terms-text">使用即表示您同意我们的<a href="#">服务条款</a></span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import FileUpload from '../features/FileUpload.vue'
import FormatSelector from '../features/FormatSelector.vue'
export default {
name: 'ConversionWidget',
components: {
FileUpload,
FormatSelector
},
data() {
return {
emailNotification: false
}
}
}
</script>
<style scoped>
.conversion-widget {
background-color: #FFFFFF;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 30px;
max-width: 800px;
margin: 0 auto;
}
.conversion-steps {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
color: #333333;
}
.step.active .step-number {
background-color: #0066FF;
color: white;
}
.step-number {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #E6F0FF;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-bottom: 8px;
}
.step-connector {
width: 80px;
height: 2px;
background-color: #E6F0FF;
margin: 0 10px;
}
.conversion-container {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
.additional-options {
margin-top: 20px;
font-size: 0.9rem;
}
.notification-option {
display: flex;
align-items: center;
margin-bottom: 10px;
cursor: pointer;
}
.notification-option input {
margin-right: 8px;
}
.terms-protection {
display: flex;
flex-direction: column;
gap: 5px;
font-size: 0.8rem;
}
.protection-link {
color: #0066FF;
text-decoration: none;
}
.terms-text {
color: #666666;
}
.terms-text a {
color: #0066FF;
text-decoration: none;
}
.convert-btn {
background-color: #1473E6;
color: white;
border: none;
border-radius: 4px;
padding: 12px 24px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
}
.convert-btn:hover {
background-color: #0052cc;
}
@media (min-width: 768px) {
.conversion-container {
grid-template-columns: repeat(3, 1fr);
}
.additional-options {
grid-column: span 3;
}
}
</style>

@ -1,142 +0,0 @@
<template>
<header class="header">
<div class="container header-container">
<div class="logo">
<span class="logo-text">ZAMZAR</span>
<span class="logo-icon"></span>
</div>
<nav class="navigation">
<ul class="nav-list">
<li class="nav-item"><a href="#">API</a></li>
<li class="nav-item"><a href="#">格式</a></li>
<li class="nav-item"><a href="#">我的文件</a></li>
<li class="nav-item dropdown">
<a href="#">转换器 <span class="dropdown-icon"></span></a>
</li>
<li class="nav-item"><a href="#">价格</a></li>
<li class="nav-item"><a href="#">帮助</a></li>
</ul>
</nav>
<div class="auth-buttons">
<button class="btn btn-outline">登录</button>
<button class="btn btn-filled">注册</button>
</div>
</div>
</header>
</template>
<script>
export default {
name: 'AppHeader'
}
</script>
<style scoped>
.header {
height: 80px;
width: 100%;
background-color: #FFFFFF;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header-container {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
font-weight: bold;
font-size: 1.5rem;
}
.logo-text {
color: #0066FF;
}
.logo-icon {
color: #66CC33;
margin-left: 4px;
}
.navigation {
flex: 1;
margin: 0 20px;
}
.nav-list {
display: flex;
list-style: none;
justify-content: center;
}
.nav-item {
margin: 0 15px;
}
.nav-item a {
text-decoration: none;
color: #333333;
font-weight: 500;
transition: color 0.3s;
}
.nav-item a:hover {
color: #0066FF;
}
.dropdown-icon {
font-size: 0.7rem;
margin-left: 4px;
}
.auth-buttons {
display: flex;
gap: 10px;
}
.btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
}
.btn-outline {
background: transparent;
border: 1px solid #0066FF;
color: #0066FF;
}
.btn-outline:hover {
background-color: rgba(0, 102, 255, 0.1);
}
.btn-filled {
background-color: #0066FF;
border: 1px solid #0066FF;
color: white;
}
.btn-filled:hover {
background-color: #0052cc;
}
@media (max-width: 992px) {
.navigation {
display: none;
}
}
@media (max-width: 768px) {
.auth-buttons {
display: none;
}
}
</style>

@ -1,13 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const app = createApp(App)
app.use(ElementPlus, {
locale: zhCn
})
app.mount('#app')

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