基本界面

main
LiRen-qiu 5 months ago
parent b15d3f1428
commit 9df7b13034

@ -21,7 +21,6 @@ export default {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 20px;
}
body {
@ -29,4 +28,25 @@ body {
padding: 0;
background-color: #f5f7fa;
}
/* 全局样式 */
.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>

@ -1,24 +1,370 @@
<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 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>
<!-- 步骤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-switch>
</el-form-item>
<el-form-item label="标准化日期格式">
<el-switch v-model="preprocessSettings.standardizeDates"></el-switch>
</el-form-item>
<el-form-item label="移除特殊字符">
<el-switch v-model="preprocessSettings.removeSpecialChars"></el-switch>
</el-form-item>
</el-form>
<el-button type="primary" @click="processStep(1)"></el-button>
</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>
<!-- 步骤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-switch>
</el-form-item>
<el-form-item label="生成唯一ID">
<el-switch v-model="mergeSettings.generateUniqueIds"></el-switch>
</el-form-item>
</el-form>
<el-button type="primary" @click="processStep(2)"></el-button>
</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>
<!-- 步骤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-switch>
</el-form-item>
<el-form-item label="语法检查">
<el-switch v-model="spellCheckSettings.grammarCheck"></el-switch>
</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>
<el-button type="primary" @click="processStep(3)"></el-button>
</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>
<file-upload @file-ready="onFileReady" />
</el-card>
<!-- 下层区域多步骤处理流程将在后续实现 -->
<el-card class="process-section" v-if="fileReady">
<div slot="header" class="section-header">
<span>数据处理流程</span>
<!-- 步骤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>
<el-button type="primary" @click="processStep(4)"></el-button>
</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>
<div class="placeholder-text">
<p>文件处理准备就绪后续步骤将在下一阶段实现</p>
</div>
<!-- 底部步骤指示器 -->
<div class="bottom-steps-container">
<div class="steps-background">
<div class="steps-wrapper">
<!-- 新的步骤指示器样式 -->
<div class="steps-flow">
<!-- 步骤1 -->
<div class="step-circle" :class="{ 'active': activeStep >= 0, 'completed': stepResults[0] }">1</div>
<!-- 连接线 -->
<div class="step-connector"></div>
<!-- 步骤2 -->
<div class="step-circle" :class="{ 'active': activeStep >= 1, 'completed': stepResults[1] }">2</div>
<!-- 连接线 -->
<div class="step-connector"></div>
<!-- 步骤3 -->
<div class="step-circle" :class="{ 'active': activeStep >= 2, 'completed': stepResults[2] }">3</div>
<!-- 连接线 -->
<div class="step-connector"></div>
<!-- 步骤4 -->
<div class="step-circle" :class="{ 'active': activeStep >= 3, 'completed': stepResults[3] }">4</div>
<!-- 连接线 -->
<div class="step-connector"></div>
<!-- 步骤5 -->
<div class="step-circle" :class="{ 'active': activeStep >= 4, 'completed': stepResults[4] }">5</div>
</div>
<!-- 按钮区域 -->
<div class="step-buttons-container">
<!-- 步骤1按钮 -->
<div class="step-button-wrapper">
<div class="step-button" :class="{ 'active': activeStep === 0 }" @click="goToStep(0)">
<div class="btn-icon"><i class="el-icon-upload2"></i></div>
<div class="btn-text">上传文件</div>
</div>
</div>
<!-- 步骤2按钮 -->
<div class="step-button-wrapper">
<div class="step-button" :class="{ 'active': activeStep === 1, 'disabled': !stepResults[0] }" @click="goToStep(1)">
<div class="btn-text">预处理</div>
</div>
</div>
<!-- 步骤3按钮 -->
<div class="step-button-wrapper">
<div class="step-button" :class="{ 'active': activeStep === 2, 'disabled': !stepResults[1] }" @click="goToStep(2)">
<div class="btn-text">合并格式</div>
</div>
</div>
<!-- 步骤4按钮 -->
<div class="step-button-wrapper">
<div class="step-button" :class="{ 'active': activeStep === 3, 'disabled': !stepResults[2] }" @click="goToStep(3)">
<div class="btn-text">单词纠错</div>
</div>
</div>
<!-- 步骤5按钮 -->
<div class="step-button-wrapper">
<div class="step-button" :class="{ 'active': activeStep === 4, 'disabled': !stepResults[3] }" @click="goToStep(4)">
<div class="btn-text">大模型分析</div>
</div>
</div>
</div>
</div>
</div>
</el-card>
</div>
<!-- 只保留返回按钮和导出结果按钮 -->
<div class="step-navigation">
<el-button v-if="activeStep > 0" icon="el-icon-arrow-left" @click="prevStep"></el-button>
<el-button v-if="activeStep === 4 && stepResults[4]" type="success" icon="el-icon-download" @click="exportFinalResults"></el-button>
</div>
</div>
</template>
@ -32,53 +378,450 @@ export default {
},
data() {
return {
fileReady: false,
activeStep: 0,
fileData: null,
fileType: null
fileType: null,
stepResults: {
0: false,
1: false,
2: false,
3: false,
4: false
},
preprocessSettings: {
removeEmptyLines: true,
standardizeDates: true,
removeSpecialChars: false
},
mergeSettings: {
targetFormat: 'standard',
mergeSimilarItems: true,
generateUniqueIds: true
},
spellCheckSettings: {
enabled: true,
grammarCheck: true,
termBase: 'aviation'
},
analysisSettings: {
model: 'basic',
depth: 3,
dimensions: ['情感分析', '关键信息提取']
},
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' }
],
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: '航班延误'
}
],
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%' }
]
};
},
computed: {
canGoNext() {
return this.stepResults[this.activeStep];
}
},
methods: {
onFileReady(data) {
this.fileReady = true;
this.fileData = data.fileData;
this.fileType = data.fileType;
this.$message.success('文件已准备好,可以开始处理');
this.stepResults[0] = true;
this.$message.success('文件准备就绪,可以进行下一步操作');
},
nextStep() {
if (this.activeStep < 4 && this.stepResults[this.activeStep]) {
this.activeStep += 1;
}
},
prevStep() {
if (this.activeStep > 0) {
this.activeStep -= 1;
}
},
goToStep(step) {
//
if (step === 0 || (step > 0 && this.stepResults[step-1])) {
this.activeStep = step;
//
if (step === 0 && !this.stepResults[0]) {
this.openUploadDialog();
}
//
if (step > 0 && !this.stepResults[step]) {
this.processStep(step);
}
} else {
this.$message.warning('请先完成前一步骤');
}
},
processStep(step) {
//
const loadingInstance = this.$loading({
lock: true,
text: this.getLoadingText(step),
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
//
setTimeout(() => {
loadingInstance.close();
this.stepResults[step] = true;
this.$message.success(this.getSuccessMessage(step));
}, 2000);
},
getLoadingText(step) {
const texts = {
1: '正在进行预处理...',
2: '正在合并格式...',
3: '正在进行单词纠错...',
4: '正在进行大模型分析,这可能需要一些时间...'
};
return texts[step] || '处理中...';
},
getSuccessMessage(step) {
const messages = {
1: '预处理完成!',
2: '格式合并完成!',
3: '单词纠错完成!',
4: '大模型分析完成!'
};
return messages[step] || '处理完成!';
},
exportFinalResults() {
this.$message.success('分析结果已导出为CSV文件');
},
openUploadDialog() {
// ""
//
}
}
};
</script>
<style scoped>
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
.main-page {
min-height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #e6f7ff 0%, #ffffff 100%);
}
.app-title {
.header {
text-align: center;
padding: 40px 0 20px;
background: linear-gradient(135deg, #1976d2 0%, #2196f3 100%);
color: white;
margin-bottom: 30px;
color: #409EFF;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 32px;
margin: 0 0 10px;
}
.subtitle {
font-size: 16px;
opacity: 0.9;
margin: 0;
}
.step-content {
flex: 1;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.upload-section {
.process-step {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 20px;
margin-bottom: 30px;
}
.process-section {
transition: all 0.3s ease;
.step-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #ebeef5;
}
.step-header h2 {
margin: 0 0 10px;
color: #303133;
}
.step-header p {
margin: 0;
color: #606266;
}
.section-header {
.step-body {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.settings-panel {
flex: 1;
min-width: 300px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
}
.settings-panel h3 {
margin-top: 0;
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
color: #303133;
}
.placeholder-text {
.result-panel {
flex: 2;
min-width: 400px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
}
.result-panel h3 {
margin-top: 0;
margin-bottom: 20px;
font-size: 18px;
color: #303133;
}
.result-summary {
margin: 10px 0;
}
.data-preview {
margin-top: 20px;
}
.correction-list,
.analysis-results {
margin-top: 20px;
}
/* 底部步骤指示器样式 */
.bottom-steps-container {
width: 100%;
margin-top: 30px;
}
.steps-background {
background-color: #f0f9ff;
padding: 30px 0;
background-image: linear-gradient(to bottom, #87cefa 0%, #1e90ff 100%);
border-top: 1px solid #d1e9ff;
}
.steps-wrapper {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 新的流程步骤样式 */
.steps-flow {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 30px;
}
.step-circle {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 28px;
font-weight: bold;
position: relative;
border: 2px solid white;
transition: all 0.3s;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.step-circle.active, .step-circle.completed {
background-color: #67c23a;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.step-connector {
height: 2px;
background-color: rgba(255, 255, 255, 0.7);
flex-grow: 1;
max-width: 100px;
margin: 0 5px;
}
/* 按钮样式 */
.step-buttons-container {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-top: 20px;
}
.step-button-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 180px;
}
.step-button {
background-color: #f5f7fa;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px 15px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
width: 100%;
height: 50px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.step-button:hover {
background-color: #ecf5ff;
border-color: #c6e2ff;
}
.step-button.active {
background-color: #ecf5ff;
border-color: #409eff;
color: #409eff;
}
.step-button.disabled {
cursor: not-allowed;
opacity: 0.6;
background-color: #f5f7fa;
border-color: #dcdfe6;
color: #c0c4cc;
}
.btn-icon {
font-size: 20px;
margin-bottom: 5px;
}
.btn-text {
font-size: 14px;
font-weight: 500;
}
.step-hint {
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
margin-top: 5px;
}
/* 调整导航按钮位置 */
.step-navigation {
display: flex;
justify-content: space-between;
max-width: 1200px;
margin: 20px auto;
padding: 0 20px;
}
.chart-placeholder {
height: 200px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.mock-chart {
width: 80%;
}
.chart-bar {
height: 30px;
line-height: 30px;
margin-bottom: 10px;
color: white;
text-align: center;
color: #909399;
font-style: italic;
border-radius: 4px;
}
.chart-bar.positive {
background-color: #67C23A;
}
.chart-bar.neutral {
background-color: #E6A23C;
}
.chart-bar.negative {
background-color: #F56C6C;
}
.keyword-cloud {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
align-items: center;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
}
.keyword-item {
display: inline-block;
padding: 5px 10px;
border-radius: 15px;
background-color: rgba(0,0,0,0.03);
}
</style>

@ -0,0 +1,357 @@
<template>
<div class="conversion-process">
<div class="process-container">
<div class="process-header">
<h3>文件转换进度</h3>
</div>
<div class="progress-list">
<div v-for="(file, index) in conversionFiles" :key="index" class="file-progress-item">
<div class="file-info">
<i :class="getFileIcon(file.name)"></i>
<div class="file-details">
<div class="filename">{{ file.name }}</div>
<div class="file-status">{{ getStatusText(file.status) }}</div>
</div>
</div>
<div class="progress-bar-container">
<el-progress
:percentage="file.progress"
:status="getProgressStatus(file.status)"
:format="format">
</el-progress>
</div>
<div class="file-actions">
<el-button v-if="file.status === 'completed'" type="text" icon="el-icon-download" @click="downloadFile(file)"></el-button>
<el-button v-if="file.status === 'error'" type="text" icon="el-icon-refresh" @click="retryConversion(index)"></el-button>
<el-button type="text" icon="el-icon-delete" @click="removeFileFromList(index)"></el-button>
</div>
</div>
</div>
<div class="empty-state" v-if="conversionFiles.length === 0">
<i class="el-icon-document"></i>
<p>没有正在进行的转换任务</p>
</div>
<div class="process-actions" v-if="conversionFiles.length > 0">
<el-button type="primary" plain @click="downloadAll" :disabled="!hasCompletedFiles">全部下载</el-button>
<el-button @click="clearCompleted" :disabled="!hasCompletedFiles">清除已完成</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ConversionProcess',
props: {
//
files: {
type: Array,
default: () => []
}
},
data() {
return {
conversionFiles: [] //
};
},
computed: {
hasCompletedFiles() {
return this.conversionFiles.some(file => file.status === 'completed');
}
},
watch: {
files: {
immediate: true,
handler(newFiles) {
//
if (newFiles && newFiles.length > 0) {
this.initializeConversionFiles(newFiles);
}
}
}
},
methods: {
initializeConversionFiles(newFiles) {
//
const filesToAdd = newFiles.map(file => ({
id: Date.now() + Math.random().toString(36).substring(2, 9), // ID
name: file.name,
size: file.size,
status: 'pending', // pending, converting, completed, error
progress: 0,
result: null //
}));
this.conversionFiles = [...this.conversionFiles, ...filesToAdd];
//
filesToAdd.forEach((file, index) => {
this.simulateConversion(this.conversionFiles.length - filesToAdd.length + index);
});
},
simulateConversion(fileIndex) {
// API
if (fileIndex >= this.conversionFiles.length) return;
const file = this.conversionFiles[fileIndex];
file.status = 'converting';
// 2-5
const conversionTime = 2000 + Math.random() * 3000;
const updateInterval = 200; // 200ms
const steps = conversionTime / updateInterval;
let currentStep = 0;
const progressInterval = setInterval(() => {
currentStep++;
file.progress = Math.min(99, Math.floor((currentStep / steps) * 100));
// 99%interval
if (currentStep >= steps) {
clearInterval(progressInterval);
// 95%
if (Math.random() > 0.05) {
setTimeout(() => {
file.progress = 100;
file.status = 'completed';
file.result = 'https://example.com/download/' + file.id; //
}, 500);
} else {
file.status = 'error';
}
}
}, updateInterval);
},
getFileIcon(filename) {
const extension = filename.split('.').pop().toLowerCase();
//
const iconMap = {
pdf: 'el-icon-document',
doc: 'el-icon-document',
docx: 'el-icon-document',
xls: 'el-icon-document',
xlsx: 'el-icon-document',
ppt: 'el-icon-document',
pptx: 'el-icon-document',
jpg: 'el-icon-picture',
jpeg: 'el-icon-picture',
png: 'el-icon-picture',
gif: 'el-icon-picture',
svg: 'el-icon-picture',
mp4: 'el-icon-video-camera',
avi: 'el-icon-video-camera',
mov: 'el-icon-video-camera',
mp3: 'el-icon-headset',
wav: 'el-icon-headset',
ogg: 'el-icon-headset'
};
return iconMap[extension] || 'el-icon-document';
},
getStatusText(status) {
const statusMap = {
pending: '等待中',
converting: '转换中',
completed: '已完成',
error: '转换失败'
};
return statusMap[status] || '未知状态';
},
getProgressStatus(status) {
if (status === 'completed') return 'success';
if (status === 'error') return 'exception';
return '';
},
format(percentage) {
return percentage === 100 ? '完成' : `${percentage}%`;
},
downloadFile(file) {
if (file.status !== 'completed') {
this.$message.warning('文件尚未转换完成');
return;
}
//
this.$message.success(`正在下载: ${file.name}`);
//
window.open(file.result, '_blank');
},
downloadAll() {
const completedFiles = this.conversionFiles.filter(file => file.status === 'completed');
if (completedFiles.length === 0) {
this.$message.warning('没有已完成的文件可下载');
return;
}
// ZIP
this.$message.success(`正在下载 ${completedFiles.length} 个文件`);
//
completedFiles.forEach(file => {
setTimeout(() => {
window.open(file.result, '_blank');
}, 500);
});
},
retryConversion(index) {
const file = this.conversionFiles[index];
file.progress = 0;
this.simulateConversion(index);
},
removeFileFromList(index) {
//
this.$confirm(`确定要从列表中移除 ${this.conversionFiles[index].name}`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.conversionFiles.splice(index, 1);
this.$message.success('文件已从列表中移除');
}).catch(() => {});
},
clearCompleted() {
//
this.$confirm('确定要清除所有已完成的转换任务吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.conversionFiles = this.conversionFiles.filter(file => file.status !== 'completed');
this.$message.success('已清除所有已完成的转换任务');
}).catch(() => {});
}
}
};
</script>
<style scoped>
.conversion-process {
background-color: #f5f7fa;
padding: 40px 0;
}
.process-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.process-header {
margin-bottom: 20px;
}
.process-header h3 {
font-size: 24px;
color: #303133;
}
.progress-list {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.file-progress-item {
display: flex;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
}
.file-progress-item:last-child {
border-bottom: none;
}
.file-info {
display: flex;
align-items: center;
width: 30%;
min-width: 200px;
}
.file-info i {
font-size: 24px;
margin-right: 10px;
color: #909399;
}
.file-details {
overflow: hidden;
}
.filename {
font-weight: 500;
color: #303133;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-status {
font-size: 12px;
color: #909399;
}
.progress-bar-container {
flex: 1;
margin: 0 20px;
}
.file-actions {
display: flex;
justify-content: flex-end;
white-space: nowrap;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 50px 0;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.empty-state i {
font-size: 48px;
color: #c0c4cc;
margin-bottom: 20px;
}
.empty-state p {
color: #909399;
}
.process-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.process-actions .el-button {
margin-left: 10px;
}
</style>

@ -0,0 +1,414 @@
<template>
<div class="conversion-widget">
<div class="widget-container">
<h2>文件转换轻松搞定</h2>
<p class="subtitle">转换文档图片视频和音频 - 支持1100+种格式</p>
<div class="conversion-steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-text">选择文件</div>
</div>
<div class="step-divider"></div>
<div class="step">
<div class="step-number">2</div>
<div class="step-text">选择格式</div>
</div>
<div class="step-divider"></div>
<div class="step">
<div class="step-number">3</div>
<div class="step-text">转换文件</div>
</div>
</div>
<div class="conversion-form">
<div class="upload-section">
<div class="upload-area"
@dragover.prevent="onDragOver"
@dragleave.prevent="onDragLeave"
@drop.prevent="onDrop"
:class="{ 'drag-over': isDragOver }">
<div class="upload-content">
<i class="el-icon-upload"></i>
<div class="upload-text">
拖放文件到此处
<el-dropdown trigger="click" @command="handleCommand">
<el-button type="success">
选择文件 <i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="computer">从电脑选择</el-dropdown-item>
<el-dropdown-item command="cloud">从云端选择</el-dropdown-item>
<el-dropdown-item command="url">从URL添加</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<input
ref="fileInput"
type="file"
class="file-input"
multiple
@change="onFileSelect"
/>
</div>
</div>
<div class="selected-files" v-if="selectedFiles.length > 0">
<h3>已选择文件</h3>
<ul class="file-list">
<li v-for="(file, index) in selectedFiles" :key="index" class="file-item">
<span class="file-name">{{ file.name }}</span>
<span class="file-size">{{ formatFileSize(file.size) }}</span>
<el-button type="text" icon="el-icon-delete" @click="removeFile(index)"></el-button>
</li>
</ul>
</div>
</div>
<div class="format-selector">
<span class="format-label">转换为</span>
<el-select v-model="targetFormat" placeholder="选择格式">
<el-option-group
v-for="group in formatGroups"
:key="group.label"
:label="group.label">
<el-option
v-for="format in group.options"
:key="format.value"
:label="format.label"
:value="format.value">
</el-option>
</el-option-group>
</el-select>
</div>
<el-button type="primary" class="convert-button" :disabled="!canConvert" @click="convertFiles">
立即转换
</el-button>
<div class="additional-options">
<el-checkbox v-model="emailNotification"></el-checkbox>
<div class="terms">
<el-link type="info" icon="el-icon-info">文件保护信息</el-link>
<span>通过使用我们的服务您同意我们的<el-link type="primary">服务条款</el-link></span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ConversionWidget',
data() {
return {
selectedFiles: [],
targetFormat: '',
isDragOver: false,
emailNotification: false,
formatGroups: [
{
label: '文档',
options: [
{ value: 'pdf', label: 'PDF' },
{ value: 'docx', label: 'DOCX' },
{ value: 'xlsx', label: 'XLSX' },
{ value: 'pptx', label: 'PPTX' }
]
},
{
label: '图像',
options: [
{ value: 'jpg', label: 'JPG' },
{ value: 'png', label: 'PNG' },
{ value: 'svg', label: 'SVG' },
{ value: 'gif', label: 'GIF' }
]
},
{
label: '视频',
options: [
{ value: 'mp4', label: 'MP4' },
{ value: 'avi', label: 'AVI' },
{ value: 'mov', label: 'MOV' }
]
},
{
label: '音频',
options: [
{ value: 'mp3', label: 'MP3' },
{ value: 'wav', label: 'WAV' },
{ value: 'ogg', label: 'OGG' }
]
}
]
};
},
computed: {
canConvert() {
return this.selectedFiles.length > 0 && this.targetFormat;
}
},
methods: {
onDragOver() {
this.isDragOver = true;
},
onDragLeave() {
this.isDragOver = false;
},
onDrop(event) {
this.isDragOver = false;
const files = event.dataTransfer.files;
this.addFiles(files);
},
onFileSelect(event) {
this.addFiles(event.target.files);
// file input便
this.$refs.fileInput.value = '';
},
addFiles(files) {
if (!files || files.length === 0) return;
// FileList
const fileArray = Array.from(files);
this.selectedFiles = [...this.selectedFiles, ...fileArray];
//
if (this.selectedFiles.length > 0 && !this.targetFormat) {
//
const fileType = this.selectedFiles[0].type;
if (fileType.includes('image')) {
this.targetFormat = 'jpg';
} else if (fileType.includes('video')) {
this.targetFormat = 'mp4';
} else if (fileType.includes('audio')) {
this.targetFormat = 'mp3';
} else {
this.targetFormat = 'pdf'; // PDF
}
}
},
removeFile(index) {
this.selectedFiles.splice(index, 1);
},
handleCommand(command) {
if (command === 'computer') {
this.$refs.fileInput.click();
} else if (command === 'cloud') {
this.$message.info('云端文件选择功能即将推出');
} else if (command === 'url') {
this.$message.info('URL添加功能即将推出');
}
},
formatFileSize(size) {
if (size < 1024) {
return size + ' B';
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + ' KB';
} else if (size < 1024 * 1024 * 1024) {
return (size / (1024 * 1024)).toFixed(2) + ' MB';
} else {
return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
},
convertFiles() {
//
this.$message.success(`开始转换 ${this.selectedFiles.length} 个文件到 ${this.targetFormat} 格式`);
// API
this.$emit('conversion-started', {
files: this.selectedFiles,
targetFormat: this.targetFormat,
emailNotification: this.emailNotification
});
}
}
};
</script>
<style scoped>
.conversion-widget {
background: linear-gradient(135deg, #0066FF 0%, #1473E6 100%);
padding: 60px 0;
color: #fff;
}
.widget-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
text-align: center;
}
h2 {
font-size: 36px;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 18px;
margin-bottom: 40px;
}
.conversion-steps {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40px;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
}
.step-number {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #fff;
color: #0066FF;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 18px;
margin-bottom: 10px;
}
.step-text {
font-size: 14px;
}
.step-divider {
width: 80px;
height: 2px;
background-color: rgba(255, 255, 255, 0.5);
margin: 0 15px;
}
.conversion-form {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 30px;
max-width: 800px;
margin: 0 auto;
color: #333;
}
.upload-area {
border: 2px dashed #dcdfe6;
border-radius: 6px;
padding: 30px;
margin-bottom: 20px;
transition: all 0.3s;
}
.upload-area.drag-over {
border-color: #66CC33;
background-color: rgba(102, 204, 51, 0.1);
}
.upload-content {
display: flex;
flex-direction: column;
align-items: center;
}
.upload-content i {
font-size: 48px;
color: #909399;
margin-bottom: 20px;
}
.upload-text {
margin-bottom: 20px;
}
.file-input {
display: none;
}
.selected-files {
text-align: left;
margin-bottom: 20px;
}
.file-list {
list-style: none;
padding: 0;
margin: 0;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #ebeef5;
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
}
.file-size {
color: #909399;
margin-right: 10px;
}
.format-selector {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.format-label {
margin-right: 10px;
font-weight: bold;
}
.el-select {
width: 200px;
}
.convert-button {
width: 100%;
height: 50px;
font-size: 16px;
margin-bottom: 20px;
background-color: #1473E6;
border-color: #1473E6;
}
.convert-button:hover, .convert-button:focus {
background-color: #0066FF;
border-color: #0066FF;
}
.additional-options {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.terms {
margin-top: 10px;
font-size: 12px;
color: #606266;
display: flex;
align-items: center;
}
.terms .el-link {
margin-right: 5px;
}
</style>

@ -0,0 +1,112 @@
<template>
<header class="header">
<div class="header-container">
<div class="logo">
<h1>ZAMZAR</h1>
<div class="logo-icon">
<i class="el-icon-d-arrow-right"></i>
</div>
</div>
<nav class="main-nav">
<ul>
<li><a href="#">API</a></li>
<li><a href="#">格式</a></li>
<li><a href="#">我的文件</a></li>
<li class="dropdown">
<a href="#">转换器 <i class="el-icon-arrow-down"></i></a>
</li>
<li><a href="#">价格</a></li>
<li><a href="#">帮助</a></li>
</ul>
</nav>
<div class="auth-buttons">
<el-button type="text">登录</el-button>
<el-button type="primary">注册</el-button>
</div>
</div>
</header>
</template>
<script>
export default {
name: 'AppHeader'
}
</script>
<style scoped>
.header {
background-color: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
height: 60px;
width: 100%;
}
.header-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
padding: 0 24px;
}
.logo {
display: flex;
align-items: center;
}
.logo h1 {
font-size: 1.5rem;
margin: 0;
font-weight: bold;
color: #0066FF;
}
.logo-icon {
margin-left: 5px;
color: #0066FF;
}
.main-nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.main-nav li {
margin: 0 15px;
}
.main-nav a {
text-decoration: none;
color: #333333;
font-weight: 500;
transition: color 0.3s;
}
.main-nav a:hover {
color: #0066FF;
}
.dropdown {
position: relative;
}
.auth-buttons {
display: flex;
align-items: center;
}
.auth-buttons .el-button {
margin-left: 10px;
}
.el-button--primary {
background-color: #0066FF;
border-color: #0066FF;
}
</style>
Loading…
Cancel
Save