Integrate KDocs auto-upload
This commit is contained in:
@@ -22,6 +22,9 @@ COPY requirements.txt .
|
|||||||
# 安装Python依赖
|
# 安装Python依赖
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 安装 Playwright 浏览器依赖与 Chromium
|
||||||
|
RUN python -m playwright install --with-deps chromium
|
||||||
|
|
||||||
# 复制应用程序文件
|
# 复制应用程序文件
|
||||||
COPY app.py .
|
COPY app.py .
|
||||||
COPY database.py .
|
COPY database.py .
|
||||||
|
|||||||
16
admin-frontend/src/api/kdocs.js
Normal file
16
admin-frontend/src/api/kdocs.js
Normal 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
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { computed, onMounted, ref } from 'vue'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
import { fetchSystemConfig, updateSystemConfig, executeScheduleNow } from '../api/system'
|
import { fetchSystemConfig, updateSystemConfig, executeScheduleNow } from '../api/system'
|
||||||
|
import { fetchKdocsQr, fetchKdocsStatus, clearKdocsLogin } from '../api/kdocs'
|
||||||
import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
|
import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -29,6 +30,20 @@ const autoApproveEnabled = ref(false)
|
|||||||
const autoApproveHourlyLimit = ref(10)
|
const autoApproveHourlyLimit = ref(10)
|
||||||
const autoApproveVipDays = ref(7)
|
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 = [
|
const weekdaysOptions = [
|
||||||
{ label: '周一', value: '1' },
|
{ label: '周一', value: '1' },
|
||||||
{ label: '周二', value: '2' },
|
{ label: '周二', value: '2' },
|
||||||
@@ -63,7 +78,11 @@ function normalizeBrowseType(value) {
|
|||||||
async function loadAll() {
|
async function loadAll() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
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
|
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
|
||||||
maxConcurrentPerAccount.value = system.max_concurrent_per_account ?? 1
|
maxConcurrentPerAccount.value = system.max_concurrent_per_account ?? 1
|
||||||
@@ -87,6 +106,17 @@ async function loadAll() {
|
|||||||
proxyEnabled.value = (proxy.proxy_enabled ?? 0) === 1
|
proxyEnabled.value = (proxy.proxy_enabled ?? 0) === 1
|
||||||
proxyApiUrl.value = proxy.proxy_api_url || ''
|
proxyApiUrl.value = proxy.proxy_api_url || ''
|
||||||
proxyExpireMinutes.value = proxy.proxy_expire_minutes ?? 3
|
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 {
|
} catch {
|
||||||
// handled by interceptor
|
// handled by interceptor
|
||||||
} finally {
|
} 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() {
|
async function onTestProxy() {
|
||||||
if (!proxyApiUrl.value.trim()) {
|
if (!proxyApiUrl.value.trim()) {
|
||||||
ElMessage.error('请先输入代理API地址')
|
ElMessage.error('请先输入代理API地址')
|
||||||
@@ -357,6 +445,73 @@ onMounted(loadAll)
|
|||||||
|
|
||||||
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
|
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
|
||||||
</el-card>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -378,6 +533,22 @@ onMounted(loadAll)
|
|||||||
font-weight: 800;
|
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 {
|
.help {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -30,3 +30,12 @@ export async function changePassword(payload) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchKdocsSettings() {
|
||||||
|
const { data } = await publicApi.get('/user/kdocs')
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateKdocsSettings(payload) {
|
||||||
|
const { data } = await publicApi.post('/user/kdocs', payload)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import {
|
|||||||
changePassword,
|
changePassword,
|
||||||
fetchEmailNotify,
|
fetchEmailNotify,
|
||||||
fetchUserEmail,
|
fetchUserEmail,
|
||||||
|
fetchKdocsSettings,
|
||||||
unbindEmail,
|
unbindEmail,
|
||||||
|
updateKdocsSettings,
|
||||||
updateEmailNotify,
|
updateEmailNotify,
|
||||||
} from '../api/settings'
|
} from '../api/settings'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
@@ -111,6 +113,10 @@ const passwordForm = reactive({
|
|||||||
confirm_password: '',
|
confirm_password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const kdocsLoading = ref(false)
|
||||||
|
const kdocsSaving = ref(false)
|
||||||
|
const kdocsUnitValue = ref('')
|
||||||
|
|
||||||
function syncIsMobile() {
|
function syncIsMobile() {
|
||||||
isMobile.value = Boolean(mediaQuery?.matches)
|
isMobile.value = Boolean(mediaQuery?.matches)
|
||||||
if (!isMobile.value) drawerOpen.value = false
|
if (!isMobile.value) drawerOpen.value = false
|
||||||
@@ -231,7 +237,7 @@ async function openSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
await Promise.all([loadEmailInfo(), loadEmailNotify()])
|
await Promise.all([loadEmailInfo(), loadEmailNotify(), loadKdocsSettings()])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadEmailInfo() {
|
async function loadEmailInfo() {
|
||||||
@@ -262,6 +268,30 @@ async function loadEmailNotify() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadKdocsSettings() {
|
||||||
|
kdocsLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await fetchKdocsSettings()
|
||||||
|
kdocsUnitValue.value = data?.kdocs_unit || ''
|
||||||
|
} catch {
|
||||||
|
kdocsUnitValue.value = ''
|
||||||
|
} finally {
|
||||||
|
kdocsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveKdocsSettings() {
|
||||||
|
kdocsSaving.value = true
|
||||||
|
try {
|
||||||
|
await updateKdocsSettings({ kdocs_unit: kdocsUnitValue.value.trim() })
|
||||||
|
ElMessage.success('已更新表格县区设置')
|
||||||
|
} catch {
|
||||||
|
// handled by interceptor
|
||||||
|
} finally {
|
||||||
|
kdocsSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onBindEmail() {
|
async function onBindEmail() {
|
||||||
const email = bindEmailValue.value.trim().toLowerCase()
|
const email = bindEmailValue.value.trim().toLowerCase()
|
||||||
if (!email) {
|
if (!email) {
|
||||||
@@ -635,6 +665,24 @@ async function dismissAnnouncementPermanently() {
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="表格上传" name="kdocs">
|
||||||
|
<div v-loading="kdocsLoading" class="settings-section">
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-form-item label="县区(可选)">
|
||||||
|
<el-input v-model="kdocsUnitValue" placeholder="留空使用系统默认县区" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-button type="primary" :loading="kdocsSaving" @click="saveKdocsSettings">保存</el-button>
|
||||||
|
</el-form>
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
title="自动上传开关在“账号管理”页面设置(测试功能)。"
|
||||||
|
show-icon
|
||||||
|
class="settings-hint"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="VIP信息" name="vip">
|
<el-tab-pane label="VIP信息" name="vip">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<el-alert
|
<el-alert
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
updateAccount,
|
updateAccount,
|
||||||
updateAccountRemark,
|
updateAccountRemark,
|
||||||
} from '../api/accounts'
|
} from '../api/accounts'
|
||||||
|
import { fetchKdocsSettings, updateKdocsSettings } from '../api/settings'
|
||||||
import { fetchRunStats } from '../api/stats'
|
import { fetchRunStats } from '../api/stats'
|
||||||
import { useSocket } from '../composables/useSocket'
|
import { useSocket } from '../composables/useSocket'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
@@ -57,6 +58,9 @@ watch(batchEnableScreenshot, (value) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const kdocsAutoUpload = ref(false)
|
||||||
|
const kdocsSettingsLoading = ref(false)
|
||||||
|
|
||||||
const addOpen = ref(false)
|
const addOpen = ref(false)
|
||||||
const editOpen = ref(false)
|
const editOpen = ref(false)
|
||||||
const upgradeOpen = ref(false)
|
const upgradeOpen = ref(false)
|
||||||
@@ -189,6 +193,30 @@ async function refreshAccounts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadKdocsSettings() {
|
||||||
|
kdocsSettingsLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await fetchKdocsSettings()
|
||||||
|
kdocsAutoUpload.value = Number(data?.kdocs_auto_upload || 0) === 1
|
||||||
|
} catch {
|
||||||
|
kdocsAutoUpload.value = false
|
||||||
|
} finally {
|
||||||
|
kdocsSettingsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onToggleKdocsAutoUpload(value) {
|
||||||
|
kdocsSettingsLoading.value = true
|
||||||
|
try {
|
||||||
|
await updateKdocsSettings({ kdocs_auto_upload: value ? 1 : 0 })
|
||||||
|
ElMessage.success(value ? '已开启自动上传(测试)' : '已关闭自动上传')
|
||||||
|
} catch (e) {
|
||||||
|
kdocsAutoUpload.value = !value
|
||||||
|
} finally {
|
||||||
|
kdocsSettingsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onStart(acc) {
|
async function onStart(acc) {
|
||||||
try {
|
try {
|
||||||
await startAccount(acc.id, { browse_type: browseTypeById[acc.id] || '应读', enable_screenshot: batchEnableScreenshot.value })
|
await startAccount(acc.id, { browse_type: browseTypeById[acc.id] || '应读', enable_screenshot: batchEnableScreenshot.value })
|
||||||
@@ -524,6 +552,7 @@ onMounted(async () => {
|
|||||||
unbindSocket = bindSocket()
|
unbindSocket = bindSocket()
|
||||||
|
|
||||||
await refreshAccounts()
|
await refreshAccounts()
|
||||||
|
await loadKdocsSettings()
|
||||||
await refreshStats()
|
await refreshStats()
|
||||||
syncStatsPolling()
|
syncStatsPolling()
|
||||||
})
|
})
|
||||||
@@ -612,6 +641,15 @@ onBeforeUnmount(() => {
|
|||||||
<el-option v-for="opt in browseTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
<el-option v-for="opt in browseTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-switch v-model="batchEnableScreenshot" inline-prompt active-text="截图" inactive-text="不截图" />
|
<el-switch v-model="batchEnableScreenshot" inline-prompt active-text="截图" inactive-text="不截图" />
|
||||||
|
<el-switch
|
||||||
|
v-model="kdocsAutoUpload"
|
||||||
|
:disabled="kdocsSettingsLoading"
|
||||||
|
inline-prompt
|
||||||
|
active-text="上传"
|
||||||
|
inactive-text="不传"
|
||||||
|
@change="onToggleKdocsAutoUpload"
|
||||||
|
/>
|
||||||
|
<span class="app-muted">表格(测试)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ class Config:
|
|||||||
# ==================== 浏览器配置 ====================
|
# ==================== 浏览器配置 ====================
|
||||||
SCREENSHOTS_DIR = os.environ.get('SCREENSHOTS_DIR', '截图')
|
SCREENSHOTS_DIR = os.environ.get('SCREENSHOTS_DIR', '截图')
|
||||||
COOKIES_DIR = os.environ.get('COOKIES_DIR', 'data/cookies')
|
COOKIES_DIR = os.environ.get('COOKIES_DIR', 'data/cookies')
|
||||||
|
KDOCS_LOGIN_STATE_FILE = os.environ.get('KDOCS_LOGIN_STATE_FILE', 'data/kdocs_login_state.json')
|
||||||
|
|
||||||
# ==================== 公告图片上传配置 ====================
|
# ==================== 公告图片上传配置 ====================
|
||||||
ANNOUNCEMENT_IMAGE_DIR = os.environ.get('ANNOUNCEMENT_IMAGE_DIR', 'static/announcements')
|
ANNOUNCEMENT_IMAGE_DIR = os.environ.get('ANNOUNCEMENT_IMAGE_DIR', 'static/announcements')
|
||||||
|
|||||||
22
database.py
22
database.py
@@ -100,6 +100,7 @@ from db.users import (
|
|||||||
get_pending_users,
|
get_pending_users,
|
||||||
get_user_by_id,
|
get_user_by_id,
|
||||||
get_user_by_username,
|
get_user_by_username,
|
||||||
|
get_user_kdocs_settings,
|
||||||
get_user_stats,
|
get_user_stats,
|
||||||
get_user_vip_info,
|
get_user_vip_info,
|
||||||
get_vip_config,
|
get_vip_config,
|
||||||
@@ -108,6 +109,7 @@ from db.users import (
|
|||||||
remove_user_vip,
|
remove_user_vip,
|
||||||
set_default_vip_days,
|
set_default_vip_days,
|
||||||
set_user_vip,
|
set_user_vip,
|
||||||
|
update_user_kdocs_settings,
|
||||||
verify_user,
|
verify_user,
|
||||||
)
|
)
|
||||||
from db.security import record_login_context
|
from db.security import record_login_context
|
||||||
@@ -118,7 +120,7 @@ config = get_config()
|
|||||||
DB_FILE = config.DB_FILE
|
DB_FILE = config.DB_FILE
|
||||||
|
|
||||||
# 数据库版本 (用于迁移管理)
|
# 数据库版本 (用于迁移管理)
|
||||||
DB_VERSION = 16
|
DB_VERSION = 17
|
||||||
|
|
||||||
|
|
||||||
# ==================== 系统配置缓存(P1 / O-03) ====================
|
# ==================== 系统配置缓存(P1 / O-03) ====================
|
||||||
@@ -194,6 +196,15 @@ def update_system_config(
|
|||||||
auto_approve_enabled=None,
|
auto_approve_enabled=None,
|
||||||
auto_approve_hourly_limit=None,
|
auto_approve_hourly_limit=None,
|
||||||
auto_approve_vip_days=None,
|
auto_approve_vip_days=None,
|
||||||
|
kdocs_enabled=None,
|
||||||
|
kdocs_doc_url=None,
|
||||||
|
kdocs_default_unit=None,
|
||||||
|
kdocs_sheet_name=None,
|
||||||
|
kdocs_sheet_index=None,
|
||||||
|
kdocs_unit_column=None,
|
||||||
|
kdocs_image_column=None,
|
||||||
|
kdocs_admin_notify_enabled=None,
|
||||||
|
kdocs_admin_notify_email=None,
|
||||||
):
|
):
|
||||||
"""更新系统配置(写入后立即失效缓存)。"""
|
"""更新系统配置(写入后立即失效缓存)。"""
|
||||||
ok = _update_system_config(
|
ok = _update_system_config(
|
||||||
@@ -211,6 +222,15 @@ def update_system_config(
|
|||||||
auto_approve_enabled=auto_approve_enabled,
|
auto_approve_enabled=auto_approve_enabled,
|
||||||
auto_approve_hourly_limit=auto_approve_hourly_limit,
|
auto_approve_hourly_limit=auto_approve_hourly_limit,
|
||||||
auto_approve_vip_days=auto_approve_vip_days,
|
auto_approve_vip_days=auto_approve_vip_days,
|
||||||
|
kdocs_enabled=kdocs_enabled,
|
||||||
|
kdocs_doc_url=kdocs_doc_url,
|
||||||
|
kdocs_default_unit=kdocs_default_unit,
|
||||||
|
kdocs_sheet_name=kdocs_sheet_name,
|
||||||
|
kdocs_sheet_index=kdocs_sheet_index,
|
||||||
|
kdocs_unit_column=kdocs_unit_column,
|
||||||
|
kdocs_image_column=kdocs_image_column,
|
||||||
|
kdocs_admin_notify_enabled=kdocs_admin_notify_enabled,
|
||||||
|
kdocs_admin_notify_email=kdocs_admin_notify_email,
|
||||||
)
|
)
|
||||||
if ok:
|
if ok:
|
||||||
invalidate_system_config_cache()
|
invalidate_system_config_cache()
|
||||||
|
|||||||
54
db/admin.py
54
db/admin.py
@@ -172,6 +172,15 @@ def get_system_config_raw() -> dict:
|
|||||||
"auto_approve_enabled": 0,
|
"auto_approve_enabled": 0,
|
||||||
"auto_approve_hourly_limit": 10,
|
"auto_approve_hourly_limit": 10,
|
||||||
"auto_approve_vip_days": 7,
|
"auto_approve_vip_days": 7,
|
||||||
|
"kdocs_enabled": 0,
|
||||||
|
"kdocs_doc_url": "",
|
||||||
|
"kdocs_default_unit": "",
|
||||||
|
"kdocs_sheet_name": "",
|
||||||
|
"kdocs_sheet_index": 0,
|
||||||
|
"kdocs_unit_column": "A",
|
||||||
|
"kdocs_image_column": "D",
|
||||||
|
"kdocs_admin_notify_enabled": 0,
|
||||||
|
"kdocs_admin_notify_email": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -191,6 +200,15 @@ def update_system_config(
|
|||||||
auto_approve_enabled=None,
|
auto_approve_enabled=None,
|
||||||
auto_approve_hourly_limit=None,
|
auto_approve_hourly_limit=None,
|
||||||
auto_approve_vip_days=None,
|
auto_approve_vip_days=None,
|
||||||
|
kdocs_enabled=None,
|
||||||
|
kdocs_doc_url=None,
|
||||||
|
kdocs_default_unit=None,
|
||||||
|
kdocs_sheet_name=None,
|
||||||
|
kdocs_sheet_index=None,
|
||||||
|
kdocs_unit_column=None,
|
||||||
|
kdocs_image_column=None,
|
||||||
|
kdocs_admin_notify_enabled=None,
|
||||||
|
kdocs_admin_notify_email=None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""更新系统配置(仅更新DB,不做缓存处理)。"""
|
"""更新系统配置(仅更新DB,不做缓存处理)。"""
|
||||||
allowed_fields = {
|
allowed_fields = {
|
||||||
@@ -208,6 +226,15 @@ def update_system_config(
|
|||||||
"auto_approve_enabled",
|
"auto_approve_enabled",
|
||||||
"auto_approve_hourly_limit",
|
"auto_approve_hourly_limit",
|
||||||
"auto_approve_vip_days",
|
"auto_approve_vip_days",
|
||||||
|
"kdocs_enabled",
|
||||||
|
"kdocs_doc_url",
|
||||||
|
"kdocs_default_unit",
|
||||||
|
"kdocs_sheet_name",
|
||||||
|
"kdocs_sheet_index",
|
||||||
|
"kdocs_unit_column",
|
||||||
|
"kdocs_image_column",
|
||||||
|
"kdocs_admin_notify_enabled",
|
||||||
|
"kdocs_admin_notify_email",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +285,33 @@ def update_system_config(
|
|||||||
if auto_approve_vip_days is not None:
|
if auto_approve_vip_days is not None:
|
||||||
updates.append("auto_approve_vip_days = ?")
|
updates.append("auto_approve_vip_days = ?")
|
||||||
params.append(auto_approve_vip_days)
|
params.append(auto_approve_vip_days)
|
||||||
|
if kdocs_enabled is not None:
|
||||||
|
updates.append("kdocs_enabled = ?")
|
||||||
|
params.append(kdocs_enabled)
|
||||||
|
if kdocs_doc_url is not None:
|
||||||
|
updates.append("kdocs_doc_url = ?")
|
||||||
|
params.append(kdocs_doc_url)
|
||||||
|
if kdocs_default_unit is not None:
|
||||||
|
updates.append("kdocs_default_unit = ?")
|
||||||
|
params.append(kdocs_default_unit)
|
||||||
|
if kdocs_sheet_name is not None:
|
||||||
|
updates.append("kdocs_sheet_name = ?")
|
||||||
|
params.append(kdocs_sheet_name)
|
||||||
|
if kdocs_sheet_index is not None:
|
||||||
|
updates.append("kdocs_sheet_index = ?")
|
||||||
|
params.append(kdocs_sheet_index)
|
||||||
|
if kdocs_unit_column is not None:
|
||||||
|
updates.append("kdocs_unit_column = ?")
|
||||||
|
params.append(kdocs_unit_column)
|
||||||
|
if kdocs_image_column is not None:
|
||||||
|
updates.append("kdocs_image_column = ?")
|
||||||
|
params.append(kdocs_image_column)
|
||||||
|
if kdocs_admin_notify_enabled is not None:
|
||||||
|
updates.append("kdocs_admin_notify_enabled = ?")
|
||||||
|
params.append(kdocs_admin_notify_enabled)
|
||||||
|
if kdocs_admin_notify_email is not None:
|
||||||
|
updates.append("kdocs_admin_notify_email = ?")
|
||||||
|
params.append(kdocs_admin_notify_email)
|
||||||
|
|
||||||
if not updates:
|
if not updates:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ def migrate_database(conn, target_version: int) -> None:
|
|||||||
if current_version < 16:
|
if current_version < 16:
|
||||||
_migrate_to_v16(conn)
|
_migrate_to_v16(conn)
|
||||||
current_version = 16
|
current_version = 16
|
||||||
|
if current_version < 17:
|
||||||
|
_migrate_to_v17(conn)
|
||||||
|
current_version = 17
|
||||||
|
|
||||||
if current_version != int(target_version):
|
if current_version != int(target_version):
|
||||||
set_current_version(conn, int(target_version))
|
set_current_version(conn, int(target_version))
|
||||||
@@ -687,3 +690,41 @@ def _migrate_to_v16(conn):
|
|||||||
cursor.execute("ALTER TABLE announcements ADD COLUMN image_url TEXT")
|
cursor.execute("ALTER TABLE announcements ADD COLUMN image_url TEXT")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
print(" ✓ 添加 announcements.image_url 字段")
|
print(" ✓ 添加 announcements.image_url 字段")
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_to_v17(conn):
|
||||||
|
"""迁移到版本17 - 金山文档上传配置与用户开关"""
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("PRAGMA table_info(system_config)")
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
system_fields = [
|
||||||
|
("kdocs_enabled", "INTEGER DEFAULT 0"),
|
||||||
|
("kdocs_doc_url", "TEXT DEFAULT ''"),
|
||||||
|
("kdocs_default_unit", "TEXT DEFAULT ''"),
|
||||||
|
("kdocs_sheet_name", "TEXT DEFAULT ''"),
|
||||||
|
("kdocs_sheet_index", "INTEGER DEFAULT 0"),
|
||||||
|
("kdocs_unit_column", "TEXT DEFAULT 'A'"),
|
||||||
|
("kdocs_image_column", "TEXT DEFAULT 'D'"),
|
||||||
|
("kdocs_admin_notify_enabled", "INTEGER DEFAULT 0"),
|
||||||
|
("kdocs_admin_notify_email", "TEXT DEFAULT ''"),
|
||||||
|
]
|
||||||
|
for field, ddl in system_fields:
|
||||||
|
if field not in columns:
|
||||||
|
cursor.execute(f"ALTER TABLE system_config ADD COLUMN {field} {ddl}")
|
||||||
|
print(f" ✓ 添加 system_config.{field} 字段")
|
||||||
|
|
||||||
|
cursor.execute("PRAGMA table_info(users)")
|
||||||
|
columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
|
||||||
|
user_fields = [
|
||||||
|
("kdocs_unit", "TEXT DEFAULT ''"),
|
||||||
|
("kdocs_auto_upload", "INTEGER DEFAULT 0"),
|
||||||
|
]
|
||||||
|
for field, ddl in user_fields:
|
||||||
|
if field not in columns:
|
||||||
|
cursor.execute(f"ALTER TABLE users ADD COLUMN {field} {ddl}")
|
||||||
|
print(f" ✓ 添加 users.{field} 字段")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|||||||
11
db/schema.py
11
db/schema.py
@@ -33,6 +33,8 @@ def ensure_schema(conn) -> None:
|
|||||||
email TEXT,
|
email TEXT,
|
||||||
email_verified INTEGER DEFAULT 0,
|
email_verified INTEGER DEFAULT 0,
|
||||||
email_notify_enabled INTEGER DEFAULT 1,
|
email_notify_enabled INTEGER DEFAULT 1,
|
||||||
|
kdocs_unit TEXT DEFAULT '',
|
||||||
|
kdocs_auto_upload INTEGER DEFAULT 0,
|
||||||
status TEXT DEFAULT 'approved',
|
status TEXT DEFAULT 'approved',
|
||||||
vip_expire_time TIMESTAMP,
|
vip_expire_time TIMESTAMP,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
@@ -213,6 +215,15 @@ def ensure_schema(conn) -> None:
|
|||||||
auto_approve_enabled INTEGER DEFAULT 0,
|
auto_approve_enabled INTEGER DEFAULT 0,
|
||||||
auto_approve_hourly_limit INTEGER DEFAULT 10,
|
auto_approve_hourly_limit INTEGER DEFAULT 10,
|
||||||
auto_approve_vip_days INTEGER DEFAULT 7,
|
auto_approve_vip_days INTEGER DEFAULT 7,
|
||||||
|
kdocs_enabled INTEGER DEFAULT 0,
|
||||||
|
kdocs_doc_url TEXT DEFAULT '',
|
||||||
|
kdocs_default_unit TEXT DEFAULT '',
|
||||||
|
kdocs_sheet_name TEXT DEFAULT '',
|
||||||
|
kdocs_sheet_index INTEGER DEFAULT 0,
|
||||||
|
kdocs_unit_column TEXT DEFAULT 'A',
|
||||||
|
kdocs_image_column TEXT DEFAULT 'D',
|
||||||
|
kdocs_admin_notify_enabled INTEGER DEFAULT 0,
|
||||||
|
kdocs_admin_notify_email TEXT DEFAULT '',
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|||||||
33
db/users.py
33
db/users.py
@@ -217,6 +217,39 @@ def get_user_by_id(user_id):
|
|||||||
return dict(user) if user else None
|
return dict(user) if user else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_kdocs_settings(user_id):
|
||||||
|
"""获取用户的金山文档配置"""
|
||||||
|
user = get_user_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"kdocs_unit": user.get("kdocs_unit") or "",
|
||||||
|
"kdocs_auto_upload": 1 if user.get("kdocs_auto_upload") else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def update_user_kdocs_settings(user_id, *, kdocs_unit=None, kdocs_auto_upload=None) -> bool:
|
||||||
|
"""更新用户的金山文档配置"""
|
||||||
|
updates = []
|
||||||
|
params = []
|
||||||
|
if kdocs_unit is not None:
|
||||||
|
updates.append("kdocs_unit = ?")
|
||||||
|
params.append(kdocs_unit)
|
||||||
|
if kdocs_auto_upload is not None:
|
||||||
|
updates.append("kdocs_auto_upload = ?")
|
||||||
|
params.append(kdocs_auto_upload)
|
||||||
|
|
||||||
|
if not updates:
|
||||||
|
return False
|
||||||
|
|
||||||
|
params.append(user_id)
|
||||||
|
with db_pool.get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(f"UPDATE users SET {', '.join(updates)} WHERE id = ?", params)
|
||||||
|
conn.commit()
|
||||||
|
return cursor.rowcount > 0
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_username(username):
|
def get_user_by_username(username):
|
||||||
"""根据用户名获取用户"""
|
"""根据用户名获取用户"""
|
||||||
with db_pool.get_db() as conn:
|
with db_pool.get_db() as conn:
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ python-dotenv==1.0.0
|
|||||||
beautifulsoup4==4.12.2
|
beautifulsoup4==4.12.2
|
||||||
cryptography>=41.0.0
|
cryptography>=41.0.0
|
||||||
Pillow>=10.0.0
|
Pillow>=10.0.0
|
||||||
|
playwright==1.42.0
|
||||||
|
|||||||
@@ -671,6 +671,15 @@ def update_system_config_api():
|
|||||||
auto_approve_enabled = data.get("auto_approve_enabled")
|
auto_approve_enabled = data.get("auto_approve_enabled")
|
||||||
auto_approve_hourly_limit = data.get("auto_approve_hourly_limit")
|
auto_approve_hourly_limit = data.get("auto_approve_hourly_limit")
|
||||||
auto_approve_vip_days = data.get("auto_approve_vip_days")
|
auto_approve_vip_days = data.get("auto_approve_vip_days")
|
||||||
|
kdocs_enabled = data.get("kdocs_enabled")
|
||||||
|
kdocs_doc_url = data.get("kdocs_doc_url")
|
||||||
|
kdocs_default_unit = data.get("kdocs_default_unit")
|
||||||
|
kdocs_sheet_name = data.get("kdocs_sheet_name")
|
||||||
|
kdocs_sheet_index = data.get("kdocs_sheet_index")
|
||||||
|
kdocs_unit_column = data.get("kdocs_unit_column")
|
||||||
|
kdocs_image_column = data.get("kdocs_image_column")
|
||||||
|
kdocs_admin_notify_enabled = data.get("kdocs_admin_notify_enabled")
|
||||||
|
kdocs_admin_notify_email = data.get("kdocs_admin_notify_email")
|
||||||
|
|
||||||
if max_concurrent is not None:
|
if max_concurrent is not None:
|
||||||
if not isinstance(max_concurrent, int) or max_concurrent < 1:
|
if not isinstance(max_concurrent, int) or max_concurrent < 1:
|
||||||
@@ -718,6 +727,66 @@ def update_system_config_api():
|
|||||||
if not isinstance(auto_approve_vip_days, int) or auto_approve_vip_days < 0:
|
if not isinstance(auto_approve_vip_days, int) or auto_approve_vip_days < 0:
|
||||||
return jsonify({"error": "注册赠送VIP天数不能为负数"}), 400
|
return jsonify({"error": "注册赠送VIP天数不能为负数"}), 400
|
||||||
|
|
||||||
|
if kdocs_enabled is not None:
|
||||||
|
if isinstance(kdocs_enabled, bool):
|
||||||
|
kdocs_enabled = 1 if kdocs_enabled else 0
|
||||||
|
if kdocs_enabled not in (0, 1):
|
||||||
|
return jsonify({"error": "表格上传开关必须是0或1"}), 400
|
||||||
|
|
||||||
|
if kdocs_doc_url is not None:
|
||||||
|
kdocs_doc_url = str(kdocs_doc_url or "").strip()
|
||||||
|
if kdocs_doc_url and not is_safe_outbound_url(kdocs_doc_url):
|
||||||
|
return jsonify({"error": "文档链接格式不正确"}), 400
|
||||||
|
|
||||||
|
if kdocs_default_unit is not None:
|
||||||
|
kdocs_default_unit = str(kdocs_default_unit or "").strip()
|
||||||
|
if len(kdocs_default_unit) > 50:
|
||||||
|
return jsonify({"error": "默认县区长度不能超过50"}), 400
|
||||||
|
|
||||||
|
if kdocs_sheet_name is not None:
|
||||||
|
kdocs_sheet_name = str(kdocs_sheet_name or "").strip()
|
||||||
|
if len(kdocs_sheet_name) > 50:
|
||||||
|
return jsonify({"error": "Sheet名称长度不能超过50"}), 400
|
||||||
|
|
||||||
|
if kdocs_sheet_index is not None:
|
||||||
|
try:
|
||||||
|
kdocs_sheet_index = int(kdocs_sheet_index)
|
||||||
|
except Exception:
|
||||||
|
return jsonify({"error": "Sheet序号必须是数字"}), 400
|
||||||
|
if kdocs_sheet_index < 0:
|
||||||
|
return jsonify({"error": "Sheet序号不能为负数"}), 400
|
||||||
|
|
||||||
|
if kdocs_unit_column is not None:
|
||||||
|
kdocs_unit_column = str(kdocs_unit_column or "").strip().upper()
|
||||||
|
if not kdocs_unit_column:
|
||||||
|
return jsonify({"error": "县区列不能为空"}), 400
|
||||||
|
import re
|
||||||
|
|
||||||
|
if not re.match(r"^[A-Z]{1,3}$", kdocs_unit_column):
|
||||||
|
return jsonify({"error": "县区列格式错误"}), 400
|
||||||
|
|
||||||
|
if kdocs_image_column is not None:
|
||||||
|
kdocs_image_column = str(kdocs_image_column or "").strip().upper()
|
||||||
|
if not kdocs_image_column:
|
||||||
|
return jsonify({"error": "图片列不能为空"}), 400
|
||||||
|
import re
|
||||||
|
|
||||||
|
if not re.match(r"^[A-Z]{1,3}$", kdocs_image_column):
|
||||||
|
return jsonify({"error": "图片列格式错误"}), 400
|
||||||
|
|
||||||
|
if kdocs_admin_notify_enabled is not None:
|
||||||
|
if isinstance(kdocs_admin_notify_enabled, bool):
|
||||||
|
kdocs_admin_notify_enabled = 1 if kdocs_admin_notify_enabled else 0
|
||||||
|
if kdocs_admin_notify_enabled not in (0, 1):
|
||||||
|
return jsonify({"error": "管理员通知开关必须是0或1"}), 400
|
||||||
|
|
||||||
|
if kdocs_admin_notify_email is not None:
|
||||||
|
kdocs_admin_notify_email = str(kdocs_admin_notify_email or "").strip()
|
||||||
|
if kdocs_admin_notify_email:
|
||||||
|
is_valid, error_msg = validate_email(kdocs_admin_notify_email)
|
||||||
|
if not is_valid:
|
||||||
|
return jsonify({"error": error_msg}), 400
|
||||||
|
|
||||||
old_config = database.get_system_config() or {}
|
old_config = database.get_system_config() or {}
|
||||||
|
|
||||||
if not database.update_system_config(
|
if not database.update_system_config(
|
||||||
@@ -732,6 +801,15 @@ def update_system_config_api():
|
|||||||
auto_approve_enabled=auto_approve_enabled,
|
auto_approve_enabled=auto_approve_enabled,
|
||||||
auto_approve_hourly_limit=auto_approve_hourly_limit,
|
auto_approve_hourly_limit=auto_approve_hourly_limit,
|
||||||
auto_approve_vip_days=auto_approve_vip_days,
|
auto_approve_vip_days=auto_approve_vip_days,
|
||||||
|
kdocs_enabled=kdocs_enabled,
|
||||||
|
kdocs_doc_url=kdocs_doc_url,
|
||||||
|
kdocs_default_unit=kdocs_default_unit,
|
||||||
|
kdocs_sheet_name=kdocs_sheet_name,
|
||||||
|
kdocs_sheet_index=kdocs_sheet_index,
|
||||||
|
kdocs_unit_column=kdocs_unit_column,
|
||||||
|
kdocs_image_column=kdocs_image_column,
|
||||||
|
kdocs_admin_notify_enabled=kdocs_admin_notify_enabled,
|
||||||
|
kdocs_admin_notify_email=kdocs_admin_notify_email,
|
||||||
):
|
):
|
||||||
return jsonify({"error": "更新失败"}), 400
|
return jsonify({"error": "更新失败"}), 400
|
||||||
|
|
||||||
@@ -763,6 +841,52 @@ def update_system_config_api():
|
|||||||
return jsonify({"message": "系统配置已更新"})
|
return jsonify({"message": "系统配置已更新"})
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api_bp.route("/kdocs/status", methods=["GET"])
|
||||||
|
@admin_required
|
||||||
|
def get_kdocs_status_api():
|
||||||
|
"""获取金山文档上传状态"""
|
||||||
|
try:
|
||||||
|
from services.kdocs_uploader import get_kdocs_uploader
|
||||||
|
|
||||||
|
uploader = get_kdocs_uploader()
|
||||||
|
status = uploader.get_status()
|
||||||
|
return jsonify(status)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": f"获取状态失败: {e}"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api_bp.route("/kdocs/qr", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def get_kdocs_qr_api():
|
||||||
|
"""获取金山文档登录二维码"""
|
||||||
|
try:
|
||||||
|
from services.kdocs_uploader import get_kdocs_uploader
|
||||||
|
|
||||||
|
uploader = get_kdocs_uploader()
|
||||||
|
result = uploader.request_qr()
|
||||||
|
if not result.get("success"):
|
||||||
|
return jsonify({"error": result.get("error", "获取二维码失败")}), 400
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": f"获取二维码失败: {e}"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api_bp.route("/kdocs/clear-login", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def clear_kdocs_login_api():
|
||||||
|
"""清除金山文档登录态"""
|
||||||
|
try:
|
||||||
|
from services.kdocs_uploader import get_kdocs_uploader
|
||||||
|
|
||||||
|
uploader = get_kdocs_uploader()
|
||||||
|
result = uploader.clear_login()
|
||||||
|
if not result.get("success"):
|
||||||
|
return jsonify({"error": result.get("error", "清除失败")}), 400
|
||||||
|
return jsonify({"success": True})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": f"清除失败: {e}"}), 500
|
||||||
|
|
||||||
|
|
||||||
@admin_api_bp.route("/schedule/execute", methods=["POST"])
|
@admin_api_bp.route("/schedule/execute", methods=["POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def execute_schedule_now():
|
def execute_schedule_now():
|
||||||
|
|||||||
@@ -148,6 +148,50 @@ def get_user_email():
|
|||||||
return jsonify({"email": user.get("email", ""), "email_verified": user.get("email_verified", False)})
|
return jsonify({"email": user.get("email", ""), "email_verified": user.get("email_verified", False)})
|
||||||
|
|
||||||
|
|
||||||
|
@api_user_bp.route("/api/user/kdocs", methods=["GET"])
|
||||||
|
@login_required
|
||||||
|
def get_user_kdocs_settings():
|
||||||
|
"""获取当前用户的金山文档设置"""
|
||||||
|
settings = database.get_user_kdocs_settings(current_user.id)
|
||||||
|
if not settings:
|
||||||
|
return jsonify({"kdocs_unit": "", "kdocs_auto_upload": 0})
|
||||||
|
return jsonify(settings)
|
||||||
|
|
||||||
|
|
||||||
|
@api_user_bp.route("/api/user/kdocs", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def update_user_kdocs_settings():
|
||||||
|
"""更新当前用户的金山文档设置"""
|
||||||
|
data = request.get_json() or {}
|
||||||
|
kdocs_unit = data.get("kdocs_unit")
|
||||||
|
kdocs_auto_upload = data.get("kdocs_auto_upload")
|
||||||
|
|
||||||
|
if kdocs_unit is not None:
|
||||||
|
kdocs_unit = str(kdocs_unit or "").strip()
|
||||||
|
if len(kdocs_unit) > 50:
|
||||||
|
return jsonify({"error": "县区长度不能超过50"}), 400
|
||||||
|
|
||||||
|
if kdocs_auto_upload is not None:
|
||||||
|
if isinstance(kdocs_auto_upload, bool):
|
||||||
|
kdocs_auto_upload = 1 if kdocs_auto_upload else 0
|
||||||
|
try:
|
||||||
|
kdocs_auto_upload = int(kdocs_auto_upload)
|
||||||
|
except Exception:
|
||||||
|
return jsonify({"error": "自动上传开关必须是0或1"}), 400
|
||||||
|
if kdocs_auto_upload not in (0, 1):
|
||||||
|
return jsonify({"error": "自动上传开关必须是0或1"}), 400
|
||||||
|
|
||||||
|
if not database.update_user_kdocs_settings(
|
||||||
|
current_user.id,
|
||||||
|
kdocs_unit=kdocs_unit,
|
||||||
|
kdocs_auto_upload=kdocs_auto_upload,
|
||||||
|
):
|
||||||
|
return jsonify({"error": "更新失败"}), 400
|
||||||
|
|
||||||
|
settings = database.get_user_kdocs_settings(current_user.id) or {"kdocs_unit": "", "kdocs_auto_upload": 0}
|
||||||
|
return jsonify({"success": True, "settings": settings})
|
||||||
|
|
||||||
|
|
||||||
@api_user_bp.route("/api/user/bind-email", methods=["POST"])
|
@api_user_bp.route("/api/user/bind-email", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@require_ip_not_locked
|
@require_ip_not_locked
|
||||||
|
|||||||
605
services/kdocs_uploader.py
Normal file
605
services/kdocs_uploader.py
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import database
|
||||||
|
import email_service
|
||||||
|
from app_config import get_config
|
||||||
|
from services.client_log import log_to_client
|
||||||
|
from services.runtime import get_logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
|
||||||
|
except Exception: # pragma: no cover - 运行环境缺少 playwright 时降级
|
||||||
|
sync_playwright = None
|
||||||
|
|
||||||
|
class PlaywrightTimeoutError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
logger = get_logger("app")
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
|
||||||
|
class KDocsUploader:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._queue: queue.Queue = queue.Queue(maxsize=int(os.environ.get("KDOCS_QUEUE_MAXSIZE", "200")))
|
||||||
|
self._thread = threading.Thread(target=self._run, name="kdocs-uploader", daemon=True)
|
||||||
|
self._running = False
|
||||||
|
self._last_error: Optional[str] = None
|
||||||
|
self._last_success_at: Optional[float] = None
|
||||||
|
self._login_required = False
|
||||||
|
self._playwright = None
|
||||||
|
self._browser = None
|
||||||
|
self._context = None
|
||||||
|
self._page = None
|
||||||
|
self._last_qr_image: Optional[bytes] = None
|
||||||
|
self._last_login_check: float = 0.0
|
||||||
|
self._last_login_ok: Optional[bool] = None
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
if self._running:
|
||||||
|
return
|
||||||
|
self._running = True
|
||||||
|
self._thread.start()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
if not self._running:
|
||||||
|
return
|
||||||
|
self._running = False
|
||||||
|
self._queue.put({"action": "shutdown"})
|
||||||
|
|
||||||
|
def get_status(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"queue_size": self._queue.qsize(),
|
||||||
|
"login_required": self._login_required,
|
||||||
|
"last_error": self._last_error,
|
||||||
|
"last_success_at": self._last_success_at,
|
||||||
|
"last_login_ok": self._last_login_ok,
|
||||||
|
}
|
||||||
|
|
||||||
|
def enqueue_upload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
user_id: int,
|
||||||
|
account_id: str,
|
||||||
|
unit: str,
|
||||||
|
name: str,
|
||||||
|
image_path: str,
|
||||||
|
) -> bool:
|
||||||
|
if not self._running:
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"user_id": int(user_id),
|
||||||
|
"account_id": str(account_id),
|
||||||
|
"unit": unit,
|
||||||
|
"name": name,
|
||||||
|
"image_path": image_path,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
self._queue.put({"action": "upload", "payload": payload}, timeout=1)
|
||||||
|
return True
|
||||||
|
except queue.Full:
|
||||||
|
self._last_error = "上传队列已满"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def request_qr(self, timeout: int = 30) -> Dict[str, Any]:
|
||||||
|
return self._submit_command("qr", timeout=timeout)
|
||||||
|
|
||||||
|
def clear_login(self, timeout: int = 20) -> Dict[str, Any]:
|
||||||
|
return self._submit_command("clear_login", timeout=timeout)
|
||||||
|
|
||||||
|
def refresh_login_status(self, timeout: int = 20) -> Dict[str, Any]:
|
||||||
|
return self._submit_command("status", timeout=timeout)
|
||||||
|
|
||||||
|
def _submit_command(self, action: str, timeout: int = 30) -> Dict[str, Any]:
|
||||||
|
if not self._running:
|
||||||
|
self.start()
|
||||||
|
resp_queue: queue.Queue = queue.Queue(maxsize=1)
|
||||||
|
self._queue.put({"action": action, "response": resp_queue})
|
||||||
|
try:
|
||||||
|
return resp_queue.get(timeout=timeout)
|
||||||
|
except queue.Empty:
|
||||||
|
return {"success": False, "error": "操作超时"}
|
||||||
|
|
||||||
|
def _run(self) -> None:
|
||||||
|
while True:
|
||||||
|
task = self._queue.get()
|
||||||
|
if not task:
|
||||||
|
continue
|
||||||
|
action = task.get("action")
|
||||||
|
if action == "shutdown":
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
if action == "upload":
|
||||||
|
self._handle_upload(task.get("payload") or {})
|
||||||
|
elif action == "qr":
|
||||||
|
result = self._handle_qr()
|
||||||
|
task.get("response").put(result)
|
||||||
|
elif action == "clear_login":
|
||||||
|
result = self._handle_clear_login()
|
||||||
|
task.get("response").put(result)
|
||||||
|
elif action == "status":
|
||||||
|
result = self._handle_status_check()
|
||||||
|
task.get("response").put(result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[KDocs] 处理任务失败: {e}")
|
||||||
|
|
||||||
|
self._cleanup_browser()
|
||||||
|
|
||||||
|
def _load_system_config(self) -> Dict[str, Any]:
|
||||||
|
return database.get_system_config() or {}
|
||||||
|
|
||||||
|
def _ensure_playwright(self) -> bool:
|
||||||
|
if sync_playwright is None:
|
||||||
|
self._last_error = "playwright 未安装"
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if self._playwright is None:
|
||||||
|
self._playwright = sync_playwright().start()
|
||||||
|
if self._browser is None:
|
||||||
|
headless = os.environ.get("KDOCS_HEADLESS", "true").lower() != "false"
|
||||||
|
self._browser = self._playwright.chromium.launch(headless=headless)
|
||||||
|
if self._context is None:
|
||||||
|
storage_state = getattr(config, "KDOCS_LOGIN_STATE_FILE", "data/kdocs_login_state.json")
|
||||||
|
if os.path.exists(storage_state):
|
||||||
|
self._context = self._browser.new_context(storage_state=storage_state)
|
||||||
|
else:
|
||||||
|
self._context = self._browser.new_context()
|
||||||
|
if self._page is None or self._page.is_closed():
|
||||||
|
self._page = self._context.new_page()
|
||||||
|
self._page.set_default_timeout(int(getattr(config, "DEFAULT_TIMEOUT", 60000)))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = f"浏览器启动失败: {e}"
|
||||||
|
self._cleanup_browser()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _cleanup_browser(self) -> None:
|
||||||
|
try:
|
||||||
|
if self._page:
|
||||||
|
self._page.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._page = None
|
||||||
|
try:
|
||||||
|
if self._context:
|
||||||
|
self._context.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._context = None
|
||||||
|
try:
|
||||||
|
if self._browser:
|
||||||
|
self._browser.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._browser = None
|
||||||
|
try:
|
||||||
|
if self._playwright:
|
||||||
|
self._playwright.stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._playwright = None
|
||||||
|
|
||||||
|
def _open_document(self, doc_url: str) -> bool:
|
||||||
|
try:
|
||||||
|
self._page.goto(doc_url)
|
||||||
|
time.sleep(1)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = f"打开文档失败: {e}"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_logged_in(self) -> bool:
|
||||||
|
try:
|
||||||
|
login_btn = self._page.get_by_role("button", name="登录并加入编辑")
|
||||||
|
if login_btn.is_visible(timeout=1500):
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _ensure_login_dialog(self) -> None:
|
||||||
|
try:
|
||||||
|
login_btn = self._page.get_by_role("button", name="登录并加入编辑")
|
||||||
|
if login_btn.is_visible(timeout=1500):
|
||||||
|
login_btn.click()
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
wechat_btn = self._page.get_by_role("button", name="微信登录")
|
||||||
|
if wechat_btn.is_visible(timeout=3000):
|
||||||
|
wechat_btn.click()
|
||||||
|
time.sleep(1.5)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _capture_qr_image(self) -> Optional[bytes]:
|
||||||
|
candidates = [
|
||||||
|
self._page.locator("canvas"),
|
||||||
|
self._page.locator("img[alt*='二维码']"),
|
||||||
|
self._page.locator("img[src*='qr']"),
|
||||||
|
]
|
||||||
|
for locator in candidates:
|
||||||
|
try:
|
||||||
|
if locator.count() < 1:
|
||||||
|
continue
|
||||||
|
target = locator.first
|
||||||
|
target.wait_for(state="visible", timeout=5000)
|
||||||
|
return target.screenshot()
|
||||||
|
except PlaywrightTimeoutError:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
return self._page.screenshot(full_page=True)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _save_login_state(self) -> None:
|
||||||
|
try:
|
||||||
|
storage_state = getattr(config, "KDOCS_LOGIN_STATE_FILE", "data/kdocs_login_state.json")
|
||||||
|
os.makedirs(os.path.dirname(storage_state), exist_ok=True)
|
||||||
|
self._context.storage_state(path=storage_state)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[KDocs] 保存登录态失败: {e}")
|
||||||
|
|
||||||
|
def _handle_qr(self) -> Dict[str, Any]:
|
||||||
|
cfg = self._load_system_config()
|
||||||
|
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||||
|
if not doc_url:
|
||||||
|
return {"success": False, "error": "未配置金山文档链接"}
|
||||||
|
if not self._ensure_playwright():
|
||||||
|
return {"success": False, "error": self._last_error or "浏览器不可用"}
|
||||||
|
if not self._open_document(doc_url):
|
||||||
|
return {"success": False, "error": self._last_error or "打开文档失败"}
|
||||||
|
|
||||||
|
if self._is_logged_in():
|
||||||
|
self._login_required = False
|
||||||
|
self._last_login_ok = True
|
||||||
|
self._save_login_state()
|
||||||
|
return {"success": True, "logged_in": True, "qr_image": ""}
|
||||||
|
|
||||||
|
self._ensure_login_dialog()
|
||||||
|
qr_image = self._capture_qr_image()
|
||||||
|
if not qr_image:
|
||||||
|
self._last_error = "二维码获取失败"
|
||||||
|
return {"success": False, "error": self._last_error}
|
||||||
|
|
||||||
|
self._last_qr_image = qr_image
|
||||||
|
self._login_required = True
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"logged_in": False,
|
||||||
|
"qr_image": base64.b64encode(qr_image).decode("ascii"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _handle_clear_login(self) -> Dict[str, Any]:
|
||||||
|
storage_state = getattr(config, "KDOCS_LOGIN_STATE_FILE", "data/kdocs_login_state.json")
|
||||||
|
try:
|
||||||
|
if os.path.exists(storage_state):
|
||||||
|
os.remove(storage_state)
|
||||||
|
except Exception as e:
|
||||||
|
return {"success": False, "error": f"清除登录态失败: {e}"}
|
||||||
|
|
||||||
|
self._login_required = False
|
||||||
|
self._last_login_ok = None
|
||||||
|
self._cleanup_browser()
|
||||||
|
return {"success": True}
|
||||||
|
|
||||||
|
def _handle_status_check(self) -> Dict[str, Any]:
|
||||||
|
cfg = self._load_system_config()
|
||||||
|
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||||
|
if not doc_url:
|
||||||
|
return {"success": True, "logged_in": False, "error": "未配置文档链接"}
|
||||||
|
if not self._ensure_playwright():
|
||||||
|
return {"success": False, "logged_in": False, "error": self._last_error or "浏览器不可用"}
|
||||||
|
if not self._open_document(doc_url):
|
||||||
|
return {"success": False, "logged_in": False, "error": self._last_error or "打开文档失败"}
|
||||||
|
logged_in = self._is_logged_in()
|
||||||
|
self._last_login_ok = logged_in
|
||||||
|
self._login_required = not logged_in
|
||||||
|
if logged_in:
|
||||||
|
self._save_login_state()
|
||||||
|
return {"success": True, "logged_in": logged_in}
|
||||||
|
|
||||||
|
def _handle_upload(self, payload: Dict[str, Any]) -> None:
|
||||||
|
cfg = self._load_system_config()
|
||||||
|
if int(cfg.get("kdocs_enabled", 0) or 0) != 1:
|
||||||
|
return
|
||||||
|
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||||
|
if not doc_url:
|
||||||
|
return
|
||||||
|
|
||||||
|
unit = (payload.get("unit") or "").strip()
|
||||||
|
name = (payload.get("name") or "").strip()
|
||||||
|
image_path = payload.get("image_path")
|
||||||
|
user_id = payload.get("user_id")
|
||||||
|
account_id = payload.get("account_id")
|
||||||
|
|
||||||
|
if not unit or not name:
|
||||||
|
return
|
||||||
|
if not image_path or not os.path.exists(image_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._ensure_playwright():
|
||||||
|
self._notify_admin(unit, name, image_path, self._last_error or "浏览器不可用")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._open_document(doc_url):
|
||||||
|
self._notify_admin(unit, name, image_path, self._last_error or "打开文档失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._is_logged_in():
|
||||||
|
self._login_required = True
|
||||||
|
self._last_login_ok = False
|
||||||
|
self._notify_admin(unit, name, image_path, "登录已失效,请管理员重新扫码登录")
|
||||||
|
return
|
||||||
|
self._login_required = False
|
||||||
|
self._last_login_ok = True
|
||||||
|
|
||||||
|
sheet_name = (cfg.get("kdocs_sheet_name") or "").strip()
|
||||||
|
sheet_index = int(cfg.get("kdocs_sheet_index") or 0)
|
||||||
|
unit_col = (cfg.get("kdocs_unit_column") or "A").strip().upper()
|
||||||
|
image_col = (cfg.get("kdocs_image_column") or "D").strip().upper()
|
||||||
|
|
||||||
|
success = False
|
||||||
|
error_msg = ""
|
||||||
|
for attempt in range(2):
|
||||||
|
try:
|
||||||
|
if sheet_name or sheet_index:
|
||||||
|
self._select_sheet(sheet_name, sheet_index)
|
||||||
|
row_num = self._find_person_with_unit(unit, name, unit_col)
|
||||||
|
if row_num < 0:
|
||||||
|
error_msg = f"未找到人员: {unit}-{name}"
|
||||||
|
break
|
||||||
|
success = self._upload_image_to_cell(row_num, image_path, image_col)
|
||||||
|
if success:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self._last_success_at = time.time()
|
||||||
|
self._last_error = None
|
||||||
|
log_to_client(f"已上传表格截图: {unit}-{name}", user_id, account_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not error_msg:
|
||||||
|
error_msg = "上传失败"
|
||||||
|
self._last_error = error_msg
|
||||||
|
self._notify_admin(unit, name, image_path, error_msg)
|
||||||
|
log_to_client(f"表格上传失败: {error_msg}", user_id, account_id)
|
||||||
|
|
||||||
|
def _notify_admin(self, unit: str, name: str, image_path: str, error: str) -> None:
|
||||||
|
cfg = self._load_system_config()
|
||||||
|
if int(cfg.get("kdocs_admin_notify_enabled", 0) or 0) != 1:
|
||||||
|
return
|
||||||
|
to_email = (cfg.get("kdocs_admin_notify_email") or "").strip()
|
||||||
|
if not to_email:
|
||||||
|
return
|
||||||
|
settings = email_service.get_email_settings()
|
||||||
|
if not settings.get("enabled", False):
|
||||||
|
return
|
||||||
|
subject = "金山文档上传失败提醒"
|
||||||
|
body = (
|
||||||
|
f"上传失败\n\n人员: {unit}-{name}\n图片: {image_path}\n错误: {error}\n\n"
|
||||||
|
"请检查登录状态或表格配置。"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
email_service.send_email_async(
|
||||||
|
to_email=to_email,
|
||||||
|
subject=subject,
|
||||||
|
body=body,
|
||||||
|
email_type="kdocs_upload_failed",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[KDocs] 发送管理员邮件失败: {e}")
|
||||||
|
|
||||||
|
def _select_sheet(self, sheet_name: str, sheet_index: int) -> None:
|
||||||
|
if sheet_name:
|
||||||
|
candidates = [
|
||||||
|
self._page.locator("[role='tab']").filter(has_text=sheet_name),
|
||||||
|
self._page.locator(".sheet-tab").filter(has_text=sheet_name),
|
||||||
|
self._page.locator(".sheet-tab-name").filter(has_text=sheet_name),
|
||||||
|
]
|
||||||
|
for locator in candidates:
|
||||||
|
try:
|
||||||
|
if locator.count() < 1:
|
||||||
|
continue
|
||||||
|
locator.first.click()
|
||||||
|
time.sleep(0.5)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sheet_index > 0:
|
||||||
|
idx = sheet_index - 1
|
||||||
|
candidates = [
|
||||||
|
self._page.locator("[role='tab']"),
|
||||||
|
self._page.locator(".sheet-tab"),
|
||||||
|
self._page.locator(".sheet-tab-name"),
|
||||||
|
]
|
||||||
|
for locator in candidates:
|
||||||
|
try:
|
||||||
|
if locator.count() <= idx:
|
||||||
|
continue
|
||||||
|
locator.nth(idx).click()
|
||||||
|
time.sleep(0.5)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _get_current_cell_address(self) -> str:
|
||||||
|
name_box = self._page.locator('#root input[type="text"]').first
|
||||||
|
return name_box.input_value()
|
||||||
|
|
||||||
|
def _navigate_to_cell(self, cell_address: str) -> None:
|
||||||
|
name_box = self._page.locator('#root input[type="text"]').first
|
||||||
|
name_box.click()
|
||||||
|
name_box.fill(cell_address)
|
||||||
|
name_box.press("Enter")
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
def _get_cell_value(self, cell_address: str) -> str:
|
||||||
|
self._navigate_to_cell(cell_address)
|
||||||
|
time.sleep(0.3)
|
||||||
|
try:
|
||||||
|
formula_bar = self._page.locator('input[type="text"]').nth(1)
|
||||||
|
value = formula_bar.input_value()
|
||||||
|
if value:
|
||||||
|
return value.strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
formula_inputs = self._page.locator('textbox')
|
||||||
|
for i in range(formula_inputs.count()):
|
||||||
|
try:
|
||||||
|
value = formula_inputs.nth(i).input_value()
|
||||||
|
if value:
|
||||||
|
import re
|
||||||
|
|
||||||
|
if not re.match(r"^[A-Z]+\d+$", value.strip()):
|
||||||
|
return value.strip()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _search_person(self, name: str) -> None:
|
||||||
|
self._page.keyboard.press("Control+f")
|
||||||
|
time.sleep(0.3)
|
||||||
|
search_input = None
|
||||||
|
try:
|
||||||
|
search_input = self._page.get_by_role("textbox").nth(3)
|
||||||
|
search_input.fill(name)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
search_input = self._page.locator("input[placeholder*='查找']").first
|
||||||
|
search_input.fill(name)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(0.2)
|
||||||
|
try:
|
||||||
|
find_btn = self._page.get_by_role("button", name="查找").nth(2)
|
||||||
|
find_btn.click()
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self._page.get_by_role("button", name="查找").first.click()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
def _find_next(self) -> None:
|
||||||
|
try:
|
||||||
|
find_btn = self._page.get_by_role("button", name="查找").nth(2)
|
||||||
|
find_btn.click()
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self._page.get_by_role("button", name="查找").first.click()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
def _close_search(self) -> None:
|
||||||
|
self._page.keyboard.press("Escape")
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
def _extract_row_number(self, cell_address: str) -> int:
|
||||||
|
import re
|
||||||
|
|
||||||
|
match = re.search(r"(\\d+)$", cell_address)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def _verify_unit_by_navigation(self, row_num: int, unit: str, unit_col: str) -> bool:
|
||||||
|
cell_address = f"{unit_col}{row_num}"
|
||||||
|
cell_value = self._get_cell_value(cell_address)
|
||||||
|
if cell_value:
|
||||||
|
return cell_value == unit
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _find_person_with_unit(self, unit: str, name: str, unit_col: str, max_attempts: int = 50) -> int:
|
||||||
|
self._search_person(name)
|
||||||
|
found_rows = set()
|
||||||
|
for _ in range(max_attempts):
|
||||||
|
self._close_search()
|
||||||
|
current_address = self._get_current_cell_address()
|
||||||
|
row_num = self._extract_row_number(current_address)
|
||||||
|
if row_num == -1:
|
||||||
|
return -1
|
||||||
|
if row_num in found_rows:
|
||||||
|
return -1
|
||||||
|
found_rows.add(row_num)
|
||||||
|
|
||||||
|
if self._verify_unit_by_navigation(row_num, unit, unit_col):
|
||||||
|
return row_num
|
||||||
|
|
||||||
|
self._page.keyboard.press("Control+f")
|
||||||
|
time.sleep(0.2)
|
||||||
|
self._find_next()
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def _upload_image_to_cell(self, row_num: int, image_path: str, image_col: str) -> bool:
|
||||||
|
cell_address = f"{image_col}{row_num}"
|
||||||
|
self._navigate_to_cell(cell_address)
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._page.keyboard.press("Delete")
|
||||||
|
time.sleep(0.1)
|
||||||
|
self._page.keyboard.press("Backspace")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
insert_btn = self._page.get_by_role("button", name="插入")
|
||||||
|
insert_btn.click()
|
||||||
|
time.sleep(0.3)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"打开插入菜单失败: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_btn = self._page.get_by_role("button", name="图片")
|
||||||
|
image_btn.click()
|
||||||
|
time.sleep(0.3)
|
||||||
|
cell_image_option = self._page.get_by_role("option", name="单元格图片")
|
||||||
|
cell_image_option.click()
|
||||||
|
time.sleep(0.2)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"选择单元格图片失败: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
local_option = self._page.get_by_role("option", name="本地")
|
||||||
|
with self._page.expect_file_chooser() as fc_info:
|
||||||
|
local_option.click()
|
||||||
|
file_chooser = fc_info.value
|
||||||
|
file_chooser.set_files(image_path)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"上传文件失败: {e}")
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
_kdocs_uploader: Optional[KDocsUploader] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_kdocs_uploader() -> KDocsUploader:
|
||||||
|
global _kdocs_uploader
|
||||||
|
if _kdocs_uploader is None:
|
||||||
|
_kdocs_uploader = KDocsUploader()
|
||||||
|
_kdocs_uploader.start()
|
||||||
|
return _kdocs_uploader
|
||||||
@@ -388,6 +388,29 @@ def take_screenshot_for_account(
|
|||||||
|
|
||||||
account_name = account.remark if account.remark else account.username
|
account_name = account.remark if account.remark else account.username
|
||||||
|
|
||||||
|
try:
|
||||||
|
if screenshot_path and result and result.get("success"):
|
||||||
|
cfg = database.get_system_config() or {}
|
||||||
|
if int(cfg.get("kdocs_enabled", 0) or 0) == 1:
|
||||||
|
doc_url = (cfg.get("kdocs_doc_url") or "").strip()
|
||||||
|
if doc_url:
|
||||||
|
user_cfg = database.get_user_kdocs_settings(user_id) or {}
|
||||||
|
if int(user_cfg.get("kdocs_auto_upload", 0) or 0) == 1:
|
||||||
|
unit = (user_cfg.get("kdocs_unit") or cfg.get("kdocs_default_unit") or "").strip()
|
||||||
|
name = (account.remark or "").strip()
|
||||||
|
if unit and name:
|
||||||
|
from services.kdocs_uploader import get_kdocs_uploader
|
||||||
|
|
||||||
|
get_kdocs_uploader().enqueue_upload(
|
||||||
|
user_id=user_id,
|
||||||
|
account_id=account_id,
|
||||||
|
unit=unit,
|
||||||
|
name=name,
|
||||||
|
image_path=screenshot_path,
|
||||||
|
)
|
||||||
|
except Exception as kdocs_error:
|
||||||
|
logger.warning(f"表格上传任务提交失败: {kdocs_error}")
|
||||||
|
|
||||||
if batch_id:
|
if batch_id:
|
||||||
_batch_task_record_result(
|
_batch_task_record_result(
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"_email-Ct3wXHpw.js": {
|
"_email-B1605y-H.js": {
|
||||||
"file": "assets/email-Ct3wXHpw.js",
|
"file": "assets/email-B1605y-H.js",
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_system-BTlwVb4V.js": {
|
"_system-C--cT22w.js": {
|
||||||
"file": "assets/system-BTlwVb4V.js",
|
"file": "assets/system-C--cT22w.js",
|
||||||
"name": "system",
|
"name": "system",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_tasks-DjrYX1kb.js": {
|
"_tasks-BNEjyFS7.js": {
|
||||||
"file": "assets/tasks-DjrYX1kb.js",
|
"file": "assets/tasks-BNEjyFS7.js",
|
||||||
"name": "tasks",
|
"name": "tasks",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_users-DXwhA9ak.js": {
|
"_users-Z6zennED.js": {
|
||||||
"file": "assets/users-DXwhA9ak.js",
|
"file": "assets/users-Z6zennED.js",
|
||||||
"name": "users",
|
"name": "users",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-CRihQT8G.js",
|
"file": "assets/index-a8w3mx3g.js",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"src": "index.html",
|
"src": "index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/AnnouncementsPage.vue": {
|
"src/pages/AnnouncementsPage.vue": {
|
||||||
"file": "assets/AnnouncementsPage-BVJSt6Za.js",
|
"file": "assets/AnnouncementsPage-B4NSG18W.js",
|
||||||
"name": "AnnouncementsPage",
|
"name": "AnnouncementsPage",
|
||||||
"src": "src/pages/AnnouncementsPage.vue",
|
"src": "src/pages/AnnouncementsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -60,12 +60,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/EmailPage.vue": {
|
"src/pages/EmailPage.vue": {
|
||||||
"file": "assets/EmailPage-BU-aLz58.js",
|
"file": "assets/EmailPage-bnS2lxdA.js",
|
||||||
"name": "EmailPage",
|
"name": "EmailPage",
|
||||||
"src": "src/pages/EmailPage.vue",
|
"src": "src/pages/EmailPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_email-Ct3wXHpw.js",
|
"_email-B1605y-H.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/FeedbacksPage.vue": {
|
"src/pages/FeedbacksPage.vue": {
|
||||||
"file": "assets/FeedbacksPage-BeAqV1pz.js",
|
"file": "assets/FeedbacksPage-VbmcQLrX.js",
|
||||||
"name": "FeedbacksPage",
|
"name": "FeedbacksPage",
|
||||||
"src": "src/pages/FeedbacksPage.vue",
|
"src": "src/pages/FeedbacksPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -85,13 +85,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/LogsPage.vue": {
|
"src/pages/LogsPage.vue": {
|
||||||
"file": "assets/LogsPage-C25hwKy7.js",
|
"file": "assets/LogsPage-CYxgYYkM.js",
|
||||||
"name": "LogsPage",
|
"name": "LogsPage",
|
||||||
"src": "src/pages/LogsPage.vue",
|
"src": "src/pages/LogsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_users-DXwhA9ak.js",
|
"_users-Z6zennED.js",
|
||||||
"_tasks-DjrYX1kb.js",
|
"_tasks-BNEjyFS7.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -99,22 +99,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ReportPage.vue": {
|
"src/pages/ReportPage.vue": {
|
||||||
"file": "assets/ReportPage-DcTJhEeS.js",
|
"file": "assets/ReportPage-CWobMgMS.js",
|
||||||
"name": "ReportPage",
|
"name": "ReportPage",
|
||||||
"src": "src/pages/ReportPage.vue",
|
"src": "src/pages/ReportPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_email-Ct3wXHpw.js",
|
"_email-B1605y-H.js",
|
||||||
"_tasks-DjrYX1kb.js",
|
"_tasks-BNEjyFS7.js",
|
||||||
"_system-BTlwVb4V.js"
|
"_system-C--cT22w.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/ReportPage-Q8rCsG8A.css"
|
"assets/ReportPage-Q8rCsG8A.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SecurityPage.vue": {
|
"src/pages/SecurityPage.vue": {
|
||||||
"file": "assets/SecurityPage-6yFvpr3a.js",
|
"file": "assets/SecurityPage-CBvaqgN-.js",
|
||||||
"name": "SecurityPage",
|
"name": "SecurityPage",
|
||||||
"src": "src/pages/SecurityPage.vue",
|
"src": "src/pages/SecurityPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SettingsPage.vue": {
|
"src/pages/SettingsPage.vue": {
|
||||||
"file": "assets/SettingsPage-CixYahEQ.js",
|
"file": "assets/SettingsPage-DTIxDo9J.js",
|
||||||
"name": "SettingsPage",
|
"name": "SettingsPage",
|
||||||
"src": "src/pages/SettingsPage.vue",
|
"src": "src/pages/SettingsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -138,25 +138,25 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SystemPage.vue": {
|
"src/pages/SystemPage.vue": {
|
||||||
"file": "assets/SystemPage-oGtrzs5v.js",
|
"file": "assets/SystemPage-C7s6uqSA.js",
|
||||||
"name": "SystemPage",
|
"name": "SystemPage",
|
||||||
"src": "src/pages/SystemPage.vue",
|
"src": "src/pages/SystemPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_system-BTlwVb4V.js",
|
"_system-C--cT22w.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/SystemPage-DdMZ1omu.css"
|
"assets/SystemPage-H89YK9Pc.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/UsersPage.vue": {
|
"src/pages/UsersPage.vue": {
|
||||||
"file": "assets/UsersPage-1x6SHDoq.js",
|
"file": "assets/UsersPage-CIttj4S8.js",
|
||||||
"name": "UsersPage",
|
"name": "UsersPage",
|
||||||
"src": "src/pages/UsersPage.vue",
|
"src": "src/pages/UsersPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_users-DXwhA9ak.js",
|
"_users-Z6zennED.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a as m,_ as B,r as p,f as u,g as T,h as P,j as r,m as a,w as l,q as x,L as i,K as b}from"./index-CRihQT8G.js";async function C(o){const{data:s}=await m.put("/admin/username",{new_username:o});return s}async function S(o){const{data:s}=await m.put("/admin/password",{new_password:o});return s}async function U(){const{data:o}=await m.post("/logout");return o}const A={class:"page-stack"},E={__name:"SettingsPage",setup(o){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await U()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function h(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return P(),T("div",A,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新用户名"},{default:l(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:l(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新密码"},{default:l(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:h},{default:l(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},M=B(E,[["__scopeId","data-v-12a26d11"]]);export{M as default};
|
import{a as m,_ as B,r as p,f as u,g as T,h as P,j as r,m as a,w as l,q as x,L as i,K as b}from"./index-a8w3mx3g.js";async function C(o){const{data:s}=await m.put("/admin/username",{new_username:o});return s}async function S(o){const{data:s}=await m.put("/admin/password",{new_password:o});return s}async function U(){const{data:o}=await m.post("/logout");return o}const A={class:"page-stack"},E={__name:"SettingsPage",setup(o){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await U()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function h(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return P(),T("div",A,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新用户名"},{default:l(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:l(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:l(()=>[a(w,{label:"新密码"},{default:l(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:h},{default:l(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},M=B(E,[["__scopeId","data-v-12a26d11"]]);export{M as default};
|
||||||
17
static/admin/assets/SystemPage-C7s6uqSA.js
Normal file
17
static/admin/assets/SystemPage-C7s6uqSA.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.page-stack[data-v-5ced7917]{display:flex;flex-direction:column;gap:12px}.card[data-v-5ced7917]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-5ced7917]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-5ced7917]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-5ced7917]{display:flex;flex-wrap:wrap;gap:10px}
|
|
||||||
1
static/admin/assets/SystemPage-H89YK9Pc.css
Normal file
1
static/admin/assets/SystemPage-H89YK9Pc.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.page-stack[data-v-965e79dd]{display:flex;flex-direction:column;gap:12px}.card[data-v-965e79dd]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-965e79dd]{margin:0 0 12px;font-size:14px;font-weight:800}.kdocs-qr[data-v-965e79dd]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-965e79dd]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-965e79dd]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-965e79dd]{display:flex;flex-wrap:wrap;gap:10px}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a as n}from"./index-CRihQT8G.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};
|
import{a as n}from"./index-a8w3mx3g.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{a}from"./index-CRihQT8G.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function o(){const{data:t}=await a.post("/schedule/execute",{});return t}export{o as e,s as f,c as u};
|
import{a}from"./index-a8w3mx3g.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function o(){const{data:t}=await a.post("/schedule/execute",{});return t}export{o as e,s as f,c as u};
|
||||||
@@ -1 +1 @@
|
|||||||
import{a}from"./index-CRihQT8G.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};
|
import{a}from"./index-a8w3mx3g.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};
|
||||||
@@ -1 +1 @@
|
|||||||
import{a as t}from"./index-CRihQT8G.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
|
import{a as t}from"./index-a8w3mx3g.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>后台管理 - 知识管理平台</title>
|
<title>后台管理 - 知识管理平台</title>
|
||||||
<script type="module" crossorigin src="./assets/index-CRihQT8G.js"></script>
|
<script type="module" crossorigin src="./assets/index-a8w3mx3g.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-DxTKnDeo.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-DxTKnDeo.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"_accounts-XHQJZlkc.js": {
|
"_accounts--hQB0HMJ.js": {
|
||||||
"file": "assets/accounts-XHQJZlkc.js",
|
"file": "assets/accounts--hQB0HMJ.js",
|
||||||
"name": "accounts",
|
"name": "accounts",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"_auth-CXXxlo9_.js": {
|
"_auth-Cm-kEDXF.js": {
|
||||||
"file": "assets/auth-CXXxlo9_.js",
|
"file": "assets/auth-Cm-kEDXF.js",
|
||||||
"name": "auth",
|
"name": "auth",
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html"
|
"index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-B3Praptc.js",
|
"file": "assets/index-D7kwaKcv.js",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"src": "index.html",
|
"src": "index.html",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -28,68 +28,68 @@
|
|||||||
"src/pages/ScreenshotsPage.vue"
|
"src/pages/ScreenshotsPage.vue"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-HG0IyblY.css"
|
"assets/index-BVjJVlht.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/AccountsPage.vue": {
|
"src/pages/AccountsPage.vue": {
|
||||||
"file": "assets/AccountsPage-BWLUZGwv.js",
|
"file": "assets/AccountsPage-DOK2DZIQ.js",
|
||||||
"name": "AccountsPage",
|
"name": "AccountsPage",
|
||||||
"src": "src/pages/AccountsPage.vue",
|
"src": "src/pages/AccountsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_accounts-XHQJZlkc.js",
|
"_accounts--hQB0HMJ.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/AccountsPage-CkDdMK5Q.css"
|
"assets/AccountsPage-Cwi1Wpgg.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/LoginPage.vue": {
|
"src/pages/LoginPage.vue": {
|
||||||
"file": "assets/LoginPage-iG0xN46c.js",
|
"file": "assets/LoginPage-Bh9f42Ek.js",
|
||||||
"name": "LoginPage",
|
"name": "LoginPage",
|
||||||
"src": "src/pages/LoginPage.vue",
|
"src": "src/pages/LoginPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-CXXxlo9_.js"
|
"_auth-Cm-kEDXF.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/LoginPage-CnwOLKJz.css"
|
"assets/LoginPage-CnwOLKJz.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/RegisterPage.vue": {
|
"src/pages/RegisterPage.vue": {
|
||||||
"file": "assets/RegisterPage-DKtQs7_M.js",
|
"file": "assets/RegisterPage-DXsTO3Cf.js",
|
||||||
"name": "RegisterPage",
|
"name": "RegisterPage",
|
||||||
"src": "src/pages/RegisterPage.vue",
|
"src": "src/pages/RegisterPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-CXXxlo9_.js"
|
"_auth-Cm-kEDXF.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/RegisterPage-BOcNcW5D.css"
|
"assets/RegisterPage-BOcNcW5D.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ResetPasswordPage.vue": {
|
"src/pages/ResetPasswordPage.vue": {
|
||||||
"file": "assets/ResetPasswordPage-DY3NSKCZ.js",
|
"file": "assets/ResetPasswordPage-CbAWo2Ml.js",
|
||||||
"name": "ResetPasswordPage",
|
"name": "ResetPasswordPage",
|
||||||
"src": "src/pages/ResetPasswordPage.vue",
|
"src": "src/pages/ResetPasswordPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"index.html",
|
"index.html",
|
||||||
"_auth-CXXxlo9_.js"
|
"_auth-Cm-kEDXF.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/ResetPasswordPage-DybfLMAw.css"
|
"assets/ResetPasswordPage-DybfLMAw.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/SchedulesPage.vue": {
|
"src/pages/SchedulesPage.vue": {
|
||||||
"file": "assets/SchedulesPage-CtGw4yPv.js",
|
"file": "assets/SchedulesPage-CmS2H_0Y.js",
|
||||||
"name": "SchedulesPage",
|
"name": "SchedulesPage",
|
||||||
"src": "src/pages/SchedulesPage.vue",
|
"src": "src/pages/SchedulesPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_accounts-XHQJZlkc.js",
|
"_accounts--hQB0HMJ.js",
|
||||||
"index.html"
|
"index.html"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/ScreenshotsPage.vue": {
|
"src/pages/ScreenshotsPage.vue": {
|
||||||
"file": "assets/ScreenshotsPage-BfoUmvOa.js",
|
"file": "assets/ScreenshotsPage-BU4GP4nV.js",
|
||||||
"name": "ScreenshotsPage",
|
"name": "ScreenshotsPage",
|
||||||
"src": "src/pages/ScreenshotsPage.vue",
|
"src": "src/pages/ScreenshotsPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/pages/VerifyResultPage.vue": {
|
"src/pages/VerifyResultPage.vue": {
|
||||||
"file": "assets/VerifyResultPage-QlobZKo3.js",
|
"file": "assets/VerifyResultPage-DUMGEC2F.js",
|
||||||
"name": "VerifyResultPage",
|
"name": "VerifyResultPage",
|
||||||
"src": "src/pages/VerifyResultPage.vue",
|
"src": "src/pages/VerifyResultPage.vue",
|
||||||
"isDynamicEntry": true,
|
"isDynamicEntry": true,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.page[data-v-b4e85160]{display:flex;flex-direction:column;gap:12px}.stat-card[data-v-b4e85160],.panel[data-v-b4e85160]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-label[data-v-b4e85160]{font-size:12px}.stat-value[data-v-b4e85160]{margin-top:6px;font-size:22px;font-weight:900;letter-spacing:.2px}.stat-suffix[data-v-b4e85160]{margin-left:6px;font-size:12px;font-weight:600}.upgrade-banner[data-v-b4e85160]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.upgrade-actions[data-v-b4e85160]{margin-top:10px}.panel-head[data-v-b4e85160]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-b4e85160]{font-size:16px;font-weight:900}.panel-actions[data-v-b4e85160]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.toolbar[data-v-b4e85160]{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;border:1px dashed rgba(17,24,39,.14);border-radius:12px;background:#f6f7fb99}.toolbar-left[data-v-b4e85160],.toolbar-middle[data-v-b4e85160],.toolbar-right[data-v-b4e85160]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.toolbar-right[data-v-b4e85160]{margin-left:auto;justify-content:flex-end}.grid[data-v-b4e85160]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;align-items:start}.account-card[data-v-b4e85160]{border-radius:14px;border:1px solid var(--app-border)}.card-top[data-v-b4e85160]{display:flex;gap:10px}.card-check[data-v-b4e85160]{padding-top:2px}.card-main[data-v-b4e85160]{min-width:0;flex:1}.card-title[data-v-b4e85160]{display:flex;align-items:center;justify-content:space-between;gap:10px}.card-name[data-v-b4e85160]{font-size:14px;font-weight:900;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-sub[data-v-b4e85160]{margin-top:6px;font-size:12px;line-height:1.4;word-break:break-word}.progress[data-v-b4e85160]{margin-top:12px}.progress-meta[data-v-b4e85160]{margin-top:6px;display:flex;justify-content:space-between;gap:10px;font-size:12px}.card-controls[data-v-b4e85160]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.card-buttons[data-v-b4e85160]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.vip-body[data-v-b4e85160]{padding:12px 0 0}.vip-tip[data-v-b4e85160]{margin-top:10px;font-size:13px;line-height:1.6}@media(max-width:480px){.grid[data-v-b4e85160]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-b4e85160]{width:100%;justify-content:flex-end}.toolbar-left[data-v-b4e85160],.toolbar-middle[data-v-b4e85160],.toolbar-right[data-v-b4e85160]{width:100%}.toolbar-right[data-v-b4e85160]{margin-left:0;justify-content:flex-end}}
|
|
||||||
1
static/app/assets/AccountsPage-Cwi1Wpgg.css
Normal file
1
static/app/assets/AccountsPage-Cwi1Wpgg.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.page[data-v-ceefeac8]{display:flex;flex-direction:column;gap:12px}.stat-card[data-v-ceefeac8],.panel[data-v-ceefeac8]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-label[data-v-ceefeac8]{font-size:12px}.stat-value[data-v-ceefeac8]{margin-top:6px;font-size:22px;font-weight:900;letter-spacing:.2px}.stat-suffix[data-v-ceefeac8]{margin-left:6px;font-size:12px;font-weight:600}.upgrade-banner[data-v-ceefeac8]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.upgrade-actions[data-v-ceefeac8]{margin-top:10px}.panel-head[data-v-ceefeac8]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-ceefeac8]{font-size:16px;font-weight:900}.panel-actions[data-v-ceefeac8]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.toolbar[data-v-ceefeac8]{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;border:1px dashed rgba(17,24,39,.14);border-radius:12px;background:#f6f7fb99}.toolbar-left[data-v-ceefeac8],.toolbar-middle[data-v-ceefeac8],.toolbar-right[data-v-ceefeac8]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.toolbar-right[data-v-ceefeac8]{margin-left:auto;justify-content:flex-end}.grid[data-v-ceefeac8]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;align-items:start}.account-card[data-v-ceefeac8]{border-radius:14px;border:1px solid var(--app-border)}.card-top[data-v-ceefeac8]{display:flex;gap:10px}.card-check[data-v-ceefeac8]{padding-top:2px}.card-main[data-v-ceefeac8]{min-width:0;flex:1}.card-title[data-v-ceefeac8]{display:flex;align-items:center;justify-content:space-between;gap:10px}.card-name[data-v-ceefeac8]{font-size:14px;font-weight:900;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-sub[data-v-ceefeac8]{margin-top:6px;font-size:12px;line-height:1.4;word-break:break-word}.progress[data-v-ceefeac8]{margin-top:12px}.progress-meta[data-v-ceefeac8]{margin-top:6px;display:flex;justify-content:space-between;gap:10px;font-size:12px}.card-controls[data-v-ceefeac8]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.card-buttons[data-v-ceefeac8]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.vip-body[data-v-ceefeac8]{padding:12px 0 0}.vip-tip[data-v-ceefeac8]{margin-top:10px;font-size:13px;line-height:1.6}@media(max-width:480px){.grid[data-v-ceefeac8]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-ceefeac8]{width:100%;justify-content:flex-end}.toolbar-left[data-v-ceefeac8],.toolbar-middle[data-v-ceefeac8],.toolbar-right[data-v-ceefeac8]{width:100%}.toolbar-right[data-v-ceefeac8]{margin-left:0;justify-content:flex-end}}
|
||||||
1
static/app/assets/AccountsPage-DOK2DZIQ.js
Normal file
1
static/app/assets/AccountsPage-DOK2DZIQ.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as M,r as j,a as d,c as B,o as A,b as U,d as l,w as o,e as v,u as H,f as b,g as n,h as N,i as E,j as P,t as q,k as S,E as c,v as z}from"./index-B3Praptc.js";import{g as F,f as G,b as J}from"./auth-CXXxlo9_.js";const O={class:"auth-wrap"},Q={class:"hint app-muted"},W={class:"captcha-row"},X=["src"],Y={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),K=B(()=>f.value?"邮箱 *":"邮箱(可选)"),R=B(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await F();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function D(){try{const u=await G();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function I(){t.value="",_.value="",k.value=""}async function C(){I();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=z(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await J({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function L(){T.push("/login")}return A(async()=>{await y(),await D()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),U("div",O,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),N(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):E("",!0),_.value?(b(),N(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):E("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:K.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",Q,q(R.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",W,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),U("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,X)):E("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[S("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[S("注册",-1)])]),_:1},8,["loading"]),n("div",Y,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:L},{default:o(()=>[...e[9]||(e[9]=[S("立即登录",-1)])]),_:1})])]),_:1})])}}},te=M(Z,[["__scopeId","data-v-a9d7804f"]]);export{te as default};
|
import{_ as M,r as j,a as d,c as B,o as A,b as U,d as l,w as o,e as v,u as H,f as b,g as n,h as N,i as E,j as P,t as q,k as S,E as c,v as z}from"./index-D7kwaKcv.js";import{g as F,f as G,b as J}from"./auth-Cm-kEDXF.js";const O={class:"auth-wrap"},Q={class:"hint app-muted"},W={class:"captcha-row"},X=["src"],Y={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=j({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),K=B(()=>f.value?"邮箱 *":"邮箱(可选)"),R=B(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await F();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function D(){try{const u=await G();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function I(){t.value="",_.value="",k.value=""}async function C(){I();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=z(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await J({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function L(){T.push("/login")}return A(async()=>{await y(),await D()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),U("div",O,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),N(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):E("",!0),_.value?(b(),N(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):E("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:K.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",Q,q(R.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",W,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),U("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,X)):E("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[S("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[S("注册",-1)])]),_:1},8,["loading"]),n("div",Y,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:L},{default:o(()=>[...e[9]||(e[9]=[S("立即登录",-1)])]),_:1})])]),_:1})])}}},te=M(Z,[["__scopeId","data-v-a9d7804f"]]);export{te as default};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as w,g as m,F as T,k,h as q,i as x,j as z,t as G,v as H,E as y}from"./index-B3Praptc.js";import{c as J}from"./auth-CXXxlo9_.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),_=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!_.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=H(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await J({token:r.value,new_password:o}),_.value="密码重置成功!3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const f=p?.response?.data;y.error(f?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),f=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return w(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"知识管理平台"),m("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(w(),v(T,{key:1},[_.value?(w(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:_.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码(至少8位且包含字母和数字)"},{default:a(()=>[s(f,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(f,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(w(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(w(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),m("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},oe=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{oe as default};
|
import{_ as L,a as n,l as M,r as U,c as j,o as F,m as K,b as v,d as s,w as a,e as l,u as D,f as w,g as m,F as T,k,h as q,i as x,j as z,t as G,v as H,E as y}from"./index-D7kwaKcv.js";import{c as J}from"./auth-Cm-kEDXF.js";const O={class:"auth-wrap"},Q={class:"actions"},W={class:"actions"},X={key:0,class:"app-muted"},Y={__name:"ResetPasswordPage",setup(Z){const B=M(),A=D(),r=n(String(B.params.token||"")),i=n(!0),b=n(""),t=U({newPassword:"",confirmPassword:""}),g=n(!1),_=n(""),d=n(0);let u=null;function C(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=j(()=>!!(i.value&&r.value&&!_.value));function S(){A.push("/login")}function N(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=H(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}g.value=!0;try{await J({token:r.value,new_password:o}),_.value="密码重置成功!3秒后跳转到登录页面...",y.success("密码重置成功"),N()}catch(p){const f=p?.response?.data;y.error(f?.error||"重置失败")}finally{g.value=!1}}return F(()=>{const o=C();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),K(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),f=l("el-input"),h=l("el-form-item"),R=l("el-form"),E=l("el-card");return w(),v("div",O,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"知识管理平台"),m("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(w(),v(T,{key:1},[_.value?(w(),q(c,{key:0,type:"success",closable:!1,title:"重置成功",description:_.value,"show-icon":"",class:"alert"},null,8,["description"])):x("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(h,{label:"新密码(至少8位且包含字母和数字)"},{default:a(()=>[s(f,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>t.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(h,{label:"确认密码"},{default:a(()=>[s(f,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>t.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:z(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:g.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",W,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(w(),v("span",X,G(d.value)+" 秒后自动跳转…",1)):x("",!0)])],64)):(w(),v(T,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),m("div",Q,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},oe=L(Y,[["__scopeId","data-v-0bbb511c"]]);export{oe as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-B3Praptc.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
|
import{_ as U,a as o,c as I,o as E,m as R,b as k,d as i,w as s,e as d,u as W,f as _,g as l,i as B,h as $,k as T,t as v}from"./index-D7kwaKcv.js";const j={class:"auth-wrap"},z={class:"actions"},D={key:0,class:"countdown app-muted"},M={__name:"VerifyResultPage",setup(q){const x=W(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function C(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function N(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const A=I(()=>!!(r.value&&u.value)),b=I(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await x.push(e)}}function P(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return E(()=>{const e=C();N(e),P()}),R(()=>{a&&window.clearInterval(a)}),(e,t)=>{const h=d("el-button"),V=d("el-result"),L=d("el-card");return _(),k("div",j,[i(L,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(V,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",z,[i(h,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[T(v(w.value),1)]),_:1}),A.value?(_(),$(h,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[T(v(r.value),1)]),_:1})):B("",!0)]),b.value?(_(),k("div",D,v(n.value)+" 秒后自动跳转... ",1)):B("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=U(M,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
|
||||||
@@ -1 +1 @@
|
|||||||
import{p as c}from"./index-B3Praptc.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};
|
import{p as c}from"./index-D7kwaKcv.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};
|
||||||
@@ -1 +1 @@
|
|||||||
import{p as s}from"./index-B3Praptc.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};
|
import{p as s}from"./index-D7kwaKcv.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||||
<title>知识管理平台</title>
|
<title>知识管理平台</title>
|
||||||
<script type="module" crossorigin src="./assets/index-B3Praptc.js"></script>
|
<script type="module" crossorigin src="./assets/index-D7kwaKcv.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-HG0IyblY.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-BVjJVlht.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>该页面需要启用 JavaScript 才能使用。</noscript>
|
<noscript>该页面需要启用 JavaScript 才能使用。</noscript>
|
||||||
|
|||||||
Reference in New Issue
Block a user