Files
ruoyi-vue/src/views/jarvis/docSync/components/TencentDocConfig.vue
2026-03-01 00:07:16 +08:00

724 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="tendoc-config">
<div class="config-container">
<!-- 左侧配置表单 -->
<div class="config-left">
<!-- 授权状态 -->
<el-card class="config-section">
<div slot="header" class="section-header">
<i class="el-icon-key"></i>
<span>授权状态</span>
</div>
<div class="auth-status">
<el-tag v-if="config.hasAccessToken" type="success" size="medium">
<i class="el-icon-circle-check"></i> {{ config.accessTokenStatus }}
</el-tag>
<el-tag v-else type="danger" size="medium">
<i class="el-icon-circle-close"></i> {{ config.accessTokenStatus }}
</el-tag>
<el-button
v-if="!config.hasAccessToken"
type="primary"
size="small"
icon="el-icon-unlock"
@click="handleAuth"
style="margin-left: 10px;"
>
立即授权
</el-button>
<el-button
v-else
type="info"
size="small"
icon="el-icon-refresh"
@click="handleRefreshAuth"
style="margin-left: 10px;"
>
刷新状态
</el-button>
</div>
<div v-if="config.hint" class="config-hint" style="margin-top: 10px; color: #909399; font-size: 12px;">
<i class="el-icon-info"></i> {{ config.hint }}
</div>
</el-card>
<!-- 文档配置表单 -->
<el-card class="config-section">
<div slot="header" class="section-header">
<i class="el-icon-document"></i>
<span>H-TF订单自动写入配置</span>
<el-tag v-if="config.isConfigured" type="success" size="mini" style="margin-left: 10px;">
<i class="el-icon-success"></i> 已配置
</el-tag>
<el-tag v-else type="warning" size="mini" style="margin-left: 10px;">
<i class="el-icon-warning"></i> 未配置
</el-tag>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="100px" size="small">
<el-form-item label="文件ID" prop="fileId">
<el-input
v-model="form.fileId"
placeholder="例如DUW50RUprWXh2TGJK"
clearable
>
<el-button
slot="append"
icon="el-icon-search"
@click="handleFetchSheets"
:disabled="!form.fileId"
>
获取工作表
</el-button>
</el-input>
</el-form-item>
<el-form-item label="工作表ID" prop="sheetId">
<el-select
v-if="sheetList.length > 0"
v-model="form.sheetId"
placeholder="请选择工作表"
style="width: 100%;"
clearable
>
<el-option
v-for="sheet in sheetList"
:key="sheet.sheetId"
:label="sheet.title"
:value="sheet.sheetId"
>
<span style="float: left">{{ sheet.title }}</span>
<span style="float: right; color: #8492a6; font-size: 12px;">{{ sheet.sheetId }}</span>
</el-option>
</el-select>
<el-input
v-else
v-model="form.sheetId"
placeholder="例如BB08J2"
clearable
/>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="表头行号" prop="headerRow">
<el-input-number
v-model="form.headerRow"
:min="1"
controls-position="right"
style="width: 100%;"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="起始行号" prop="startRow">
<el-input-number
v-model="form.startRow"
:min="1"
controls-position="right"
style="width: 100%;"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="handleSave" :loading="saveLoading" icon="el-icon-check">
保存配置
</el-button>
<el-button @click="handleTest" :loading="testLoading" icon="el-icon-setting">
测试配置
</el-button>
<el-button @click="handleClear" :loading="clearLoading" type="danger" plain icon="el-icon-delete">
清除配置
</el-button>
<el-button @click="showOperationLogs = true" icon="el-icon-document">
操作日志
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<!-- 右侧状态信息 -->
<div class="config-right">
<!-- 配置状态提示 -->
<el-card class="status-card-wrapper">
<div class="status-card" :class="config.isConfigured ? 'success' : 'warning'">
<div class="status-icon" :class="config.isConfigured ? 'success' : 'warning'">
<i :class="config.isConfigured ? 'el-icon-success' : 'el-icon-warning'"></i>
</div>
<div class="status-text">
<div class="status-title">{{ config.isConfigured ? '配置完成' : '配置未完成' }}</div>
<div class="status-desc">{{ config.hint || (config.isConfigured ? 'H-TF订单将自动写入腾讯文档' : '请完成配置') }}</div>
</div>
</div>
</el-card>
<!-- 表格行数从接口获取用于决定同步范围 -->
<el-card v-if="config.progressHint || config.currentProgress" class="progress-card-wrapper">
<div slot="header" class="card-header">
<i class="el-icon-data-line"></i>
<span>表格行数</span>
</div>
<div class="progress-content">
<div v-if="config.currentProgress" class="progress-detail">
<div class="progress-item">
<span class="label">当前有数据行数</span>
<span class="value"> {{ config.currentProgress }} 接口获取</span>
</div>
<div class="progress-item">
<span class="label">下次同步起始</span>
<span class="value"> {{ config.nextStartRow != null ? config.nextStartRow : form.startRow }} </span>
</div>
<div class="progress-hint">
<i class="el-icon-info"></i>
由接口实时获取表格行数不再使用本地保存的进度
</div>
</div>
<div v-else class="no-progress">
{{ config.progressHint || '暂无表格行数(请先授权并配置)' }}
</div>
</div>
</el-card>
<!-- 快速帮助 -->
<el-card class="help-card-wrapper">
<div slot="header" class="card-header">
<i class="el-icon-question"></i>
<span>配置说明</span>
</div>
<div class="help-content">
<div class="help-item">
<i class="el-icon-check"></i>
<span>文件ID从腾讯文档URL中获取</span>
</div>
<div class="help-item">
<i class="el-icon-check"></i>
<span>点击"获取工作表"自动加载</span>
</div>
<div class="help-item">
<i class="el-icon-check"></i>
<span>表头行号默认为第2行</span>
</div>
<div class="help-item">
<i class="el-icon-check"></i>
<span>数据起始行默认为第3行</span>
</div>
</div>
</el-card>
</div>
</div>
<!-- 操作日志查看对话框 -->
<tencent-doc-operation-logs
v-model="showOperationLogs"
:file-id="form.fileId"
:sheet-id="form.sheetId"
/>
</div>
</template>
<script>
import {
getAutoWriteConfig,
updateAutoWriteConfig,
testAutoWriteConfig,
clearAutoWriteConfig,
getDocSheetList,
getTencentDocAuthUrl
} from '@/api/jarvis/tendoc'
import TencentDocOperationLogs from '@/views/system/jdorder/components/TencentDocOperationLogs'
export default {
name: 'TencentDocConfig',
components: {
TencentDocOperationLogs
},
data() {
return {
showOperationLogs: false,
config: {
hasAccessToken: false,
accessTokenStatus: '未授权',
fileId: '',
sheetId: '',
appId: '',
apiBaseUrl: '',
isConfigured: false,
hint: '',
progressHint: '',
currentProgress: null
},
form: {
fileId: '',
sheetId: '',
headerRow: 2,
startRow: 3
},
rules: {
fileId: [
{ required: true, message: '请输入文件ID', trigger: 'blur' }
],
sheetId: [
{ required: true, message: '请输入工作表ID', trigger: 'blur' }
],
headerRow: [
{ required: true, message: '请输入表头行号', trigger: 'blur' },
{ type: 'number', min: 1, message: '表头行号必须大于0', trigger: 'blur' }
],
startRow: [
{ required: true, message: '请输入数据起始行', trigger: 'blur' },
{ type: 'number', min: 1, message: '数据起始行必须大于0', trigger: 'blur' }
]
},
sheetList: [],
saveLoading: false,
testLoading: false,
clearLoading: false
}
},
created() {
this.loadConfig()
},
methods: {
/** 刷新配置 */
refresh() {
this.loadConfig()
},
/** 加载当前配置 */
async loadConfig() {
try {
const res = await getAutoWriteConfig()
if (res.code === 200 && res.data) {
this.config = res.data
this.form.fileId = res.data.fileId || ''
this.form.sheetId = res.data.sheetId || ''
// 确保 headerRow 和 startRow 是数字类型
this.form.headerRow = parseInt(res.data.headerRow) || 2
this.form.startRow = parseInt(res.data.startRow) || 3
console.log('配置加载成功 - headerRow:', this.form.headerRow, 'startRow:', this.form.startRow)
}
} catch (e) {
this.$message.error('加载配置失败:' + (e.message || '未知错误'))
}
},
/** 打开授权页面 */
async handleAuth() {
try {
const res = await getTencentDocAuthUrl()
if (res.code !== 200 || !res.data) {
this.$message.error('获取授权URL失败')
return
}
const authUrl = res.data
const width = 600
const height = 700
const left = (window.screen.width - width) / 2
const top = (window.screen.height - height) / 2
window.open(
authUrl,
'腾讯文档授权',
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
)
this.$message.success('授权页面已打开,请在新窗口中完成授权')
// 监听授权完成消息
const messageHandler = (event) => {
if (event.data && event.data.type === 'tendoc_oauth_callback') {
window.removeEventListener('message', messageHandler)
this.loadConfig()
this.$message.success('授权完成')
}
}
window.addEventListener('message', messageHandler)
// 3秒后刷新配置状态
setTimeout(() => {
this.loadConfig()
}, 3000)
} catch (e) {
this.$message.error('打开授权页面失败:' + (e.message || '未知错误'))
}
},
/** 刷新授权状态 */
async handleRefreshAuth() {
await this.loadConfig()
this.$message.success('授权状态已刷新')
},
/** 获取工作表列表 */
async handleFetchSheets() {
if (!this.form.fileId) {
this.$message.warning('请先输入文件ID')
return
}
try {
this.$message.info('正在获取工作表列表...')
const res = await getDocSheetList(this.form.fileId)
if (res.code === 200 && res.data && res.data.sheets) {
this.sheetList = res.data.sheets
this.$message.success(`获取成功,共 ${this.sheetList.length} 个工作表`)
} else {
this.$message.error('获取工作表列表失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$message.error('获取工作表列表失败:' + (e.message || '未知错误'))
}
},
/** 保存配置 */
handleSave() {
this.$refs.form.validate(async valid => {
if (!valid) {
return
}
this.saveLoading = true
try {
const res = await updateAutoWriteConfig({
fileId: this.form.fileId,
sheetId: this.form.sheetId,
headerRow: this.form.headerRow,
startRow: this.form.startRow
})
if (res.code === 200) {
this.$message.success(`配置保存成功!表头第${this.form.headerRow}行,数据从第${this.form.startRow}行开始`)
console.log('配置保存成功 - 保存的值:', {
fileId: this.form.fileId,
sheetId: this.form.sheetId,
headerRow: this.form.headerRow,
startRow: this.form.startRow
})
// 延迟重新加载配置,确保后端已保存
setTimeout(() => {
this.loadConfig()
}, 500)
} else {
this.$message.error('保存失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$message.error('保存失败:' + (e.message || '未知错误'))
} finally {
this.saveLoading = false
}
})
},
/** 测试配置 */
async handleTest() {
this.testLoading = true
try {
const res = await testAutoWriteConfig()
if (res.code === 200) {
this.$alert(
'<pre style="text-align: left; max-height: 400px; overflow: auto;">' +
JSON.stringify(res.data, null, 2) +
'</pre>',
'测试成功',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '确定',
type: 'success'
}
)
} else {
this.$message.error('测试失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$message.error('测试失败:' + (e.message || '未知错误'))
} finally {
this.testLoading = false
}
},
/** 清除配置 */
async handleClear() {
try {
await this.$confirm('确定要清除配置吗?这不会清除授权令牌。', '提示', {
type: 'warning'
})
this.clearLoading = true
const res = await clearAutoWriteConfig()
if (res.code === 200) {
this.$message.success('配置已清除')
this.form.fileId = ''
this.form.sheetId = ''
this.form.startRow = 3
this.sheetList = []
this.loadConfig()
} else {
this.$message.error('清除失败:' + (res.msg || '未知错误'))
}
} catch (e) {
if (e !== 'cancel') {
this.$message.error('清除失败:' + (e.message || '未知错误'))
}
} finally {
this.clearLoading = false
}
}
}
}
</script>
<style scoped>
/* 容器布局 */
.config-container {
display: flex;
gap: 20px;
min-height: 400px;
}
.config-left {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
}
.config-right {
width: 300px;
display: flex;
flex-direction: column;
gap: 15px;
}
/* 配置区块 */
.config-section {
background: #f5f7fa;
border-radius: 6px;
}
.section-header {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #303133;
}
.section-header i {
margin-right: 6px;
font-size: 16px;
color: #409eff;
}
/* 授权状态 */
.auth-status {
display: flex;
align-items: center;
gap: 10px;
}
.config-hint {
margin-top: 10px;
color: #909399;
font-size: 12px;
}
/* 状态卡片 */
.status-card-wrapper {
padding: 0;
border: none;
box-shadow: none;
}
.status-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
padding: 20px;
color: white;
display: flex;
align-items: center;
gap: 15px;
box-shadow: 0 2px 12px rgba(102, 126, 234, 0.3);
}
.status-card.warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.status-icon {
width: 48px;
height: 48px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
flex-shrink: 0;
}
.status-icon.success {
background: rgba(103, 194, 58, 0.2);
}
.status-icon.warning {
background: rgba(230, 162, 60, 0.2);
}
.status-text {
flex: 1;
}
.status-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 5px;
}
.status-desc {
font-size: 12px;
opacity: 0.9;
line-height: 1.5;
}
/* 进度卡片 */
.progress-card-wrapper {
padding: 0;
border: none;
box-shadow: none;
}
.progress-card-wrapper >>> .el-card__body {
padding: 0;
}
.card-header {
background: #f5f7fa;
padding: 12px 15px;
font-size: 14px;
font-weight: 500;
color: #303133;
display: flex;
align-items: center;
border-bottom: 1px solid #e4e7ed;
}
.card-header i {
margin-right: 6px;
color: #409eff;
}
.progress-content {
padding: 15px;
}
.progress-detail {
display: flex;
flex-direction: column;
gap: 10px;
}
.progress-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #f0f9ff;
border-radius: 4px;
border-left: 3px solid #409eff;
}
.progress-item .label {
font-size: 13px;
color: #606266;
}
.progress-item .value {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.progress-hint {
font-size: 12px;
color: #909399;
padding: 8px 12px;
background: #fef0f0;
border-radius: 4px;
border-left: 3px solid #f56c6c;
display: flex;
align-items: center;
gap: 5px;
}
.no-progress {
font-size: 13px;
color: #909399;
text-align: center;
padding: 10px;
}
/* 帮助卡片 */
.help-card-wrapper {
padding: 0;
border: none;
box-shadow: none;
}
.help-card-wrapper >>> .el-card__body {
padding: 0;
}
.help-content {
padding: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.help-item {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 13px;
color: #606266;
line-height: 1.6;
}
.help-item i {
color: #67c23a;
margin-top: 2px;
flex-shrink: 0;
}
/* Element UI 覆盖样式 */
.config-section >>> .el-form-item {
margin-bottom: 18px;
}
.config-section >>> .el-form-item__label {
font-weight: 500;
color: #606266;
}
.config-section >>> .el-input-number {
width: 100%;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.config-container {
flex-direction: column;
}
.config-right {
width: 100%;
}
}
</style>