446 lines
14 KiB
Vue
446 lines
14 KiB
Vue
<template>
|
||
<div class="kdocs-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="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="isAuthorized && tokenInfo" class="token-info" style="margin-top: 15px;">
|
||
<el-descriptions :column="2" border size="small">
|
||
<el-descriptions-item label="用户ID">{{ tokenInfo.userId || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="Token状态">
|
||
<el-tag v-if="tokenInfo.isValid" type="success" size="small">有效</el-tag>
|
||
<el-tag v-else type="warning" size="small">已过期</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-if="tokenInfo.expiresIn" label="有效期">
|
||
{{ Math.floor(tokenInfo.expiresIn / 60) }} 分钟
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</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;">已配置</el-tag>
|
||
<el-tag v-else type="warning" size="mini" style="margin-left: 10px;">未配置</el-tag>
|
||
</div>
|
||
<el-form ref="form" :model="form" :rules="rules" label-width="120px" size="small">
|
||
<el-form-item label="file_token" prop="fileId">
|
||
<el-input
|
||
v-model="form.fileId"
|
||
placeholder="个人云文档列表接口返回的 file_token(文档 open_id)"
|
||
clearable
|
||
>
|
||
<el-button
|
||
slot="append"
|
||
icon="el-icon-search"
|
||
:disabled="!form.fileId || !isAuthorized"
|
||
@click="handleTestRead"
|
||
>测试读取</el-button>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="工作表 sheet_idx">
|
||
<el-input-number v-model="form.sheetIdx" :min="0" controls-position="right" style="width: 100%;" />
|
||
</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" :loading="saveLoading" icon="el-icon-check" @click="handleSave">保存配置</el-button>
|
||
<el-button :loading="testLoading" icon="el-icon-setting" @click="handleTest">测试配置</el-button>
|
||
<el-button type="danger" plain :loading="clearLoading" icon="el-icon-delete" @click="handleClear">清除配置</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 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>file_token 来自「在线表格管理」文件列表或开放平台文档列表</span></div>
|
||
<div class="help-item"><i class="el-icon-check"></i><span>工作表读写使用 KSheet 单元格接口,sheet_idx 与后台 sheet 一致</span></div>
|
||
<div class="help-item"><i class="el-icon-check"></i><span>数据表(db)需改用数据表记录类 API,本页为工作表(et)场景</span></div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getKdocsAuthUrl,
|
||
getKdocsTokenStatus,
|
||
readKdocsCells,
|
||
updateKdocsCells
|
||
} from '@/api/jarvis/kdocs'
|
||
|
||
const LS_KEY = 'kdocs_auto_write_config'
|
||
|
||
export default {
|
||
name: 'KdocsCloudConfig',
|
||
data() {
|
||
return {
|
||
isAuthorized: false,
|
||
userId: 'default_user',
|
||
tokenInfo: null,
|
||
config: { isConfigured: false, hint: '' },
|
||
form: {
|
||
fileId: '',
|
||
sheetIdx: 0,
|
||
headerRow: 2,
|
||
startRow: 3
|
||
},
|
||
rules: {
|
||
fileId: [{ required: true, message: '请输入 file_token', 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' }
|
||
]
|
||
},
|
||
saveLoading: false,
|
||
testLoading: false,
|
||
clearLoading: false
|
||
}
|
||
},
|
||
created() {
|
||
this.checkAuthStatus()
|
||
this.loadConfig()
|
||
},
|
||
methods: {
|
||
refresh() {
|
||
this.checkAuthStatus()
|
||
this.loadConfig()
|
||
},
|
||
async checkAuthStatus() {
|
||
try {
|
||
const response = await getKdocsTokenStatus(this.userId)
|
||
if (response.code === 200) {
|
||
// 有 token 即视为已授权;isValid 单独展示(避免 expires_in 缺失时误判未保存)
|
||
this.isAuthorized = !!response.data.hasToken
|
||
this.tokenInfo = response.data
|
||
if (response.data.userId) {
|
||
this.userId = response.data.userId
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
}
|
||
},
|
||
loadConfig() {
|
||
try {
|
||
const raw = localStorage.getItem(LS_KEY)
|
||
if (raw) {
|
||
const c = JSON.parse(raw)
|
||
this.form.fileId = c.fileId || ''
|
||
this.form.sheetIdx = c.sheetIdx != null ? c.sheetIdx : 0
|
||
this.form.headerRow = c.headerRow || 2
|
||
this.form.startRow = c.startRow || 3
|
||
this.config.isConfigured = !!c.fileId
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
}
|
||
},
|
||
extractValues(payload) {
|
||
if (!payload) return []
|
||
if (Array.isArray(payload.values)) return payload.values
|
||
if (payload.data && Array.isArray(payload.data.values)) return payload.data.values
|
||
return []
|
||
},
|
||
async handleAuthorize() {
|
||
try {
|
||
const response = await getKdocsAuthUrl()
|
||
if (response.code === 200) {
|
||
const w = 600
|
||
const h = 700
|
||
window.open(
|
||
response.data,
|
||
'KdocsAuth',
|
||
`width=${w},height=${h},left=${(screen.width - w) / 2},top=${(screen.height - h) / 2},resizable=yes,scrollbars=yes`
|
||
)
|
||
this.$message.success('请在弹出窗口完成授权')
|
||
const handler = (event) => {
|
||
if (event.data && event.data.type === 'kdocs_oauth_callback') {
|
||
window.removeEventListener('message', handler)
|
||
if (event.data.userId) {
|
||
this.userId = event.data.userId
|
||
}
|
||
setTimeout(() => {
|
||
this.checkAuthStatus()
|
||
this.$message.success('授权完成')
|
||
}, 500)
|
||
}
|
||
}
|
||
window.addEventListener('message', handler)
|
||
setTimeout(() => this.checkAuthStatus(), 3000)
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e.msg || e.message || '获取授权地址失败')
|
||
}
|
||
},
|
||
async handleRefreshAuth() {
|
||
await this.checkAuthStatus()
|
||
this.$message.success('已刷新')
|
||
},
|
||
async handleTestRead() {
|
||
if (!this.isAuthorized) {
|
||
this.$message.warning('请先授权')
|
||
return
|
||
}
|
||
if (!this.form.fileId) {
|
||
this.$message.warning('请输入 file_token')
|
||
return
|
||
}
|
||
try {
|
||
const response = await readKdocsCells({
|
||
userId: this.userId,
|
||
fileToken: this.form.fileId,
|
||
sheetIdx: this.form.sheetIdx,
|
||
range: 'A1:B5'
|
||
})
|
||
if (response.code === 200) {
|
||
this.$message.success('读取成功')
|
||
console.log('read', response.data)
|
||
} else {
|
||
this.$message.warning(response.msg || '读取失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e.msg || e.message || '读取失败')
|
||
}
|
||
},
|
||
handleSave() {
|
||
this.$refs.form.validate(async (valid) => {
|
||
if (!valid) return
|
||
if (!this.isAuthorized) {
|
||
this.$message.warning('请先授权')
|
||
return
|
||
}
|
||
this.saveLoading = true
|
||
try {
|
||
localStorage.setItem(
|
||
LS_KEY,
|
||
JSON.stringify({
|
||
fileId: this.form.fileId,
|
||
sheetIdx: this.form.sheetIdx,
|
||
headerRow: this.form.headerRow,
|
||
startRow: this.form.startRow
|
||
})
|
||
)
|
||
this.config.isConfigured = true
|
||
this.config.hint = '将使用金山文档 KSheet 写入工作表'
|
||
this.$message.success('已保存')
|
||
} finally {
|
||
this.saveLoading = false
|
||
}
|
||
})
|
||
},
|
||
handleTest() {
|
||
if (!this.isAuthorized) {
|
||
this.$message.warning('请先授权')
|
||
return
|
||
}
|
||
this.$refs.form.validate(async (valid) => {
|
||
if (!valid) return
|
||
this.testLoading = true
|
||
try {
|
||
const readRes = await readKdocsCells({
|
||
userId: this.userId,
|
||
fileToken: this.form.fileId,
|
||
sheetIdx: this.form.sheetIdx,
|
||
range: 'A1:B5'
|
||
})
|
||
if (readRes.code !== 200) {
|
||
this.$message.error(readRes.msg || '读失败')
|
||
return
|
||
}
|
||
const testRange = `A${this.form.startRow}:B${this.form.startRow}`
|
||
const writeRes = await updateKdocsCells({
|
||
userId: this.userId,
|
||
fileToken: this.form.fileId,
|
||
sheetIdx: this.form.sheetIdx,
|
||
range: testRange,
|
||
values: [['测试1', '测试2']]
|
||
})
|
||
if (writeRes.code === 200) {
|
||
this.$message.success('读写测试成功')
|
||
} else {
|
||
this.$message.warning(writeRes.msg || '写失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e.msg || e.message || '测试失败')
|
||
} finally {
|
||
this.testLoading = false
|
||
}
|
||
})
|
||
},
|
||
handleClear() {
|
||
this.$confirm('确定清除本地配置?', '提示', { type: 'warning' })
|
||
.then(() => {
|
||
localStorage.removeItem(LS_KEY)
|
||
this.form.fileId = ''
|
||
this.form.sheetIdx = 0
|
||
this.form.headerRow = 2
|
||
this.form.startRow = 3
|
||
this.config.isConfigured = false
|
||
this.config.hint = ''
|
||
this.$message.success('已清除')
|
||
})
|
||
.catch(() => {})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.kdocs-config {
|
||
padding: 0;
|
||
}
|
||
.config-container {
|
||
display: flex;
|
||
gap: 20px;
|
||
}
|
||
.config-left {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.config-right {
|
||
width: 300px;
|
||
flex-shrink: 0;
|
||
}
|
||
.config-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.auth-status {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.status-card-wrapper {
|
||
margin-bottom: 20px;
|
||
}
|
||
.status-card {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
}
|
||
.status-card.success {
|
||
background-color: #f0f9ff;
|
||
border: 1px solid #b3d8ff;
|
||
}
|
||
.status-card.warning {
|
||
background-color: #fef0f0;
|
||
border: 1px solid #fbc4c4;
|
||
}
|
||
.status-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
margin-right: 15px;
|
||
}
|
||
.status-icon.success {
|
||
background-color: #67c23a;
|
||
color: white;
|
||
}
|
||
.status-icon.warning {
|
||
background-color: #e6a23c;
|
||
color: white;
|
||
}
|
||
.status-text {
|
||
flex: 1;
|
||
}
|
||
.status-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
.status-desc {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.help-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
.help-item i {
|
||
color: #67c23a;
|
||
margin-top: 3px;
|
||
}
|
||
</style>
|