This commit is contained in:
Leo
2026-01-15 16:32:00 +08:00
parent 27f40074e3
commit 09cb3c2862
4 changed files with 702 additions and 0 deletions

View File

@@ -238,6 +238,22 @@ export const dynamicRoutes = [
}
]
},
// 文档同步配置
{
path: '/docSync',
component: Layout,
redirect: 'noredirect',
name: 'DocSync',
meta: { title: '文档同步配置', icon: 'document' },
children: [
{
path: 'index',
component: () => import('@/views/jarvis/docSync/index'),
name: 'DocSyncIndex',
meta: { title: '文档同步配置', icon: 'document' }
}
]
},
// 线报群管理
{
path: '/xbgroup',

View File

@@ -0,0 +1,406 @@
<template>
<div class="tendoc-config">
<!-- 授权状态 -->
<el-card class="auth-card" style="margin-bottom: 20px;">
<div slot="header">
<span><i class="el-icon-key"></i> 授权状态</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>
<div slot="header">
<span><i class="el-icon-setting"></i> 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="120px" size="small">
<el-form-item label="文件ID" prop="fileId">
<el-input
v-model="form.fileId"
placeholder="例如DUW50RUprWXh2TGJK"
clearable
style="width: 400px;"
>
<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: 400px;"
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
style="width: 400px;"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="表头行号" prop="headerRow">
<el-input-number
v-model="form.headerRow"
:min="1"
:max="100"
style="width: 100%;"
/>
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
表头所在的行号从1开始
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据起始行" prop="startRow">
<el-input-number
v-model="form.startRow"
:min="1"
:max="10000"
style="width: 100%;"
/>
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
数据开始的行号从1开始
</div>
</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>
<!-- 操作日志对话框 -->
<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,
getTokenStatus
} 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: '',
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
}
},
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 || ''
this.form.headerRow = parseInt(res.data.headerRow) || 2
this.form.startRow = parseInt(res.data.startRow) || 3
}
} 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}行开始`)
this.loadConfig()
} 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.$message.success('配置测试通过!')
} else {
this.$message.error('配置测试失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$message.error('配置测试失败:' + (e.message || '未知错误'))
} finally {
this.testLoading = false
}
},
/** 清除配置 */
handleClear() {
this.$confirm('确定要清除配置吗?此操作不可恢复!', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
this.clearLoading = true
try {
const res = await clearAutoWriteConfig()
if (res.code === 200) {
this.$message.success('配置已清除')
this.form = {
fileId: '',
sheetId: '',
headerRow: 2,
startRow: 3
}
this.sheetList = []
this.loadConfig()
} else {
this.$message.error('清除配置失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$message.error('清除配置失败:' + (e.message || '未知错误'))
} finally {
this.clearLoading = false
}
})
}
}
}
</script>
<style scoped>
.tendoc-config {
padding: 0;
}
.auth-card {
margin-bottom: 20px;
}
.auth-status {
display: flex;
align-items: center;
}
.config-hint {
margin-top: 10px;
color: #909399;
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<div class="wps365-config">
<!-- 授权状态 -->
<el-card class="auth-card" style="margin-bottom: 20px;">
<div slot="header">
<span><i class="el-icon-key"></i> 授权状态</span>
</div>
<div class="auth-status">
<el-tag v-if="isAuthorized" type="success" size="medium">
<i class="el-icon-circle-check"></i> 已授权
</el-tag>
<el-tag v-else type="danger" size="medium">
<i class="el-icon-circle-close"></i> 未授权
</el-tag>
<el-button
v-if="!isAuthorized"
type="primary"
size="small"
icon="el-icon-unlock"
@click="handleAuthorize"
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="userInfo" class="user-info" style="margin-top: 15px;">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="用户ID">{{ userInfo.user_id || '-' }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ userInfo.name || '-' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ userInfo.email || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- 使用说明 -->
<el-card>
<div slot="header">
<span><i class="el-icon-info"></i> 使用说明</span>
</div>
<div class="usage-info">
<el-alert
title="配置步骤"
type="info"
:closable="false"
show-icon>
<ol style="margin: 10px 0; padding-left: 20px;">
<li>在WPS365开放平台注册应用并获取AppID和AppKey</li>
<li>配置回调地址<code>https://jarvis.van333.cn/wps365-callback</code></li>
<li>在权限管理中添加 <code>file.read</code> <code>ksheet:read</code> 权限</li>
<li>点击"立即授权"完成用户授权</li>
<li>授权成功后即可使用API读取和编辑在线表格</li>
</ol>
</el-alert>
<div style="margin-top: 15px;">
<el-link type="primary" href="https://open.wps.cn/" target="_blank" icon="el-icon-link">
WPS365开放平台
</el-link>
<el-link type="primary" href="/doc/WPS365集成使用说明.md" target="_blank" icon="el-icon-document" style="margin-left: 20px;">
查看详细文档
</el-link>
</div>
</div>
</el-card>
</div>
</template>
<script>
import {
getWPS365AuthUrl,
getWPS365TokenStatus,
getWPS365UserInfo
} from '@/api/jarvis/wps365'
export default {
name: 'WPS365Config',
data() {
return {
isAuthorized: false,
userInfo: null,
userId: 'default_user' // TODO: 从当前登录用户获取
}
},
created() {
this.checkAuthStatus()
},
methods: {
/** 刷新配置 */
refresh() {
this.checkAuthStatus()
},
/** 检查授权状态 */
async checkAuthStatus() {
try {
const response = await getWPS365TokenStatus(this.userId)
if (response.code === 200) {
this.isAuthorized = response.data.hasToken && response.data.isValid
if (this.isAuthorized) {
this.loadUserInfo()
}
}
} catch (error) {
console.error('检查授权状态失败', error)
}
},
/** 加载用户信息 */
async loadUserInfo() {
try {
const response = await getWPS365UserInfo(this.userId)
if (response.code === 200) {
this.userInfo = response.data
}
} catch (error) {
console.error('加载用户信息失败', error)
}
},
/** 处理授权 */
async handleAuthorize() {
try {
const response = await getWPS365AuthUrl()
if (response.code === 200) {
const width = 600
const height = 700
const left = (window.screen.width - width) / 2
const top = (window.screen.height - height) / 2
window.open(
response.data,
'WPS365授权',
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
)
this.$message.success('授权页面已打开,请在新窗口中完成授权')
// 监听授权完成消息
const messageHandler = (event) => {
if (event.data && event.data.type === 'wps365_oauth_callback') {
window.removeEventListener('message', messageHandler)
this.checkAuthStatus()
this.$message.success('授权完成')
}
}
window.addEventListener('message', messageHandler)
// 3秒后刷新配置状态
setTimeout(() => {
this.checkAuthStatus()
}, 3000)
}
} catch (error) {
this.$message.error('获取授权URL失败' + (error.msg || error.message))
}
},
/** 刷新授权状态 */
async handleRefreshAuth() {
await this.checkAuthStatus()
this.$message.success('授权状态已刷新')
}
}
}
</script>
<style scoped>
.wps365-config {
padding: 0;
}
.auth-card {
margin-bottom: 20px;
}
.auth-status {
display: flex;
align-items: center;
}
.user-info {
margin-top: 15px;
}
.usage-info {
line-height: 1.8;
}
.usage-info ol {
margin: 10px 0;
padding-left: 20px;
}
.usage-info code {
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
color: #e83e8c;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="app-container">
<el-card>
<div slot="header" class="clearfix">
<span style="font-weight: bold; font-size: 16px;">
<i class="el-icon-document"></i> 文档同步配置
</span>
</div>
<!-- Tab切换 -->
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
<el-tab-pane label="腾讯文档" name="tendoc">
<span slot="label">
<i class="el-icon-document"></i> 腾讯文档
</span>
<TencentDocConfig ref="tendocConfig" />
</el-tab-pane>
<el-tab-pane label="WPS365" name="wps365">
<span slot="label">
<i class="el-icon-document-copy"></i> WPS365
</span>
<WPS365Config ref="wps365Config" />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import TencentDocConfig from './components/TencentDocConfig'
import WPS365Config from './components/WPS365Config'
export default {
name: 'DocSync',
components: {
TencentDocConfig,
WPS365Config
},
data() {
return {
activeTab: 'tendoc'
}
},
methods: {
handleTabClick(tab) {
// Tab切换时的处理
this.$nextTick(() => {
if (tab.name === 'tendoc' && this.$refs.tendocConfig) {
this.$refs.tendocConfig.refresh()
} else if (tab.name === 'wps365' && this.$refs.wps365Config) {
this.$refs.wps365Config.refresh()
}
})
}
}
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
::v-deep .el-tabs__header {
margin-bottom: 20px;
}
</style>