Integrate KDocs auto-upload

This commit is contained in:
2026-01-07 12:32:41 +08:00
parent 5137addacc
commit 3bae759afc
52 changed files with 1348 additions and 106 deletions

View File

@@ -0,0 +1,16 @@
import { api } from './client'
export async function fetchKdocsStatus() {
const { data } = await api.get('/kdocs/status')
return data
}
export async function fetchKdocsQr() {
const { data } = await api.post('/kdocs/qr', {})
return data
}
export async function clearKdocsLogin() {
const { data } = await api.post('/kdocs/clear-login', {})
return data
}

View File

@@ -3,6 +3,7 @@ import { computed, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fetchSystemConfig, updateSystemConfig, executeScheduleNow } from '../api/system'
import { fetchKdocsQr, fetchKdocsStatus, clearKdocsLogin } from '../api/kdocs'
import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
const loading = ref(false)
@@ -29,6 +30,20 @@ const autoApproveEnabled = ref(false)
const autoApproveHourlyLimit = ref(10)
const autoApproveVipDays = ref(7)
// 金山文档上传
const kdocsEnabled = ref(false)
const kdocsDocUrl = ref('')
const kdocsDefaultUnit = ref('')
const kdocsSheetName = ref('')
const kdocsSheetIndex = ref(0)
const kdocsUnitColumn = ref('A')
const kdocsImageColumn = ref('D')
const kdocsAdminNotifyEnabled = ref(false)
const kdocsAdminNotifyEmail = ref('')
const kdocsStatus = ref({})
const kdocsQrOpen = ref(false)
const kdocsQrImage = ref('')
const weekdaysOptions = [
{ label: '周一', value: '1' },
{ label: '周二', value: '2' },
@@ -63,7 +78,11 @@ function normalizeBrowseType(value) {
async function loadAll() {
loading.value = true
try {
const [system, proxy] = await Promise.all([fetchSystemConfig(), fetchProxyConfig()])
const [system, proxy, kdocsInfo] = await Promise.all([
fetchSystemConfig(),
fetchProxyConfig(),
fetchKdocsStatus().catch(() => ({})),
])
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
maxConcurrentPerAccount.value = system.max_concurrent_per_account ?? 1
@@ -87,6 +106,17 @@ async function loadAll() {
proxyEnabled.value = (proxy.proxy_enabled ?? 0) === 1
proxyApiUrl.value = proxy.proxy_api_url || ''
proxyExpireMinutes.value = proxy.proxy_expire_minutes ?? 3
kdocsEnabled.value = (system.kdocs_enabled ?? 0) === 1
kdocsDocUrl.value = system.kdocs_doc_url || ''
kdocsDefaultUnit.value = system.kdocs_default_unit || ''
kdocsSheetName.value = system.kdocs_sheet_name || ''
kdocsSheetIndex.value = system.kdocs_sheet_index ?? 0
kdocsUnitColumn.value = (system.kdocs_unit_column || 'A').toUpperCase()
kdocsImageColumn.value = (system.kdocs_image_column || 'D').toUpperCase()
kdocsAdminNotifyEnabled.value = (system.kdocs_admin_notify_enabled ?? 0) === 1
kdocsAdminNotifyEmail.value = system.kdocs_admin_notify_email || ''
kdocsStatus.value = kdocsInfo || {}
} catch {
// handled by interceptor
} finally {
@@ -196,6 +226,64 @@ async function saveProxy() {
}
}
async function saveKdocsConfig() {
const payload = {
kdocs_enabled: kdocsEnabled.value ? 1 : 0,
kdocs_doc_url: kdocsDocUrl.value.trim(),
kdocs_default_unit: kdocsDefaultUnit.value.trim(),
kdocs_sheet_name: kdocsSheetName.value.trim(),
kdocs_sheet_index: Number(kdocsSheetIndex.value) || 0,
kdocs_unit_column: kdocsUnitColumn.value.trim().toUpperCase(),
kdocs_image_column: kdocsImageColumn.value.trim().toUpperCase(),
kdocs_admin_notify_enabled: kdocsAdminNotifyEnabled.value ? 1 : 0,
kdocs_admin_notify_email: kdocsAdminNotifyEmail.value.trim(),
}
try {
const res = await updateSystemConfig(payload)
ElMessage.success(res?.message || '表格配置已更新')
} catch {
// handled by interceptor
}
}
async function refreshKdocsStatus() {
try {
kdocsStatus.value = await fetchKdocsStatus()
} catch {
// handled by interceptor
}
}
async function onFetchKdocsQr() {
try {
const res = await fetchKdocsQr()
if (res?.logged_in) {
ElMessage.success('当前已登录,无需扫码')
await refreshKdocsStatus()
return
}
kdocsQrImage.value = res?.qr_image || ''
if (!kdocsQrImage.value) {
ElMessage.warning('未获取到二维码')
return
}
kdocsQrOpen.value = true
} catch {
// handled by interceptor
}
}
async function onClearKdocsLogin() {
try {
await clearKdocsLogin()
ElMessage.success('登录态已清除')
await refreshKdocsStatus()
} catch {
// handled by interceptor
}
}
async function onTestProxy() {
if (!proxyApiUrl.value.trim()) {
ElMessage.error('请先输入代理API地址')
@@ -357,6 +445,73 @@ onMounted(loadAll)
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
</el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<h3 class="section-title">金山文档上传</h3>
<el-form label-width="130px">
<el-form-item label="启用上传">
<el-switch v-model="kdocsEnabled" />
<div class="help">表格结构变化时可先关闭避免错误上传</div>
</el-form-item>
<el-form-item label="文档链接">
<el-input v-model="kdocsDocUrl" placeholder="https://kdocs.cn/..." />
</el-form-item>
<el-form-item label="默认县区">
<el-input v-model="kdocsDefaultUnit" placeholder="如:道县(用户可覆盖)" />
</el-form-item>
<el-form-item label="Sheet名称">
<el-input v-model="kdocsSheetName" placeholder="留空使用第一个Sheet" />
</el-form-item>
<el-form-item label="Sheet序号">
<el-input-number v-model="kdocsSheetIndex" :min="0" :max="50" />
<div class="help">0 表示第一个Sheet</div>
</el-form-item>
<el-form-item label="县区列">
<el-input v-model="kdocsUnitColumn" placeholder="A" style="max-width: 120px" />
</el-form-item>
<el-form-item label="图片列">
<el-input v-model="kdocsImageColumn" placeholder="D" style="max-width: 120px" />
</el-form-item>
<el-form-item label="管理员通知">
<el-switch v-model="kdocsAdminNotifyEnabled" />
</el-form-item>
<el-form-item label="通知邮箱">
<el-input v-model="kdocsAdminNotifyEmail" placeholder="admin@example.com" />
</el-form-item>
</el-form>
<div class="row-actions">
<el-button type="primary" @click="saveKdocsConfig">保存表格配置</el-button>
<el-button @click="refreshKdocsStatus">刷新状态</el-button>
<el-button type="success" plain @click="onFetchKdocsQr">获取二维码</el-button>
<el-button type="danger" plain @click="onClearKdocsLogin">清除登录</el-button>
</div>
<div class="help">
登录状态
<span v-if="kdocsStatus.last_login_ok === true">已登录</span>
<span v-else-if="kdocsStatus.login_required">需要扫码</span>
<span v-else>未知</span>
· 队列 {{ kdocsStatus.queue_size || 0 }}
<span v-if="kdocsStatus.last_error">· 最近错误{{ kdocsStatus.last_error }}</span>
</div>
</el-card>
<el-dialog v-model="kdocsQrOpen" title="扫码登录" width="min(420px, 92vw)">
<div class="kdocs-qr">
<img v-if="kdocsQrImage" :src="`data:image/png;base64,${kdocsQrImage}`" alt="KDocs QR" />
<div class="help">请使用管理员微信扫码登录</div>
</div>
</el-dialog>
</div>
</template>
@@ -378,6 +533,22 @@ onMounted(loadAll)
font-weight: 800;
}
.kdocs-qr {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.kdocs-qr img {
width: 260px;
max-width: 100%;
border: 1px solid var(--app-border);
border-radius: 8px;
padding: 8px;
background: #fff;
}
.help {
margin-top: 6px;
font-size: 12px;