Files
ruoyi-vue/src/views/system/jdorder/components/TencentDocAutoWriteConfig.vue
2026-03-01 00:07:16 +08:00

740 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>
<el-dialog
:visible.sync="visible"
width="900px"
:close-on-click-modal="false"
@close="handleClose"
top="5vh"
>
<!-- 自定义标题 -->
<div slot="title" class="dialog-title">
<i class="el-icon-setting"></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>
<div class="config-container">
<!-- 左侧配置表单 -->
<div class="config-left">
<!-- 授权状态 -->
<div class="config-section">
<div 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"
>
立即授权
</el-button>
</div>
</div>
<!-- 文档配置表单 -->
<div class="config-section">
<div class="section-header">
<i class="el-icon-document"></i>
<span>目标文档</span>
</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>
</div>
</div>
<!-- 右侧状态信息 -->
<div class="config-right">
<!-- 配置状态提示 -->
<div class="status-card">
<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 }}</div>
</div>
</div>
<!-- 表格行数从接口获取用于决定同步范围 -->
<div v-if="config.progressHint || config.currentProgress" class="progress-card">
<div 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>
</div>
<!-- 快速帮助 -->
<div class="help-card">
<div 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>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div slot="footer" class="footer-buttons">
<div class="footer-left">
<el-button @click="showOperationLogs = true" icon="el-icon-document" size="small">
操作日志
</el-button>
<el-button @click="handleTest" :loading="testLoading" icon="el-icon-setting" size="small">
测试配置
</el-button>
</div>
<div class="footer-right">
<el-button @click="handleClear" :loading="clearLoading" type="danger" plain size="small">
清除配置
</el-button>
<el-button @click="handleClose" size="small">取消</el-button>
<el-button type="primary" @click="handleSave" :loading="saveLoading" size="small">
<i class="el-icon-check"></i> 保存配置
</el-button>
</div>
</div>
<!-- 操作日志查看对话框 -->
<tencent-doc-operation-logs
v-model="showOperationLogs"
:file-id="form.fileId"
:sheet-id="form.sheetId"
/>
</el-dialog>
</template>
<script>
import {
getAutoWriteConfig,
updateAutoWriteConfig,
testAutoWriteConfig,
clearAutoWriteConfig,
getDocSheetList,
getTencentDocAuthUrl
} from '@/api/jarvis/tendoc'
import TencentDocOperationLogs from './TencentDocOperationLogs'
export default {
name: 'TencentDocAutoWriteConfig',
components: {
TencentDocOperationLogs
},
props: {
value: {
type: Boolean,
default: false
}
},
data() {
return {
showOperationLogs: false,
visible: false,
config: {
hasAccessToken: false,
accessTokenStatus: '未授权',
fileId: '',
sheetId: '',
appId: '',
apiBaseUrl: '',
isConfigured: false,
hint: ''
},
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
}
},
watch: {
value(val) {
this.visible = val
if (val) {
this.loadConfig()
}
},
visible(val) {
this.$emit('input', val)
}
},
methods: {
/** 加载当前配置 */
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('授权页面已打开,请在新窗口中完成授权')
// 1秒后刷新配置状态
setTimeout(() => {
this.loadConfig()
}, 1000)
} catch (e) {
this.$message.error('打开授权页面失败:' + (e.message || '未知错误'))
}
},
/** 获取工作表列表 */
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)
this.$emit('config-updated')
} 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()
this.$emit('config-updated')
} else {
this.$message.error('清除失败:' + (res.msg || '未知错误'))
}
} catch (e) {
if (e !== 'cancel') {
this.$message.error('清除失败:' + (e.message || '未知错误'))
}
} finally {
this.clearLoading = false
}
},
/** 关闭对话框 */
handleClose() {
this.visible = false
this.sheetList = []
}
}
}
</script>
<style scoped>
/* 标题样式 */
.dialog-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 500;
}
.dialog-title i {
margin-right: 8px;
font-size: 18px;
}
/* 容器布局 */
.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;
padding: 15px;
}
.section-header {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #e4e7ed;
}
.section-header i {
margin-right: 6px;
font-size: 16px;
color: #409eff;
}
/* 授权状态 */
.auth-status {
display: flex;
align-items: center;
gap: 10px;
}
/* 状态卡片 */
.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 {
background: white;
border: 1px solid #e4e7ed;
border-radius: 6px;
overflow: hidden;
}
.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 {
background: white;
border: 1px solid #e4e7ed;
border-radius: 6px;
overflow: hidden;
}
.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;
}
/* 底部按钮 */
.footer-buttons {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
}
.footer-left,
.footer-right {
display: flex;
gap: 8px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.config-container {
flex-direction: column;
}
.config-right {
width: 100%;
}
}
/* 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%;
}
</style>