初始提交:办公智能编稿系统

- 完成Vue.js办公智能编稿系统基础架构
- 实现首页导航界面,包含4个主要功能模块
- 完成技术文档生成器,支持4步流程:选择类型→输入需求→生成文档→预览导出
- 完成办公助手界面,支持9个业务分析代理
- 实现自定义头像系统,显示代理名称首字符
- 添加销售分析、客户分析、产品分析等业务代理
- 集成Element Plus UI组件库
- 配置开发环境和构建系统
This commit is contained in:
yxj52 2025-08-30 09:32:07 +08:00
parent 1387d804a2
commit 37b159fb7e
22 changed files with 8924 additions and 8 deletions

99
.gitignore vendored
View File

@ -1,11 +1,94 @@
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# Dependencies
node_modules/
package-lock.json
# TODO: where does this rule come from?
docs/_book
# Build outputs
dist/
build/
# TODO: where does this rule come from?
test/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/

1501
dify_api.md Normal file

File diff suppressed because it is too large Load Diff

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>办公智能编稿系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "office-intelligent-drafting",
"version": "1.0.0",
"description": "办公智能编稿系统",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"echarts": "^5.6.0",
"element-plus": "^2.3.8",
"marked": "^16.2.0",
"vue": "^3.3.4",
"vue-echarts": "^7.0.3",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.4.5"
}
}

112
src/App.vue Normal file
View File

@ -0,0 +1,112 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
/* 全局样式 */
.page-container {
min-height: 100vh;
background-color: #f5f7fa;
}
.page-header {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px 0;
margin-bottom: 30px;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.page-title {
display: flex;
align-items: center;
gap: 15px;
}
.page-title h1 {
font-size: 28px;
color: #303133;
margin: 0;
font-weight: 600;
}
.page-title .title-icon {
font-size: 32px;
color: #409EFF;
}
.back-button {
display: flex;
align-items: center;
gap: 8px;
color: #606266;
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
transition: all 0.3s ease;
}
.back-button:hover {
background-color: #f5f7fa;
color: #409EFF;
}
.page-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px 40px 20px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 15px;
text-align: center;
}
.page-title h1 {
font-size: 24px;
}
.page-content {
padding: 0 15px 30px 15px;
}
}
</style>

112
src/App_new.vue Normal file
View File

@ -0,0 +1,112 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
/* 全局样式 */
.page-container {
min-height: 100vh;
background-color: #f5f7fa;
}
.page-header {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px 0;
margin-bottom: 30px;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.page-title {
display: flex;
align-items: center;
gap: 15px;
}
.page-title h1 {
font-size: 28px;
color: #303133;
margin: 0;
font-weight: 600;
}
.page-title .title-icon {
font-size: 32px;
color: #409EFF;
}
.back-button {
display: flex;
align-items: center;
gap: 8px;
color: #606266;
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
transition: all 0.3s ease;
}
.back-button:hover {
background-color: #f5f7fa;
color: #409EFF;
}
.page-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px 40px 20px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 15px;
text-align: center;
}
.page-title h1 {
font-size: 24px;
}
.page-content {
padding: 0 15px 30px 15px;
}
}
</style>

314
src/App_old.vue Normal file
View File

@ -0,0 +1,314 @@
<template>
<div id="app">
<div class="app-container">
<!-- 移动端菜单按钮 -->
<div class="mobile-menu-button" v-if="isMobile">
<el-button @click="toggleSidebar" type="primary" plain>
<i :class="sidebarVisible ? 'el-icon-close' : 'el-icon-menu'"></i>
</el-button>
</div>
<!-- 桌面端侧边栏切换按钮 -->
<div class="desktop-sidebar-toggle" v-if="!isMobile && shouldHideSidebar">
<el-button @click="toggleSidebar" type="primary" circle>
<i :class="sidebarHidden ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'"></i>
</el-button>
</div>
<!-- 侧边栏 -->
<div class="sidebar" :class="{
'sidebar-hidden': (isMobile && !sidebarVisible) || (!isMobile && shouldHideSidebar && !sidebarHidden)
}">>
<div class="logo">
<h2>办公智能编稿系统</h2>
</div>
<el-menu
:default-active="$route.path"
class="sidebar-menu"
router
background-color="#f5f7fa"
text-color="#303133"
active-text-color="#409EFF"
@select="handleMenuSelect"
>
<el-menu-item index="/document-generation">
<i class="el-icon-document"></i>
<span>文档生成</span>
</el-menu-item>
<el-menu-item index="/office-assistant">
<i class="el-icon-service"></i>
<span>办公助手</span>
</el-menu-item>
<el-menu-item index="/knowledge-qa">
<i class="el-icon-chat-line-square"></i>
<span>知识问答</span>
</el-menu-item>
<el-menu-item index="/office-agent">
<i class="el-icon-user"></i>
<span>办公智能体</span>
</el-menu-item>
</el-menu>
</div>
<!-- 遮罩层 -->
<div
class="sidebar-overlay"
v-if="isMobile && sidebarVisible"
@click="closeSidebar"
></div>
<!-- 主内容区域 -->
<div class="main-content" :class="{
'content-full-width': (!isMobile && shouldHideSidebar && !sidebarHidden)
}">
<router-view />
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useRoute } from 'vue-router'
export default {
name: 'App',
setup() {
const route = useRoute()
const isMobile = ref(false)
const sidebarVisible = ref(false)
const sidebarHidden = ref(false)
//
const hideSidebarRoutes = [
'/document-generation/technical-document',
'/document-generation/speech-generation'
]
//
const shouldHideSidebar = computed(() => {
return hideSidebarRoutes.includes(route.path) || sidebarHidden.value
})
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
if (!isMobile.value) {
sidebarVisible.value = false
}
}
const toggleSidebar = () => {
if (shouldHideSidebar.value && !isMobile.value) {
sidebarHidden.value = !sidebarHidden.value
} else {
sidebarVisible.value = !sidebarVisible.value
}
}
const closeSidebar = () => {
sidebarVisible.value = false
}
const handleMenuSelect = () => {
if (isMobile.value) {
sidebarVisible.value = false
}
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
return {
isMobile,
sidebarVisible,
sidebarHidden,
shouldHideSidebar,
toggleSidebar,
closeSidebar,
handleMenuSelect
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100vh;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app-container {
display: flex;
height: 100vh;
}
.sidebar {
width: 250px;
background-color: #f5f7fa;
border-right: 1px solid #e4e7ed;
flex-shrink: 0;
transition: transform 0.3s ease;
}
.sidebar-hidden {
transform: translateX(-250px);
}
.logo {
padding: 20px;
text-align: center;
border-bottom: 1px solid #e4e7ed;
}
.logo h2 {
color: #303133;
font-size: 18px;
font-weight: 600;
margin: 0;
}
.sidebar-menu {
border: none;
padding: 10px 0;
}
.sidebar-menu .el-menu-item {
height: 56px;
line-height: 56px;
margin: 0 10px;
border-radius: 6px;
}
.sidebar-menu .el-menu-item i {
margin-right: 8px;
font-size: 18px;
}
.main-content {
flex: 1;
overflow-y: auto;
background-color: #ffffff;
transition: margin-left 0.3s ease;
}
.content-full-width {
margin-left: -250px;
width: calc(100% + 250px);
}
/* 响应式设计 */
.mobile-menu-button {
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
display: none;
}
.desktop-sidebar-toggle {
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
transition: left 0.3s ease;
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
@media (max-width: 768px) {
.mobile-menu-button {
display: block;
}
.desktop-sidebar-toggle {
display: none;
}
.app-container {
flex-direction: row;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 1000;
transform: translateX(0);
transition: transform 0.3s ease;
}
.sidebar-hidden {
transform: translateX(-100%);
}
.main-content {
width: 100%;
padding-top: 60px;
margin-left: 0 !important;
}
.content-full-width {
margin-left: 0 !important;
width: 100% !important;
}
}
/* 为视图页面添加通用样式 */
.welcome-section {
text-align: center;
padding: 40px 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin-bottom: 30px;
}
.welcome-section h1 {
font-size: 32px;
color: #303133;
margin-bottom: 10px;
font-weight: 600;
}
.welcome-section p {
font-size: 16px;
color: #606266;
margin: 0;
}
.content-wrapper {
padding: 20px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
max-width: 1200px;
margin: 0 auto;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,356 @@
<template>
<div class="error-correction">
<div class="correction-container">
<div class="input-section">
<h4>输入需要检查的文本</h4>
<el-input
v-model="inputText"
type="textarea"
:rows="8"
placeholder="请粘贴或输入需要检查错误的文本内容..."
class="input-textarea"
/>
<div class="input-actions">
<el-button type="primary" @click="checkErrors" :loading="checking">
<i class="el-icon-search"></i>
检查错误
</el-button>
<el-button @click="clearInput">清空</el-button>
<el-button @click="loadSampleText" type="info">加载示例</el-button>
</div>
</div>
<div class="result-section" v-if="errors.length > 0">
<h4>检查结果</h4>
<div class="error-stats">
<el-alert
:title="`发现 ${errors.length} 处需要修正的地方`"
type="warning"
:closable="false"
show-icon
/>
</div>
<div class="errors-list">
<el-card
v-for="(error, index) in errors"
:key="index"
class="error-item"
shadow="hover"
>
<div class="error-content">
<div class="error-header">
<el-tag :type="getErrorTypeColor(error.type)" size="small">
{{ error.type }}
</el-tag>
<span class="error-position"> {{ error.line }} </span>
</div>
<div class="error-detail">
<div class="original-text">
<strong>原文</strong>
<span class="highlight-error">{{ error.original }}</span>
</div>
<div class="suggested-text">
<strong>建议</strong>
<span class="highlight-suggestion">{{ error.suggestion }}</span>
</div>
<div class="error-reason">
<strong>说明</strong>{{ error.reason }}
</div>
</div>
<div class="error-actions">
<el-button size="small" type="primary" @click="acceptSuggestion(index)">
采纳建议
</el-button>
<el-button size="small" @click="ignoreSuggestion(index)">
忽略
</el-button>
</div>
</div>
</el-card>
</div>
<div class="global-actions">
<el-button type="success" @click="acceptAllSuggestions">
采纳全部建议
</el-button>
<el-button @click="applyCorrections">
应用修正
</el-button>
</div>
</div>
<div class="output-section" v-if="correctedText">
<h4>修正后的文本</h4>
<el-input
v-model="correctedText"
type="textarea"
:rows="8"
readonly
class="output-textarea"
/>
<div class="output-actions">
<el-button type="primary" @click="copyToClipboard">
<i class="el-icon-document-copy"></i>
复制文本
</el-button>
<el-button @click="downloadText">
<i class="el-icon-download"></i>
下载文件
</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const inputText = ref('')
const correctedText = ref('')
const checking = ref(false)
const errors = ref([])
const checkErrors = async () => {
if (!inputText.value.trim()) {
ElMessage.warning('请输入需要检查的文本')
return
}
checking.value = true
try {
//
await new Promise(resolve => setTimeout(resolve, 2000))
//
errors.value = [
{
type: '拼写错误',
line: 1,
original: '办公室',
suggestion: '办公室',
reason: '检测到可能的拼写错误',
position: { start: 10, end: 13 }
},
{
type: '语法错误',
line: 2,
original: '我们需要尽快的完成',
suggestion: '我们需要尽快完成',
reason: '"的"字使用不当,建议删除',
position: { start: 25, end: 32 }
},
{
type: '标点符号',
line: 3,
original: '项目进展顺利,',
suggestion: '项目进展顺利,',
reason: '应使用中文逗号',
position: { start: 45, end: 51 }
},
{
type: '格式问题',
line: 4,
original: ' 多余的空格',
suggestion: '多余的空格',
reason: '段落开头有多余的空格',
position: { start: 60, end: 67 }
}
]
ElMessage.success('错误检查完成')
} catch (error) {
ElMessage.error('检查失败,请重试')
} finally {
checking.value = false
}
}
const getErrorTypeColor = (type) => {
const colorMap = {
'拼写错误': 'danger',
'语法错误': 'warning',
'标点符号': 'info',
'格式问题': 'primary'
}
return colorMap[type] || 'default'
}
const acceptSuggestion = (index) => {
errors.value[index].accepted = true
ElMessage.success('已采纳建议')
}
const ignoreSuggestion = (index) => {
errors.value.splice(index, 1)
ElMessage.info('已忽略该建议')
}
const acceptAllSuggestions = () => {
errors.value.forEach(error => {
error.accepted = true
})
ElMessage.success('已采纳全部建议')
}
const applyCorrections = () => {
let text = inputText.value
const acceptedErrors = errors.value.filter(error => error.accepted)
//
acceptedErrors.sort((a, b) => b.position.start - a.position.start)
acceptedErrors.forEach(error => {
text = text.substring(0, error.position.start) +
error.suggestion +
text.substring(error.position.end)
})
correctedText.value = text
ElMessage.success('修正已应用')
}
const clearInput = () => {
inputText.value = ''
errors.value = []
correctedText.value = ''
}
const loadSampleText = () => {
inputText.value = `办公室工作报告
我们需要尽快的完成这个项目项目进展顺利,团队成员都很积极
多余的空格应该被删除
请大家注意以下几点:
1按时提交报告
2保证质量
3加强沟通
谢谢大家的配合`
ElMessage.success('示例文本已加载')
}
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(correctedText.value)
ElMessage.success('已复制到剪贴板')
} catch (error) {
ElMessage.error('复制失败')
}
}
const downloadText = () => {
const blob = new Blob([correctedText.value], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '修正后的文本.txt'
link.click()
URL.revokeObjectURL(url)
ElMessage.success('文件下载开始')
}
</script>
<style scoped>
.error-correction {
padding: 20px;
}
.correction-container {
max-width: 1000px;
margin: 0 auto;
}
.input-section,
.result-section,
.output-section {
margin-bottom: 30px;
}
h4 {
margin: 0 0 15px 0;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.input-textarea,
.output-textarea {
margin-bottom: 15px;
}
.input-actions,
.output-actions,
.global-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.error-stats {
margin-bottom: 20px;
}
.errors-list {
margin-bottom: 20px;
}
.error-item {
margin-bottom: 15px;
}
.error-content {
padding: 15px;
}
.error-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.error-position {
color: #909399;
font-size: 12px;
}
.error-detail {
margin-bottom: 15px;
}
.error-detail > div {
margin-bottom: 8px;
line-height: 1.5;
}
.highlight-error {
background-color: #FEF0F0;
color: #F56C6C;
padding: 2px 4px;
border-radius: 3px;
}
.highlight-suggestion {
background-color: #F0F9FF;
color: #409EFF;
padding: 2px 4px;
border-radius: 3px;
}
.error-actions {
display: flex;
gap: 10px;
}
.global-actions {
text-align: center;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
</style>

View File

@ -0,0 +1,652 @@
<template>
<div class="key-info-extraction">
<div class="extraction-container">
<div class="input-section">
<h4>输入文档内容</h4>
<el-tabs v-model="inputMethod" class="input-tabs">
<el-tab-pane label="文本输入" name="text">
<el-input
v-model="inputText"
type="textarea"
:rows="10"
placeholder="请粘贴或输入需要提取关键信息的文档内容..."
class="input-textarea"
/>
</el-tab-pane>
<el-tab-pane label="文件上传" name="file">
<el-upload
class="file-upload"
drag
:before-upload="beforeUpload"
:on-change="handleFileChange"
:auto-upload="false"
accept=".txt,.doc,.docx,.pdf"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文档拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持 TXTDOCDOCXPDF 格式文件大小不超过 10MB
</div>
</el-upload>
</el-tab-pane>
</el-tabs>
<div class="extraction-options">
<h5>提取选项</h5>
<el-checkbox-group v-model="extractionTypes">
<el-checkbox label="keywords">关键词</el-checkbox>
<el-checkbox label="summary">摘要</el-checkbox>
<el-checkbox label="entities">实体识别</el-checkbox>
<el-checkbox label="topics">主题分析</el-checkbox>
<el-checkbox label="sentiment">情感分析</el-checkbox>
<el-checkbox label="structure">文档结构</el-checkbox>
</el-checkbox-group>
</div>
<div class="input-actions">
<el-button type="primary" @click="extractInformation" :loading="extracting">
<i class="el-icon-search"></i>
提取关键信息
</el-button>
<el-button @click="clearInput">清空</el-button>
<el-button @click="loadSampleDocument" type="info">加载示例</el-button>
</div>
</div>
<div class="results-section" v-if="extractionResults">
<h4>提取结果</h4>
<!-- 关键词 -->
<el-card v-if="extractionResults.keywords" class="result-card">
<template #header>
<div class="card-header">
<span>关键词</span>
<el-tag size="small">{{ extractionResults.keywords.length }} </el-tag>
</div>
</template>
<div class="keywords-container">
<el-tag
v-for="(keyword, index) in extractionResults.keywords"
:key="index"
class="keyword-tag"
:type="getKeywordType(keyword.importance)"
effect="light"
>
{{ keyword.word }}
<span class="keyword-score">({{ keyword.score }})</span>
</el-tag>
</div>
</el-card>
<!-- 摘要 -->
<el-card v-if="extractionResults.summary" class="result-card">
<template #header>
<div class="card-header">
<span>文档摘要</span>
<el-tag size="small">{{ extractionResults.summary.length }} </el-tag>
</div>
</template>
<div class="summary-content">
<p>{{ extractionResults.summary }}</p>
</div>
</el-card>
<!-- 实体识别 -->
<el-card v-if="extractionResults.entities" class="result-card">
<template #header>
<div class="card-header">
<span>实体识别</span>
<el-tag size="small">{{ extractionResults.entities.length }} </el-tag>
</div>
</template>
<div class="entities-container">
<div
v-for="(entity, index) in extractionResults.entities"
:key="index"
class="entity-item"
>
<el-tag :type="getEntityType(entity.type)" size="small">{{ entity.type }}</el-tag>
<span class="entity-text">{{ entity.text }}</span>
<span class="entity-confidence">{{ entity.confidence }}%</span>
</div>
</div>
</el-card>
<!-- 主题分析 -->
<el-card v-if="extractionResults.topics" class="result-card">
<template #header>
<div class="card-header">
<span>主题分析</span>
<el-tag size="small">{{ extractionResults.topics.length }} 个主题</el-tag>
</div>
</template>
<div class="topics-container">
<div
v-for="(topic, index) in extractionResults.topics"
:key="index"
class="topic-item"
>
<div class="topic-header">
<h6>{{ topic.name }}</h6>
<el-progress
:percentage="topic.relevance"
:stroke-width="6"
:show-text="false"
/>
<span class="topic-score">{{ topic.relevance }}%</span>
</div>
<div class="topic-keywords">
<el-tag
v-for="keyword in topic.keywords"
:key="keyword"
size="mini"
type="info"
>
{{ keyword }}
</el-tag>
</div>
</div>
</div>
</el-card>
<!-- 情感分析 -->
<el-card v-if="extractionResults.sentiment" class="result-card">
<template #header>
<div class="card-header">
<span>情感分析</span>
<el-tag
:type="getSentimentType(extractionResults.sentiment.overall)"
size="small"
>
{{ getSentimentLabel(extractionResults.sentiment.overall) }}
</el-tag>
</div>
</template>
<div class="sentiment-container">
<div class="sentiment-overall">
<div class="sentiment-scores">
<div class="score-item">
<span>积极:</span>
<el-progress
:percentage="extractionResults.sentiment.positive * 100"
status="success"
:stroke-width="8"
/>
</div>
<div class="score-item">
<span>中性:</span>
<el-progress
:percentage="extractionResults.sentiment.neutral * 100"
:stroke-width="8"
/>
</div>
<div class="score-item">
<span>消极:</span>
<el-progress
:percentage="extractionResults.sentiment.negative * 100"
status="exception"
:stroke-width="8"
/>
</div>
</div>
</div>
</div>
</el-card>
<!-- 文档结构 -->
<el-card v-if="extractionResults.structure" class="result-card">
<template #header>
<div class="card-header">
<span>文档结构</span>
</div>
</template>
<div class="structure-container">
<el-tree
:data="extractionResults.structure"
:props="{ children: 'children', label: 'title' }"
default-expand-all
class="structure-tree"
/>
</div>
</el-card>
<div class="results-actions">
<el-button type="primary" @click="exportResults">
<i class="el-icon-download"></i>
导出结果
</el-button>
<el-button @click="shareResults">
<i class="el-icon-share"></i>
分享结果
</el-button>
<el-button @click="saveToHistory">
<i class="el-icon-folder-add"></i>
保存到历史
</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const inputMethod = ref('text')
const inputText = ref('')
const extracting = ref(false)
const extractionTypes = ref(['keywords', 'summary', 'entities'])
const extractionResults = ref(null)
const extractInformation = async () => {
if (!inputText.value.trim()) {
ElMessage.warning('请输入需要分析的文档内容')
return
}
if (extractionTypes.value.length === 0) {
ElMessage.warning('请选择至少一种提取类型')
return
}
extracting.value = true
try {
//
await new Promise(resolve => setTimeout(resolve, 2500))
const results = {}
if (extractionTypes.value.includes('keywords')) {
results.keywords = [
{ word: '办公自动化', score: 95, importance: 'high' },
{ word: '工作效率', score: 88, importance: 'high' },
{ word: '数字化转型', score: 82, importance: 'medium' },
{ word: '团队协作', score: 75, importance: 'medium' },
{ word: '项目管理', score: 70, importance: 'medium' },
{ word: '技术创新', score: 65, importance: 'low' }
]
}
if (extractionTypes.value.includes('summary')) {
results.summary = '本文档主要讨论了办公自动化在现代企业中的重要性,强调了数字化转型对提高工作效率的作用。文章分析了当前办公环境面临的挑战,并提出了通过技术创新和团队协作来解决问题的方案。'
}
if (extractionTypes.value.includes('entities')) {
results.entities = [
{ text: '张经理', type: '人名', confidence: 98 },
{ text: '北京分公司', type: '机构', confidence: 95 },
{ text: '2024年3月', type: '时间', confidence: 92 },
{ text: 'Office365', type: '产品', confidence: 89 },
{ text: '人工智能', type: '概念', confidence: 85 }
]
}
if (extractionTypes.value.includes('topics')) {
results.topics = [
{
name: '办公自动化',
relevance: 85,
keywords: ['自动化', '效率', '工具', '流程']
},
{
name: '数字化转型',
relevance: 72,
keywords: ['数字化', '转型', '技术', '创新']
},
{
name: '团队管理',
relevance: 65,
keywords: ['团队', '管理', '协作', '沟通']
}
]
}
if (extractionTypes.value.includes('sentiment')) {
results.sentiment = {
overall: 'positive',
positive: 0.65,
neutral: 0.25,
negative: 0.10
}
}
if (extractionTypes.value.includes('structure')) {
results.structure = [
{
title: '1. 引言',
children: [
{ title: '1.1 背景介绍' },
{ title: '1.2 研究目的' }
]
},
{
title: '2. 主要内容',
children: [
{ title: '2.1 现状分析' },
{ title: '2.2 解决方案' },
{ title: '2.3 实施计划' }
]
},
{
title: '3. 总结',
children: [
{ title: '3.1 主要结论' },
{ title: '3.2 建议' }
]
}
]
}
extractionResults.value = results
ElMessage.success('信息提取完成')
} catch (error) {
ElMessage.error('提取失败,请重试')
} finally {
extracting.value = false
}
}
const getKeywordType = (importance) => {
const typeMap = {
'high': 'danger',
'medium': 'warning',
'low': 'info'
}
return typeMap[importance] || 'default'
}
const getEntityType = (type) => {
const typeMap = {
'人名': 'success',
'机构': 'primary',
'时间': 'warning',
'产品': 'info',
'概念': 'default'
}
return typeMap[type] || 'default'
}
const getSentimentType = (sentiment) => {
const typeMap = {
'positive': 'success',
'neutral': 'info',
'negative': 'danger'
}
return typeMap[sentiment] || 'default'
}
const getSentimentLabel = (sentiment) => {
const labelMap = {
'positive': '积极',
'neutral': '中性',
'negative': '消极'
}
return labelMap[sentiment] || '未知'
}
const beforeUpload = (file) => {
const isValidType = ['.txt', '.doc', '.docx', '.pdf'].some(ext =>
file.name.toLowerCase().endsWith(ext)
)
const isLt10M = file.size / 1024 / 1024 < 10
if (!isValidType) {
ElMessage.error('只能上传 TXT、DOC、DOCX、PDF 格式的文件!')
return false
}
if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB!')
return false
}
return true
}
const handleFileChange = (file) => {
//
inputText.value = '这是从上传文件中提取的内容示例...'
ElMessage.success('文件上传成功,内容已提取')
}
const clearInput = () => {
inputText.value = ''
extractionResults.value = null
}
const loadSampleDocument = () => {
inputText.value = `办公自动化系统实施方案
项目背景
随着数字化转型的推进我公司决定实施办公自动化系统以提高工作效率和管理水平本项目由张经理负责计划在2024年3月正式启动
现状分析
目前公司存在以下问题
1. 文档管理分散缺乏统一平台
2. 工作流程繁琐审批效率低下
3. 信息共享不及时团队协作困难
4. 数据统计手工操作容易出错
解决方案
1. 部署Office365办公套件
2. 建立统一的文档管理系统
3. 优化工作流程实现电子化审批
4. 引入人工智能技术提升自动化水平
实施计划
第一阶段系统部署和员工培训
第二阶段业务流程梳理和优化
第三阶段系统上线和效果评估
预期效果
通过实施办公自动化系统预计能够
- 提高工作效率30%以上
- 减少纸质文档使用50%
- 缩短审批流程时间60%
- 提升数据准确性和及时性`
ElMessage.success('示例文档已加载')
}
const exportResults = () => {
const results = JSON.stringify(extractionResults.value, null, 2)
const blob = new Blob([results], { type: 'application/json;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `关键信息提取结果_${new Date().toLocaleDateString()}.json`
link.click()
URL.revokeObjectURL(url)
ElMessage.success('结果导出成功')
}
const shareResults = () => {
ElMessage.info('分享功能开发中...')
}
const saveToHistory = () => {
ElMessage.success('结果已保存到历史记录')
}
</script>
<style scoped>
.key-info-extraction {
padding: 20px;
}
.extraction-container {
max-width: 1000px;
margin: 0 auto;
}
.input-section,
.results-section {
margin-bottom: 30px;
}
h4, h5 {
margin: 0 0 15px 0;
color: #303133;
font-weight: 600;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
.input-tabs {
margin-bottom: 20px;
}
.input-textarea {
margin-bottom: 15px;
}
.file-upload {
width: 100%;
}
.extraction-options {
margin: 20px 0;
padding: 15px;
background: #F5F7FA;
border-radius: 6px;
}
.input-actions,
.results-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.results-actions {
justify-content: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
.result-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.keywords-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.keyword-tag {
margin: 2px;
}
.keyword-score {
font-size: 12px;
opacity: 0.8;
}
.summary-content p {
line-height: 1.6;
color: #606266;
margin: 0;
}
.entities-container {
display: grid;
gap: 10px;
}
.entity-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
background: #F5F7FA;
border-radius: 4px;
}
.entity-text {
flex: 1;
color: #303133;
}
.entity-confidence {
font-size: 12px;
color: #909399;
}
.topics-container {
display: grid;
gap: 15px;
}
.topic-item {
padding: 15px;
border: 1px solid #EBEEF5;
border-radius: 6px;
}
.topic-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 10px;
}
.topic-header h6 {
margin: 0;
min-width: 100px;
color: #303133;
}
.topic-score {
font-size: 12px;
color: #909399;
min-width: 40px;
}
.topic-keywords {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.sentiment-container {
padding: 15px;
}
.sentiment-scores {
display: grid;
gap: 15px;
}
.score-item {
display: flex;
align-items: center;
gap: 15px;
}
.score-item span {
min-width: 50px;
color: #606266;
}
.structure-container {
padding: 15px;
}
.structure-tree {
border: none;
}
</style>

View File

@ -0,0 +1,738 @@
<template>
<div class="multilingual-processing">
<div class="processing-container">
<div class="language-selector">
<h4>语言设置</h4>
<div class="language-config">
<div class="source-language">
<label>源语言</label>
<el-select v-model="sourceLanguage" placeholder="选择源语言">
<el-option
v-for="lang in languages"
:key="lang.code"
:label="lang.name"
:value="lang.code"
/>
</el-select>
</div>
<div class="language-switch">
<el-button
type="text"
icon="el-icon-sort"
@click="switchLanguages"
class="switch-btn"
/>
</div>
<div class="target-language">
<label>目标语言</label>
<el-select v-model="targetLanguage" placeholder="选择目标语言">
<el-option
v-for="lang in languages"
:key="lang.code"
:label="lang.name"
:value="lang.code"
/>
</el-select>
</div>
</div>
</div>
<div class="processing-modes">
<h4>处理模式</h4>
<el-radio-group v-model="processingMode" class="mode-group">
<el-radio label="translation">文本翻译</el-radio>
<el-radio label="detection">语言检测</el-radio>
<el-radio label="correction">语法纠错</el-radio>
<el-radio label="optimization">文本优化</el-radio>
</el-radio-group>
</div>
<div class="input-output-section">
<div class="input-panel">
<div class="panel-header">
<h5>输入文本</h5>
<div class="input-controls">
<el-button size="small" @click="clearInput">清空</el-button>
<el-button size="small" @click="loadSampleText" type="info">示例</el-button>
<el-button size="small" @click="pasteFromClipboard">粘贴</el-button>
</div>
</div>
<el-input
v-model="inputText"
type="textarea"
:rows="12"
placeholder="请输入需要处理的文本..."
class="input-textarea"
show-word-limit
maxlength="5000"
/>
<div class="input-stats">
<el-tag size="small">字数: {{ inputText.length }}</el-tag>
<el-tag size="small" type="info" v-if="detectedLanguage">
检测语言: {{ getLanguageName(detectedLanguage) }}
</el-tag>
</div>
</div>
<div class="output-panel">
<div class="panel-header">
<h5>处理结果</h5>
<div class="output-controls">
<el-button size="small" @click="copyResult" :disabled="!outputText">复制</el-button>
<el-button size="small" @click="downloadResult" :disabled="!outputText">下载</el-button>
<el-button size="small" @click="shareResult" :disabled="!outputText">分享</el-button>
</div>
</div>
<el-input
v-model="outputText"
type="textarea"
:rows="12"
placeholder="处理结果将显示在这里..."
readonly
class="output-textarea"
/>
<div class="output-stats" v-if="outputText">
<el-tag size="small">字数: {{ outputText.length }}</el-tag>
<el-tag size="small" type="success" v-if="processingTime">
处理时间: {{ processingTime }}ms
</el-tag>
<el-tag size="small" type="warning" v-if="confidence">
置信度: {{ confidence }}%
</el-tag>
</div>
</div>
</div>
<div class="processing-actions">
<el-button
type="primary"
@click="processText"
:loading="processing"
:disabled="!inputText.trim()"
size="large"
>
<i :class="getProcessingIcon()"></i>
{{ getProcessingLabel() }}
</el-button>
<el-button @click="swapInputOutput" :disabled="!outputText">
<i class="el-icon-refresh-left"></i>
交换内容
</el-button>
<el-button @click="showHistory">
<i class="el-icon-time"></i>
历史记录
</el-button>
</div>
<!-- 高级设置 -->
<el-collapse v-model="advancedSettings" class="advanced-settings">
<el-collapse-item title="高级设置" name="advanced">
<div class="settings-content">
<div class="setting-group">
<label>翻译质量</label>
<el-radio-group v-model="translationQuality" size="small">
<el-radio label="fast">快速</el-radio>
<el-radio label="balanced">平衡</el-radio>
<el-radio label="accurate">精确</el-radio>
</el-radio-group>
</div>
<div class="setting-group">
<label>专业领域</label>
<el-select v-model="domain" placeholder="选择专业领域" size="small">
<el-option label="通用" value="general" />
<el-option label="商务" value="business" />
<el-option label="技术" value="technical" />
<el-option label="法律" value="legal" />
<el-option label="医学" value="medical" />
<el-option label="教育" value="education" />
</el-select>
</div>
<div class="setting-group">
<el-checkbox v-model="preserveFormatting">保持格式</el-checkbox>
<el-checkbox v-model="autoDetectLanguage">自动检测语言</el-checkbox>
<el-checkbox v-model="enableGlossary">使用术语库</el-checkbox>
</div>
</div>
</el-collapse-item>
</el-collapse>
<!-- 批量处理 -->
<el-card class="batch-processing" v-if="showBatchMode">
<template #header>
<div class="card-header">
<span>批量处理</span>
<el-button size="small" @click="showBatchMode = false">关闭</el-button>
</div>
</template>
<el-upload
class="batch-upload"
drag
multiple
:before-upload="beforeBatchUpload"
:on-change="handleBatchFileChange"
:auto-upload="false"
accept=".txt,.doc,.docx"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">批量上传文档进行处理</div>
<div class="el-upload__tip" slot="tip">
支持 TXTDOCDOCX 格式最多 10 个文件
</div>
</el-upload>
</el-card>
</div>
<!-- 历史记录对话框 -->
<el-dialog
v-model="historyVisible"
title="处理历史"
width="80%"
>
<div class="history-content">
<div
v-for="(item, index) in processingHistory"
:key="index"
class="history-item"
>
<div class="history-header">
<span class="history-time">{{ formatTime(item.timestamp) }}</span>
<el-tag size="small" :type="getModeType(item.mode)">{{ getModeLabel(item.mode) }}</el-tag>
<span class="history-languages">{{ getLanguageName(item.sourceLanguage) }} {{ getLanguageName(item.targetLanguage) }}</span>
</div>
<div class="history-content-preview">
<div class="history-input">
<strong>输入</strong>{{ item.input.substring(0, 100) }}{{ item.input.length > 100 ? '...' : '' }}
</div>
<div class="history-output">
<strong>输出</strong>{{ item.output.substring(0, 100) }}{{ item.output.length > 100 ? '...' : '' }}
</div>
</div>
<div class="history-actions">
<el-button size="small" @click="loadHistoryItem(item)">加载</el-button>
<el-button size="small" type="danger" @click="deleteHistoryItem(index)">删除</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
const sourceLanguage = ref('zh-CN')
const targetLanguage = ref('en-US')
const processingMode = ref('translation')
const inputText = ref('')
const outputText = ref('')
const processing = ref(false)
const processingTime = ref(0)
const confidence = ref(0)
const detectedLanguage = ref('')
const translationQuality = ref('balanced')
const domain = ref('general')
const preserveFormatting = ref(true)
const autoDetectLanguage = ref(true)
const enableGlossary = ref(false)
const advancedSettings = ref([])
const showBatchMode = ref(false)
const historyVisible = ref(false)
const processingHistory = ref([])
const languages = [
{ code: 'zh-CN', name: '中文(简体)' },
{ code: 'zh-TW', name: '中文(繁体)' },
{ code: 'en-US', name: '英语' },
{ code: 'ja-JP', name: '日语' },
{ code: 'ko-KR', name: '韩语' },
{ code: 'fr-FR', name: '法语' },
{ code: 'de-DE', name: '德语' },
{ code: 'es-ES', name: '西班牙语' },
{ code: 'it-IT', name: '意大利语' },
{ code: 'pt-PT', name: '葡萄牙语' },
{ code: 'ru-RU', name: '俄语' },
{ code: 'ar-SA', name: '阿拉伯语' }
]
//
watch(inputText, (newValue) => {
if (autoDetectLanguage.value && newValue.trim()) {
detectLanguage(newValue)
}
}, { debounce: 500 })
const detectLanguage = async (text) => {
//
const chineseRegex = /[\u4e00-\u9fff]/
const englishRegex = /[a-zA-Z]/
if (chineseRegex.test(text)) {
detectedLanguage.value = 'zh-CN'
} else if (englishRegex.test(text)) {
detectedLanguage.value = 'en-US'
}
}
const processText = async () => {
if (!inputText.value.trim()) {
ElMessage.warning('请输入需要处理的文本')
return
}
processing.value = true
const startTime = Date.now()
try {
//
await new Promise(resolve => setTimeout(resolve, 1500))
let result = ''
switch (processingMode.value) {
case 'translation':
result = await translateText(inputText.value)
break
case 'detection':
result = await detectTextLanguage(inputText.value)
break
case 'correction':
result = await correctGrammar(inputText.value)
break
case 'optimization':
result = await optimizeText(inputText.value)
break
}
outputText.value = result
processingTime.value = Date.now() - startTime
confidence.value = Math.floor(Math.random() * 20) + 80 // 80-99%
//
processingHistory.value.unshift({
mode: processingMode.value,
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
input: inputText.value,
output: result,
timestamp: new Date(),
processingTime: processingTime.value,
confidence: confidence.value
})
ElMessage.success('处理完成')
} catch (error) {
ElMessage.error('处理失败,请重试')
} finally {
processing.value = false
}
}
const translateText = async (text) => {
//
const translations = {
'zh-CN': {
'en-US': 'This is a translated text from Chinese to English. The original content has been processed through our advanced translation system.',
'ja-JP': 'これは中国語から日本語に翻訳されたテキストです。元のコンテンツは高度な翻訳システムを通じて処理されました。'
},
'en-US': {
'zh-CN': '这是从英语翻译成中文的文本。原始内容已通过我们先进的翻译系统进行处理。',
'ja-JP': 'これは英語から日本語に翻訳されたテキストです。'
}
}
return translations[sourceLanguage.value]?.[targetLanguage.value] ||
`翻译结果:${text}(从${getLanguageName(sourceLanguage.value)}${getLanguageName(targetLanguage.value)}`
}
const detectTextLanguage = async (text) => {
return `语言检测结果:
检测到的主要语言${getLanguageName(detectedLanguage.value || sourceLanguage.value)}
置信度${confidence.value}%
文本特征包含${text.length}个字符
建议处理方式${detectedLanguage.value === 'zh-CN' ? '中文文本处理' : '外语文本处理'}`
}
const correctGrammar = async (text) => {
return `语法纠错结果:
原文${text}
建议修正
1. 删除多余的空格和标点符号
2. 调整语句结构使表达更清晰
3. 修正可能的语法错误
纠错后文本
${text.replace(/\s+/g, ' ').trim()}
已应用基础语法规则和标点符号规范化`
}
const optimizeText = async (text) => {
return `文本优化结果:
原文长度${text.length}字符
优化建议
1. 精简冗余表达
2. 提升语言流畅度
3. 增强逻辑结构
优化后文本
${text.substring(0, 200)}...示例
改进要点
- 语言更加简洁明了
- 逻辑结构更加清晰
- 表达更加准确`
}
const switchLanguages = () => {
const temp = sourceLanguage.value
sourceLanguage.value = targetLanguage.value
targetLanguage.value = temp
}
const swapInputOutput = () => {
const temp = inputText.value
inputText.value = outputText.value
outputText.value = temp
}
const clearInput = () => {
inputText.value = ''
outputText.value = ''
detectedLanguage.value = ''
}
const loadSampleText = () => {
const samples = {
'zh-CN': '这是一个中文示例文本。我们正在测试多语言处理功能,包括翻译、语言检测、语法纠错和文本优化等功能。希望这个系统能够帮助用户更好地处理多语言内容。',
'en-US': 'This is an English sample text. We are testing multilingual processing features, including translation, language detection, grammar correction, and text optimization. We hope this system can help users better handle multilingual content.',
'ja-JP': 'これは日本語のサンプルテキストです。翻訳、言語検出、文法修正、テキスト最適化などの多言語処理機能をテストしています。'
}
inputText.value = samples[sourceLanguage.value] || samples['zh-CN']
ElMessage.success('示例文本已加载')
}
const pasteFromClipboard = async () => {
try {
const text = await navigator.clipboard.readText()
inputText.value = text
ElMessage.success('已从剪贴板粘贴文本')
} catch (error) {
ElMessage.error('无法访问剪贴板')
}
}
const copyResult = async () => {
try {
await navigator.clipboard.writeText(outputText.value)
ElMessage.success('结果已复制到剪贴板')
} catch (error) {
ElMessage.error('复制失败')
}
}
const downloadResult = () => {
const blob = new Blob([outputText.value], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `处理结果_${new Date().toLocaleDateString()}.txt`
link.click()
URL.revokeObjectURL(url)
ElMessage.success('文件下载开始')
}
const shareResult = () => {
ElMessage.info('分享功能开发中...')
}
const showHistory = () => {
historyVisible.value = true
}
const loadHistoryItem = (item) => {
sourceLanguage.value = item.sourceLanguage
targetLanguage.value = item.targetLanguage
processingMode.value = item.mode
inputText.value = item.input
outputText.value = item.output
historyVisible.value = false
ElMessage.success('历史记录已加载')
}
const deleteHistoryItem = (index) => {
processingHistory.value.splice(index, 1)
ElMessage.success('历史记录已删除')
}
const beforeBatchUpload = (file) => {
const isValidType = ['.txt', '.doc', '.docx'].some(ext =>
file.name.toLowerCase().endsWith(ext)
)
if (!isValidType) {
ElMessage.error('只能上传 TXT、DOC、DOCX 格式的文件!')
return false
}
return true
}
const handleBatchFileChange = (file, fileList) => {
if (fileList.length > 10) {
ElMessage.warning('最多只能上传 10 个文件')
return
}
ElMessage.success(`已选择 ${fileList.length} 个文件`)
}
const getLanguageName = (code) => {
const lang = languages.find(l => l.code === code)
return lang ? lang.name : code
}
const getProcessingIcon = () => {
const icons = {
'translation': 'el-icon-refresh',
'detection': 'el-icon-search',
'correction': 'el-icon-edit',
'optimization': 'el-icon-magic-stick'
}
return icons[processingMode.value] || 'el-icon-processor'
}
const getProcessingLabel = () => {
const labels = {
'translation': '开始翻译',
'detection': '检测语言',
'correction': '纠正语法',
'optimization': '优化文本'
}
return labels[processingMode.value] || '开始处理'
}
const getModeType = (mode) => {
const types = {
'translation': 'primary',
'detection': 'info',
'correction': 'warning',
'optimization': 'success'
}
return types[mode] || 'default'
}
const getModeLabel = (mode) => {
const labels = {
'translation': '翻译',
'detection': '检测',
'correction': '纠错',
'optimization': '优化'
}
return labels[mode] || mode
}
const formatTime = (date) => {
return date.toLocaleString('zh-CN')
}
</script>
<style scoped>
.multilingual-processing {
padding: 20px;
}
.processing-container {
max-width: 1200px;
margin: 0 auto;
}
.language-selector,
.processing-modes {
margin-bottom: 20px;
}
h4, h5 {
margin: 0 0 15px 0;
color: #303133;
font-weight: 600;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
.language-config {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.source-language,
.target-language {
display: flex;
align-items: center;
gap: 10px;
}
.language-switch {
display: flex;
align-items: center;
}
.switch-btn {
font-size: 18px;
color: #409EFF;
}
.mode-group {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.input-output-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.input-output-section {
grid-template-columns: 1fr;
}
}
.input-panel,
.output-panel {
border: 1px solid #EBEEF5;
border-radius: 6px;
padding: 15px;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.input-controls,
.output-controls {
display: flex;
gap: 5px;
}
.input-textarea,
.output-textarea {
margin-bottom: 10px;
}
.input-stats,
.output-stats {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.processing-actions {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.advanced-settings {
margin-bottom: 20px;
}
.settings-content {
display: grid;
gap: 15px;
}
.setting-group {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.setting-group label {
min-width: 80px;
color: #606266;
}
.batch-processing {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.batch-upload {
width: 100%;
}
.history-content {
max-height: 500px;
overflow-y: auto;
}
.history-item {
border: 1px solid #EBEEF5;
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 10px;
}
.history-time {
color: #909399;
font-size: 12px;
}
.history-languages {
color: #606266;
font-size: 12px;
}
.history-content-preview {
margin-bottom: 10px;
}
.history-input,
.history-output {
margin-bottom: 5px;
color: #606266;
line-height: 1.5;
}
.history-actions {
display: flex;
gap: 10px;
}
</style>

View File

@ -0,0 +1,533 @@
<template>
<div class="speech-to-text">
<div class="stt-container">
<div class="recording-section">
<h4>语音录制</h4>
<div class="recording-controls">
<div class="record-button-container">
<el-button
:type="isRecording ? 'danger' : 'primary'"
:icon="isRecording ? 'el-icon-video-pause' : 'el-icon-microphone'"
size="large"
circle
@click="toggleRecording"
:disabled="!isSupported"
class="record-button"
/>
<p class="record-status">
{{ isRecording ? '正在录音...' : (isSupported ? '点击开始录音' : '浏览器不支持录音功能') }}
</p>
</div>
<div class="recording-info" v-if="isRecording">
<div class="recording-time">录音时长: {{ formatTime(recordingTime) }}</div>
<div class="volume-indicator">
<el-progress
:percentage="volumeLevel"
:show-text="false"
:stroke-width="6"
status="success"
/>
<span class="volume-text">音量</span>
</div>
</div>
</div>
</div>
<div class="upload-section">
<h4>上传音频文件</h4>
<el-upload
class="audio-upload"
drag
:before-upload="beforeUpload"
:on-change="handleFileChange"
:auto-upload="false"
accept="audio/*"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将音频文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持 MP3WAVM4A 等格式文件大小不超过 100MB
</div>
</el-upload>
</div>
<div class="audio-preview" v-if="audioFile || audioBlob">
<h4>音频预览</h4>
<div class="audio-player">
<audio
ref="audioPlayer"
controls
:src="audioUrl"
class="audio-element"
></audio>
<div class="audio-actions">
<el-button type="primary" @click="transcribeAudio" :loading="transcribing">
<i class="el-icon-s-promotion"></i>
开始转换
</el-button>
<el-button @click="clearAudio">清除音频</el-button>
</div>
</div>
</div>
<div class="transcription-result" v-if="transcriptionText">
<h4>转换结果</h4>
<el-card class="result-card">
<div class="transcription-content">
<el-input
v-model="transcriptionText"
type="textarea"
:rows="8"
placeholder="转换的文字内容将显示在这里..."
class="transcription-textarea"
/>
<div class="transcription-stats">
<el-tag size="small">字数: {{ transcriptionText.length }}</el-tag>
<el-tag size="small" type="info">转换时间: {{ transcriptionTime }}</el-tag>
<el-tag size="small" type="success">准确度: {{ transcriptionAccuracy }}%</el-tag>
</div>
<div class="transcription-actions">
<el-button type="primary" @click="copyTranscription">
<i class="el-icon-document-copy"></i>
复制文本
</el-button>
<el-button @click="downloadTranscription">
<i class="el-icon-download"></i>
下载文档
</el-button>
<el-button @click="editTranscription">
<i class="el-icon-edit"></i>
编辑优化
</el-button>
</div>
</div>
</el-card>
</div>
<div class="transcription-history" v-if="transcriptionHistory.length > 0">
<h4>转换历史</h4>
<div class="history-list">
<el-card
v-for="(item, index) in transcriptionHistory"
:key="index"
class="history-item"
shadow="hover"
>
<div class="history-content">
<div class="history-header">
<span class="history-time">{{ formatDateTime(item.timestamp) }}</span>
<el-tag size="small" :type="item.type === 'file' ? 'primary' : 'success'">
{{ item.type === 'file' ? '文件上传' : '实时录音' }}
</el-tag>
</div>
<div class="history-text">
{{ item.text.substring(0, 100) }}{{ item.text.length > 100 ? '...' : '' }}
</div>
<div class="history-actions">
<el-button size="small" @click="loadHistory(item)">加载</el-button>
<el-button size="small" type="danger" @click="deleteHistory(index)">删除</el-button>
</div>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
const isSupported = ref(false)
const isRecording = ref(false)
const recordingTime = ref(0)
const volumeLevel = ref(0)
const audioFile = ref(null)
const audioBlob = ref(null)
const audioUrl = ref('')
const transcribing = ref(false)
const transcriptionText = ref('')
const transcriptionTime = ref(0)
const transcriptionAccuracy = ref(0)
const transcriptionHistory = ref([])
let mediaRecorder = null
let recordingInterval = null
let recordingStartTime = 0
onMounted(() => {
checkBrowserSupport()
loadTranscriptionHistory()
})
onUnmounted(() => {
if (recordingInterval) {
clearInterval(recordingInterval)
}
})
const checkBrowserSupport = () => {
isSupported.value = navigator.mediaDevices && navigator.mediaDevices.getUserMedia
}
const toggleRecording = async () => {
if (isRecording.value) {
stopRecording()
} else {
await startRecording()
}
}
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
mediaRecorder = new MediaRecorder(stream)
const audioChunks = []
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data)
}
mediaRecorder.onstop = () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' })
setAudioBlob(audioBlob)
stream.getTracks().forEach(track => track.stop())
}
mediaRecorder.start()
isRecording.value = true
recordingStartTime = Date.now()
//
recordingInterval = setInterval(() => {
recordingTime.value = Math.floor((Date.now() - recordingStartTime) / 1000)
//
volumeLevel.value = Math.random() * 100
}, 100)
ElMessage.success('开始录音')
} catch (error) {
ElMessage.error('无法访问麦克风,请检查权限设置')
}
}
const stopRecording = () => {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop()
}
isRecording.value = false
if (recordingInterval) {
clearInterval(recordingInterval)
recordingInterval = null
}
ElMessage.success('录音结束')
}
const setAudioBlob = (blob) => {
audioBlob.value = blob
audioUrl.value = URL.createObjectURL(blob)
}
const beforeUpload = (file) => {
const isAudio = file.type.startsWith('audio/')
const isLt100M = file.size / 1024 / 1024 < 100
if (!isAudio) {
ElMessage.error('只能上传音频文件!')
return false
}
if (!isLt100M) {
ElMessage.error('文件大小不能超过 100MB!')
return false
}
return true
}
const handleFileChange = (file) => {
audioFile.value = file.raw
audioUrl.value = URL.createObjectURL(file.raw)
}
const transcribeAudio = async () => {
if (!audioFile.value && !audioBlob.value) {
ElMessage.warning('请先录制或上传音频文件')
return
}
transcribing.value = true
const startTime = Date.now()
try {
//
await new Promise(resolve => setTimeout(resolve, 3000))
//
const sampleTexts = [
'尊敬的各位领导、同事们,大家好!今天我想和大家分享一下我们团队在过去一个季度的工作成果。首先,我们成功完成了三个重要项目,这些项目不仅提升了我们的技术能力,也为公司带来了可观的经济效益。',
'会议的主要议题包括:第一,讨论新产品的市场定位策略;第二,评估当前项目的进展情况;第三,制定下一阶段的工作计划。我认为我们需要加强团队协作,提高工作效率。',
'根据最新的市场调研数据显示,我们的产品在同类竞品中具有明显优势。建议我们继续保持技术创新,同时加大市场推广力度,争取在下个季度实现更大突破。'
]
transcriptionText.value = sampleTexts[Math.floor(Math.random() * sampleTexts.length)]
transcriptionTime.value = Math.round((Date.now() - startTime) / 1000)
transcriptionAccuracy.value = Math.floor(Math.random() * 10) + 90 // 90-99%
//
transcriptionHistory.value.unshift({
text: transcriptionText.value,
timestamp: new Date(),
type: audioFile.value ? 'file' : 'recording',
accuracy: transcriptionAccuracy.value,
duration: transcriptionTime.value
})
saveTranscriptionHistory()
ElMessage.success('语音转换完成')
} catch (error) {
ElMessage.error('转换失败,请重试')
} finally {
transcribing.value = false
}
}
const clearAudio = () => {
audioFile.value = null
audioBlob.value = null
if (audioUrl.value) {
URL.revokeObjectURL(audioUrl.value)
}
audioUrl.value = ''
recordingTime.value = 0
}
const copyTranscription = async () => {
try {
await navigator.clipboard.writeText(transcriptionText.value)
ElMessage.success('已复制到剪贴板')
} catch (error) {
ElMessage.error('复制失败')
}
}
const downloadTranscription = () => {
const blob = new Blob([transcriptionText.value], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `语音转文字_${new Date().toLocaleDateString()}.txt`
link.click()
URL.revokeObjectURL(url)
ElMessage.success('文件下载开始')
}
const editTranscription = () => {
ElMessage.info('您可以直接在文本框中编辑内容')
}
const loadHistory = (item) => {
transcriptionText.value = item.text
transcriptionAccuracy.value = item.accuracy
transcriptionTime.value = item.duration
ElMessage.success('历史记录已加载')
}
const deleteHistory = (index) => {
transcriptionHistory.value.splice(index, 1)
saveTranscriptionHistory()
ElMessage.success('历史记录已删除')
}
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
const formatDateTime = (date) => {
return date.toLocaleString('zh-CN')
}
const loadTranscriptionHistory = () => {
try {
const saved = localStorage.getItem('transcription-history')
if (saved) {
transcriptionHistory.value = JSON.parse(saved).map(item => ({
...item,
timestamp: new Date(item.timestamp)
}))
}
} catch (error) {
console.error('Failed to load transcription history:', error)
}
}
const saveTranscriptionHistory = () => {
try {
localStorage.setItem('transcription-history', JSON.stringify(transcriptionHistory.value))
} catch (error) {
console.error('Failed to save transcription history:', error)
}
}
</script>
<style scoped>
.speech-to-text {
padding: 20px;
}
.stt-container {
max-width: 800px;
margin: 0 auto;
}
.recording-section,
.upload-section,
.audio-preview,
.transcription-result,
.transcription-history {
margin-bottom: 30px;
}
h4 {
margin: 0 0 15px 0;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.recording-controls {
text-align: center;
padding: 30px;
border: 2px dashed #DCDFE6;
border-radius: 8px;
}
.record-button-container {
margin-bottom: 20px;
}
.record-button {
width: 80px;
height: 80px;
font-size: 24px;
margin-bottom: 15px;
}
.record-status {
color: #606266;
margin: 0;
}
.recording-info {
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
}
.recording-time {
font-size: 18px;
font-weight: 600;
color: #E6A23C;
}
.volume-indicator {
display: flex;
align-items: center;
gap: 10px;
width: 150px;
}
.volume-text {
font-size: 12px;
color: #909399;
}
.audio-upload {
width: 100%;
}
.audio-player {
text-align: center;
}
.audio-element {
width: 100%;
margin-bottom: 15px;
}
.audio-actions,
.transcription-actions {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.result-card {
margin-top: 15px;
}
.transcription-content {
padding: 20px;
}
.transcription-textarea {
margin-bottom: 15px;
}
.transcription-stats {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.history-list {
display: grid;
gap: 15px;
}
.history-item {
transition: transform 0.2s ease;
}
.history-item:hover {
transform: translateY(-2px);
}
.history-content {
padding: 15px;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.history-time {
color: #909399;
font-size: 12px;
}
.history-text {
color: #606266;
line-height: 1.5;
margin-bottom: 10px;
}
.history-actions {
display: flex;
gap: 10px;
}
</style>

59
src/main.js Normal file
View File

@ -0,0 +1,59 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import App from './App.vue'
import router from './router'
import ECharts from 'vue-echarts'
import { use } from "echarts/core"
// 手动引入 ECharts 各模块来减小打包体积
import {
CanvasRenderer
} from 'echarts/renderers'
import {
BarChart,
LineChart,
PieChart,
RadarChart
} from 'echarts/charts'
import {
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent,
RadarComponent
} from 'echarts/components'
use([
CanvasRenderer,
BarChart,
LineChart,
PieChart,
RadarChart,
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent,
RadarComponent
])
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
// 全局注册Vue-ECharts
app.component('v-chart', ECharts)
// 全局注册ElMessage
app.config.globalProperties.$message = ElMessage
window.ElMessage = ElMessage
// 注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')

80
src/router/index.js Normal file
View File

@ -0,0 +1,80 @@
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
const HomePage = () => import('../views/HomePage.vue')
const DocumentGeneration = () => import('../views/DocumentGeneration.vue')
const OfficeAssistant = () => import('../views/OfficeAssistant.vue')
const KnowledgeQA = () => import('../views/KnowledgeQA.vue')
const OfficeAgent = () => import('../views/OfficeAgent.vue')
// 导入具体功能组件
const TechnicalDocumentGenerator = () => import('../components/TechnicalDocumentGenerator.vue')
const SpeechGenerator = () => import('../components/SpeechGenerator.vue')
const routes = [
{
path: '/',
name: 'HomePage',
component: HomePage,
meta: {
title: '办公智能编稿系统'
}
},
{
path: '/document-generation',
name: 'DocumentGeneration',
component: DocumentGeneration,
meta: {
title: '文档生成'
}
},
{
path: '/document-generation/technical-document',
name: 'TechnicalDocument',
component: TechnicalDocumentGenerator,
meta: {
title: '技术文件生成',
parent: '/document-generation'
}
},
{
path: '/document-generation/speech-generation',
name: 'SpeechGeneration',
component: SpeechGenerator,
meta: {
title: '讲话稿生成',
parent: '/document-generation'
}
},
{
path: '/office-assistant',
name: 'OfficeAssistant',
component: OfficeAssistant,
meta: {
title: '办公助手'
}
},
{
path: '/knowledge-qa',
name: 'KnowledgeQA',
component: KnowledgeQA,
meta: {
title: '知识问答'
}
},
{
path: '/office-agent',
name: 'OfficeAgent',
component: OfficeAgent,
meta: {
title: '办公智能体'
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

View File

@ -0,0 +1,147 @@
<template>
<div class="page-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-content">
<router-link to="/" class="back-button">
<i class="el-icon-arrow-left"></i>
返回主页
</router-link>
<div class="page-title">
<i class="el-icon-document title-icon"></i>
<h1>文档生成</h1>
</div>
<div></div> <!-- 占位元素保持布局平衡 -->
</div>
</div>
<!-- 页面内容 -->
<div class="page-content">
<div class="feature-grid">
<el-card
v-for="item in documentFeatures"
:key="item.id"
class="feature-card"
shadow="hover"
@click="navigateToFeature(item)"
>
<div class="feature-content">
<div class="feature-icon">
<i :class="item.icon"></i>
</div>
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
<div class="feature-action">
<el-button type="primary">
开始使用
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const documentFeatures = [
{
id: 'technical-document',
title: '技术文件生成',
description: '根据技术要求,智能生成标准化的技术文件',
icon: 'el-icon-document',
route: '/document-generation/technical-document'
},
{
id: 'speech-generation',
title: '讲话稿生成',
description: '根据主题和要求,快速生成高质量的讲话稿',
icon: 'el-icon-microphone',
route: '/document-generation/speech-generation'
}
]
const navigateToFeature = (feature) => {
router.push(feature.route)
}
</script>
<style scoped>
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.feature-card {
cursor: pointer;
transition: transform 0.2s ease;
border-radius: 12px;
overflow: hidden;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-content {
text-align: center;
padding: 40px 30px;
}
.feature-icon {
font-size: 64px;
color: #409EFF;
margin-bottom: 20px;
}
.feature-icon i {
font-size: 64px;
}
h3 {
margin: 20px 0 15px 0;
color: #303133;
font-size: 24px;
font-weight: 600;
}
p {
color: #606266;
line-height: 1.6;
margin: 0 0 25px 0;
font-size: 16px;
}
.feature-action {
margin-top: 20px;
}
@media (max-width: 768px) {
.feature-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.feature-content {
padding: 30px 20px;
}
.feature-icon {
font-size: 48px;
}
.feature-icon i {
font-size: 48px;
}
h3 {
font-size: 20px;
}
}
</style>

346
src/views/HomePage.vue Normal file
View File

@ -0,0 +1,346 @@
<template>
<div class="home-page">
<!-- 顶部导航 -->
<header class="header">
<div class="header-content">
<div class="logo-section">
<h1>办公智能编稿系统</h1>
<p>智能化办公文档处理平台提升您的工作效率</p>
</div>
</div>
</header>
<!-- 主要功能区域 -->
<main class="main-content">
<div class="features-container">
<div class="features-grid">
<div
v-for="feature in features"
:key="feature.id"
class="feature-card"
@click="navigateToFeature(feature)"
>
<div class="feature-icon">
<i :class="feature.icon"></i>
</div>
<div class="feature-content">
<h3>{{ feature.title }}</h3>
<p>{{ feature.description }}</p>
<div class="feature-details">
<ul>
<li v-for="detail in feature.details" :key="detail">{{ detail }}</li>
</ul>
</div>
<div class="feature-action">
<el-button type="primary" size="large">
开始使用
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 底部信息 -->
<footer class="footer">
<div class="footer-content">
<p>&copy; 2024 办公智能编稿系统. 版权所有</p>
</div>
</footer>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const features = [
{
id: 'document-generation',
title: '文档生成',
description: '智能生成各类办公文档,包括技术文件、讲话稿等',
icon: 'el-icon-document',
route: '/document-generation',
details: [
'技术文件智能生成',
'讲话稿快速创建',
'标准格式自动排版',
'内容质量智能优化'
]
},
{
id: 'office-assistant',
title: '办公助手',
description: '多功能办公辅助工具,提升工作效率',
icon: 'el-icon-service',
route: '/office-assistant',
details: [
'文档错误智能纠正',
'语音转文字功能',
'关键信息快速提取',
'多语言处理支持'
]
},
{
id: 'knowledge-qa',
title: '知识问答',
description: '基于知识库的智能问答系统',
icon: 'el-icon-chat-line-square',
route: '/knowledge-qa',
details: [
'知识库智能检索',
'专业问题解答',
'历史记录管理',
'相关内容推荐'
]
},
{
id: 'office-agent',
title: '办公智能体',
description: '专业的AI助手协助完成各类办公任务',
icon: 'el-icon-user',
route: '/office-agent',
details: [
'多专业领域智能体',
'对话式任务协助',
'个性化工作建议',
'24小时在线服务'
]
}
]
const navigateToFeature = (feature) => {
router.push(feature.route)
}
</script>
<style scoped>
.home-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
flex-direction: column;
}
.header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
padding: 40px 0;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
text-align: center;
}
.logo-section h1 {
font-size: 48px;
color: white;
margin-bottom: 15px;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.logo-section p {
font-size: 20px;
color: rgba(255, 255, 255, 0.9);
margin: 0;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.main-content {
flex: 1;
padding: 60px 20px;
}
.features-container {
max-width: 1400px;
margin: 0 auto;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 30px;
align-items: stretch;
}
.feature-card {
background: white;
border-radius: 20px;
padding: 40px 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.feature-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.feature-card:hover {
transform: translateY(-10px);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
}
.feature-icon {
width: 100px;
height: 100px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
}
.feature-icon i {
font-size: 48px;
color: white;
}
.feature-content {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
}
.feature-content h3 {
font-size: 28px;
color: #303133;
margin-bottom: 15px;
font-weight: 600;
}
.feature-content p {
font-size: 16px;
color: #606266;
line-height: 1.6;
margin-bottom: 25px;
}
.feature-details {
flex: 1;
margin-bottom: 30px;
}
.feature-details ul {
list-style: none;
padding: 0;
margin: 0;
}
.feature-details li {
font-size: 14px;
color: #909399;
margin-bottom: 8px;
padding-left: 20px;
position: relative;
}
.feature-details li::before {
content: '✓';
position: absolute;
left: 0;
color: #67C23A;
font-weight: bold;
}
.feature-action {
margin-top: auto;
}
.footer {
background: rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding: 20px 0;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
text-align: center;
}
.footer-content p {
color: rgba(255, 255, 255, 0.8);
margin: 0;
font-size: 14px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.logo-section h1 {
font-size: 36px;
}
.logo-section p {
font-size: 16px;
}
.features-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.feature-card {
padding: 30px 20px;
}
.feature-icon {
width: 80px;
height: 80px;
}
.feature-icon i {
font-size: 36px;
}
.feature-content h3 {
font-size: 24px;
}
.main-content {
padding: 40px 20px;
}
}
@media (max-width: 480px) {
.header {
padding: 30px 0;
}
.logo-section h1 {
font-size: 28px;
}
.logo-section p {
font-size: 14px;
}
.feature-content h3 {
font-size: 20px;
}
}
</style>

288
src/views/KnowledgeQA.vue Normal file
View File

@ -0,0 +1,288 @@
<template>
<div class="knowledge-qa">
<div class="welcome-section">
<h1>知识问答</h1>
<p>基于知识库的智能问答系统</p>
</div>
<div class="content-wrapper">
<div class="qa-container">
<div class="search-section">
<el-card>
<h3>知识库检索</h3>
<div class="search-form">
<el-input
v-model="searchQuery"
placeholder="请输入您的问题..."
type="textarea"
:rows="3"
class="search-input"
/>
<div class="search-actions">
<el-button type="primary" @click="searchKnowledge" :loading="searching">
<i class="el-icon-search"></i>
搜索知识库
</el-button>
<el-button @click="clearSearch">
清空
</el-button>
</div>
</div>
</el-card>
</div>
<div class="results-section" v-if="searchResults.length > 0">
<el-card>
<h3>搜索结果</h3>
<div class="results-list">
<el-card
v-for="(result, index) in searchResults"
:key="index"
class="result-item"
shadow="hover"
>
<div class="result-content">
<h4>{{ result.title }}</h4>
<p>{{ result.content }}</p>
<div class="result-meta">
<el-tag size="small">相关度: {{ result.relevance }}%</el-tag>
<el-tag size="small" type="info">{{ result.category }}</el-tag>
</div>
</div>
</el-card>
</div>
</el-card>
</div>
<div class="qa-history" v-if="qaHistory.length > 0">
<el-card>
<h3>历史问答</h3>
<div class="history-list">
<div
v-for="(item, index) in qaHistory"
:key="index"
class="history-item"
>
<div class="question">
<strong></strong>{{ item.question }}
</div>
<div class="answer">
<strong></strong>{{ item.answer }}
</div>
<div class="timestamp">
{{ formatTime(item.timestamp) }}
</div>
</div>
</div>
</el-card>
</div>
<div class="empty-state" v-if="searchResults.length === 0 && !searching">
<el-empty description="暂无搜索结果,请输入问题进行检索">
<el-button type="primary" @click="loadSampleQuestions">
查看示例问题
</el-button>
</el-empty>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const searchQuery = ref('')
const searching = ref(false)
const searchResults = ref([])
const qaHistory = ref([])
const searchKnowledge = async () => {
if (!searchQuery.value.trim()) {
ElMessage.warning('请输入搜索内容')
return
}
searching.value = true
try {
// API
await new Promise(resolve => setTimeout(resolve, 1500))
//
const mockResults = [
{
title: '办公文档格式规范',
content: '根据您的问题,办公文档应遵循统一的格式规范,包括标题层级、字体大小、行间距等要求...',
relevance: 95,
category: '文档规范'
},
{
title: '公文写作标准',
content: '公文写作需要遵循特定的结构和语言规范,包括发文字号、标题、正文、落款等要素...',
relevance: 88,
category: '写作指南'
},
{
title: '会议纪要模板',
content: '标准的会议纪要应包含会议基本信息、参会人员、讨论议题、决议事项等内容...',
relevance: 75,
category: '模板参考'
}
]
searchResults.value = mockResults
//
qaHistory.value.unshift({
question: searchQuery.value,
answer: `找到 ${mockResults.length} 条相关结果`,
timestamp: new Date()
})
ElMessage.success('搜索完成')
} catch (error) {
ElMessage.error('搜索失败,请重试')
} finally {
searching.value = false
}
}
const clearSearch = () => {
searchQuery.value = ''
searchResults.value = []
}
const loadSampleQuestions = () => {
const samples = [
'如何写好技术文档?',
'会议纪要的标准格式是什么?',
'公文写作有哪些注意事项?'
]
ElMessage.info('可以尝试搜索以下问题:' + samples.join('、'))
}
const formatTime = (date) => {
return date.toLocaleString('zh-CN')
}
</script>
<style scoped>
.knowledge-qa {
padding: 20px;
}
.welcome-section {
text-align: center;
margin-bottom: 40px;
}
.welcome-section h1 {
font-size: 28px;
color: #303133;
margin-bottom: 10px;
}
.welcome-section p {
font-size: 16px;
color: #606266;
}
.content-wrapper {
margin-top: 20px;
}
.qa-container {
max-width: 1000px;
margin: 0 auto;
}
.search-section {
margin-bottom: 20px;
}
.search-form {
margin-top: 15px;
}
.search-input {
margin-bottom: 15px;
}
.search-actions {
display: flex;
gap: 10px;
}
.results-section {
margin-bottom: 20px;
}
.results-list {
margin-top: 15px;
}
.result-item {
margin-bottom: 15px;
}
.result-content h4 {
margin: 0 0 10px 0;
color: #409EFF;
}
.result-content p {
margin: 0 0 10px 0;
color: #606266;
line-height: 1.6;
}
.result-meta {
display: flex;
gap: 10px;
}
.qa-history {
margin-bottom: 20px;
}
.history-list {
margin-top: 15px;
}
.history-item {
border-bottom: 1px solid #EBEEF5;
padding: 15px 0;
}
.history-item:last-child {
border-bottom: none;
}
.question {
margin-bottom: 8px;
color: #303133;
}
.answer {
margin-bottom: 8px;
color: #606266;
}
.timestamp {
font-size: 12px;
color: #909399;
}
.empty-state {
text-align: center;
padding: 40px 0;
}
h3 {
margin: 0 0 15px 0;
color: #303133;
}
</style>

583
src/views/OfficeAgent.vue Normal file
View File

@ -0,0 +1,583 @@
<template>
<div class="office-agent">
<div class="welcome-section">
<h1>办公智能体</h1>
<p>专业的AI助手协助完成各类办公任务</p>
</div>
<div class="content-wrapper">
<div class="agent-container">
<div class="agent-grid">
<el-card
v-for="agent in agents"
:key="agent.id"
class="agent-card"
shadow="hover"
@click="selectAgent(agent)"
>
<div class="agent-content">
<div class="agent-avatar">
<div
class="agent-avatar-custom"
:style="`
background: ${getAgentIconColor(agent.id)};
width: 60px;
height: 60px;
border-radius: 50%;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
`"
>
{{ getFirstChar(agent.name) }}
</div>
</div>
<h3>{{ agent.name }}</h3>
<p>{{ agent.description }}</p>
<div class="agent-capabilities">
<el-tag
v-for="capability in agent.capabilities"
:key="capability"
size="small"
class="capability-tag"
>
{{ capability }}
</el-tag>
</div>
<div class="agent-status">
<el-badge
:value="agent.status === 'online' ? '在线' : '离线'"
:type="agent.status === 'online' ? 'success' : 'info'"
>
</el-badge>
</div>
</div>
</el-card>
</div>
<!-- 智能体对话界面 -->
<el-dialog
v-model="chatVisible"
:title="currentAgent?.name"
width="80%"
:before-close="closeChatDialog"
>
<div class="chat-container">
<div class="chat-messages" ref="chatMessages">
<div
v-for="(message, index) in chatHistory"
:key="index"
:class="['message', message.type]"
>
<div class="message-avatar">
<div
v-if="message.type === 'user'"
class="chat-avatar-custom user-avatar"
>
</div>
<div
v-else
class="chat-avatar-custom agent-avatar"
:style="`background: ${getAgentIconColor(currentAgent?.id)};`"
>
{{ getFirstChar(currentAgent?.name) }}
</div>
</div>
<div class="message-content">
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div>
</div>
</div>
<div class="chat-input">
<el-input
v-model="currentMessage"
placeholder="请输入您的需求..."
type="textarea"
:rows="3"
@keydown.ctrl.enter="sendMessage"
/>
<div class="chat-actions">
<el-button type="primary" @click="sendMessage" :loading="sending">
发送 (Ctrl+Enter)
</el-button>
<el-button @click="clearChat">清空对话</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
const chatVisible = ref(false)
const currentAgent = ref(null)
const currentMessage = ref('')
const sending = ref(false)
const chatHistory = ref([])
const chatMessages = ref(null)
//
const getFirstChar = (title) => {
const firstChar = title ? title.charAt(0) : ''
console.log('获取首字:', title, '->', firstChar)
return firstChar
}
//
const getAgentIconColor = (agentId) => {
const colors = {
'sales-data-analyst': 'linear-gradient(135deg, #FF6B6B, #FF8E53)',
'customer-behavior-analyst': 'linear-gradient(135deg, #4ECDC4, #44A08D)',
'product-analyst': 'linear-gradient(135deg, #A8E6CF, #88C999)',
'document-expert': 'linear-gradient(135deg, #409EFF, #5DADE2)',
'data-analyst': 'linear-gradient(135deg, #67C23A, #58D68D)',
'meeting-assistant': 'linear-gradient(135deg, #E6A23C, #F4D03F)',
'translation-expert': 'linear-gradient(135deg, #F56C6C, #EC7063)',
'workflow-optimizer': 'linear-gradient(135deg, #9B59B6, #BB8FCE)',
'legal-advisor': 'linear-gradient(135deg, #2C3E50, #566573)'
}
return colors[agentId] || 'linear-gradient(135deg, #409EFF, #67C23A)'
}
const agents = [
{
id: 'sales-data-analyst',
name: '销售数据分析',
description: '专业的销售数据分析师,提供销售趋势、业绩预测和市场洞察',
icon: 'el-icon-trend-charts',
avatar: null,
status: 'online',
capabilities: ['销售趋势分析', '业绩预测', '市场洞察', '竞品对比']
},
{
id: 'customer-behavior-analyst',
name: '客户行为分析',
description: '深度分析客户行为模式,优化客户体验和提升转化率',
icon: 'el-icon-user',
avatar: null,
status: 'online',
capabilities: ['行为模式分析', '用户画像', '转化率优化', '客户细分']
},
{
id: 'product-analyst',
name: '产品分析',
description: '全面的产品数据分析,助力产品优化和市场策略制定',
icon: 'el-icon-goods',
avatar: null,
status: 'online',
capabilities: ['产品性能分析', '用户反馈分析', '竞品分析', '市场定位']
},
{
id: 'document-expert',
name: '文档专家',
description: '专业的文档编写和格式化助手,擅长各类公文、报告的起草',
icon: 'el-icon-document',
avatar: null,
status: 'online',
capabilities: ['公文写作', '格式规范', '内容优化', '模板生成']
},
{
id: 'data-analyst',
name: '数据分析师',
description: '智能数据分析和图表制作,帮助您从数据中获得洞察',
icon: 'el-icon-data-analysis',
avatar: null,
status: 'online',
capabilities: ['数据分析', '图表制作', '报表生成', '趋势预测']
},
{
id: 'meeting-assistant',
name: '会议助手',
description: '会议管理和纪要整理的专业助手,提高会议效率',
icon: 'el-icon-timer',
avatar: null,
status: 'online',
capabilities: ['会议安排', '纪要整理', '任务跟踪', '时间管理']
},
{
id: 'translation-expert',
name: '翻译专家',
description: '多语言翻译和本地化服务,支持多种专业领域',
icon: 'el-icon-globe',
avatar: null,
status: 'online',
capabilities: ['多语言翻译', '术语管理', '本地化', '语言检查']
},
{
id: 'workflow-optimizer',
name: '流程优化师',
description: '办公流程分析和优化建议,提升工作效率',
icon: 'el-icon-setting',
avatar: null,
status: 'online',
capabilities: ['流程分析', '效率优化', '自动化建议', '工具推荐']
},
{
id: 'legal-advisor',
name: '法务顾问',
description: '法律文件审查和合规性检查,确保文档合规',
icon: 'el-icon-medal',
avatar: null,
status: 'online',
capabilities: ['合规检查', '法律审查', '风险评估', '条款建议']
}
]
const selectAgent = (agent) => {
currentAgent.value = agent
chatVisible.value = true
//
if (chatHistory.value.length === 0) {
chatHistory.value.push({
type: 'agent',
content: `您好!我是${agent.name}${agent.description}我能为您做些什么?`,
timestamp: new Date()
})
}
}
const sendMessage = async () => {
if (!currentMessage.value.trim()) {
ElMessage.warning('请输入消息内容')
return
}
//
chatHistory.value.push({
type: 'user',
content: currentMessage.value,
timestamp: new Date()
})
const userMessage = currentMessage.value
currentMessage.value = ''
sending.value = true
try {
// AI
await new Promise(resolve => setTimeout(resolve, 1500))
//
let response = generateAgentResponse(currentAgent.value, userMessage)
chatHistory.value.push({
type: 'agent',
content: response,
timestamp: new Date()
})
//
nextTick(() => {
if (chatMessages.value) {
chatMessages.value.scrollTop = chatMessages.value.scrollHeight
}
})
} catch (error) {
ElMessage.error('发送失败,请重试')
} finally {
sending.value = false
}
}
const generateAgentResponse = (agent, message) => {
const responses = {
'sales-data-analyst': [
'根据您的销售数据,我分析出了一些有趣的趋势和模式...',
'让我为您提供详细的销售业绩分析和市场预测...',
'从数据来看,这个销售策略的效果明显,建议进一步优化...'
],
'customer-behavior-analyst': [
'通过分析客户行为数据,我发现了几个关键的用户模式...',
'这些客户行为指标显示出很大的优化空间,让我为您详细分析...',
'基于用户画像分析,我建议采用以下客户细分策略...'
],
'product-analyst': [
'根据产品数据分析,我发现了产品性能的几个关键指标...',
'用户反馈数据显示,产品在以下几个方面还有提升空间...',
'竞品分析结果表明,我们的产品在市场定位上具有以下优势...'
],
'document-expert': [
'我理解您的文档需求。根据您的描述,我建议采用以下结构...',
'对于这类文档,我们通常遵循规范的格式要求...',
'让我为您提供一个详细的文档框架和写作建议...'
],
'data-analyst': [
'基于您提供的数据需求,我建议使用以下分析方法...',
'这个数据趋势很有意思,让我为您做进一步的分析...',
'我可以帮您生成相应的图表和分析报告...'
],
'meeting-assistant': [
'关于会议安排,我建议考虑以下几个要点...',
'让我帮您整理会议纪要的标准格式...',
'我可以协助您制定会议议程和跟踪后续任务...'
],
'translation-expert': [
'针对这个翻译需求,我需要了解具体的语言对和专业领域...',
'让我为您提供准确的翻译和本地化建议...',
'这个术语在不同语境下有不同的表达方式...'
],
'workflow-optimizer': [
'通过分析您的工作流程,我发现了一些优化机会...',
'建议您采用以下流程改进措施来提高效率...',
'我可以为您推荐一些自动化工具和最佳实践...'
],
'legal-advisor': [
'从合规角度来看,这个文档需要注意以下几个要点...',
'让我为您检查相关的法律条款和风险点...',
'建议在文档中添加必要的免责声明和合规要求...'
]
}
const agentResponses = responses[agent.id] || ['我正在分析您的需求,请稍候...']
return agentResponses[Math.floor(Math.random() * agentResponses.length)]
}
const clearChat = () => {
chatHistory.value = []
if (currentAgent.value) {
chatHistory.value.push({
type: 'agent',
content: `您好!我是${currentAgent.value.name}${currentAgent.value.description}我能为您做些什么?`,
timestamp: new Date()
})
}
}
const closeChatDialog = (done) => {
chatVisible.value = false
currentAgent.value = null
chatHistory.value = []
done()
}
const formatTime = (date) => {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
}
</script>
<style scoped>
.office-agent {
padding: 20px;
}
.welcome-section {
text-align: center;
margin-bottom: 40px;
}
.welcome-section h1 {
font-size: 28px;
color: #303133;
margin-bottom: 10px;
}
.welcome-section p {
font-size: 16px;
color: #606266;
}
/* 响应式头像 */
@media (max-width: 768px) {
.agent-avatar-custom {
width: 50px !important;
height: 50px !important;
font-size: 20px !important;
}
.chat-avatar-custom {
width: 28px !important;
height: 28px !important;
font-size: 12px !important;
}
}
.content-wrapper {
margin-top: 20px;
}
.agent-container {
max-width: 1200px;
margin: 0 auto;
}
.agent-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.agent-card {
cursor: pointer;
transition: transform 0.2s ease;
}
.agent-card:hover {
transform: translateY(-5px);
}
.agent-content {
text-align: center;
padding: 20px;
position: relative;
}
.agent-avatar {
margin-bottom: 15px;
display: flex;
justify-content: center;
}
.agent-avatar-custom {
transition: transform 0.3s ease, box-shadow 0.3s ease;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.agent-avatar-custom:hover {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4) !important;
}
.agent-status {
position: absolute;
top: 10px;
right: 10px;
}
h3 {
margin: 15px 0 10px 0;
color: #303133;
font-size: 20px;
font-weight: 600;
}
p {
color: #606266;
line-height: 1.5;
margin: 0 0 15px 0;
}
.agent-capabilities {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.capability-tag {
margin: 2px;
}
.chat-container {
height: 500px;
display: flex;
flex-direction: column;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px;
border: 1px solid #EBEEF5;
border-radius: 4px;
margin-bottom: 15px;
}
.message {
display: flex;
margin-bottom: 20px;
}
.message.user {
flex-direction: row-reverse;
}
.message.user .message-content {
text-align: right;
margin-right: 10px;
}
.message.agent .message-content {
margin-left: 10px;
}
.message-avatar {
flex-shrink: 0;
}
.chat-avatar-custom {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.chat-avatar-custom.user-avatar {
background: linear-gradient(135deg, #909399, #606266);
}
.chat-avatar-custom.agent-avatar {
transition: transform 0.2s ease;
}
.chat-avatar-custom.agent-avatar:hover {
transform: scale(1.05);
}
.message-content {
flex: 1;
max-width: 70%;
}
.message-text {
background: #F5F7FA;
padding: 10px 15px;
border-radius: 10px;
margin-bottom: 5px;
line-height: 1.5;
}
.message.user .message-text {
background: #409EFF;
color: white;
}
.message-time {
font-size: 12px;
color: #909399;
}
.chat-input {
border-top: 1px solid #EBEEF5;
padding-top: 15px;
}
.chat-actions {
margin-top: 10px;
display: flex;
gap: 10px;
}
</style>

View File

@ -0,0 +1,207 @@
<template>
<div class="page-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-content">
<router-link to="/" class="back-button">
<i class="el-icon-arrow-left"></i>
返回主页
</router-link>
<div class="page-title">
<i class="el-icon-service title-icon"></i>
<h1>办公助手</h1>
</div>
<div></div> <!-- 占位元素保持布局平衡 -->
</div>
</div>
<!-- 页面内容 -->
<div class="page-content">
<div class="feature-grid">
<el-card
v-for="item in assistantFeatures"
:key="item.id"
class="feature-card"
shadow="hover"
>
<div class="feature-content">
<div class="feature-icon">
<i :class="item.icon"></i>
</div>
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
<div class="feature-action">
<el-button type="primary" @click="openFeature(item)">
开始使用
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
</div>
</el-card>
</div>
</div>
<!-- 功能对话框 -->
<el-dialog
v-model="dialogVisible"
:title="currentFeature?.title"
width="80%"
:before-close="handleClose"
>
<div class="dialog-content">
<component
:is="currentComponent"
v-if="currentComponent"
@close="closeDialog"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
//
import ErrorCorrection from '../components/office-assistant/ErrorCorrection.vue'
import SpeechToText from '../components/office-assistant/SpeechToText.vue'
import KeyInfoExtraction from '../components/office-assistant/KeyInfoExtraction.vue'
import MultilingualProcessing from '../components/office-assistant/MultilingualProcessing.vue'
const dialogVisible = ref(false)
const currentFeature = ref(null)
const currentComponent = ref(null)
const assistantFeatures = [
{
id: 'typo-correction',
title: '错误纠正',
description: '智能检测并纠正文档中的拼写、语法和格式错误',
icon: 'el-icon-edit-outline',
component: 'ErrorCorrection'
},
{
id: 'speech-to-text',
title: '语音转文字',
description: '将语音录音转换为准确的文字内容',
icon: 'el-icon-microphone',
component: 'SpeechToText'
},
{
id: 'key-info-extraction',
title: '关键信息提取',
description: '从长篇文档中快速提取关键信息和要点',
icon: 'el-icon-search',
component: 'KeyInfoExtraction'
},
{
id: 'multilingual-processing',
title: '多语言处理',
description: '支持多种语言的翻译和处理功能',
icon: 'el-icon-globe',
component: 'MultilingualProcessing'
}
]
const componentMap = {
ErrorCorrection,
SpeechToText,
KeyInfoExtraction,
MultilingualProcessing
}
const openFeature = (feature) => {
currentFeature.value = feature
currentComponent.value = componentMap[feature.component]
dialogVisible.value = true
}
const closeDialog = () => {
dialogVisible.value = false
currentFeature.value = null
currentComponent.value = null
}
const handleClose = (done) => {
closeDialog()
done()
}
</script>
<style scoped>
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.feature-card {
transition: transform 0.2s ease;
border-radius: 12px;
overflow: hidden;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-content {
text-align: center;
padding: 40px 30px;
}
.feature-icon {
font-size: 64px;
color: #409EFF;
margin-bottom: 20px;
}
.feature-icon i {
font-size: 64px;
}
h3 {
margin: 20px 0 15px 0;
color: #303133;
font-size: 24px;
font-weight: 600;
}
p {
color: #606266;
line-height: 1.6;
margin: 0 0 25px 0;
font-size: 16px;
}
.feature-action {
margin-top: 20px;
}
.dialog-content {
padding: 20px;
}
@media (max-width: 768px) {
.feature-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.feature-content {
padding: 30px 20px;
}
.feature-icon {
font-size: 48px;
}
.feature-icon i {
font-size: 48px;
}
h3 {
font-size: 20px;
}
}
</style>

18
vite.config.js Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://1.94.234.11:8080',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})

23
功能描述.txt Normal file
View File

@ -0,0 +1,23 @@
帮我使用vue前端技术
来实现一个办公智能编稿系统
最左边是功能菜单,分别是 文档生成、专业办公辅助、知识问答、办公智能体
点击菜单后 显示的是一个个子功能,以卡片的形式。在右边显示,比如文档生成的功能有,会议纪要总结,讲话稿生成,工作报告等。
文档生成子功能最前面增加一个技术文件生成
界面功能
1标题及要点
界面字段:标题,功能和指标,附加信息
按钮,下一步
2全文要点
界面字段:左边全文要点生成,右边要点编辑
按钮:再次生成,重置,下一步
3提纲
界面字段:提纲生成,提纲编辑
按钮:再次生成,重置,下一步
4正文
界面字段:左边提纲,右边正文
按钮:再次生成,重置,导出
分步
先把我实现这部分效果,不要帮我运行,告诉我怎么做就行