This commit is contained in:
van
2026-03-23 17:04:47 +08:00
parent 1ddf792076
commit e63ab42c41
11 changed files with 883 additions and 1214 deletions

View File

@@ -0,0 +1,325 @@
<template>
<div class="app-container">
<el-card>
<div slot="header" class="clearfix">
<span>金山文档 在线表格</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="handleRefresh" :loading="loading">刷新</el-button>
</div>
<el-alert v-if="!isAuthorized" title="未授权" type="warning" :closable="false" show-icon>
<template slot="default">
<span>请完成金山文档开放平台授权个人云</span>
<el-button type="primary" size="small" style="margin-left: 10px" @click="handleAuthorize">立即授权</el-button>
</template>
</el-alert>
<el-alert v-else title="已授权" type="success" :closable="false" show-icon>
<template slot="default">
<span>授权状态正常</span>
</template>
</el-alert>
<el-card v-if="isAuthorized && userInfo" style="margin-top: 20px">
<div slot="header">用户信息</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="open_id">{{ userInfo.user_id || userInfo.open_id || '-' }}</el-descriptions-item>
<el-descriptions-item label="昵称">{{ userInfo.nickname || userInfo.name || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card v-if="isAuthorized" style="margin-top: 20px">
<div slot="header">
<span>文件列表个人云扁平列表</span>
<el-button type="primary" size="small" style="float: right" @click="handleLoadFiles(true)">加载 / 刷新</el-button>
</div>
<p v-if="listHint" class="list-hint">{{ listHint }}</p>
<el-table v-loading="fileListLoading" :data="fileList" border style="width: 100%">
<el-table-column prop="file_name" label="文件名" min-width="200" />
<el-table-column prop="file_token" label="file_token" min-width="260" show-overflow-tooltip />
<el-table-column prop="file_type" label="类型" width="100" />
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button type="success" size="mini" @click="handleEditFile(scope.row)">编辑表格</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="hasMoreFiles" style="margin-top: 12px">
<el-button type="default" size="small" :loading="fileListLoading" @click="handleLoadMore">加载更多</el-button>
</div>
</el-card>
<el-dialog title="编辑表格" :visible.sync="editDialogVisible" width="80%" :close-on-click-modal="false">
<div v-if="currentFile">
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="工作表">
<el-select v-model="currentSheetIdx" placeholder="请选择" @change="handleLoadSheetData">
<el-option
v-for="sheet in sheetList"
:key="sheet.sheet_idx + '-' + sheet.name"
:label="sheet.name + ' (idx:' + sheet.sheet_idx + ')'"
:value="sheet.sheet_idx"
/>
</el-select>
</el-form-item>
<el-form-item label="单元格范围">
<el-input v-model="cellRange" placeholder="例如A1:B10" style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLoadSheetData">读取数据</el-button>
<el-button type="success" @click="handleUpdateCells">保存数据</el-button>
</el-form-item>
</el-form>
<el-table v-loading="sheetDataLoading" :data="sheetData" border style="width: 100%; margin-top: 20px">
<el-table-column
v-for="(col, index) in sheetColumns"
:key="index"
:prop="'col' + index"
:label="getColumnLabel(index)"
min-width="120"
>
<template slot-scope="scope">
<el-input v-model="scope.row['col' + index]" size="small" />
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</el-card>
</div>
</template>
<script>
import {
getKdocsAuthUrl,
getKdocsTokenStatus,
getKdocsUserInfo,
getKdocsFileList,
getKdocsSheetList,
readKdocsCells,
updateKdocsCells
} from '@/api/jarvis/kdocs'
export default {
name: 'KdocsCloud',
data() {
return {
loading: false,
isAuthorized: false,
userInfo: null,
userId: 'default_user',
fileList: [],
fileListLoading: false,
listHint: '',
hasMoreFiles: false,
nextOffset: null,
nextFilter: null,
editDialogVisible: false,
currentFile: null,
currentSheetIdx: 0,
sheetList: [],
sheetData: [],
sheetDataLoading: false,
cellRange: 'A1:Z100',
sheetColumns: []
}
},
created() {
this.checkAuthStatus()
},
methods: {
async checkAuthStatus() {
try {
const response = await getKdocsTokenStatus(this.userId)
if (response.code === 200) {
this.isAuthorized = response.data.hasToken && response.data.isValid
if (this.isAuthorized) {
this.loadUserInfo()
}
}
} catch (e) {
console.error(e)
}
},
async loadUserInfo() {
try {
const response = await getKdocsUserInfo(this.userId)
if (response.code === 200) {
this.userInfo = response.data
}
} catch (e) {
console.error(e)
}
},
async handleAuthorize() {
try {
const response = await getKdocsAuthUrl()
if (response.code === 200) {
window.open(response.data, '_blank')
this.$message.success('请在新窗口完成授权,完成后回到本页刷新')
}
} catch (e) {
this.$message.error(e.msg || e.message || '获取授权地址失败')
}
},
handleRefresh() {
this.checkAuthStatus()
if (this.isAuthorized) {
this.handleLoadFiles(true)
}
},
/** @param {boolean} reset 是否重置游标从第一页拉取 */
async handleLoadFiles(reset) {
if (!this.isAuthorized) {
this.$message.warning('请先授权')
return
}
if (reset) {
this.nextOffset = null
this.nextFilter = null
this.fileList = []
}
this.fileListLoading = true
this.listHint = ''
try {
const params = {
userId: this.userId,
page: 1,
pageSize: 50
}
if (this.nextOffset != null) {
params.next_offset = this.nextOffset
}
if (this.nextFilter) {
params.next_filter = this.nextFilter
}
const response = await getKdocsFileList(params)
if (response.code === 200) {
const chunk = response.data.files || []
this.fileList = reset ? chunk : this.fileList.concat(chunk)
this.nextOffset = response.data.next_offset
this.nextFilter = response.data.next_filter || null
this.hasMoreFiles = !!response.data.has_more
this.listHint =
'file_token 请使用列表中的值(来自文档 open_id。在线表格工作表请选 sheet_type 为 et 的 sheet数据表为 db 时需用数据表 API。'
}
} catch (e) {
this.$message.error(e.msg || e.message || '加载失败')
} finally {
this.fileListLoading = false
}
},
handleLoadMore() {
if (!this.hasMoreFiles) return
this.handleLoadFiles(false)
},
async handleEditFile(file) {
this.currentFile = file
this.editDialogVisible = true
try {
const response = await getKdocsSheetList(this.userId, file.file_token)
if (response.code === 200) {
this.sheetList = response.data.sheets || []
if (this.sheetList.length > 0) {
const first = this.sheetList[0]
this.currentSheetIdx = first.sheet_idx != null ? first.sheet_idx : 0
this.handleLoadSheetData()
} else {
this.$message.warning('未获取到工作表列表')
}
}
} catch (e) {
this.$message.error(e.msg || e.message || '加载工作表失败')
}
},
extractCellValues(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 handleLoadSheetData() {
if (!this.currentFile) return
this.sheetDataLoading = true
try {
const response = await readKdocsCells({
userId: this.userId,
fileToken: this.currentFile.file_token,
sheetIdx: this.currentSheetIdx,
range: this.cellRange
})
if (response.code === 200) {
this.processSheetData(this.extractCellValues(response.data))
}
} catch (e) {
this.$message.error(e.msg || e.message || '读取失败')
} finally {
this.sheetDataLoading = false
}
},
processSheetData(values) {
if (!values || values.length === 0) {
this.sheetData = []
this.sheetColumns = []
return
}
const maxCols = Math.max(...values.map(row => (row ? row.length : 0)))
this.sheetColumns = Array.from({ length: maxCols }, (_, i) => i)
this.sheetData = values.map(row => {
const rowData = {}
const r = row || []
r.forEach((cell, index) => {
rowData['col' + index] = cell !== null && cell !== undefined ? String(cell) : ''
})
for (let i = r.length; i < maxCols; i++) {
rowData['col' + i] = ''
}
return rowData
})
},
getColumnLabel(index) {
let label = ''
let num = index
while (num >= 0) {
label = String.fromCharCode(65 + (num % 26)) + label
num = Math.floor(num / 26) - 1
}
return label
},
async handleUpdateCells() {
if (!this.currentFile) return
const values = this.sheetData.map(row => {
return this.sheetColumns.map(colIndex => {
const v = row['col' + colIndex]
return v !== undefined ? v : ''
})
})
try {
const response = await updateKdocsCells({
userId: this.userId,
fileToken: this.currentFile.file_token,
sheetIdx: this.currentSheetIdx,
range: this.cellRange,
values
})
if (response.code === 200) {
this.$message.success('保存成功')
}
} catch (e) {
this.$message.error(e.msg || e.message || '保存失败')
}
}
}
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.list-hint {
color: #909399;
font-size: 13px;
margin-bottom: 10px;
}
</style>