feat: add Space aggregate login

This commit is contained in:
237899745
2026-05-27 20:39:46 +08:00
parent e725db79a9
commit 056948612a
136 changed files with 2405 additions and 322 deletions

View File

@@ -20,3 +20,19 @@ export async function executeScheduleNow() {
const { data } = await api.post('/schedule/execute', {}) const { data } = await api.post('/schedule/execute', {})
return data return data
} }
export async function fetchSocialLoginConfig() {
const { data } = await api.get('/social-login/config')
return data
}
export async function updateSocialLoginConfig(payload) {
const { data } = await api.post('/social-login/config', payload || {})
systemConfigGetter.clear()
return data
}
export async function testSocialLoginConfig(payload) {
const { data } = await api.post('/social-login/test', payload || {})
return data
}

View File

@@ -2,7 +2,7 @@
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { fetchSystemConfig, updateSystemConfig } from '../api/system' import { fetchSocialLoginConfig, fetchSystemConfig, testSocialLoginConfig, updateSocialLoginConfig, updateSystemConfig } from '../api/system'
import { fetchKdocsQr, fetchKdocsStatus, clearKdocsLogin } from '../api/kdocs' import { fetchKdocsQr, fetchKdocsStatus, clearKdocsLogin } from '../api/kdocs'
import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy' import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
import { getCachedKdocsStatus, preloadKdocsStatus, updateCachedKdocsStatus } from '../utils/kdocsStatusCache' import { getCachedKdocsStatus, preloadKdocsStatus, updateCachedKdocsStatus } from '../utils/kdocsStatusCache'
@@ -22,6 +22,21 @@ const autoApproveEnabled = ref(false)
const autoApproveHourlyLimit = ref(10) const autoApproveHourlyLimit = ref(10)
const autoApproveVipDays = ref(7) const autoApproveVipDays = ref(7)
const socialLoginEnabled = ref(false)
const socialLoginEndpoint = ref('https://www.spacezs.cn/connect.php')
const socialLoginAppid = ref('')
const socialLoginAppkey = ref('')
const socialLoginAppkeyMasked = ref('')
const socialLoginAppkeyConfigured = ref(false)
const socialLoginProviders = ref(['wx'])
const socialLoginSaving = ref(false)
const socialLoginTesting = ref(false)
const socialProviderOptions = [
{ label: 'QQ', value: 'qq' },
{ label: '微信', value: 'wx' },
{ label: '支付宝', value: 'alipay' },
]
const kdocsEnabled = ref(false) const kdocsEnabled = ref(false)
const kdocsDocUrl = ref('') const kdocsDocUrl = ref('')
const kdocsDefaultUnit = ref('') const kdocsDefaultUnit = ref('')
@@ -83,9 +98,10 @@ function setKdocsHint(message) {
async function loadAll() { async function loadAll() {
loading.value = true loading.value = true
try { try {
const [system, proxy] = await Promise.all([ const [system, proxy, social] = await Promise.all([
fetchSystemConfig(), fetchSystemConfig(),
fetchProxyConfig(), fetchProxyConfig(),
fetchSocialLoginConfig(),
]) ])
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2 maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
@@ -112,6 +128,16 @@ async function loadAll() {
kdocsRowEnd.value = system.kdocs_row_end ?? 0 kdocsRowEnd.value = system.kdocs_row_end ?? 0
kdocsAdminNotifyEnabled.value = (system.kdocs_admin_notify_enabled ?? 0) === 1 kdocsAdminNotifyEnabled.value = (system.kdocs_admin_notify_enabled ?? 0) === 1
kdocsAdminNotifyEmail.value = system.kdocs_admin_notify_email || '' kdocsAdminNotifyEmail.value = system.kdocs_admin_notify_email || ''
socialLoginEnabled.value = (social.social_login_enabled ?? 0) === 1
socialLoginEndpoint.value = social.social_login_endpoint || 'https://www.spacezs.cn/connect.php'
socialLoginAppid.value = social.social_login_appid || ''
socialLoginAppkey.value = ''
socialLoginAppkeyMasked.value = social.social_login_appkey_masked || ''
socialLoginAppkeyConfigured.value = Boolean(social.social_login_appkey_configured)
socialLoginProviders.value = Array.isArray(social.social_login_providers) && social.social_login_providers.length
? social.social_login_providers
: ['wx']
} catch { } catch {
// handled by interceptor // handled by interceptor
} finally { } finally {
@@ -233,6 +259,78 @@ async function saveAutoApprove() {
} }
} }
function socialLoginPayload() {
return {
social_login_enabled: socialLoginEnabled.value ? 1 : 0,
social_login_endpoint: socialLoginEndpoint.value.trim() || 'https://www.spacezs.cn/connect.php',
social_login_appid: socialLoginAppid.value.trim(),
social_login_appkey: socialLoginAppkey.value.trim(),
social_login_providers: socialLoginProviders.value,
}
}
function validateSocialLoginForm() {
if (!socialLoginEnabled.value) return true
if (!socialLoginProviders.value.length) {
ElMessage.error('请选择至少一种登录方式')
return false
}
if (!socialLoginEndpoint.value.trim()) {
ElMessage.error('请输入聚合接口地址')
return false
}
if (!socialLoginAppid.value.trim()) {
ElMessage.error('请输入 APPID')
return false
}
if (!socialLoginAppkey.value.trim() && !socialLoginAppkeyConfigured.value) {
ElMessage.error('请输入 APPKEY')
return false
}
return true
}
async function saveSocialLogin() {
if (!validateSocialLoginForm()) return
socialLoginSaving.value = true
try {
const res = await updateSocialLoginConfig(socialLoginPayload())
const cfg = res?.config || {}
socialLoginAppkey.value = ''
socialLoginAppkeyMasked.value = cfg.social_login_appkey_masked || socialLoginAppkeyMasked.value
socialLoginAppkeyConfigured.value = Boolean(cfg.social_login_appkey_configured)
ElMessage.success(res?.message || '聚合登录配置已保存')
} catch {
// handled by interceptor
} finally {
socialLoginSaving.value = false
}
}
async function onTestSocialLogin() {
if (!validateSocialLoginForm()) return
socialLoginTesting.value = true
try {
const url = new URL(window.location.href)
url.pathname = '/login'
url.search = ''
url.hash = ''
const provider = socialLoginProviders.value[0] || 'wx'
const res = await testSocialLoginConfig({
...socialLoginPayload(),
provider,
redirect_uri: url.toString(),
})
await ElMessageBox.alert(res?.success ? '连接正常' : '测试完成', '聚合登录测试', {
confirmButtonText: '知道了',
})
} catch {
// handled by interceptor
} finally {
socialLoginTesting.value = false
}
}
async function saveKdocsConfig() { async function saveKdocsConfig() {
const payload = { const payload = {
kdocs_enabled: kdocsEnabled.value ? 1 : 0, kdocs_enabled: kdocsEnabled.value ? 1 : 0,
@@ -459,6 +557,43 @@ onMounted(loadAll)
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button> <el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
</div> </div>
</el-card> </el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card section-card social-card">
<h3 class="section-title">聚合登录</h3>
<div class="section-sub app-muted">QQ微信支付宝快捷登录</div>
<el-form label-width="122px">
<el-form-item label="启用">
<el-switch v-model="socialLoginEnabled" />
</el-form-item>
<el-form-item label="登录方式">
<el-checkbox-group v-model="socialLoginProviders">
<el-checkbox v-for="item in socialProviderOptions" :key="item.value" :label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="接口地址">
<el-input v-model="socialLoginEndpoint" placeholder="https://www.spacezs.cn/connect.php" />
</el-form-item>
<el-form-item label="APPID">
<el-input v-model="socialLoginAppid" placeholder="Space APPID" />
</el-form-item>
<el-form-item label="APPKEY">
<el-input v-model="socialLoginAppkey" type="password" show-password placeholder="留空则保持当前密钥" />
<div v-if="socialLoginAppkeyConfigured" class="help">当前{{ socialLoginAppkeyMasked }}</div>
</el-form-item>
</el-form>
<div class="row-actions">
<el-button type="primary" :loading="socialLoginSaving" @click="saveSocialLogin">保存聚合登录</el-button>
<el-button :loading="socialLoginTesting" @click="onTestSocialLogin">测试连接</el-button>
</div>
</el-card>
</div> </div>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card kdocs-card"> <el-card shadow="never" :body-style="{ padding: '16px' }" class="card kdocs-card">
@@ -743,6 +878,12 @@ onMounted(loadAll)
} }
} }
@media (min-width: 1201px) {
.social-card {
grid-column: span 3;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.config-grid { .config-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@@ -12,6 +12,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"element-plus": "^2.11.3", "element-plus": "^2.11.3",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"qrcode.vue": "^3.6.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"vue": "^3.5.24", "vue": "^3.5.24",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
@@ -1991,6 +1992,15 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/qrcode.vue": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.9.1.tgz",
"integrity": "sha512-CpHVRz5iveqwRFh+nzzSYV9hPWU6q+YSOKyq5ZievjQIBv4bIIDzajGgtNz/yYSlczjAkYM3GNAQJHwwCukMEQ==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/quansync": { "node_modules/quansync": {
"version": "0.2.11", "version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",

View File

@@ -13,6 +13,7 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"element-plus": "^2.11.3", "element-plus": "^2.11.3",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"qrcode.vue": "^3.6.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"vue": "^3.5.24", "vue": "^3.5.24",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"

View File

@@ -44,3 +44,23 @@ export async function confirmPasswordReset(payload) {
const { data } = await publicApi.post('/reset-password-confirm', payload) const { data } = await publicApi.post('/reset-password-confirm', payload)
return data return data
} }
export async function fetchSocialConfig() {
const { data } = await publicApi.get('/auth/social/config')
return data
}
export async function socialLoginUrl(payload) {
const { data } = await publicApi.post('/auth/social/login-url', payload || {})
return data
}
export async function socialPoll(payload) {
const { data } = await publicApi.post('/auth/social/poll', payload || {})
return data
}
export async function socialCallback(payload) {
const { data } = await publicApi.post('/auth/social/callback', payload || {})
return data
}

View File

@@ -69,3 +69,18 @@ export async function reportUserPasskeyClientError(payload) {
const { data } = await publicApi.post('/user/passkeys/client-error', payload || {}) const { data } = await publicApi.post('/user/passkeys/client-error', payload || {})
return data return data
} }
export async function fetchSocialBindings() {
const { data } = await publicApi.get('/user/social-bindings')
return data
}
export async function bindSocial(payload) {
const { data } = await publicApi.post('/user/social-bindings', payload || {})
return data
}
export async function unbindSocial(provider) {
const { data } = await publicApi.delete(`/user/social-bindings/${encodeURIComponent(provider)}`)
return data
}

View File

@@ -0,0 +1,238 @@
<script setup>
import { computed, onBeforeUnmount, ref } from 'vue'
import { ElMessage } from 'element-plus'
import QrcodeVue from 'qrcode.vue'
import { socialLoginUrl, socialPoll } from '../api/auth'
const props = defineProps({
providers: {
type: Array,
default: () => [],
},
mode: {
type: String,
default: 'login',
},
redirectUri: {
type: String,
required: true,
},
block: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['error'])
const providerLabels = {
qq: 'QQ',
wx: '微信',
alipay: '支付宝',
}
const loadingProvider = ref('')
const qrOpen = ref(false)
const qrValue = ref('')
const qrProvider = ref('wx')
let pollTimer = null
let pollStartedAt = 0
const enabledProviders = computed(() => props.providers.filter((item) => providerLabels[item]))
function stopPolling() {
if (pollTimer) {
window.clearTimeout(pollTimer)
pollTimer = null
}
}
function closeQr() {
stopPolling()
qrOpen.value = false
qrValue.value = ''
}
function qrPrompt(provider) {
if (provider === 'wx') return '请使用微信扫描二维码点关注后登录'
if (provider === 'qq') return '请使用 QQ 扫描二维码登录'
return '请使用支付宝扫描二维码登录'
}
function providerIcon(provider) {
if (provider === 'wx') return '微'
if (provider === 'qq') return 'Q'
return '支'
}
function emitError(error, fallback) {
const data = error?.response?.data
const message = data?.error || data?.message || fallback
emit('error', message)
ElMessage.error(message)
}
function schedulePoll(provider, state, intervalSeconds) {
stopPolling()
pollStartedAt = Date.now()
const tick = async () => {
if (Date.now() - pollStartedAt > 5 * 60 * 1000) {
closeQr()
ElMessage.warning('二维码已过期,请重新获取')
return
}
try {
const result = await socialPoll({ provider, state })
if (result?.status === 'authorized' && result?.url) {
closeQr()
window.location.assign(result.url)
return
}
pollTimer = window.setTimeout(tick, Math.max(Number(intervalSeconds || 2), 2) * 1000)
} catch (error) {
closeQr()
emitError(error, '扫码状态获取失败,请重新尝试')
}
}
pollTimer = window.setTimeout(tick, Math.max(Number(intervalSeconds || 2), 2) * 1000)
}
async function start(provider) {
if (loadingProvider.value) return
loadingProvider.value = provider
try {
const data = await socialLoginUrl({
provider,
mode: props.mode === 'bind' ? 'bind' : 'login',
redirect_uri: props.redirectUri,
})
if (provider !== 'wx') {
window.location.assign(data.url)
return
}
const value = data.scan_url || data.qrcode || data.url
if (!value || !data.scan_state) {
ElMessage.error('微信二维码获取失败')
return
}
qrProvider.value = provider
qrValue.value = value
qrOpen.value = true
schedulePoll(provider, data.scan_state, data.scan_poll_interval || 2)
} catch (error) {
emitError(error, '获取聚合登录地址失败')
} finally {
loadingProvider.value = ''
}
}
onBeforeUnmount(() => {
stopPolling()
})
</script>
<template>
<div v-if="enabledProviders.length" class="social-login-buttons" :class="{ block }">
<button
v-for="provider in enabledProviders"
:key="provider"
type="button"
class="social-btn"
:class="`provider-${provider}`"
:disabled="Boolean(loadingProvider)"
@click="start(provider)"
>
<span class="social-icon">{{ providerIcon(provider) }}</span>
<span>{{ mode === 'bind' ? `绑定${providerLabels[provider]}` : `${providerLabels[provider]}登录` }}</span>
</button>
<el-dialog v-model="qrOpen" :title="`${providerLabels[qrProvider]}登录`" width="min(340px, 92vw)" @close="closeQr">
<div class="social-qr-box">
<QrcodeVue v-if="qrValue" :value="qrValue" :size="220" level="M" />
<div class="social-qr-prompt">{{ qrPrompt(qrProvider) }}</div>
</div>
</el-dialog>
</div>
</template>
<style scoped>
.social-login-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.social-login-buttons.block {
align-items: stretch;
flex-direction: column;
}
.social-btn {
height: 40px;
border-radius: 10px;
border: 1px solid rgba(17, 24, 39, 0.14);
background: #fff;
color: #111827;
font-size: 13px;
font-weight: 800;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 12px;
}
.block .social-btn {
width: 100%;
}
.social-btn:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.social-btn:hover:not(:disabled) {
background: #f8fafc;
}
.social-icon {
width: 22px;
height: 22px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
line-height: 1;
}
.provider-wx .social-icon {
background: #16a34a;
}
.provider-qq .social-icon {
background: #2563eb;
}
.provider-alipay .social-icon {
background: #1677ff;
}
.social-qr-box {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.social-qr-prompt {
font-size: 13px;
color: #374151;
text-align: center;
}
</style>

View File

@@ -7,8 +7,10 @@ import 'element-plus/es/components/message/style/css'
import 'element-plus/es/components/message-box/style/css' import 'element-plus/es/components/message-box/style/css'
import { fetchActiveAnnouncement, dismissAnnouncement } from '../api/announcements' import { fetchActiveAnnouncement, dismissAnnouncement } from '../api/announcements'
import { fetchSocialConfig } from '../api/auth'
import { fetchMyFeedbacks, submitFeedback } from '../api/feedback' import { fetchMyFeedbacks, submitFeedback } from '../api/feedback'
import { import {
bindSocial,
bindEmail, bindEmail,
changePassword, changePassword,
createUserPasskeyOptions, createUserPasskeyOptions,
@@ -18,11 +20,14 @@ import {
fetchUserPasskeys, fetchUserPasskeys,
fetchUserEmail, fetchUserEmail,
fetchKdocsSettings, fetchKdocsSettings,
fetchSocialBindings,
reportUserPasskeyClientError, reportUserPasskeyClientError,
unbindSocial,
unbindEmail, unbindEmail,
updateKdocsSettings, updateKdocsSettings,
updateEmailNotify, updateEmailNotify,
} from '../api/settings' } from '../api/settings'
import SocialLoginButtons from '../components/SocialLoginButtons.vue'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey' import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey'
import { validateStrongPassword } from '../utils/password' import { validateStrongPassword } from '../utils/password'
@@ -132,6 +137,12 @@ const passkeyRegisterOptions = ref(null)
const passkeyRegisterOptionsAt = ref(0) const passkeyRegisterOptionsAt = ref(0)
const PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS = 240000 const PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS = 240000
const socialConfig = ref({ enabled: false, providers: [] })
const socialBindings = ref([])
const socialBindingsLoading = ref(false)
const socialBindLoading = ref(false)
const pendingSettingsBindKey = 'zsglpt_social_settings_bind_token'
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
@@ -262,7 +273,76 @@ async function openSettings() {
} }
async function loadSettings() { async function loadSettings() {
await Promise.all([loadEmailInfo(), loadEmailNotify(), loadKdocsSettings(), loadPasskeys()]) await Promise.all([loadEmailInfo(), loadEmailNotify(), loadKdocsSettings(), loadPasskeys(), loadSocialBindings()])
}
function socialBindRedirectUri() {
const url = new URL(window.location.href)
url.pathname = '/social-bind-callback'
url.search = ''
url.hash = ''
return url.toString()
}
async function loadSocialBindings() {
socialBindingsLoading.value = true
try {
const [config, bindings] = await Promise.all([fetchSocialConfig(), fetchSocialBindings()])
socialConfig.value = {
enabled: Boolean(config?.enabled),
providers: Array.isArray(config?.providers) ? config.providers : [],
}
socialBindings.value = Array.isArray(bindings?.items) ? bindings.items : []
await consumePendingSocialBind()
} catch {
socialConfig.value = { enabled: false, providers: [] }
socialBindings.value = []
} finally {
socialBindingsLoading.value = false
}
}
async function consumePendingSocialBind() {
let token = ''
try {
token = window.sessionStorage.getItem(pendingSettingsBindKey) || ''
} catch {
token = ''
}
if (!token || socialBindLoading.value) return
socialBindLoading.value = true
try {
await bindSocial({ bind_token: token })
window.sessionStorage.removeItem(pendingSettingsBindKey)
ElMessage.success('快捷登录已绑定')
const bindings = await fetchSocialBindings()
socialBindings.value = Array.isArray(bindings?.items) ? bindings.items : []
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '快捷登录绑定失败')
} finally {
socialBindLoading.value = false
}
}
async function onUnbindSocial(item) {
try {
await ElMessageBox.confirm(`确定解绑${item?.provider_label || '快捷登录'}吗?`, '解绑快捷登录', {
confirmButtonText: '解绑',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
await unbindSocial(item.provider)
ElMessage.success('已解绑')
await loadSocialBindings()
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '解绑失败')
}
} }
async function loadEmailInfo() { async function loadEmailInfo() {
@@ -838,6 +918,37 @@ async function dismissAnnouncementPermanently() {
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="快捷登录" name="social">
<div class="settings-section" v-loading="socialBindingsLoading || socialBindLoading">
<el-empty v-if="!socialConfig.enabled" description="暂未启用快捷登录" />
<template v-else>
<div class="social-binding-list">
<div v-for="item in socialBindings" :key="item.provider" class="social-binding-row">
<div class="social-binding-main">
<div class="social-binding-title">
{{ item.provider_label }}
<el-tag v-if="item.bound" size="small" type="success">已绑定</el-tag>
<el-tag v-else size="small" type="info">未绑定</el-tag>
</div>
<div v-if="item.bound" class="app-muted social-binding-meta">
{{ item.nickname || '已授权账号' }}
</div>
</div>
<el-button v-if="item.bound" type="danger" text @click="onUnbindSocial(item)">解绑</el-button>
</div>
</div>
<SocialLoginButtons
:providers="socialConfig.providers"
mode="bind"
:redirect-uri="socialBindRedirectUri()"
block
@error="(message) => ElMessage.error(message)"
/>
</template>
</div>
</el-tab-pane>
<el-tab-pane label="表格上传" name="kdocs"> <el-tab-pane label="表格上传" name="kdocs">
<div v-loading="kdocsLoading" class="settings-section"> <div v-loading="kdocsLoading" class="settings-section">
<el-form label-position="top"> <el-form label-position="top">
@@ -1101,6 +1212,43 @@ async function dismissAnnouncementPermanently() {
margin-top: 10px; margin-top: 10px;
} }
.social-binding-list {
display: grid;
gap: 10px;
margin-bottom: 14px;
}
.social-binding-row {
min-height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--app-border);
border-radius: 10px;
background: #fff;
}
.social-binding-main {
min-width: 0;
}
.social-binding-title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 800;
}
.social-binding-meta {
margin-top: 4px;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vip-info { .vip-info {
margin-top: 12px; margin-top: 12px;
display: grid; display: grid;

View File

@@ -1,6 +1,8 @@
<script setup> <script setup>
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, reactive, ref } from 'vue'
import SocialLoginButtons from '../components/SocialLoginButtons.vue'
const form = reactive({ const form = reactive({
username: '', username: '',
password: '', password: '',
@@ -43,6 +45,15 @@ const resendError = ref('')
const showResendLink = computed(() => true) const showResendLink = computed(() => true)
const verifyStatusLoaded = ref(false) const verifyStatusLoaded = ref(false)
const socialConfig = ref({ enabled: false, providers: [] })
const socialCallbackLoading = ref(false)
const socialPendingKeys = {
token: 'zsglpt_social_pending_bind_token',
provider: 'zsglpt_social_pending_bind_provider',
nickname: 'zsglpt_social_pending_bind_nickname',
avatar: 'zsglpt_social_pending_bind_avatar_url',
}
function getCookie(name) { function getCookie(name) {
const escaped = String(name || '').replace(/([.*+?^${}()|[\]\\])/g, '\\$1') const escaped = String(name || '').replace(/([.*+?^${}()|[\]\\])/g, '\\$1')
@@ -104,6 +115,8 @@ const passkeyLoginOptions = (payload) => apiRequest('/passkeys/login/options', {
const passkeyLoginVerify = (payload) => apiRequest('/passkeys/login/verify', { method: 'POST', body: payload || {} }) const passkeyLoginVerify = (payload) => apiRequest('/passkeys/login/verify', { method: 'POST', body: payload || {} })
const resendVerifyEmail = (payload) => apiRequest('/resend-verify-email', { method: 'POST', body: payload || {} }) const resendVerifyEmail = (payload) => apiRequest('/resend-verify-email', { method: 'POST', body: payload || {} })
const forgotPassword = (payload) => apiRequest('/forgot-password', { method: 'POST', body: payload || {} }) const forgotPassword = (payload) => apiRequest('/forgot-password', { method: 'POST', body: payload || {} })
const fetchSocialConfig = () => apiRequest('/auth/social/config')
const socialCallbackRequest = (payload) => apiRequest('/auth/social/callback', { method: 'POST', body: payload || {} })
function base64UrlToUint8Array(base64url) { function base64UrlToUint8Array(base64url) {
const value = String(base64url || '') const value = String(base64url || '')
@@ -251,6 +264,70 @@ function redirectAfterLogin() {
}, 300) }, 300)
} }
function currentLoginRedirectUri() {
const url = new URL(window.location.href)
url.pathname = '/login'
url.search = ''
url.hash = ''
return url.toString()
}
async function loadSocialConfig() {
try {
const data = await fetchSocialConfig()
socialConfig.value = {
enabled: Boolean(data?.enabled),
providers: Array.isArray(data?.providers) ? data.providers : [],
}
} catch {
socialConfig.value = { enabled: false, providers: [] }
}
}
function savePendingSocialBind(data) {
try {
window.sessionStorage.setItem(socialPendingKeys.token, data?.bind_token || '')
window.sessionStorage.setItem(socialPendingKeys.provider, data?.provider || '')
window.sessionStorage.setItem(socialPendingKeys.nickname, data?.nickname || '')
window.sessionStorage.setItem(socialPendingKeys.avatar, data?.avatar_url || '')
} catch {
// ignore storage failures
}
}
async function handleSocialCallback() {
const params = new URLSearchParams(window.location.search || '')
const provider = String(params.get('provider') || params.get('type') || '').trim()
const code = String(params.get('code') || '').trim()
if (!provider || !code) return false
socialCallbackLoading.value = true
setNotice('success', '正在完成快捷登录...')
try {
const data = await socialCallbackRequest({ provider, code, mode: 'login' })
if (data?.requires_register && data?.bind_token) {
savePendingSocialBind(data)
setNotice('success', '请完成注册后继续')
window.setTimeout(() => {
window.location.href = '/register'
}, 500)
return true
}
if (data?.success || data?.bound) {
setNotice('success', '快捷登录成功,正在跳转...')
redirectAfterLogin()
return true
}
setNotice('error', '快捷登录失败')
} catch (e) {
const data = e?.response?.data
setNotice('error', data?.error || data?.message || '快捷登录失败')
} finally {
socialCallbackLoading.value = false
}
return true
}
async function onSubmit() { async function onSubmit() {
clearNotice() clearNotice()
@@ -421,6 +498,8 @@ function goRegister() {
} }
onMounted(async () => { onMounted(async () => {
await loadSocialConfig()
await handleSocialCallback()
if (needCaptcha.value) { if (needCaptcha.value) {
await refreshLoginCaptcha() await refreshLoginCaptcha()
} }
@@ -486,13 +565,24 @@ onMounted(async () => {
</div> </div>
</div> </div>
<button type="button" class="btn-login" :disabled="loading" @click="onSubmit"> <button type="button" class="btn-login" :disabled="loading || socialCallbackLoading" @click="onSubmit">
{{ loading ? '登录中...' : '登录系统' }} {{ loading || socialCallbackLoading ? '登录中...' : '登录系统' }}
</button> </button>
<button type="button" class="btn-passkey" :disabled="passkeyLoading" @click="onPasskeyLogin"> <button type="button" class="btn-passkey" :disabled="passkeyLoading" @click="onPasskeyLogin">
{{ passkeyLoading ? 'Passkey验证中...' : '使用 Passkey 登录' }} {{ passkeyLoading ? 'Passkey验证中...' : '使用 Passkey 登录' }}
</button> </button>
<div v-if="socialConfig.enabled" class="social-login-area">
<div class="divider"><span>快捷登录</span></div>
<SocialLoginButtons
:providers="socialConfig.providers"
mode="login"
:redirect-uri="currentLoginRedirectUri()"
block
@error="(message) => setNotice('error', message)"
/>
</div>
<div class="action-links"> <div class="action-links">
<button type="button" class="link-btn" @click="openForgot">忘记密码</button> <button type="button" class="link-btn" @click="openForgot">忘记密码</button>
<button v-if="showResendLink" type="button" class="link-btn" @click="openResend">重发验证邮件</button> <button v-if="showResendLink" type="button" class="link-btn" @click="openResend">重发验证邮件</button>
@@ -747,6 +837,28 @@ onMounted(async () => {
background: #f1f5f9; background: #f1f5f9;
} }
.social-login-area {
margin-top: 14px;
}
.divider {
display: flex;
align-items: center;
gap: 10px;
margin: 14px 0 10px;
color: #64748b;
font-size: 12px;
font-weight: 700;
}
.divider::before,
.divider::after {
content: '';
height: 1px;
flex: 1;
background: rgba(17, 24, 39, 0.12);
}
.btn-passkey:disabled, .btn-passkey:disabled,
.btn-login:disabled, .btn-login:disabled,
.btn-ghost:disabled, .btn-ghost:disabled,

View File

@@ -25,6 +25,26 @@ const errorText = ref('')
const successTitle = ref('') const successTitle = ref('')
const successDesc = ref('') const successDesc = ref('')
const socialPendingKeys = {
token: 'zsglpt_social_pending_bind_token',
provider: 'zsglpt_social_pending_bind_provider',
nickname: 'zsglpt_social_pending_bind_nickname',
avatar: 'zsglpt_social_pending_bind_avatar_url',
}
const providerLabels = {
qq: 'QQ',
wx: '微信',
alipay: '支付宝',
}
const pendingSocial = reactive({
token: '',
provider: '',
nickname: '',
avatar_url: '',
})
const emailLabel = computed(() => (emailVerifyEnabled.value ? '邮箱 *' : '邮箱(可选)')) const emailLabel = computed(() => (emailVerifyEnabled.value ? '邮箱 *' : '邮箱(可选)'))
const emailHint = computed(() => (emailVerifyEnabled.value ? '必填,用于账号验证' : '选填,用于找回密码和接收通知')) const emailHint = computed(() => (emailVerifyEnabled.value ? '必填,用于账号验证' : '选填,用于找回密码和接收通知'))
@@ -55,6 +75,35 @@ function clearAlerts() {
successDesc.value = '' successDesc.value = ''
} }
function loadPendingSocialBind() {
try {
pendingSocial.token = window.sessionStorage.getItem(socialPendingKeys.token) || ''
pendingSocial.provider = window.sessionStorage.getItem(socialPendingKeys.provider) || ''
pendingSocial.nickname = window.sessionStorage.getItem(socialPendingKeys.nickname) || ''
pendingSocial.avatar_url = window.sessionStorage.getItem(socialPendingKeys.avatar) || ''
if (pendingSocial.nickname && !form.username) {
form.username = pendingSocial.nickname.replace(/[^\w\u4e00-\u9fa5]/g, '').slice(0, 20)
}
} catch {
pendingSocial.token = ''
pendingSocial.provider = ''
pendingSocial.nickname = ''
pendingSocial.avatar_url = ''
}
}
function clearPendingSocialBind() {
try {
Object.values(socialPendingKeys).forEach((key) => window.sessionStorage.removeItem(key))
} catch {
// ignore storage failures
}
pendingSocial.token = ''
pendingSocial.provider = ''
pendingSocial.nickname = ''
pendingSocial.avatar_url = ''
}
async function onSubmit() { async function onSubmit() {
clearAlerts() clearAlerts()
@@ -104,11 +153,15 @@ async function onSubmit() {
email, email,
captcha_session: captchaSession.value, captcha_session: captchaSession.value,
captcha, captcha,
social_bind_token: pendingSocial.token || undefined,
}) })
successTitle.value = res?.message || '注册成功' successTitle.value = res?.message || '注册成功'
successDesc.value = res?.need_verify ? '请检查您的邮箱(包括垃圾邮件文件夹)' : '' successDesc.value = res?.need_verify ? '请检查您的邮箱(包括垃圾邮件文件夹)' : ''
ElMessage.success('注册成功') ElMessage.success('注册成功')
if (pendingSocial.token) {
clearPendingSocialBind()
}
form.username = '' form.username = ''
form.password = '' form.password = ''
@@ -134,6 +187,7 @@ function goLogin() {
} }
onMounted(async () => { onMounted(async () => {
loadPendingSocialBind()
await refreshCaptcha() await refreshCaptcha()
await loadEmailVerifyStatus() await loadEmailVerifyStatus()
}) })
@@ -158,6 +212,15 @@ onMounted(async () => {
class="alert" class="alert"
/> />
<el-alert
v-if="pendingSocial.token"
type="info"
:closable="false"
show-icon
class="alert"
:title="`注册后绑定${providerLabels[pendingSocial.provider] || '快捷登录'}`"
/>
<el-form label-position="top"> <el-form label-position="top">
<el-form-item label="用户名 *"> <el-form-item label="用户名 *">
<el-input v-model="form.username" placeholder="至少3个字符" autocomplete="username" /> <el-input v-model="form.username" placeholder="至少3个字符" autocomplete="username" />

View File

@@ -0,0 +1,87 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { socialCallback } from '../api/auth'
import { bindSocial } from '../api/settings'
const router = useRouter()
const statusText = ref('正在完成绑定')
const pendingSettingsBindKey = 'zsglpt_social_settings_bind_token'
onMounted(async () => {
const params = new URLSearchParams(window.location.search || '')
const provider = String(params.get('provider') || params.get('type') || '').trim()
const code = String(params.get('code') || '').trim()
if (!provider || !code) {
ElMessage.error('快捷登录回调参数不完整')
router.replace('/app/accounts')
return
}
try {
const data = await socialCallback({ provider, code, mode: 'bind' })
if (data?.success && data?.bound) {
ElMessage.success('快捷登录已绑定')
router.replace('/app/accounts')
return
}
if (!data?.bind_token) {
ElMessage.warning('未获取到绑定凭证')
router.replace('/app/accounts')
return
}
try {
await bindSocial({ bind_token: data.bind_token })
ElMessage.success('快捷登录已绑定')
} catch (error) {
if (error?.response?.status === 401) {
window.sessionStorage.setItem(pendingSettingsBindKey, data.bind_token)
ElMessage.info('请先登录后完成绑定')
router.replace('/login')
return
}
throw error
}
router.replace('/app/accounts')
} catch (error) {
const payload = error?.response?.data
statusText.value = payload?.error || '快捷登录绑定失败'
ElMessage.error(statusText.value)
router.replace('/app/accounts')
}
})
</script>
<template>
<div class="callback-wrap">
<el-card shadow="never" class="callback-card">
<el-skeleton :rows="3" animated />
<div class="callback-text">{{ statusText }}</div>
</el-card>
</div>
</template>
<style scoped>
.callback-wrap {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.callback-card {
width: min(420px, 94vw);
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
}
.callback-text {
margin-top: 12px;
color: var(--app-muted);
font-size: 13px;
text-align: center;
}
</style>

View File

@@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router'
const LoginPage = () => import('../pages/LoginPage.vue') const LoginPage = () => import('../pages/LoginPage.vue')
const RegisterPage = () => import('../pages/RegisterPage.vue') const RegisterPage = () => import('../pages/RegisterPage.vue')
const ResetPasswordPage = () => import('../pages/ResetPasswordPage.vue') const ResetPasswordPage = () => import('../pages/ResetPasswordPage.vue')
const SocialBindCallbackPage = () => import('../pages/SocialBindCallbackPage.vue')
const VerifyResultPage = () => import('../pages/VerifyResultPage.vue') const VerifyResultPage = () => import('../pages/VerifyResultPage.vue')
const AppLayout = () => import('../layouts/AppLayout.vue') const AppLayout = () => import('../layouts/AppLayout.vue')
@@ -15,6 +16,7 @@ const routes = [
{ path: '/login', name: 'login', component: LoginPage }, { path: '/login', name: 'login', component: LoginPage },
{ path: '/register', name: 'register', component: RegisterPage }, { path: '/register', name: 'register', component: RegisterPage },
{ path: '/reset-password/:token', name: 'reset_password', component: ResetPasswordPage }, { path: '/reset-password/:token', name: 'reset_password', component: ResetPasswordPage },
{ path: '/social-bind-callback', name: 'social_bind_callback', component: SocialBindCallbackPage },
{ path: '/api/verify-email/:token', name: 'verify_email', component: VerifyResultPage }, { path: '/api/verify-email/:token', name: 'verify_email', component: VerifyResultPage },
{ path: '/api/verify-bind-email/:token', name: 'verify_bind_email', component: VerifyResultPage }, { path: '/api/verify-bind-email/:token', name: 'verify_bind_email', component: VerifyResultPage },
{ {

3
app.py
View File

@@ -218,6 +218,9 @@ def enforce_csrf_protection():
"/api/login", "/api/login",
"/api/auth/login", "/api/auth/login",
"/api/generate_captcha", "/api/generate_captcha",
"/api/auth/social/login-url",
"/api/auth/social/poll",
"/api/auth/social/callback",
"/yuyx/api/passkeys/login/options", "/yuyx/api/passkeys/login/options",
"/yuyx/api/passkeys/login/verify", "/yuyx/api/passkeys/login/verify",
"/api/passkeys/login/options", "/api/passkeys/login/options",

View File

@@ -105,12 +105,19 @@ from db.schedules import (
from db.tasks import create_task_log, delete_old_task_logs, get_task_logs, get_task_stats, get_user_run_stats from db.tasks import create_task_log, delete_old_task_logs, get_task_logs, get_task_stats, get_user_run_stats
from db.users import ( from db.users import (
approve_user, approve_user,
cleanup_expired_social_pending_binds,
create_user, create_user,
create_social_pending_bind,
delete_user, delete_user,
delete_social_login_binding,
delete_social_pending_bind,
extend_user_vip, extend_user_vip,
find_social_login_binding,
find_user_social_login_binding,
get_all_users, get_all_users,
get_users_count, get_users_count,
get_pending_users, get_pending_users,
get_social_pending_bind,
get_user_by_id, get_user_by_id,
get_user_by_username, get_user_by_username,
get_user_kdocs_settings, get_user_kdocs_settings,
@@ -122,7 +129,10 @@ from db.users import (
remove_user_vip, remove_user_vip,
set_default_vip_days, set_default_vip_days,
set_user_vip, set_user_vip,
list_social_login_bindings,
update_social_login_binding_profile,
update_user_kdocs_settings, update_user_kdocs_settings,
upsert_social_login_binding,
verify_user, verify_user,
) )
from db.security import record_login_context from db.security import record_login_context
@@ -134,7 +144,7 @@ logger = get_logger(__name__)
DB_FILE = config.DB_FILE DB_FILE = config.DB_FILE
# 数据库版本 (用于迁移管理) # 数据库版本 (用于迁移管理)
DB_VERSION = 21 DB_VERSION = 22
# ==================== 系统配置缓存P1 / O-03 ==================== # ==================== 系统配置缓存P1 / O-03 ====================
@@ -261,6 +271,11 @@ def update_system_config(
kdocs_row_start=None, kdocs_row_start=None,
kdocs_row_end=None, kdocs_row_end=None,
db_slow_query_ms=None, db_slow_query_ms=None,
social_login_enabled=None,
social_login_endpoint=None,
social_login_appid=None,
social_login_appkey=None,
social_login_providers=None,
): ):
"""更新系统配置(写入后立即失效缓存)。""" """更新系统配置(写入后立即失效缓存)。"""
ok = _update_system_config( ok = _update_system_config(
@@ -290,6 +305,11 @@ def update_system_config(
kdocs_row_start=kdocs_row_start, kdocs_row_start=kdocs_row_start,
kdocs_row_end=kdocs_row_end, kdocs_row_end=kdocs_row_end,
db_slow_query_ms=db_slow_query_ms, db_slow_query_ms=db_slow_query_ms,
social_login_enabled=social_login_enabled,
social_login_endpoint=social_login_endpoint,
social_login_appid=social_login_appid,
social_login_appkey=social_login_appkey,
social_login_providers=social_login_providers,
) )
if ok: if ok:
invalidate_system_config_cache() invalidate_system_config_cache()

View File

@@ -42,6 +42,11 @@ _DEFAULT_SYSTEM_CONFIG = {
"kdocs_admin_notify_email": "", "kdocs_admin_notify_email": "",
"kdocs_row_start": 0, "kdocs_row_start": 0,
"kdocs_row_end": 0, "kdocs_row_end": 0,
"social_login_enabled": 0,
"social_login_endpoint": "https://www.spacezs.cn/connect.php",
"social_login_appid": "",
"social_login_appkey": "",
"social_login_providers": "qq,wx,alipay",
} }
_SYSTEM_CONFIG_UPDATERS = ( _SYSTEM_CONFIG_UPDATERS = (
@@ -71,6 +76,11 @@ _SYSTEM_CONFIG_UPDATERS = (
("kdocs_admin_notify_email", "kdocs_admin_notify_email"), ("kdocs_admin_notify_email", "kdocs_admin_notify_email"),
("kdocs_row_start", "kdocs_row_start"), ("kdocs_row_start", "kdocs_row_start"),
("kdocs_row_end", "kdocs_row_end"), ("kdocs_row_end", "kdocs_row_end"),
("social_login_enabled", "social_login_enabled"),
("social_login_endpoint", "social_login_endpoint"),
("social_login_appid", "social_login_appid"),
("social_login_appkey", "social_login_appkey"),
("social_login_providers", "social_login_providers"),
) )
@@ -316,6 +326,11 @@ def update_system_config(
kdocs_row_start=None, kdocs_row_start=None,
kdocs_row_end=None, kdocs_row_end=None,
db_slow_query_ms=None, db_slow_query_ms=None,
social_login_enabled=None,
social_login_endpoint=None,
social_login_appid=None,
social_login_appkey=None,
social_login_providers=None,
) -> bool: ) -> bool:
"""更新系统配置仅更新DB不做缓存处理""" """更新系统配置仅更新DB不做缓存处理"""
arg_values = { arg_values = {
@@ -345,6 +360,11 @@ def update_system_config(
"kdocs_row_start": kdocs_row_start, "kdocs_row_start": kdocs_row_start,
"kdocs_row_end": kdocs_row_end, "kdocs_row_end": kdocs_row_end,
"db_slow_query_ms": db_slow_query_ms, "db_slow_query_ms": db_slow_query_ms,
"social_login_enabled": social_login_enabled,
"social_login_endpoint": social_login_endpoint,
"social_login_appid": social_login_appid,
"social_login_appkey": social_login_appkey,
"social_login_providers": social_login_providers,
} }
updates = [] updates = []

View File

@@ -76,6 +76,7 @@ def _get_migration_steps():
(19, _migrate_to_v19), (19, _migrate_to_v19),
(20, _migrate_to_v20), (20, _migrate_to_v20),
(21, _migrate_to_v21), (21, _migrate_to_v21),
(22, _migrate_to_v22),
] ]
@@ -933,3 +934,71 @@ def _migrate_to_v21(conn):
) )
conn.commit() conn.commit()
def _migrate_to_v22(conn):
"""迁移到版本22 - Space 聚合登录配置、绑定与短期待绑定凭证。"""
cursor = conn.cursor()
system_columns = _get_table_columns(cursor, "system_config")
system_fields = [
("social_login_enabled", "INTEGER DEFAULT 0"),
("social_login_endpoint", "TEXT DEFAULT 'https://www.spacezs.cn/connect.php'"),
("social_login_appid", "TEXT DEFAULT ''"),
("social_login_appkey", "TEXT DEFAULT ''"),
("social_login_providers", "TEXT DEFAULT 'qq,wx,alipay'"),
]
for field, ddl in system_fields:
_add_column_if_missing(
cursor,
"system_config",
system_columns,
field,
ddl,
ok_message=f" [OK] 添加 system_config.{field} 字段",
)
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS social_login_bindings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
provider TEXT NOT NULL,
social_uid TEXT NOT NULL,
nickname TEXT DEFAULT '',
avatar_url TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
UNIQUE (provider, social_uid),
UNIQUE (user_id, provider),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
"""
)
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_login_bindings_user ON social_login_bindings(user_id)")
cursor.execute(
"CREATE INDEX IF NOT EXISTS idx_social_login_bindings_provider_uid ON social_login_bindings(provider, social_uid)"
)
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS social_pending_binds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL UNIQUE,
provider TEXT NOT NULL,
social_uid TEXT NOT NULL,
nickname TEXT DEFAULT '',
avatar_url TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
)
"""
)
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_pending_binds_token ON social_pending_binds(token)")
cursor.execute(
"CREATE INDEX IF NOT EXISTS idx_social_pending_binds_provider_uid ON social_pending_binds(provider, social_uid)"
)
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_pending_binds_expires ON social_pending_binds(expires_at)")
conn.commit()

View File

@@ -246,11 +246,52 @@ def ensure_schema(conn) -> None:
kdocs_admin_notify_email TEXT DEFAULT '', kdocs_admin_notify_email TEXT DEFAULT '',
kdocs_row_start INTEGER DEFAULT 0, kdocs_row_start INTEGER DEFAULT 0,
kdocs_row_end INTEGER DEFAULT 0, kdocs_row_end INTEGER DEFAULT 0,
social_login_enabled INTEGER DEFAULT 0,
social_login_endpoint TEXT DEFAULT 'https://www.spacezs.cn/connect.php',
social_login_appid TEXT DEFAULT '',
social_login_appkey TEXT DEFAULT '',
social_login_providers TEXT DEFAULT 'qq,wx,alipay',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """
) )
# 聚合登录绑定表
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS social_login_bindings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
provider TEXT NOT NULL,
social_uid TEXT NOT NULL,
nickname TEXT DEFAULT '',
avatar_url TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
UNIQUE (provider, social_uid),
UNIQUE (user_id, provider),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
"""
)
# 聚合登录短期待绑定凭证表
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS social_pending_binds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL UNIQUE,
provider TEXT NOT NULL,
social_uid TEXT NOT NULL,
nickname TEXT DEFAULT '',
avatar_url TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
)
"""
)
# 任务日志表 # 任务日志表
cursor.execute( cursor.execute(
""" """
@@ -389,6 +430,11 @@ def ensure_schema(conn) -> None:
cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_ips_user ON login_ips(user_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_ips_user ON login_ips(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_passkeys_owner ON passkeys(owner_type, owner_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_passkeys_owner ON passkeys(owner_type, owner_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_passkeys_owner_last_used ON passkeys(owner_type, owner_id, last_used_at)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_passkeys_owner_last_used ON passkeys(owner_type, owner_id, last_used_at)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_login_bindings_user ON social_login_bindings(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_login_bindings_provider_uid ON social_login_bindings(provider, social_uid)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_pending_binds_token ON social_pending_binds(token)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_pending_binds_provider_uid ON social_pending_binds(provider, social_uid)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_social_pending_binds_expires ON social_pending_binds(expires_at)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_created_at ON threat_events(created_at)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_created_at ON threat_events(created_at)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_ip ON threat_events(ip)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_ip ON threat_events(ip)")

View File

@@ -297,6 +297,156 @@ def get_user_by_username(username):
return _get_user_by_field("username", username) return _get_user_by_field("username", username)
# ==================== 聚合登录绑定 ====================
def cleanup_expired_social_pending_binds() -> None:
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM social_pending_binds WHERE expires_at < ?", (get_cst_now_str(),))
conn.commit()
def create_social_pending_bind(*, token: str, provider: str, social_uid: str, nickname: str = "", avatar_url: str = "", expires_at: str) -> dict:
cleanup_expired_social_pending_binds()
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"DELETE FROM social_pending_binds WHERE provider = ? AND social_uid = ?",
(provider, social_uid),
)
cursor.execute(
"""
INSERT INTO social_pending_binds (token, provider, social_uid, nickname, avatar_url, created_at, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
token,
provider,
social_uid,
str(nickname or "")[:128],
str(avatar_url or "")[:512],
get_cst_now_str(),
expires_at,
),
)
conn.commit()
cursor.execute("SELECT * FROM social_pending_binds WHERE token = ?", (token,))
return _row_to_dict(cursor.fetchone())
def get_social_pending_bind(token: str):
cleanup_expired_social_pending_binds()
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM social_pending_binds WHERE token = ?", ((token or "").strip(),))
return _row_to_dict(cursor.fetchone())
def delete_social_pending_bind(token: str) -> bool:
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM social_pending_binds WHERE token = ?", ((token or "").strip(),))
conn.commit()
return cursor.rowcount > 0
def find_social_login_binding(provider: str, social_uid: str):
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM social_login_bindings WHERE provider = ? AND social_uid = ?",
(provider, social_uid),
)
return _row_to_dict(cursor.fetchone())
def find_user_social_login_binding(user_id: int, provider: str):
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM social_login_bindings WHERE user_id = ? AND provider = ?",
(int(user_id), provider),
)
return _row_to_dict(cursor.fetchone())
def upsert_social_login_binding(*, user_id: int, provider: str, social_uid: str, nickname: str = "", avatar_url: str = ""):
with db_pool.get_db() as conn:
cursor = conn.cursor()
now = get_cst_now_str()
try:
cursor.execute(
"""
INSERT INTO social_login_bindings (
user_id, provider, social_uid, nickname, avatar_url, created_at, updated_at, last_login_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, provider) DO UPDATE SET
social_uid = excluded.social_uid,
nickname = excluded.nickname,
avatar_url = excluded.avatar_url,
updated_at = excluded.updated_at,
last_login_at = excluded.last_login_at
""",
(
int(user_id),
provider,
social_uid,
str(nickname or "")[:128],
str(avatar_url or "")[:512],
now,
now,
now,
),
)
conn.commit()
except sqlite3.IntegrityError:
conn.rollback()
return None
return find_user_social_login_binding(user_id, provider)
def update_social_login_binding_profile(binding_id: int, *, nickname: str = "", avatar_url: str = "") -> bool:
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
UPDATE social_login_bindings
SET nickname = ?, avatar_url = ?, updated_at = ?, last_login_at = ?
WHERE id = ?
""",
(str(nickname or "")[:128], str(avatar_url or "")[:512], get_cst_now_str(), get_cst_now_str(), int(binding_id)),
)
conn.commit()
return cursor.rowcount > 0
def list_social_login_bindings(user_id: int) -> list[dict]:
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT * FROM social_login_bindings
WHERE user_id = ?
ORDER BY created_at ASC
""",
(int(user_id),),
)
return [dict(row) for row in cursor.fetchall()]
def delete_social_login_binding(user_id: int, provider: str) -> bool:
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"DELETE FROM social_login_bindings WHERE user_id = ? AND provider = ?",
(int(user_id), provider),
)
conn.commit()
return cursor.rowcount > 0
def _normalize_limit_offset(limit, offset, *, max_limit: int = 500): def _normalize_limit_offset(limit, offset, *, max_limit: int = 500):
normalized_limit = None normalized_limit = None
if limit is not None: if limit is not None:

View File

@@ -10,6 +10,7 @@ def register_blueprints(app) -> None:
from routes.api_auth import api_auth_bp from routes.api_auth import api_auth_bp
from routes.api_schedules import api_schedules_bp from routes.api_schedules import api_schedules_bp
from routes.api_screenshots import api_screenshots_bp from routes.api_screenshots import api_screenshots_bp
from routes.api_social import api_social_bp
from routes.api_user import api_user_bp from routes.api_user import api_user_bp
from routes.health import health_bp from routes.health import health_bp
from routes.pages import pages_bp from routes.pages import pages_bp
@@ -17,6 +18,7 @@ def register_blueprints(app) -> None:
app.register_blueprint(pages_bp) app.register_blueprint(pages_bp)
app.register_blueprint(health_bp) app.register_blueprint(health_bp)
app.register_blueprint(api_auth_bp) app.register_blueprint(api_auth_bp)
app.register_blueprint(api_social_bp)
app.register_blueprint(api_user_bp) app.register_blueprint(api_user_bp)
app.register_blueprint(api_accounts_bp) app.register_blueprint(api_accounts_bp)
app.register_blueprint(api_screenshots_bp) app.register_blueprint(api_screenshots_bp)

View File

@@ -30,6 +30,7 @@ from services.passkeys import (
normalize_device_name, normalize_device_name,
verify_authentication, verify_authentication,
) )
from services.social_login import parse_providers, provider_label
from services.state import ( from services.state import (
check_ip_request_rate, check_ip_request_rate,
check_email_rate_limit, check_email_rate_limit,
@@ -228,6 +229,7 @@ def register():
email = data.get("email", "").strip().lower() email = data.get("email", "").strip().lower()
captcha_session = data.get("captcha_session", "") captcha_session = data.get("captcha_session", "")
captcha_code = data.get("captcha", "").strip() captcha_code = data.get("captcha", "").strip()
social_bind_token = str(data.get("social_bind_token") or "").strip()
if not username or not password: if not username or not password:
return jsonify({"error": "用户名和密码不能为空"}), 400 return jsonify({"error": "用户名和密码不能为空"}), 400
@@ -273,6 +275,26 @@ def register():
user_id = database.create_user(username, password, email) user_id = database.create_user(username, password, email)
if user_id: if user_id:
social_bind_message = ""
if social_bind_token:
pending = database.get_social_pending_bind(social_bind_token)
if pending:
provider = str(pending.get("provider") or "").strip().lower()
social_uid = str(pending.get("social_uid") or "").strip()
enabled_providers = parse_providers((database.get_system_config() or {}).get("social_login_providers"))
existing_identity = database.find_social_login_binding(provider, social_uid)
if provider in enabled_providers and social_uid and not existing_identity:
binding = database.upsert_social_login_binding(
user_id=user_id,
provider=provider,
social_uid=social_uid,
nickname=pending.get("nickname") or "",
avatar_url=pending.get("avatar_url") or "",
)
if binding:
database.delete_social_pending_bind(social_bind_token)
social_bind_message = f",已绑定{provider_label(provider)}"
if auto_approve_enabled: if auto_approve_enabled:
if auto_approve_vip_days > 0: if auto_approve_vip_days > 0:
database.set_user_vip(user_id, auto_approve_vip_days) database.set_user_vip(user_id, auto_approve_vip_days)
@@ -281,7 +303,7 @@ def register():
result = email_service.send_register_verification_email(email=email, username=username, user_id=user_id) result = email_service.send_register_verification_email(email=email, username=username, user_id=user_id)
if result["success"]: if result["success"]:
message = _with_vip_suffix( message = _with_vip_suffix(
"注册成功!验证邮件已发送(可直接登录,建议完成邮箱验证)", f"注册成功!验证邮件已发送(可直接登录,建议完成邮箱验证){social_bind_message}",
auto_approve_enabled, auto_approve_enabled,
auto_approve_vip_days, auto_approve_vip_days,
) )
@@ -289,13 +311,15 @@ def register():
logger.error(f"注册验证邮件发送失败: {result['error']}") logger.error(f"注册验证邮件发送失败: {result['error']}")
message = _with_vip_suffix( message = _with_vip_suffix(
f"注册成功,但验证邮件发送失败({result['error']})。你仍可直接登录", f"注册成功,但验证邮件发送失败({result['error']})。你仍可直接登录{social_bind_message}",
auto_approve_enabled, auto_approve_enabled,
auto_approve_vip_days, auto_approve_vip_days,
) )
return jsonify({"success": True, "message": message, "need_verify": True}) return jsonify({"success": True, "message": message, "need_verify": True})
message = _with_vip_suffix("注册成功!可直接登录", auto_approve_enabled, auto_approve_vip_days) message = _with_vip_suffix("注册成功!可直接登录", auto_approve_enabled, auto_approve_vip_days)
if social_bind_message:
message = f"{message}{social_bind_message}"
return jsonify({"success": True, "message": message}) return jsonify({"success": True, "message": message})
return jsonify({"error": "用户名已存在"}), 400 return jsonify({"error": "用户名已存在"}), 400

333
routes/api_social.py Normal file
View File

@@ -0,0 +1,333 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
from datetime import timedelta
import database
from app_logger import get_logger
from db.utils import get_cst_now, get_cst_now_str
from flask import Blueprint, jsonify, request
from flask_login import current_user, login_required, login_user
from services.accounts_service import load_user_accounts
from services.models import User
from services.social_login import (
BIND_TOKEN_TTL_SECONDS,
PROVIDER_LABELS,
SocialLoginError,
SpaceProfile,
admin_social_config_out,
encrypt_social_appkey,
fetch_social_login_url,
fetch_space_profile,
new_bind_token,
normalize_social_endpoint,
parse_providers,
poll_social_scan,
provider_label,
public_social_config,
)
logger = get_logger("app")
api_social_bp = Blueprint("api_social", __name__)
def _get_json_payload() -> dict:
data = request.get_json(silent=True)
return data if isinstance(data, dict) else {}
def _social_error(error: SocialLoginError):
return jsonify({"error": error.message}), error.status_code
def _allowed_redirect_hosts() -> set[str]:
hosts: set[str] = set()
host = request.headers.get("X-Forwarded-Host") or request.headers.get("Host") or ""
if host:
hosts.add(host.lower())
hostname = host.split(":", 1)[0].lower()
if hostname:
hosts.add(hostname)
return hosts
def _create_pending_from_profile(profile: SpaceProfile) -> dict:
expires_at = (get_cst_now() + timedelta(seconds=BIND_TOKEN_TTL_SECONDS)).strftime("%Y-%m-%d %H:%M:%S")
return database.create_social_pending_bind(
token=new_bind_token(),
provider=profile.provider,
social_uid=profile.social_uid,
nickname=profile.nickname,
avatar_url=profile.avatar_url,
expires_at=expires_at,
)
def _login_user_id(user_id: int) -> None:
user_obj = User(user_id)
login_user(user_obj)
load_user_accounts(user_id)
def _binding_row(provider: str, binding: dict | None) -> dict:
return {
"provider": provider,
"provider_label": provider_label(provider),
"bound": bool(binding),
"nickname": (binding or {}).get("nickname") or "",
"avatar_url": (binding or {}).get("avatar_url") or "",
"last_login_at": (binding or {}).get("last_login_at"),
"created_at": (binding or {}).get("created_at"),
}
@api_social_bp.route("/api/auth/social/config", methods=["GET"])
def social_public_config():
return jsonify(public_social_config(database.get_system_config()))
@api_social_bp.route("/api/auth/social/login-url", methods=["POST"])
def social_login_url():
data = _get_json_payload()
provider = str(data.get("provider") or "").strip().lower()
mode = "bind" if str(data.get("mode") or "").strip().lower() == "bind" else "login"
redirect_uri = str(data.get("redirect_uri") or "").strip()
try:
result = fetch_social_login_url(
database.get_system_config(),
provider=provider,
mode=mode,
redirect_uri=redirect_uri,
allowed_hosts=_allowed_redirect_hosts(),
)
except SocialLoginError as error:
logger.warning(f"[social/login-url] provider={provider or '-'} mode={mode} failed: {error.message}")
return _social_error(error)
return jsonify(result)
@api_social_bp.route("/api/auth/social/poll", methods=["POST"])
def social_poll():
data = _get_json_payload()
provider = str(data.get("provider") or "").strip().lower()
state = str(data.get("state") or "").strip()
try:
result = poll_social_scan(database.get_system_config(), provider=provider, state=state)
except SocialLoginError as error:
logger.warning(f"[social/poll] provider={provider or '-'} failed: {error.message}")
return _social_error(error)
return jsonify(result)
@api_social_bp.route("/api/auth/social/callback", methods=["POST"])
def social_callback():
data = _get_json_payload()
provider = str(data.get("provider") or data.get("type") or "").strip().lower()
code = str(data.get("code") or "").strip()
mode = "bind" if str(data.get("mode") or "").strip().lower() == "bind" else "login"
try:
profile = fetch_space_profile(database.get_system_config(), provider=provider, code=code)
except SocialLoginError as error:
logger.warning(f"[social/callback] provider={provider or '-'} mode={mode} failed: {error.message}")
return _social_error(error)
binding = database.find_social_login_binding(profile.provider, profile.social_uid)
if binding:
if mode == "bind":
current_id = int(getattr(current_user, "id", 0) or 0)
if not current_id or int(binding.get("user_id") or 0) != current_id:
return jsonify({"error": "该第三方账号已绑定其他用户"}), 409
user = database.get_user_by_id(int(binding["user_id"]))
if not user or user.get("status") != "approved":
return jsonify({"error": "绑定账号不可用"}), 401
database.update_social_login_binding_profile(
int(binding["id"]),
nickname=profile.nickname,
avatar_url=profile.avatar_url,
)
_login_user_id(int(user["id"]))
return jsonify(
{
"success": True,
"mode": mode,
"provider": profile.provider,
"provider_label": provider_label(profile.provider),
"bound": True,
"username": user.get("username") or "",
}
)
pending = _create_pending_from_profile(profile)
return jsonify(
{
"success": True,
"mode": mode,
"provider": profile.provider,
"provider_label": provider_label(profile.provider),
"requires_register": mode == "login",
"requires_existing_login": mode == "bind",
"bind_token": pending.get("token"),
"expires_in": BIND_TOKEN_TTL_SECONDS,
"nickname": pending.get("nickname") or "",
"avatar_url": pending.get("avatar_url") or "",
}
)
@api_social_bp.route("/api/user/social-bindings", methods=["GET"])
@login_required
def list_social_bindings():
cfg = database.get_system_config()
providers = parse_providers(cfg.get("social_login_providers")) or list(PROVIDER_LABELS.keys())
existing = {
item["provider"]: item
for item in database.list_social_login_bindings(int(current_user.id))
}
return jsonify({"items": [_binding_row(provider, existing.get(provider)) for provider in providers]})
@api_social_bp.route("/api/user/social-bindings", methods=["POST"])
@login_required
def bind_social_account():
data = _get_json_payload()
token = str(data.get("bind_token") or "").strip()
pending = database.get_social_pending_bind(token)
if not pending:
return jsonify({"error": "绑定凭证已过期,请重新授权"}), 404
provider = str(pending.get("provider") or "").strip().lower()
social_uid = str(pending.get("social_uid") or "").strip()
existing_identity = database.find_social_login_binding(provider, social_uid)
if existing_identity and int(existing_identity.get("user_id") or 0) != int(current_user.id):
return jsonify({"error": "该第三方账号已绑定其他用户"}), 409
existing_provider = database.find_user_social_login_binding(int(current_user.id), provider)
if existing_provider and str(existing_provider.get("social_uid") or "") != social_uid:
return jsonify({"error": f"当前账号已绑定{provider_label(provider)}"}), 409
binding = database.upsert_social_login_binding(
user_id=int(current_user.id),
provider=provider,
social_uid=social_uid,
nickname=pending.get("nickname") or "",
avatar_url=pending.get("avatar_url") or "",
)
if not binding:
return jsonify({"error": "该第三方账号已绑定其他用户"}), 409
database.delete_social_pending_bind(token)
return jsonify({"success": True, "item": _binding_row(provider, binding)})
@api_social_bp.route("/api/user/social-bindings/<provider>", methods=["DELETE"])
@login_required
def unbind_social_account(provider):
provider = str(provider or "").strip().lower()
if provider not in PROVIDER_LABELS:
return jsonify({"error": "不支持的登录方式"}), 400
if not database.delete_social_login_binding(int(current_user.id), provider):
return jsonify({"error": "绑定记录不存在"}), 404
return jsonify({"success": True})
@api_social_bp.route("/yuyx/api/social-login/config", methods=["GET"])
def admin_social_config():
from routes.decorators import admin_required
protected = admin_required(lambda: jsonify(admin_social_config_out(database.get_system_config())))
return protected()
@api_social_bp.route("/yuyx/api/social-login/test", methods=["POST"])
def test_admin_social_config():
from routes.decorators import admin_required
@admin_required
def _inner():
data = _get_json_payload()
provider = str(data.get("provider") or "wx").strip().lower()
appkey = str(data.get("social_login_appkey") or "").strip()
old_config = database.get_system_config() or {}
try:
temp_config = {
**old_config,
"social_login_enabled": 1,
"social_login_endpoint": normalize_social_endpoint(str(data.get("social_login_endpoint") or "")),
"social_login_appid": str(data.get("social_login_appid") or "").strip(),
"social_login_appkey": encrypt_social_appkey(appkey) if appkey else str(old_config.get("social_login_appkey") or ""),
"social_login_providers": ",".join(parse_providers(data.get("social_login_providers"))),
}
redirect_uri = str(data.get("redirect_uri") or "").strip()
result = fetch_social_login_url(
temp_config,
provider=provider,
mode="login",
redirect_uri=redirect_uri,
allowed_hosts=_allowed_redirect_hosts(),
)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
except SocialLoginError as error:
return _social_error(error)
return jsonify(
{
"success": True,
"provider": result.get("provider"),
"has_url": bool(result.get("url")),
"has_scan_url": bool(result.get("scan_url")),
}
)
return _inner()
@api_social_bp.route("/yuyx/api/social-login/config", methods=["POST"])
def update_admin_social_config():
from routes.decorators import admin_required
@admin_required
def _inner():
data = _get_json_payload()
enabled = 1 if data.get("social_login_enabled") in (1, True, "1", "true", "on") else 0
endpoint_raw = str(data.get("social_login_endpoint") or "").strip()
appid = str(data.get("social_login_appid") or "").strip()
appkey = str(data.get("social_login_appkey") or "").strip()
providers = parse_providers(data.get("social_login_providers"))
try:
endpoint = normalize_social_endpoint(endpoint_raw)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
old_config = database.get_system_config() or {}
old_key = str(old_config.get("social_login_appkey") or "")
if enabled:
if not providers:
return jsonify({"error": "启用聚合登录时至少选择一种登录方式"}), 400
if not appid:
return jsonify({"error": "启用聚合登录时必须填写 APPID"}), 400
if not appkey and not old_key:
return jsonify({"error": "启用聚合登录时必须填写 APPKEY"}), 400
encrypted_key = encrypt_social_appkey(appkey) if appkey else old_key
ok = database.update_system_config(
social_login_enabled=enabled,
social_login_endpoint=endpoint,
social_login_appid=appid,
social_login_appkey=encrypted_key,
social_login_providers=",".join(providers),
)
if not ok:
return jsonify({"error": "更新失败"}), 400
logger.info(f"[social/config] updated enabled={enabled} providers={','.join(providers)} at={get_cst_now_str()}")
return jsonify({"message": "聚合登录配置已更新", "config": admin_social_config_out(database.get_system_config())})
return _inner()

306
services/social_login.py Normal file
View File

@@ -0,0 +1,306 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import re
import secrets
from dataclasses import dataclass
from typing import Any
from urllib.parse import parse_qs, urlencode, urljoin, urlparse
import requests
from crypto_utils import decrypt_password, encrypt_password
DEFAULT_SPACE_ENDPOINT = "https://www.spacezs.cn/connect.php"
PROVIDER_LABELS = {"qq": "QQ", "wx": "微信", "alipay": "支付宝"}
SUPPORTED_PROVIDERS = set(PROVIDER_LABELS)
BIND_TOKEN_TTL_SECONDS = 600
SPACE_BROWSER_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
class SocialLoginError(Exception):
def __init__(self, message: str, status_code: int = 400):
super().__init__(message)
self.message = message
self.status_code = int(status_code or 400)
@dataclass
class SpaceProfile:
provider: str
social_uid: str
nickname: str = ""
avatar_url: str = ""
def provider_label(provider: str) -> str:
return PROVIDER_LABELS.get(provider, provider)
def normalize_social_endpoint(value: str) -> str:
raw = (value or DEFAULT_SPACE_ENDPOINT).strip() or DEFAULT_SPACE_ENDPOINT
parsed = urlparse(raw)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError("聚合接口地址必须是 http 或 https 地址")
path = parsed.path.rstrip("/")
if path in {"", "/"}:
return parsed._replace(path="/connect.php", params="", query="", fragment="").geturl()
if path.endswith("/connect.php"):
return parsed._replace(path=path, params="", query="", fragment="").geturl()
raise ValueError("聚合接口地址必须指向 connect.php")
def parse_providers(value: str | list[str] | tuple[str, ...] | None) -> list[str]:
items = value.split(",") if isinstance(value, str) else list(value or [])
result: list[str] = []
for item in items:
provider = str(item or "").strip().lower()
if provider in SUPPORTED_PROVIDERS and provider not in result:
result.append(provider)
return result
def social_appkey(config: dict[str, Any]) -> str:
encrypted = str((config or {}).get("social_login_appkey") or "").strip()
if not encrypted:
return ""
return decrypt_password(encrypted)
def encrypt_social_appkey(value: str) -> str:
raw = str(value or "").strip()
return encrypt_password(raw) if raw else ""
def mask_secret(value: str) -> str:
if not value:
return ""
if len(value) <= 8:
return f"{value[:2]}***"
return f"{value[:4]}***{value[-4:]}"
def admin_social_config_out(config: dict[str, Any]) -> dict[str, Any]:
key = social_appkey(config)
return {
"social_login_enabled": int(config.get("social_login_enabled") or 0),
"social_login_endpoint": config.get("social_login_endpoint") or DEFAULT_SPACE_ENDPOINT,
"social_login_appid": config.get("social_login_appid") or "",
"social_login_appkey": "",
"social_login_appkey_configured": bool(key),
"social_login_appkey_masked": mask_secret(key),
"social_login_providers": parse_providers(config.get("social_login_providers")) or ["qq", "wx", "alipay"],
}
def public_social_config(config: dict[str, Any]) -> dict[str, Any]:
providers = parse_providers(config.get("social_login_providers"))
configured = bool(config.get("social_login_appid") and social_appkey(config))
enabled = bool(int(config.get("social_login_enabled") or 0) == 1 and configured and providers)
return {"enabled": enabled, "providers": providers if enabled else []}
def validate_social_ready(config: dict[str, Any], provider: str) -> None:
provider = str(provider or "").strip().lower()
if int(config.get("social_login_enabled") or 0) != 1:
raise SocialLoginError("聚合登录未启用", 409)
if provider not in parse_providers(config.get("social_login_providers")):
raise SocialLoginError("该登录方式未启用", 400)
if not config.get("social_login_appid") or not social_appkey(config):
raise SocialLoginError("聚合登录配置不完整", 409)
def validate_redirect_uri(redirect_uri: str, *, allowed_hosts: set[str] | None = None) -> str:
uri = str(redirect_uri or "").strip()
parsed = urlparse(uri)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise SocialLoginError("redirect_uri 必须是完整的 http/https 地址", 400)
if allowed_hosts:
host = parsed.netloc.lower()
hostname = (parsed.hostname or "").lower()
if host not in allowed_hosts and hostname not in allowed_hosts:
raise SocialLoginError("redirect_uri 域名不在允许范围内", 400)
return uri
def _space_base_url(endpoint: str) -> str:
parsed = urlparse(endpoint)
return f"{parsed.scheme}://{parsed.netloc}"
def _parse_space_json(payload: dict[str, Any], error_message: str) -> dict[str, Any]:
try:
code_value = int(payload.get("code"))
except (TypeError, ValueError):
code_value = -1
if code_value != 0:
raise SocialLoginError(str(payload.get("msg") or error_message), 409)
return payload
def parse_space_scan_page(html: str) -> tuple[str, str]:
qrcode_match = re.search(r'var\s+qrcode_url\s*=\s*["\']([^"\']+)["\']', html or "")
state_match = re.search(r'var\s+state\s*=\s*["\']([^"\']+)["\']', html or "")
qrcode_url = qrcode_match.group(1).strip() if qrcode_match else ""
state = state_match.group(1).strip() if state_match else ""
if not qrcode_url or not state:
raise SocialLoginError("聚合登录扫码页面解析失败", 409)
return qrcode_url, state
def fetch_social_login_url(
config: dict[str, Any],
*,
provider: str,
mode: str,
redirect_uri: str,
allowed_hosts: set[str] | None = None,
) -> dict[str, Any]:
provider = str(provider or "").strip().lower()
mode = "bind" if str(mode or "").strip().lower() == "bind" else "login"
validate_social_ready(config, provider)
redirect_uri = validate_redirect_uri(redirect_uri, allowed_hosts=allowed_hosts)
endpoint = normalize_social_endpoint(str(config.get("social_login_endpoint") or ""))
params = {
"act": "login",
"appid": str(config.get("social_login_appid") or "").strip(),
"appkey": social_appkey(config),
"type": provider,
"redirect_uri": redirect_uri,
}
try:
response = requests.get(endpoint, params=params, timeout=15)
payload = response.json()
except ValueError as exc:
raise SocialLoginError("聚合接口未返回 JSON请检查 endpoint 是否为 connect.php", 409) from exc
except requests.RequestException as exc:
raise SocialLoginError("聚合接口连接失败", 409) from exc
data = _parse_space_json(payload, "获取聚合登录地址失败")
url = str(data.get("url") or "")
if not url:
raise SocialLoginError("聚合接口未返回授权地址", 409)
result = {
"provider": provider,
"mode": mode,
"url": url,
"qrcode": str(data.get("qrcode") or ""),
"scan_url": "",
"scan_state": "",
"scan_poll_interval": 2,
}
if provider == "wx":
scan_page_url = result["qrcode"] or result["url"]
try:
scan_response = requests.get(scan_page_url, headers=SPACE_BROWSER_HEADERS, timeout=15)
except requests.RequestException as exc:
raise SocialLoginError("微信扫码页获取失败", 409) from exc
scan_url, state = parse_space_scan_page(scan_response.text)
result["scan_url"] = scan_url
result["scan_state"] = state
return result
def poll_social_scan(config: dict[str, Any], *, provider: str, state: str) -> dict[str, Any]:
provider = str(provider or "").strip().lower()
validate_social_ready(config, provider)
if provider != "wx":
raise SocialLoginError("该登录方式不需要轮询", 400)
state_value = str(state or "").strip()
if not state_value:
raise SocialLoginError("缺少扫码状态", 400)
endpoint = normalize_social_endpoint(str(config.get("social_login_endpoint") or ""))
base_url = _space_base_url(endpoint)
ajax_url = urljoin(base_url + "/", "ajax.php")
headers = {
**SPACE_BROWSER_HEADERS,
"Referer": f"{base_url}/jump.php?state={state_value}&client=1",
"X-Requested-With": "XMLHttpRequest",
"Accept": "application/json, text/javascript, */*; q=0.01",
}
try:
response = requests.get(ajax_url, params={"act": "login", "state": state_value}, headers=headers, timeout=10)
payload = response.json()
except ValueError as exc:
raise SocialLoginError("扫码轮询未返回 JSON", 409) from exc
except requests.RequestException as exc:
raise SocialLoginError("扫码轮询连接失败", 409) from exc
try:
code_value = int(payload.get("code"))
except (TypeError, ValueError):
code_value = -1
if code_value == 1:
return {"status": "pending"}
if code_value == 0:
url = str(payload.get("url") or "")
if not url:
raise SocialLoginError("扫码成功但未返回回调地址", 409)
return {"status": "authorized", "url": url}
raise SocialLoginError(str(payload.get("msg") or "扫码登录失败"), 409)
def _profile_uid(payload: dict[str, Any]) -> str:
for key in ("social_uid", "uid", "openid", "unionid", "id"):
value = str(payload.get(key) or "").strip()
if value:
return value
raise SocialLoginError("聚合回调未返回用户唯一标识", 409)
def fetch_space_profile(config: dict[str, Any], *, provider: str, code: str) -> SpaceProfile:
provider = str(provider or "").strip().lower()
code_value = str(code or "").strip()
validate_social_ready(config, provider)
if not code_value:
raise SocialLoginError("缺少授权 code", 400)
endpoint = normalize_social_endpoint(str(config.get("social_login_endpoint") or ""))
params = {
"act": "callback",
"appid": str(config.get("social_login_appid") or "").strip(),
"appkey": social_appkey(config),
"type": provider,
"code": code_value,
}
try:
response = requests.get(endpoint, params=params, timeout=15)
payload = response.json()
except ValueError as exc:
raise SocialLoginError("聚合回调接口未返回 JSON", 409) from exc
except requests.RequestException as exc:
raise SocialLoginError("聚合回调接口连接失败", 409) from exc
data = _parse_space_json(payload, "聚合登录回调失败")
return SpaceProfile(
provider=provider,
social_uid=_profile_uid(data),
nickname=str(data.get("nickname") or data.get("nick") or data.get("name") or ""),
avatar_url=str(data.get("faceimg") or data.get("avatar") or data.get("avatar_url") or ""),
)
def callback_mode_from_redirect_query(redirect_url: str, fallback: str = "login") -> str:
parsed = urlparse(redirect_url)
mode = parse_qs(parsed.query).get("mode", [fallback])[0]
return "bind" if mode == "bind" else "login"
def append_query(url: str, values: dict[str, str]) -> str:
parsed = urlparse(url)
query = parse_qs(parsed.query)
for key, value in values.items():
query[key] = [value]
return parsed._replace(query=urlencode(query, doseq=True)).geturl()
def new_bind_token() -> str:
return secrets.token_urlsafe(32)

View File

@@ -1,42 +1,42 @@
{ {
"_MetricGrid-BnihYB_8.js": { "_MetricGrid-BR486o_b.css": {
"file": "assets/MetricGrid-BnihYB_8.js", "file": "assets/MetricGrid-BR486o_b.css",
"src": "_MetricGrid-BR486o_b.css"
},
"_MetricGrid-C3Xjc9mZ.js": {
"file": "assets/MetricGrid-C3Xjc9mZ.js",
"name": "MetricGrid", "name": "MetricGrid",
"imports": [ "imports": [
"index.html", "index.html",
"_vendor-vue-CVxSw_oJ.js" "_vendor-vue-CVxSw_oJ.js"
], ],
"css": [ "css": [
"assets/MetricGrid-yP_dkP6X.css" "assets/MetricGrid-BR486o_b.css"
] ]
}, },
"_MetricGrid-yP_dkP6X.css": { "_email-Mh1SHQbX.js": {
"file": "assets/MetricGrid-yP_dkP6X.css", "file": "assets/email-Mh1SHQbX.js",
"src": "_MetricGrid-yP_dkP6X.css"
},
"_email-px7YBG2O.js": {
"file": "assets/email-px7YBG2O.js",
"name": "email", "name": "email",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_system-ZDPnxnIu.js": { "_system-CYbWdReq.js": {
"file": "assets/system-ZDPnxnIu.js", "file": "assets/system-CYbWdReq.js",
"name": "system", "name": "system",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_tasks-Bep0SUyu.js": { "_tasks-B7oNpIBD.js": {
"file": "assets/tasks-Bep0SUyu.js", "file": "assets/tasks-B7oNpIBD.js",
"name": "tasks", "name": "tasks",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_users-te9ySk34.js": { "_users-DzDcz9C_.js": {
"file": "assets/users-te9ySk34.js", "file": "assets/users-DzDcz9C_.js",
"name": "users", "name": "users",
"imports": [ "imports": [
"index.html" "index.html"
@@ -73,7 +73,7 @@
"name": "vendor-vue" "name": "vendor-vue"
}, },
"index.html": { "index.html": {
"file": "assets/index-C1f9ticl.js", "file": "assets/index-DOvMEmc8.js",
"name": "index", "name": "index",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
@@ -95,11 +95,11 @@
"src/pages/SettingsPage.vue" "src/pages/SettingsPage.vue"
], ],
"css": [ "css": [
"assets/index-Dh0BbqTX.css" "assets/index-CPs_XZ2s.css"
] ]
}, },
"src/pages/AnnouncementsPage.vue": { "src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-C6UgwLIT.js", "file": "assets/AnnouncementsPage-Dagm5PzE.js",
"name": "AnnouncementsPage", "name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue", "src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -111,52 +111,52 @@
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/AnnouncementsPage-DOwZaaOu.css" "assets/AnnouncementsPage-tpO97PUg.css"
] ]
}, },
"src/pages/EmailPage.vue": { "src/pages/EmailPage.vue": {
"file": "assets/EmailPage-CATruPK6.js", "file": "assets/EmailPage-DiZA9Kx_.js",
"name": "EmailPage", "name": "EmailPage",
"src": "src/pages/EmailPage.vue", "src": "src/pages/EmailPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_email-px7YBG2O.js", "_email-Mh1SHQbX.js",
"index.html", "index.html",
"_MetricGrid-BnihYB_8.js", "_MetricGrid-C3Xjc9mZ.js",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/EmailPage-BmPCDPYC.css" "assets/EmailPage-CTHxGzDv.css"
] ]
}, },
"src/pages/FeedbacksPage.vue": { "src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-BAnFKHSL.js", "file": "assets/FeedbacksPage-DrMVqBKf.js",
"name": "FeedbacksPage", "name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue", "src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-BnihYB_8.js", "_MetricGrid-C3Xjc9mZ.js",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/FeedbacksPage-mrXjCiV2.css" "assets/FeedbacksPage-CPmSqIaj.css"
] ]
}, },
"src/pages/LogsPage.vue": { "src/pages/LogsPage.vue": {
"file": "assets/LogsPage-DFPeq0bL.js", "file": "assets/LogsPage-Cy6Q0ave.js",
"name": "LogsPage", "name": "LogsPage",
"src": "src/pages/LogsPage.vue", "src": "src/pages/LogsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-te9ySk34.js", "_users-DzDcz9C_.js",
"_tasks-Bep0SUyu.js", "_tasks-B7oNpIBD.js",
"index.html", "index.html",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
@@ -164,48 +164,48 @@
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/LogsPage-D1bozCEo.css" "assets/LogsPage-BUgx3sZr.css"
] ]
}, },
"src/pages/ReportPage.vue": { "src/pages/ReportPage.vue": {
"file": "assets/ReportPage-fzVH-d9u.js", "file": "assets/ReportPage-BMEJM5Hr.js",
"name": "ReportPage", "name": "ReportPage",
"src": "src/pages/ReportPage.vue", "src": "src/pages/ReportPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"index.html", "index.html",
"_email-px7YBG2O.js", "_email-Mh1SHQbX.js",
"_tasks-Bep0SUyu.js", "_tasks-B7oNpIBD.js",
"_system-ZDPnxnIu.js", "_system-CYbWdReq.js",
"_MetricGrid-BnihYB_8.js", "_MetricGrid-C3Xjc9mZ.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-misc-BeoNyvBp.js", "_vendor-misc-BeoNyvBp.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/ReportPage-BCQBCnjY.css" "assets/ReportPage-IaDpUFfl.css"
] ]
}, },
"src/pages/SecurityPage.vue": { "src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-xwMQfhuh.js", "file": "assets/SecurityPage-yzYEGeTN.js",
"name": "SecurityPage", "name": "SecurityPage",
"src": "src/pages/SecurityPage.vue", "src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-BnihYB_8.js", "_MetricGrid-C3Xjc9mZ.js",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/SecurityPage-DN76ndc_.css" "assets/SecurityPage-C2-mJ7eD.css"
] ]
}, },
"src/pages/SettingsPage.vue": { "src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-DRqlQLxJ.js", "file": "assets/SettingsPage-DF5fL8gq.js",
"name": "SettingsPage", "name": "SettingsPage",
"src": "src/pages/SettingsPage.vue", "src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -217,16 +217,16 @@
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/SettingsPage-BAa-Qu3q.css" "assets/SettingsPage-D-iYz1zh.css"
] ]
}, },
"src/pages/SystemPage.vue": { "src/pages/SystemPage.vue": {
"file": "assets/SystemPage-D3eBPCNe.js", "file": "assets/SystemPage-DrM9-RI5.js",
"name": "SystemPage", "name": "SystemPage",
"src": "src/pages/SystemPage.vue", "src": "src/pages/SystemPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_system-ZDPnxnIu.js", "_system-CYbWdReq.js",
"index.html", "index.html",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
@@ -234,16 +234,16 @@
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/SystemPage-BhhEz4Qz.css" "assets/SystemPage-CTs6qr36.css"
] ]
}, },
"src/pages/UsersPage.vue": { "src/pages/UsersPage.vue": {
"file": "assets/UsersPage-DJZUCpfb.js", "file": "assets/UsersPage-RI5S3snx.js",
"name": "UsersPage", "name": "UsersPage",
"src": "src/pages/UsersPage.vue", "src": "src/pages/UsersPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-te9ySk34.js", "_users-DzDcz9C_.js",
"index.html", "index.html",
"_vendor-element-B5S5pUKo.js", "_vendor-element-B5S5pUKo.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
@@ -251,7 +251,7 @@
"_vendor-misc-BeoNyvBp.js" "_vendor-misc-BeoNyvBp.js"
], ],
"css": [ "css": [
"assets/UsersPage-BNDnhJe0.css" "assets/UsersPage-CgYh6JHW.css"
] ]
} }
} }

View File

@@ -1 +0,0 @@
.page-stack[data-v-6f55521c]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-6f55521c]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-6f55521c]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-6f55521c]{margin-top:10px;font-size:12px;color:var(--app-muted)}.image-preview[data-v-6f55521c]{margin:6px 0 2px;display:flex;justify-content:flex-start}.image-preview img[data-v-6f55521c]{max-width:280px;max-height:160px;border-radius:8px;border:1px solid var(--app-border);object-fit:contain}.image-upload-row[data-v-6f55521c]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.image-input[data-v-6f55521c]{display:none}.image-url[data-v-6f55521c]{font-size:12px;color:var(--app-muted);word-break:break-all}.announcement-view[data-v-6f55521c]{display:flex;flex-direction:column;gap:12px}.announcement-view-text[data-v-6f55521c]{white-space:pre-wrap;line-height:1.6;font-size:14px}.announcement-view-image[data-v-6f55521c]{max-width:100%;max-height:320px;border-radius:10px;border:1px solid var(--app-border);object-fit:contain}.table-wrap[data-v-6f55521c]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-6f55521c]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-6f55521c]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -0,0 +1 @@
.page-stack[data-v-faabe575]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-faabe575]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-faabe575]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-faabe575]{margin-top:10px;font-size:12px;color:var(--app-muted)}.image-preview[data-v-faabe575]{margin:6px 0 2px;display:flex;justify-content:flex-start}.image-preview img[data-v-faabe575]{max-width:280px;max-height:160px;border-radius:8px;border:1px solid var(--app-border);object-fit:contain}.image-upload-row[data-v-faabe575]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.image-input[data-v-faabe575]{display:none}.image-url[data-v-faabe575]{font-size:12px;color:var(--app-muted);word-break:break-all}.announcement-view[data-v-faabe575]{display:flex;flex-direction:column;gap:12px}.announcement-view-text[data-v-faabe575]{white-space:pre-wrap;line-height:1.6;font-size:14px}.announcement-view-image[data-v-faabe575]{max-width:100%;max-height:320px;border-radius:10px;border:1px solid var(--app-border);object-fit:contain}.table-wrap[data-v-faabe575]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-faabe575]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-faabe575]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -1 +0,0 @@
.page-stack[data-v-4f511165]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-4f511165]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-4f511165]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-head[data-v-4f511165]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-4f511165]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-4f511165]{margin-top:8px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-4f511165]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.sub-stats[data-v-4f511165]{margin-top:12px}.ellipsis[data-v-4f511165]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-4f511165]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-4f511165]{font-size:12px}.dialog-actions[data-v-4f511165]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.spacer[data-v-4f511165]{flex:1}

View File

@@ -0,0 +1 @@
.page-stack[data-v-b1cb7390]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-b1cb7390]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-b1cb7390]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-head[data-v-b1cb7390]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-b1cb7390]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-b1cb7390]{margin-top:8px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-b1cb7390]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.sub-stats[data-v-b1cb7390]{margin-top:12px}.ellipsis[data-v-b1cb7390]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-b1cb7390]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-b1cb7390]{font-size:12px}.dialog-actions[data-v-b1cb7390]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.spacer[data-v-b1cb7390]{flex:1}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.page-stack[data-v-2e7ce230]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-2e7ce230]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-2e7ce230]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-head[data-v-2e7ce230]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-2e7ce230]{margin:0;font-size:15px;font-weight:800}.table-wrap[data-v-2e7ce230]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-2e7ce230]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-2e7ce230]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -1 +0,0 @@
.page-stack[data-v-910fe89b]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-910fe89b]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-910fe89b]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-head[data-v-910fe89b]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-910fe89b]{margin:0;font-size:15px;font-weight:800}.table-wrap[data-v-910fe89b]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-910fe89b]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-910fe89b]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -0,0 +1 @@
.page-stack[data-v-2627b2c9]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-2627b2c9]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.filters[data-v-2627b2c9]{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.table-wrap[data-v-2627b2c9]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-2627b2c9]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-2627b2c9]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-2627b2c9]{font-size:12px}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-8803eb08]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-8803eb08]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.filters[data-v-8803eb08]{display:flex;flex-wrap:wrap;gap:10px;align-items:center}.table-wrap[data-v-8803eb08]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-8803eb08]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-8803eb08]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-8803eb08]{font-size:12px}

View File

@@ -1 +1 @@
.metric-grid[data-v-00e217d4]{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--metric-min),1fr));gap:12px}.metric-card[data-v-00e217d4]{position:relative;overflow:hidden;border-radius:14px;border:1px solid var(--app-border);background:linear-gradient(180deg,#fffffffa,#fafcffe6);box-shadow:var(--app-shadow-soft);padding:13px 14px;min-height:104px}.metric-card[data-v-00e217d4]:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--metric-top, #3b82f6)}.metric-top[data-v-00e217d4]{display:flex;align-items:center;gap:8px}.metric-icon[data-v-00e217d4]{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:var(--metric-icon-bg, rgba(59, 130, 246, .12));color:var(--metric-icon-color, #1d4ed8)}.metric-label[data-v-00e217d4]{font-size:12px;color:#475569;font-weight:700;line-height:1.4}.metric-value[data-v-00e217d4]{margin-top:10px;font-size:26px;line-height:1.05;font-weight:900;color:#0f172a}.metric-hint[data-v-00e217d4]{margin-top:8px;font-size:12px;line-height:1.4}.metric-tone--blue[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #3b82f6, #06b6d4);--metric-icon-bg: rgba(59, 130, 246, .14);--metric-icon-color: #1d4ed8}.metric-tone--green[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #10b981, #22c55e);--metric-icon-bg: rgba(16, 185, 129, .14);--metric-icon-color: #047857}.metric-tone--purple[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #8b5cf6, #ec4899);--metric-icon-bg: rgba(139, 92, 246, .14);--metric-icon-color: #6d28d9}.metric-tone--orange[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #f59e0b, #f97316);--metric-icon-bg: rgba(245, 158, 11, .14);--metric-icon-color: #b45309}.metric-tone--red[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #ef4444, #f43f5e);--metric-icon-bg: rgba(239, 68, 68, .14);--metric-icon-color: #b91c1c}.metric-tone--cyan[data-v-00e217d4]{--metric-top: linear-gradient(90deg, #06b6d4, #3b82f6);--metric-icon-bg: rgba(6, 182, 212, .14);--metric-icon-color: #0e7490}@media(max-width:768px){.metric-grid[data-v-00e217d4]{grid-template-columns:repeat(2,minmax(0,1fr))}.metric-card[data-v-00e217d4]{min-height:96px}.metric-value[data-v-00e217d4]{font-size:22px}}@media(max-width:480px){.metric-grid[data-v-00e217d4]{grid-template-columns:1fr}} .metric-grid[data-v-28727c73]{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--metric-min),1fr));gap:12px}.metric-card[data-v-28727c73]{position:relative;overflow:hidden;border-radius:14px;border:1px solid var(--app-border);background:linear-gradient(180deg,#fffffffa,#fafcffe6);box-shadow:var(--app-shadow-soft);padding:13px 14px;min-height:104px}.metric-card[data-v-28727c73]:before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--metric-top, #3b82f6)}.metric-top[data-v-28727c73]{display:flex;align-items:center;gap:8px}.metric-icon[data-v-28727c73]{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:var(--metric-icon-bg, rgba(59, 130, 246, .12));color:var(--metric-icon-color, #1d4ed8)}.metric-label[data-v-28727c73]{font-size:12px;color:#475569;font-weight:700;line-height:1.4}.metric-value[data-v-28727c73]{margin-top:10px;font-size:26px;line-height:1.05;font-weight:900;color:#0f172a}.metric-hint[data-v-28727c73]{margin-top:8px;font-size:12px;line-height:1.4}.metric-tone--blue[data-v-28727c73]{--metric-top: linear-gradient(90deg, #3b82f6, #06b6d4);--metric-icon-bg: rgba(59, 130, 246, .14);--metric-icon-color: #1d4ed8}.metric-tone--green[data-v-28727c73]{--metric-top: linear-gradient(90deg, #10b981, #22c55e);--metric-icon-bg: rgba(16, 185, 129, .14);--metric-icon-color: #047857}.metric-tone--purple[data-v-28727c73]{--metric-top: linear-gradient(90deg, #8b5cf6, #ec4899);--metric-icon-bg: rgba(139, 92, 246, .14);--metric-icon-color: #6d28d9}.metric-tone--orange[data-v-28727c73]{--metric-top: linear-gradient(90deg, #f59e0b, #f97316);--metric-icon-bg: rgba(245, 158, 11, .14);--metric-icon-color: #b45309}.metric-tone--red[data-v-28727c73]{--metric-top: linear-gradient(90deg, #ef4444, #f43f5e);--metric-icon-bg: rgba(239, 68, 68, .14);--metric-icon-color: #b91c1c}.metric-tone--cyan[data-v-28727c73]{--metric-top: linear-gradient(90deg, #06b6d4, #3b82f6);--metric-icon-bg: rgba(6, 182, 212, .14);--metric-icon-color: #0e7490}@media(max-width:768px){.metric-grid[data-v-28727c73]{grid-template-columns:repeat(2,minmax(0,1fr))}.metric-card[data-v-28727c73]{min-height:96px}.metric-value[data-v-28727c73]{font-size:22px}}@media(max-width:480px){.metric-grid[data-v-28727c73]{grid-template-columns:1fr}}

View File

@@ -1 +1 @@
import{_}from"./index-C1f9ticl.js";import{aj as c,n as s,q as t,K as r,a3 as u,y as p,t as o,G as l,L as y,E as h,D as i,H as v,J as n,I as k,x as f}from"./vendor-vue-CVxSw_oJ.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,D)=>{const d=c("el-icon"),m=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(v(e.icon)))]),_:2},1024)])):l("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},w=_(N,[["__scopeId","data-v-00e217d4"]]);export{w as M}; import{_}from"./index-DOvMEmc8.js";import{aj as c,n as s,q as t,K as r,a3 as u,y as p,t as o,G as l,L as y,E as h,D as i,H as v,J as n,I as k,x as f}from"./vendor-vue-CVxSw_oJ.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,D)=>{const d=c("el-icon"),m=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(v(e.icon)))]),_:2},1024)])):l("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},w=_(N,[["__scopeId","data-v-28727c73"]]);export{w as M};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.page-stack[data-v-c89794eb]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-c89794eb]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-c89794eb]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.sub-card[data-v-c89794eb]{margin-top:12px;border-radius:var(--app-radius);border:1px solid var(--app-border)}.filters[data-v-c89794eb]{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:12px}.table-wrap[data-v-c89794eb]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-c89794eb]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mono[data-v-c89794eb]{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.pagination[data-v-c89794eb]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-c89794eb]{font-size:12px}.inner-tabs[data-v-c89794eb]{margin-top:6px}.risk-head[data-v-c89794eb]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.risk-title[data-v-c89794eb]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dialog-actions[data-v-c89794eb]{display:flex;align-items:center;gap:10px}.spacer[data-v-c89794eb]{flex:1}

View File

@@ -1 +0,0 @@
.page-stack[data-v-94e0bde6]{display:flex;flex-direction:column;gap:14px;min-width:0}.toolbar[data-v-94e0bde6]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-94e0bde6]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.sub-card[data-v-94e0bde6]{margin-top:12px;border-radius:var(--app-radius);border:1px solid var(--app-border)}.filters[data-v-94e0bde6]{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:12px}.table-wrap[data-v-94e0bde6]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.ellipsis[data-v-94e0bde6]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mono[data-v-94e0bde6]{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.pagination[data-v-94e0bde6]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-94e0bde6]{font-size:12px}.inner-tabs[data-v-94e0bde6]{margin-top:6px}.risk-head[data-v-94e0bde6]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.risk-title[data-v-94e0bde6]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dialog-actions[data-v-94e0bde6]{display:flex;align-items:center;gap:10px}.spacer[data-v-94e0bde6]{flex:1}

View File

@@ -1 +0,0 @@
.page-stack[data-v-fb202365]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-fb202365]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-fb202365]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-fb202365]{margin-top:10px;font-size:12px;color:var(--app-muted)}.help-alert[data-v-fb202365]{margin-bottom:12px}

View File

@@ -0,0 +1 @@
.page-stack[data-v-1418d488]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-1418d488]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-1418d488]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-1418d488]{margin-top:10px;font-size:12px;color:var(--app-muted)}.help-alert[data-v-1418d488]{margin-bottom:12px}

View File

@@ -1 +0,0 @@
.page-stack[data-v-ca313705]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-ca313705]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-ca313705]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-ca313705]{min-width:0}.section-title[data-v-ca313705]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-ca313705]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-ca313705]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-ca313705]{font-size:12px;display:inline-flex;align-items:center;gap:6px}.status-chip[data-v-ca313705]{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;font-size:12px;font-weight:700;border:1px solid transparent}.status-chip.is-checking[data-v-ca313705]{color:#1d4ed8;background:#dbeafe;border-color:#93c5fd}.status-chip.is-online[data-v-ca313705]{color:#065f46;background:#d1fae5;border-color:#6ee7b7}.status-chip.is-offline[data-v-ca313705]{color:#92400e;background:#fef3c7;border-color:#fcd34d}.status-chip.is-error[data-v-ca313705]{color:#991b1b;background:#fee2e2;border-color:#fca5a5}.status-chip.is-unknown[data-v-ca313705]{color:#374151;background:#f3f4f6;border-color:#d1d5db}.status-dots[data-v-ca313705]{display:inline-flex;align-items:center;gap:3px;margin-left:3px}.status-dots i[data-v-ca313705]{width:4px;height:4px;border-radius:50%;background:currentColor;opacity:.25;animation:dotPulse-ca313705 1.2s infinite ease-in-out}.status-dots i[data-v-ca313705]:nth-child(2){animation-delay:.2s}.status-dots i[data-v-ca313705]:nth-child(3){animation-delay:.4s}@keyframes dotPulse-ca313705{0%,80%,to{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-1px)}}.kdocs-form[data-v-ca313705]{margin-top:6px}.kdocs-inline[data-v-ca313705]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-ca313705]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-ca313705]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-ca313705]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-ca313705]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-ca313705]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-ca313705]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:768px){.config-grid[data-v-ca313705],.kdocs-inline[data-v-ca313705]{grid-template-columns:1fr}.kdocs-range[data-v-ca313705]{align-items:stretch}}

View File

@@ -0,0 +1 @@
.page-stack[data-v-3820d834]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-3820d834]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-3820d834]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-3820d834]{min-width:0}.section-title[data-v-3820d834]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-3820d834]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-3820d834]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-3820d834]{font-size:12px;display:inline-flex;align-items:center;gap:6px}.status-chip[data-v-3820d834]{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;font-size:12px;font-weight:700;border:1px solid transparent}.status-chip.is-checking[data-v-3820d834]{color:#1d4ed8;background:#dbeafe;border-color:#93c5fd}.status-chip.is-online[data-v-3820d834]{color:#065f46;background:#d1fae5;border-color:#6ee7b7}.status-chip.is-offline[data-v-3820d834]{color:#92400e;background:#fef3c7;border-color:#fcd34d}.status-chip.is-error[data-v-3820d834]{color:#991b1b;background:#fee2e2;border-color:#fca5a5}.status-chip.is-unknown[data-v-3820d834]{color:#374151;background:#f3f4f6;border-color:#d1d5db}.status-dots[data-v-3820d834]{display:inline-flex;align-items:center;gap:3px;margin-left:3px}.status-dots i[data-v-3820d834]{width:4px;height:4px;border-radius:50%;background:currentColor;opacity:.25;animation:dotPulse-3820d834 1.2s infinite ease-in-out}.status-dots i[data-v-3820d834]:nth-child(2){animation-delay:.2s}.status-dots i[data-v-3820d834]:nth-child(3){animation-delay:.4s}@keyframes dotPulse-3820d834{0%,80%,to{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-1px)}}.kdocs-form[data-v-3820d834]{margin-top:6px}.kdocs-inline[data-v-3820d834]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-3820d834]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-3820d834]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-3820d834]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-3820d834]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-3820d834]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-3820d834]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1201px){.social-card[data-v-3820d834]{grid-column:span 3}}@media(max-width:768px){.config-grid[data-v-3820d834],.kdocs-inline[data-v-3820d834]{grid-template-columns:1fr}.kdocs-range[data-v-3820d834]{align-items:stretch}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-11f4f270]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-11f4f270]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-11f4f270]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-11f4f270]{margin-top:10px;font-size:12px}.table-wrap[data-v-11f4f270]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.user-block[data-v-11f4f270]{display:flex;flex-direction:column;gap:2px}.user-main[data-v-11f4f270]{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}.user-sub[data-v-11f4f270]{font-size:12px}.vip-sub[data-v-11f4f270]{font-size:12px;color:#7c3aed}.actions[data-v-11f4f270]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -0,0 +1 @@
.page-stack[data-v-3e2dc1eb]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-3e2dc1eb]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-3e2dc1eb]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-3e2dc1eb]{margin-top:10px;font-size:12px}.table-wrap[data-v-3e2dc1eb]{overflow-x:auto;border-radius:10px;border:1px solid var(--app-border);background:#fff}.user-block[data-v-3e2dc1eb]{display:flex;flex-direction:column;gap:2px}.user-main[data-v-3e2dc1eb]{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}.user-sub[data-v-3e2dc1eb]{font-size:12px}.vip-sub[data-v-3e2dc1eb]{font-size:12px;color:#7c3aed}.actions[data-v-3e2dc1eb]{display:flex;flex-wrap:wrap;gap:8px}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{c as s,a as e}from"./index-C1f9ticl.js";const n=s(async()=>{const{data:a}=await e.get("/email/stats");return a},1e4);async function i(){const{data:a}=await e.get("/email/settings");return a}async function r(a){const{data:t}=await e.post("/email/settings",a);return n.clear(),t}async function o(a={}){return n.run(a)}async function l(a){const{data:t}=await e.get("/email/logs",{params:a});return t}async function u(a){const{data:t}=await e.post("/email/logs/cleanup",{days:a});return n.clear(),t}export{l as a,i as b,u as c,o as f,r as u}; import{c as s,a as e}from"./index-DOvMEmc8.js";const n=s(async()=>{const{data:a}=await e.get("/email/stats");return a},1e4);async function i(){const{data:a}=await e.get("/email/settings");return a}async function r(a){const{data:t}=await e.post("/email/settings",a);return n.clear(),t}async function o(a={}){return n.run(a)}async function l(a){const{data:t}=await e.get("/email/logs",{params:a});return t}async function u(a){const{data:t}=await e.post("/email/logs/cleanup",{days:a});return n.clear(),t}export{l as a,i as b,u as c,o as f,r as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{c as s,a as n}from"./index-DOvMEmc8.js";const o=s(async()=>{const{data:t}=await n.get("/system/config");return t},15e3);async function i(t={}){return o.run(t)}async function e(t){const{data:a}=await n.post("/system/config",t);return o.clear(),a}async function r(){const{data:t}=await n.get("/social-login/config");return t}async function f(t){const{data:a}=await n.post("/social-login/config",t||{});return o.clear(),a}async function g(t){const{data:a}=await n.post("/social-login/test",t||{});return a}export{r as a,f as b,i as f,g as t,e as u};

View File

@@ -1 +0,0 @@
import{c as s,a}from"./index-C1f9ticl.js";const e=s(async()=>{const{data:t}=await a.get("/system/config");return t},15e3);async function o(t={}){return e.run(t)}async function r(t){const{data:n}=await a.post("/system/config",t);return e.clear(),n}export{o as f,r as u};

View File

@@ -1 +1 @@
import{c as s,a}from"./index-C1f9ticl.js";const c=s(async()=>{const{data:t}=await a.get("/server/info");return t},3e4),o=s(async()=>{const{data:t}=await a.get("/docker_stats");return t},8e3),u=s(async()=>{const{data:t}=await a.get("/request_metrics");return t},1e4),i=s(async()=>{const{data:t}=await a.get("/slow_sql_metrics");return t},1e4),e=s(async()=>{const{data:t}=await a.get("/task/stats");return t},4e3),r=s(async()=>{const{data:t}=await a.get("/task/running");return t},2e3);async function g(t={}){return c.run(t)}async function y(t={}){return o.run(t)}async function d(t={}){return u.run(t)}async function k(t={}){return i.run(t)}async function l(t={}){return e.run(t)}async function w(t={}){return r.run(t)}async function _(t){const{data:n}=await a.get("/task/logs",{params:t});return n}async function h(t){const{data:n}=await a.post("/task/logs/clear",{days:t});return e.clear(),r.clear(),n}export{w as a,g as b,y as c,d,k as e,l as f,_ as g,h}; import{c as s,a}from"./index-DOvMEmc8.js";const c=s(async()=>{const{data:t}=await a.get("/server/info");return t},3e4),o=s(async()=>{const{data:t}=await a.get("/docker_stats");return t},8e3),u=s(async()=>{const{data:t}=await a.get("/request_metrics");return t},1e4),i=s(async()=>{const{data:t}=await a.get("/slow_sql_metrics");return t},1e4),e=s(async()=>{const{data:t}=await a.get("/task/stats");return t},4e3),r=s(async()=>{const{data:t}=await a.get("/task/running");return t},2e3);async function g(t={}){return c.run(t)}async function y(t={}){return o.run(t)}async function d(t={}){return u.run(t)}async function k(t={}){return i.run(t)}async function l(t={}){return e.run(t)}async function w(t={}){return r.run(t)}async function _(t){const{data:n}=await a.get("/task/logs",{params:t});return n}async function h(t){const{data:n}=await a.post("/task/logs/clear",{days:t});return e.clear(),r.clear(),n}export{w as a,g as b,y as c,d,k as e,l as f,_ as g,h};

View File

@@ -1 +1 @@
import{a as t}from"./index-C1f9ticl.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-DOvMEmc8.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};

View File

@@ -5,15 +5,16 @@
<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-C1f9ticl.js"></script> <script type="module" crossorigin src="./assets/index-DOvMEmc8.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-vue-CVxSw_oJ.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-vue-CVxSw_oJ.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-misc-BeoNyvBp.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-misc-BeoNyvBp.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-element-B5S5pUKo.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-element-B5S5pUKo.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-axios-B9ygI19o.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-axios-B9ygI19o.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-element-C68yOrAy.css"> <link rel="stylesheet" crossorigin href="./assets/vendor-element-C68yOrAy.css">
<link rel="stylesheet" crossorigin href="./assets/index-Dh0BbqTX.css"> <link rel="stylesheet" crossorigin href="./assets/index-CPs_XZ2s.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
</body> </body>
</html> </html>

View File

@@ -1,111 +1,203 @@
{ {
"_accounts-3bM7Wy59.js": { "_SocialLoginButtons-BlVSr6Mm.js": {
"file": "assets/accounts-3bM7Wy59.js", "file": "assets/SocialLoginButtons-BlVSr6Mm.js",
"name": "accounts", "name": "SocialLoginButtons",
"imports": [ "imports": [
"_http-CdvgQxJu.js" "_base-xgxQQEpV.js",
"_el-overlay-hge8bsIn.js",
"_vendor-vue-WbiK4TmU.js",
"_auth-B5cl_nsV.js",
"_style-CEbARg1o.js",
"_http-BoPYlvwK.js"
], ],
"css": [ "css": [
"assets/accounts-D_6SYB2i.css" "assets/SocialLoginButtons-qO3SCoE7.css"
] ]
}, },
"_SocialLoginButtons-qO3SCoE7.css": {
"file": "assets/SocialLoginButtons-qO3SCoE7.css",
"src": "_SocialLoginButtons-qO3SCoE7.css"
},
"_accounts-D_6SYB2i.css": { "_accounts-D_6SYB2i.css": {
"file": "assets/accounts-D_6SYB2i.css", "file": "assets/accounts-D_6SYB2i.css",
"src": "_accounts-D_6SYB2i.css" "src": "_accounts-D_6SYB2i.css"
}, },
"_auth-CX9p6ZYg.js": { "_accounts-DzntEHJR.js": {
"file": "assets/auth-CX9p6ZYg.js", "file": "assets/accounts-DzntEHJR.js",
"name": "accounts",
"imports": [
"_http-BoPYlvwK.js"
],
"css": [
"assets/accounts-D_6SYB2i.css"
]
},
"_aria-DLpFpzDe.js": {
"file": "assets/aria-DLpFpzDe.js",
"name": "aria"
},
"_auth-B5cl_nsV.js": {
"file": "assets/auth-B5cl_nsV.js",
"name": "auth", "name": "auth",
"imports": [ "imports": [
"_http-CdvgQxJu.js" "_http-BoPYlvwK.js"
]
},
"_base-CiSqh4F9.css": {
"file": "assets/base-CiSqh4F9.css",
"src": "_base-CiSqh4F9.css"
},
"_base-xgxQQEpV.js": {
"file": "assets/base-xgxQQEpV.js",
"name": "base",
"imports": [
"_vendor-vue-WbiK4TmU.js"
],
"css": [
"assets/base-CiSqh4F9.css"
] ]
}, },
"_el-alert-B-NgiIln.css": { "_el-alert-B-NgiIln.css": {
"file": "assets/el-alert-B-NgiIln.css", "file": "assets/el-alert-B-NgiIln.css",
"src": "_el-alert-B-NgiIln.css" "src": "_el-alert-B-NgiIln.css"
}, },
"_el-alert-DB2IQLpH.js": { "_el-alert-DTUOkrAB.js": {
"file": "assets/el-alert-DB2IQLpH.js", "file": "assets/el-alert-DTUOkrAB.js",
"name": "el-alert", "name": "el-alert",
"imports": [ "imports": [
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_http-CdvgQxJu.js" "_el-button-LKkD3jQh.js",
"_el-input-BaZNy9Kg.js",
"_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js",
"_index-CoYtSGUZ.js"
], ],
"css": [ "css": [
"assets/el-alert-B-NgiIln.css" "assets/el-alert-B-NgiIln.css"
] ]
}, },
"_el-button-DF1Fi_iE.css": { "_el-button-BRDnKxwT.css": {
"file": "assets/el-button-DF1Fi_iE.css", "file": "assets/el-button-BRDnKxwT.css",
"src": "_el-button-DF1Fi_iE.css" "src": "_el-button-BRDnKxwT.css"
}, },
"_el-button-DWxIvzz-.js": { "_el-button-LKkD3jQh.js": {
"file": "assets/el-button-DWxIvzz-.js", "file": "assets/el-button-LKkD3jQh.js",
"name": "el-button", "name": "el-button",
"imports": [ "imports": [
"_vendor-vue-DxN60LNb.js" "_base-xgxQQEpV.js",
"_index-CoYtSGUZ.js",
"_vendor-vue-WbiK4TmU.js"
], ],
"css": [ "css": [
"assets/el-button-DF1Fi_iE.css" "assets/el-button-BRDnKxwT.css"
] ]
}, },
"_el-card-BqOrgVp1.css": { "_el-card-BqOrgVp1.css": {
"file": "assets/el-card-BqOrgVp1.css", "file": "assets/el-card-BqOrgVp1.css",
"src": "_el-card-BqOrgVp1.css" "src": "_el-card-BqOrgVp1.css"
}, },
"_el-card-DfVpO1U5.js": { "_el-card-CfK866jr.js": {
"file": "assets/el-card-DfVpO1U5.js", "file": "assets/el-card-CfK866jr.js",
"name": "el-card", "name": "el-card",
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_vendor-vue-DxN60LNb.js" "_vendor-vue-WbiK4TmU.js"
], ],
"css": [ "css": [
"assets/el-card-BqOrgVp1.css" "assets/el-card-BqOrgVp1.css"
] ]
}, },
"_el-overlay-Bd56Lw6C.css": { "_el-empty-B4_NEFfq.js": {
"file": "assets/el-overlay-Bd56Lw6C.css", "file": "assets/el-empty-B4_NEFfq.js",
"src": "_el-overlay-Bd56Lw6C.css" "name": "el-empty",
},
"_el-overlay-C_JJBVfE.js": {
"file": "assets/el-overlay-C_JJBVfE.js",
"name": "el-overlay",
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_http-CdvgQxJu.js", "_el-input-BaZNy9Kg.js",
"_vendor-vue-DxN60LNb.js" "_http-BoPYlvwK.js",
"_vendor-vue-WbiK4TmU.js",
"_aria-DLpFpzDe.js",
"_el-overlay-hge8bsIn.js",
"_index-CoYtSGUZ.js",
"_el-button-LKkD3jQh.js"
], ],
"css": [ "css": [
"assets/el-overlay-Bd56Lw6C.css" "assets/el-empty-D4G4LZ50.css"
] ]
}, },
"_el-empty-D4G4LZ50.css": {
"file": "assets/el-empty-D4G4LZ50.css",
"src": "_el-empty-D4G4LZ50.css"
},
"_el-input-BaZNy9Kg.js": {
"file": "assets/el-input-BaZNy9Kg.js",
"name": "el-input",
"imports": [
"_base-xgxQQEpV.js",
"_vendor-vue-WbiK4TmU.js",
"_http-BoPYlvwK.js",
"_aria-DLpFpzDe.js",
"_el-button-LKkD3jQh.js"
],
"css": [
"assets/el-input-D6B3r8CH.css"
]
},
"_el-input-D6B3r8CH.css": {
"file": "assets/el-input-D6B3r8CH.css",
"src": "_el-input-D6B3r8CH.css"
},
"_el-overlay-hge8bsIn.js": {
"file": "assets/el-overlay-hge8bsIn.js",
"name": "el-overlay",
"imports": [
"_vendor-vue-WbiK4TmU.js",
"_base-xgxQQEpV.js",
"_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js",
"_index-CoYtSGUZ.js"
],
"css": [
"assets/el-overlay-hkg5a9kt.css"
]
},
"_el-overlay-hkg5a9kt.css": {
"file": "assets/el-overlay-hkg5a9kt.css",
"src": "_el-overlay-hkg5a9kt.css"
},
"_el-pagination-B1FwbX1n.css": { "_el-pagination-B1FwbX1n.css": {
"file": "assets/el-pagination-B1FwbX1n.css", "file": "assets/el-pagination-B1FwbX1n.css",
"src": "_el-pagination-B1FwbX1n.css" "src": "_el-pagination-B1FwbX1n.css"
}, },
"_el-pagination-BY1uI-wO.js": { "_el-pagination-kVJ2XlAP.js": {
"file": "assets/el-pagination-BY1uI-wO.js", "file": "assets/el-pagination-kVJ2XlAP.js",
"name": "el-pagination", "name": "el-pagination",
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_el-select-B0VMg2td.js", "_el-select-CBs1QjJm.js",
"_http-CdvgQxJu.js" "_http-BoPYlvwK.js",
"_el-input-BaZNy9Kg.js",
"_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js"
], ],
"css": [ "css": [
"assets/el-pagination-B1FwbX1n.css" "assets/el-pagination-B1FwbX1n.css"
] ]
}, },
"_el-select-B0VMg2td.js": { "_el-select-CBs1QjJm.js": {
"file": "assets/el-select-B0VMg2td.js", "file": "assets/el-select-CBs1QjJm.js",
"name": "el-select", "name": "el-select",
"imports": [ "imports": [
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_el-overlay-C_JJBVfE.js", "_el-empty-B4_NEFfq.js",
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_http-CdvgQxJu.js" "_aria-DLpFpzDe.js",
"_el-input-BaZNy9Kg.js",
"_index-CoYtSGUZ.js",
"_http-BoPYlvwK.js",
"_el-button-LKkD3jQh.js",
"_el-overlay-hge8bsIn.js"
], ],
"css": [ "css": [
"assets/el-select-D_oyzAZN.css" "assets/el-select-D_oyzAZN.css"
@@ -115,40 +207,56 @@
"file": "assets/el-select-D_oyzAZN.css", "file": "assets/el-select-D_oyzAZN.css",
"src": "_el-select-D_oyzAZN.css" "src": "_el-select-D_oyzAZN.css"
}, },
"_http-CdvgQxJu.js": { "_el-skeleton-item-BLY1jEuR.css": {
"file": "assets/http-CdvgQxJu.js", "file": "assets/el-skeleton-item-BLY1jEuR.css",
"name": "http", "src": "_el-skeleton-item-BLY1jEuR.css"
},
"_el-skeleton-item-CD5Idavp.js": {
"file": "assets/el-skeleton-item-CD5Idavp.js",
"name": "el-skeleton-item",
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js"
"_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/http-D6B3r8CH.css" "assets/el-skeleton-item-BLY1jEuR.css"
] ]
}, },
"_http-D6B3r8CH.css": { "_http-BoPYlvwK.js": {
"file": "assets/http-D6B3r8CH.css", "file": "assets/http-BoPYlvwK.js",
"src": "_http-D6B3r8CH.css" "name": "http",
"imports": [
"_vendor-vue-WbiK4TmU.js",
"_base-xgxQQEpV.js",
"_vendor-axios-B9ygI19o.js"
]
}, },
"_isArrayLikeObject-BjIRF-cS.js": { "_index-CoYtSGUZ.js": {
"file": "assets/isArrayLikeObject-BjIRF-cS.js", "file": "assets/index-CoYtSGUZ.js",
"name": "index",
"imports": [
"_base-xgxQQEpV.js",
"_vendor-vue-WbiK4TmU.js"
]
},
"_isArrayLikeObject-B5fs56rA.js": {
"file": "assets/isArrayLikeObject-B5fs56rA.js",
"name": "isArrayLikeObject", "name": "isArrayLikeObject",
"imports": [ "imports": [
"_http-CdvgQxJu.js", "_el-input-BaZNy9Kg.js",
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-overlay-C_JJBVfE.js" "_el-empty-B4_NEFfq.js"
] ]
}, },
"_password-7ryi82gE.js": { "_password-7ryi82gE.js": {
"file": "assets/password-7ryi82gE.js", "file": "assets/password-7ryi82gE.js",
"name": "password" "name": "password"
}, },
"_settings-Ddo8isuv.js": { "_settings-Db4PmPGC.js": {
"file": "assets/settings-Ddo8isuv.js", "file": "assets/settings-Db4PmPGC.js",
"name": "settings", "name": "settings",
"imports": [ "imports": [
"_http-CdvgQxJu.js" "_http-BoPYlvwK.js"
] ]
}, },
"_style-BHGuKLUF.css": { "_style-BHGuKLUF.css": {
@@ -166,15 +274,19 @@
"file": "assets/user-B7bO5p8k.css", "file": "assets/user-B7bO5p8k.css",
"src": "_user-B7bO5p8k.css" "src": "_user-B7bO5p8k.css"
}, },
"_user-Bl59IefW.js": { "_user-BlXB4Zbh.js": {
"file": "assets/user-Bl59IefW.js", "file": "assets/user-BlXB4Zbh.js",
"name": "user", "name": "user",
"imports": [ "imports": [
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_http-CdvgQxJu.js", "_el-input-BaZNy9Kg.js",
"_el-alert-DB2IQLpH.js", "_aria-DLpFpzDe.js",
"_el-overlay-C_JJBVfE.js" "_el-button-LKkD3jQh.js",
"_index-CoYtSGUZ.js",
"_el-alert-DTUOkrAB.js",
"_el-empty-B4_NEFfq.js",
"_http-BoPYlvwK.js"
], ],
"css": [ "css": [
"assets/user-B7bO5p8k.css" "assets/user-B7bO5p8k.css"
@@ -188,23 +300,24 @@
"file": "assets/vendor-realtime-CA1CrNgP.js", "file": "assets/vendor-realtime-CA1CrNgP.js",
"name": "vendor-realtime" "name": "vendor-realtime"
}, },
"_vendor-vue-DxN60LNb.js": { "_vendor-vue-WbiK4TmU.js": {
"file": "assets/vendor-vue-DxN60LNb.js", "file": "assets/vendor-vue-WbiK4TmU.js",
"name": "vendor-vue" "name": "vendor-vue"
}, },
"index.html": { "index.html": {
"file": "assets/app-BmSIJu6s.js", "file": "assets/app-CV_JALyE.js",
"name": "app", "name": "app",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-DxN60LNb.js" "_vendor-vue-WbiK4TmU.js"
], ],
"dynamicImports": [ "dynamicImports": [
"src/pages/LoginPage.vue", "src/pages/LoginPage.vue",
"src/pages/RegisterPage.vue", "src/pages/RegisterPage.vue",
"src/pages/ResetPasswordPage.vue", "src/pages/ResetPasswordPage.vue",
"src/pages/SocialBindCallbackPage.vue",
"src/pages/VerifyResultPage.vue", "src/pages/VerifyResultPage.vue",
"src/layouts/AppLayout.vue", "src/layouts/AppLayout.vue",
"src/pages/AccountsPage.vue", "src/pages/AccountsPage.vue",
@@ -213,172 +326,244 @@
] ]
}, },
"login.html": { "login.html": {
"file": "assets/login-BtMsx-ZC.js", "file": "assets/login-rQcRwu0T.js",
"name": "login", "name": "login",
"src": "login.html", "src": "login.html",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"src/pages/LoginPage.vue" "src/pages/LoginPage.vue",
"_SocialLoginButtons-BlVSr6Mm.js",
"_base-xgxQQEpV.js",
"_el-overlay-hge8bsIn.js",
"_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js",
"_vendor-axios-B9ygI19o.js",
"_index-CoYtSGUZ.js",
"_auth-B5cl_nsV.js"
] ]
}, },
"src/layouts/AppLayout.vue": { "src/layouts/AppLayout.vue": {
"file": "assets/AppLayout-xWeMM3hH.js", "file": "assets/AppLayout-D9A8Va7K.js",
"name": "AppLayout", "name": "AppLayout",
"src": "src/layouts/AppLayout.vue", "src": "src/layouts/AppLayout.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_user-Bl59IefW.js", "_user-BlXB4Zbh.js",
"_el-overlay-C_JJBVfE.js", "_el-empty-B4_NEFfq.js",
"_el-alert-DB2IQLpH.js", "_el-alert-DTUOkrAB.js",
"_http-CdvgQxJu.js", "_el-skeleton-item-CD5Idavp.js",
"_vendor-vue-DxN60LNb.js", "_el-input-BaZNy9Kg.js",
"_settings-Ddo8isuv.js", "_el-overlay-hge8bsIn.js",
"_el-button-LKkD3jQh.js",
"_vendor-vue-WbiK4TmU.js",
"_http-BoPYlvwK.js",
"_auth-B5cl_nsV.js",
"_settings-Db4PmPGC.js",
"_SocialLoginButtons-BlVSr6Mm.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_isArrayLikeObject-BjIRF-cS.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js",
"_isArrayLikeObject-B5fs56rA.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/AppLayout-DxoFHO3h.css" "assets/AppLayout-CJKAa2WS.css"
] ]
}, },
"src/pages/AccountsPage.vue": { "src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-DnOxRP7e.js", "file": "assets/AccountsPage-B7MLZrfr.js",
"name": "AccountsPage", "name": "AccountsPage",
"src": "src/pages/AccountsPage.vue", "src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-overlay-C_JJBVfE.js", "_el-overlay-hge8bsIn.js",
"_el-alert-DB2IQLpH.js", "_el-alert-DTUOkrAB.js",
"_http-CdvgQxJu.js", "_el-input-BaZNy9Kg.js",
"_user-Bl59IefW.js", "_user-BlXB4Zbh.js",
"_accounts-3bM7Wy59.js", "_accounts-DzntEHJR.js",
"_el-select-B0VMg2td.js", "_el-empty-B4_NEFfq.js",
"_el-card-DfVpO1U5.js", "_el-skeleton-item-CD5Idavp.js",
"_settings-Ddo8isuv.js", "_el-select-CBs1QjJm.js",
"_el-button-LKkD3jQh.js",
"_el-card-CfK866jr.js",
"_settings-Db4PmPGC.js",
"_http-BoPYlvwK.js",
"_vendor-realtime-CA1CrNgP.js", "_vendor-realtime-CA1CrNgP.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/AccountsPage-iiBFNme8.css" "assets/AccountsPage-DKewJ7S7.css"
] ]
}, },
"src/pages/LoginPage.vue": { "src/pages/LoginPage.vue": {
"file": "assets/LoginPage-D5iXLq7p.js", "file": "assets/LoginPage-BtooAZsk.js",
"name": "LoginPage", "name": "LoginPage",
"src": "src/pages/LoginPage.vue", "src": "src/pages/LoginPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_style-CEbARg1o.js" "_SocialLoginButtons-BlVSr6Mm.js",
"_style-CEbARg1o.js",
"_base-xgxQQEpV.js",
"_el-overlay-hge8bsIn.js",
"_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js",
"_vendor-axios-B9ygI19o.js",
"_index-CoYtSGUZ.js",
"_auth-B5cl_nsV.js"
], ],
"css": [ "css": [
"assets/LoginPage-DTj5KeC4.css" "assets/LoginPage-vCVLchWz.css"
] ]
}, },
"src/pages/RegisterPage.vue": { "src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-4xFnBJCQ.js", "file": "assets/RegisterPage-Cb1mme2j.js",
"name": "RegisterPage", "name": "RegisterPage",
"src": "src/pages/RegisterPage.vue", "src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-card-DfVpO1U5.js", "_el-card-CfK866jr.js",
"_el-alert-DB2IQLpH.js", "_el-alert-DTUOkrAB.js",
"_http-CdvgQxJu.js", "_el-button-LKkD3jQh.js",
"_vendor-vue-DxN60LNb.js", "_el-input-BaZNy9Kg.js",
"_auth-CX9p6ZYg.js", "_vendor-vue-WbiK4TmU.js",
"_auth-B5cl_nsV.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js",
"_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/RegisterPage-BOcNcW5D.css" "assets/RegisterPage-LYXwWYc1.css"
] ]
}, },
"src/pages/ResetPasswordPage.vue": { "src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage-lX7l6Nbu.js", "file": "assets/ResetPasswordPage-CUOK0fe1.js",
"name": "ResetPasswordPage", "name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue", "src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-card-DfVpO1U5.js", "_el-card-CfK866jr.js",
"_el-alert-DB2IQLpH.js", "_el-alert-DTUOkrAB.js",
"_http-CdvgQxJu.js", "_el-input-BaZNy9Kg.js",
"_vendor-vue-DxN60LNb.js", "_el-button-LKkD3jQh.js",
"_auth-CX9p6ZYg.js", "_vendor-vue-WbiK4TmU.js",
"_auth-B5cl_nsV.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js",
"_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/ResetPasswordPage-DybfLMAw.css" "assets/ResetPasswordPage-DAB63ins.css"
] ]
}, },
"src/pages/SchedulesPage.vue": { "src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-TUv7nqYq.js", "file": "assets/SchedulesPage-0TKGPmUl.js",
"name": "SchedulesPage", "name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue", "src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-overlay-C_JJBVfE.js", "_el-empty-B4_NEFfq.js",
"_el-alert-DB2IQLpH.js", "_el-overlay-hge8bsIn.js",
"_el-select-B0VMg2td.js", "_el-alert-DTUOkrAB.js",
"_user-Bl59IefW.js", "_el-select-CBs1QjJm.js",
"_accounts-3bM7Wy59.js", "_user-BlXB4Zbh.js",
"_http-CdvgQxJu.js", "_accounts-DzntEHJR.js",
"_el-pagination-BY1uI-wO.js", "_el-input-BaZNy9Kg.js",
"_el-card-DfVpO1U5.js", "_el-pagination-kVJ2XlAP.js",
"_el-card-CfK866jr.js",
"_el-skeleton-item-CD5Idavp.js",
"_el-button-LKkD3jQh.js",
"_http-BoPYlvwK.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_isArrayLikeObject-BjIRF-cS.js", "_aria-DLpFpzDe.js",
"_isArrayLikeObject-B5fs56rA.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/SchedulesPage-BIuHs5oJ.css" "assets/SchedulesPage-Dxq2ghmQ.css"
] ]
}, },
"src/pages/ScreenshotsPage.vue": { "src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-7CRd3Hlo.js", "file": "assets/ScreenshotsPage-F6GpvKGW.js",
"name": "ScreenshotsPage", "name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue", "src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-overlay-C_JJBVfE.js", "_el-overlay-hge8bsIn.js",
"_el-pagination-BY1uI-wO.js", "_el-pagination-kVJ2XlAP.js",
"_el-select-B0VMg2td.js", "_el-empty-B4_NEFfq.js",
"_http-CdvgQxJu.js", "_el-select-CBs1QjJm.js",
"_el-card-DfVpO1U5.js", "_el-input-BaZNy9Kg.js",
"_el-card-CfK866jr.js",
"_el-skeleton-item-CD5Idavp.js",
"_el-button-LKkD3jQh.js",
"_http-BoPYlvwK.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-DxN60LNb.js", "_vendor-vue-WbiK4TmU.js",
"_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/ScreenshotsPage-30dzddw-.css" "assets/ScreenshotsPage-BhLfAzHf.css"
]
},
"src/pages/SocialBindCallbackPage.vue": {
"file": "assets/SocialBindCallbackPage-DraQ_mks.js",
"name": "SocialBindCallbackPage",
"src": "src/pages/SocialBindCallbackPage.vue",
"isDynamicEntry": true,
"imports": [
"_base-xgxQQEpV.js",
"_el-card-CfK866jr.js",
"_el-skeleton-item-CD5Idavp.js",
"_vendor-vue-WbiK4TmU.js",
"_auth-B5cl_nsV.js",
"_settings-Db4PmPGC.js",
"_style-CEbARg1o.js",
"_http-BoPYlvwK.js",
"_vendor-axios-B9ygI19o.js"
],
"css": [
"assets/SocialBindCallbackPage-BZgzv_7a.css"
] ]
}, },
"src/pages/VerifyResultPage.vue": { "src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-bifpPyoE.js", "file": "assets/VerifyResultPage-BUSE4fL8.js",
"name": "VerifyResultPage", "name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue", "src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_el-button-DWxIvzz-.js", "_base-xgxQQEpV.js",
"_el-card-DfVpO1U5.js", "_el-card-CfK866jr.js",
"_vendor-vue-DxN60LNb.js", "_el-button-LKkD3jQh.js",
"_style-CEbARg1o.js" "_vendor-vue-WbiK4TmU.js",
"_style-CEbARg1o.js",
"_index-CoYtSGUZ.js"
], ],
"css": [ "css": [
"assets/VerifyResultPage-efSXaaKI.css" "assets/VerifyResultPage-BnGv8vyq.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

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{E as D}from"./el-button-DWxIvzz-.js";import{E as F}from"./el-card-DfVpO1U5.js";import{E as L,a as M,b as j}from"./el-alert-DB2IQLpH.js";import{E as q,a as c}from"./http-CdvgQxJu.js";import{f as H,g as d,h as B,i as z,j as S,q as s,s as o,u as G,o as g,k as n,c as U,l as C,m as I,t as J,x}from"./vendor-vue-DxN60LNb.js";import{g as O,f as Q,r as W}from"./auth-CX9p6ZYg.js";import{v as X}from"./password-7ryi82gE.js";import{_ as Y}from"./style-CEbARg1o.js";import"./vendor-axios-B9ygI19o.js";const Z={class:"auth-wrap"},$={class:"hint app-muted"},ee={class:"captcha-row"},ae=["src"],te={class:"actions"},se={__name:"RegisterPage",setup(le){const N=G(),a=H({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),v=d(""),h=d(""),b=d(!1),t=d(""),w=d(""),V=d(""),P=B(()=>f.value?"邮箱 *":"邮箱(可选)"),T=B(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function _(){try{const u=await O();h.value=u?.session_id||"",v.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",v.value=""}}async function K(){try{const u=await Q();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function R(){t.value="",w.value="",V.value=""}async function E(){R();const u=a.username.trim(),e=a.password,y=a.confirm_password,l=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=X(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==y){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!l){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(l&&!l.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}b.value=!0;try{const m=await W({username:u,password:e,email:l,captcha_session:h.value,captcha:i});w.value=m?.message||"注册成功",V.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 k=m?.response?.data;t.value=k?.error||"注册失败",c.error(t.value),await _()}finally{b.value=!1}}function A(){N.push("/login")}return z(async()=>{await _(),await K()}),(u,e)=>{const y=L,l=q,i=j,p=D,m=M,k=F;return g(),S("div",Z,[s(k,{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?(g(),U(y,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):C("",!0),w.value?(g(),U(y,{key:1,type:"success",closable:!1,title:w.value,description:V.value,"show-icon":"",class:"alert"},null,8,["title","description"])):C("",!0),s(m,{"label-position":"top"},{default:o(()=>[s(i,{label:"用户名 *"},{default:o(()=>[s(l,{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}),s(i,{label:"密码 *"},{default:o(()=>[s(l,{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}),s(i,{label:"确认密码 *"},{default:o(()=>[s(l,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:I(E,["enter"])},null,8,["modelValue"])]),_:1}),s(i,{label:P.value},{default:o(()=>[s(l,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",$,J(T.value),1)]),_:1},8,["label"]),s(i,{label:"验证码 *"},{default:o(()=>[n("div",ee,[s(l,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:I(E,["enter"])},null,8,["modelValue"]),v.value?(g(),S("img",{key:0,class:"captcha-img",src:v.value,alt:"验证码",title:"点击刷新",onClick:_},null,8,ae)):C("",!0),s(p,{onClick:_},{default:o(()=>[...e[7]||(e[7]=[x("刷新",-1)])]),_:1})])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:b.value,onClick:E},{default:o(()=>[...e[8]||(e[8]=[x("注册",-1)])]),_:1},8,["loading"]),n("div",te,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),s(p,{link:"",type:"primary",onClick:A},{default:o(()=>[...e[9]||(e[9]=[x("立即登录",-1)])]),_:1})])]),_:1})])}}},fe=Y(se,[["__scopeId","data-v-a9d7804f"]]);export{fe as default};

View File

@@ -1 +0,0 @@
.auth-wrap[data-v-a9d7804f]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-a9d7804f]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-a9d7804f]{margin-bottom:14px}.brand-title[data-v-a9d7804f]{font-size:18px;font-weight:900}.brand-sub[data-v-a9d7804f]{margin-top:4px;font-size:12px}.alert[data-v-a9d7804f]{margin-bottom:12px}.hint[data-v-a9d7804f]{margin-top:6px;font-size:12px}.captcha-row[data-v-a9d7804f]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-a9d7804f]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}.submit-btn[data-v-a9d7804f]{width:100%;margin-top:4px}.actions[data-v-a9d7804f]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.auth-wrap[data-v-64094e78]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-64094e78]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-64094e78]{margin-bottom:14px}.brand-title[data-v-64094e78]{font-size:18px;font-weight:900}.brand-sub[data-v-64094e78]{margin-top:4px;font-size:12px}.alert[data-v-64094e78]{margin-bottom:12px}.hint[data-v-64094e78]{margin-top:6px;font-size:12px}.captcha-row[data-v-64094e78]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-64094e78]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}.submit-btn[data-v-64094e78]{width:100%;margin-top:4px}.actions[data-v-64094e78]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}

View File

@@ -0,0 +1 @@
import"./base-xgxQQEpV.js";import{E as R}from"./el-card-CfK866jr.js";import{E as F,a as L,b as M}from"./el-alert-DTUOkrAB.js";import{E as U}from"./el-input-BaZNy9Kg.js";import{E as j}from"./el-button-LKkD3jQh.js";import{g as n,y as K,f as z,h as D,i as q,z as G,j as _,p as s,s as t,u as H,o as p,k as m,F as V,x as b,c as J,l as h,m as O,t as Q}from"./vendor-vue-WbiK4TmU.js";import{c as W}from"./auth-B5cl_nsV.js";import{v as X}from"./password-7ryi82gE.js";import{_ as Y}from"./style-CEbARg1o.js";import{E as v}from"./http-BoPYlvwK.js";import"./aria-DLpFpzDe.js";import"./index-CoYtSGUZ.js";import"./vendor-axios-B9ygI19o.js";const Z={class:"auth-wrap"},$={class:"actions"},ee={class:"actions"},oe={key:0,class:"app-muted"},se={__name:"ResetPasswordPage",setup(te){const T=K(),x=H(),r=n(String(T.params.token||"")),l=n(!0),y=n(""),a=z({newPassword:"",confirmPassword:""}),g=n(!1),f=n(""),i=n(0);let d=null;function B(){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 k=D(()=>!!(l.value&&r.value&&!f.value));function E(){x.push("/login")}function A(){i.value=3,d=window.setInterval(()=>{i.value-=1,i.value<=0&&(window.clearInterval(d),d=null,window.location.href="/login")},1e3)}async function I(){if(!k.value)return;const o=a.newPassword,e=a.confirmPassword,u=X(o);if(!u.ok){v.error(u.message);return}if(o!==e){v.error("两次输入的密码不一致");return}g.value=!0;try{await W({token:r.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",v.success("密码重置成功"),A()}catch(c){const w=c?.response?.data;v.error(w?.error||"重置失败")}finally{g.value=!1}}return q(()=>{const o=B();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),l.value=!!o?.valid,y.value=o?.error_message||(l.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(l.value=!1,y.value="重置链接无效或已过期,请重新申请密码重置")}),G(()=>{d&&window.clearInterval(d)}),(o,e)=>{const u=F,c=j,w=U,S=M,C=L,N=R;return p(),_("div",Z,[s(N,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:t(()=>[e[5]||(e[5]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"知识管理平台"),m("div",{class:"brand-sub app-muted"},"重置密码")],-1)),l.value?(p(),_(V,{key:1},[f.value?(p(),J(u,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):h("",!0),s(C,{"label-position":"top"},{default:t(()=>[s(S,{label:"新密码至少8位且包含字母和数字"},{default:t(()=>[s(w,{modelValue:a.newPassword,"onUpdate:modelValue":e[0]||(e[0]=P=>a.newPassword=P),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(S,{label:"确认密码"},{default:t(()=>[s(w,{modelValue:a.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=P=>a.confirmPassword=P),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:O(I,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(c,{type:"primary",class:"submit-btn",loading:g.value,disabled:!k.value,onClick:I},{default:t(()=>[...e[3]||(e[3]=[b(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",ee,[s(c,{link:"",type:"primary",onClick:E},{default:t(()=>[...e[4]||(e[4]=[b("返回登录",-1)])]),_:1}),i.value>0?(p(),_("span",oe,Q(i.value)+" 秒后自动跳转…",1)):h("",!0)])],64)):(p(),_(V,{key:0},[s(u,{type:"error",closable:!1,title:"链接已失效",description:y.value,"show-icon":""},null,8,["description"]),m("div",$,[s(c,{type:"primary",onClick:E},{default:t(()=>[...e[2]||(e[2]=[b("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},ve=Y(se,[["__scopeId","data-v-6a8fefa8"]]);export{ve as default};

View File

@@ -0,0 +1 @@
.auth-wrap[data-v-6a8fefa8]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-6a8fefa8]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-6a8fefa8]{margin-bottom:14px}.brand-title[data-v-6a8fefa8]{font-size:18px;font-weight:900}.brand-sub[data-v-6a8fefa8]{margin-top:4px;font-size:12px}.alert[data-v-6a8fefa8]{margin-bottom:12px}.submit-btn[data-v-6a8fefa8]{width:100%;margin-top:4px}.actions[data-v-6a8fefa8]{margin-top:16px;display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap}

View File

@@ -1 +0,0 @@
.auth-wrap[data-v-0bbb511c]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-0bbb511c]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-0bbb511c]{margin-bottom:14px}.brand-title[data-v-0bbb511c]{font-size:18px;font-weight:900}.brand-sub[data-v-0bbb511c]{margin-top:4px;font-size:12px}.alert[data-v-0bbb511c]{margin-bottom:12px}.submit-btn[data-v-0bbb511c]{width:100%;margin-top:4px}.actions[data-v-0bbb511c]{margin-top:16px;display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap}

View File

@@ -1 +0,0 @@
import{E as R}from"./el-button-DWxIvzz-.js";import{E as F}from"./el-card-DfVpO1U5.js";import{E as L,a as M,b as U}from"./el-alert-DB2IQLpH.js";import{E as j,a as _}from"./http-CdvgQxJu.js";import{g as n,y as K,f as q,h as z,i as D,z as G,j as v,q as s,s as a,u as H,o as p,k as m,F as V,x as P,c as J,l as h,m as O,t as Q}from"./vendor-vue-DxN60LNb.js";import{c as W}from"./auth-CX9p6ZYg.js";import{v as X}from"./password-7ryi82gE.js";import{_ as Y}from"./style-CEbARg1o.js";import"./vendor-axios-B9ygI19o.js";const Z={class:"auth-wrap"},$={class:"actions"},ee={class:"actions"},oe={key:0,class:"app-muted"},se={__name:"ResetPasswordPage",setup(ae){const T=K(),x=H(),l=n(String(T.params.token||"")),r=n(!0),y=n(""),t=q({newPassword:"",confirmPassword:""}),b=n(!1),f=n(""),i=n(0);let d=null;function B(){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 k=z(()=>!!(r.value&&l.value&&!f.value));function E(){x.push("/login")}function A(){i.value=3,d=window.setInterval(()=>{i.value-=1,i.value<=0&&(window.clearInterval(d),d=null,window.location.href="/login")},1e3)}async function I(){if(!k.value)return;const o=t.newPassword,e=t.confirmPassword,u=X(o);if(!u.ok){_.error(u.message);return}if(o!==e){_.error("两次输入的密码不一致");return}b.value=!0;try{await W({token:l.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",_.success("密码重置成功"),A()}catch(c){const w=c?.response?.data;_.error(w?.error||"重置失败")}finally{b.value=!1}}return D(()=>{const o=B();o?.page==="reset_password"?(l.value=String(o?.token||l.value||""),r.value=!!o?.valid,y.value=o?.error_message||(r.value?"":"重置链接无效或已过期,请重新申请密码重置")):l.value||(r.value=!1,y.value="重置链接无效或已过期,请重新申请密码重置")}),G(()=>{d&&window.clearInterval(d)}),(o,e)=>{const u=L,c=R,w=j,S=U,C=M,N=F;return p(),v("div",Z,[s(N,{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)),r.value?(p(),v(V,{key:1},[f.value?(p(),J(u,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):h("",!0),s(C,{"label-position":"top"},{default:a(()=>[s(S,{label:"新密码至少8位且包含字母和数字"},{default:a(()=>[s(w,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=g=>t.newPassword=g),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(S,{label:"确认密码"},{default:a(()=>[s(w,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=g=>t.confirmPassword=g),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:O(I,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(c,{type:"primary",class:"submit-btn",loading:b.value,disabled:!k.value,onClick:I},{default:a(()=>[...e[3]||(e[3]=[P(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),m("div",ee,[s(c,{link:"",type:"primary",onClick:E},{default:a(()=>[...e[4]||(e[4]=[P("返回登录",-1)])]),_:1}),i.value>0?(p(),v("span",oe,Q(i.value)+" 秒后自动跳转…",1)):h("",!0)])],64)):(p(),v(V,{key:0},[s(u,{type:"error",closable:!1,title:"链接已失效",description:y.value,"show-icon":""},null,8,["description"]),m("div",$,[s(c,{type:"primary",onClick:E},{default:a(()=>[...e[2]||(e[2]=[P("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},me=Y(se,[["__scopeId","data-v-0bbb511c"]]);export{me as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.panel[data-v-07cdff63]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.panel-head[data-v-07cdff63]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-07cdff63]{font-size:16px;font-weight:900}.panel-actions[data-v-07cdff63]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.grid[data-v-07cdff63]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;align-items:start}.pagination[data-v-07cdff63]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.page-hint[data-v-07cdff63]{font-size:12px}.shot-card[data-v-07cdff63]{border-radius:14px;border:1px solid var(--app-border);overflow:hidden}.shot-img[data-v-07cdff63]{width:100%;aspect-ratio:16/9;object-fit:cover;cursor:pointer;display:block}.shot-body[data-v-07cdff63]{padding:12px}.shot-name[data-v-07cdff63]{font-size:13px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shot-meta[data-v-07cdff63]{margin-top:4px;font-size:12px}.shot-actions[data-v-07cdff63]{margin-top:10px;display:flex;flex-wrap:wrap;gap:6px}.preview[data-v-07cdff63]{display:flex;justify-content:center}.preview-img[data-v-07cdff63]{max-width:100%;max-height:78vh;object-fit:contain;border-radius:10px;border:1px solid var(--app-border);background:#fff}@media(max-width:480px){.grid[data-v-07cdff63]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-07cdff63]{width:100%;justify-content:flex-end}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.panel[data-v-4a5df754]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.panel-head[data-v-4a5df754]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-4a5df754]{font-size:16px;font-weight:900}.panel-actions[data-v-4a5df754]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.grid[data-v-4a5df754]{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;align-items:start}.pagination[data-v-4a5df754]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.page-hint[data-v-4a5df754]{font-size:12px}.shot-card[data-v-4a5df754]{border-radius:14px;border:1px solid var(--app-border);overflow:hidden}.shot-img[data-v-4a5df754]{width:100%;aspect-ratio:16/9;object-fit:cover;cursor:pointer;display:block}.shot-body[data-v-4a5df754]{padding:12px}.shot-name[data-v-4a5df754]{font-size:13px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shot-meta[data-v-4a5df754]{margin-top:4px;font-size:12px}.shot-actions[data-v-4a5df754]{margin-top:10px;display:flex;flex-wrap:wrap;gap:6px}.preview[data-v-4a5df754]{display:flex;justify-content:center}.preview-img[data-v-4a5df754]{max-width:100%;max-height:78vh;object-fit:contain;border-radius:10px;border:1px solid var(--app-border);background:#fff}@media(max-width:480px){.grid[data-v-4a5df754]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-4a5df754]{width:100%;justify-content:flex-end}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.callback-wrap[data-v-2c9ef71e]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.callback-card[data-v-2c9ef71e]{width:min(420px,94vw);border-radius:var(--app-radius);border:1px solid var(--app-border)}.callback-text[data-v-2c9ef71e]{margin-top:12px;color:var(--app-muted);font-size:13px;text-align:center}

View File

@@ -0,0 +1 @@
import"./base-xgxQQEpV.js";import{E as l}from"./el-card-CfK866jr.js";import{E as p}from"./el-skeleton-item-CD5Idavp.js";import{g as d,i as _,u as m,j as u,p as i,s as f,o as g,k,t as b}from"./vendor-vue-WbiK4TmU.js";import{s as w}from"./auth-B5cl_nsV.js";import{b as h}from"./settings-Db4PmPGC.js";import{_ as S}from"./style-CEbARg1o.js";import{E as t}from"./http-BoPYlvwK.js";import"./vendor-axios-B9ygI19o.js";const v={class:"callback-wrap"},y={class:"callback-text"},E="zsglpt_social_settings_bind_token",x={__name:"SocialBindCallbackPage",setup(B){const a=m(),c=d("正在完成绑定");return _(async()=>{const o=new URLSearchParams(window.location.search||""),n=String(o.get("provider")||o.get("type")||"").trim(),s=String(o.get("code")||"").trim();if(!n||!s){t.error("快捷登录回调参数不完整"),a.replace("/app/accounts");return}try{const e=await w({provider:n,code:s,mode:"bind"});if(e?.success&&e?.bound){t.success("快捷登录已绑定"),a.replace("/app/accounts");return}if(!e?.bind_token){t.warning("未获取到绑定凭证"),a.replace("/app/accounts");return}try{await h({bind_token:e.bind_token}),t.success("快捷登录已绑定")}catch(r){if(r?.response?.status===401){window.sessionStorage.setItem(E,e.bind_token),t.info("请先登录后完成绑定"),a.replace("/login");return}throw r}a.replace("/app/accounts")}catch(e){const r=e?.response?.data;c.value=r?.error||"快捷登录绑定失败",t.error(c.value),a.replace("/app/accounts")}}),(o,n)=>{const s=p,e=l;return g(),u("div",v,[i(e,{shadow:"never",class:"callback-card"},{default:f(()=>[i(s,{rows:3,animated:""}),k("div",y,b(c.value),1)]),_:1})])}}},D=S(x,[["__scopeId","data-v-2c9ef71e"]]);export{D as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.social-login-buttons[data-v-809d50db]{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}.social-login-buttons.block[data-v-809d50db]{align-items:stretch;flex-direction:column}.social-btn[data-v-809d50db]{height:40px;border-radius:10px;border:1px solid rgba(17,24,39,.14);background:#fff;color:#111827;font-size:13px;font-weight:800;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 12px}.block .social-btn[data-v-809d50db]{width:100%}.social-btn[data-v-809d50db]:disabled{cursor:not-allowed;opacity:.7}.social-btn[data-v-809d50db]:hover:not(:disabled){background:#f8fafc}.social-icon[data-v-809d50db]{width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;color:#fff;font-size:12px;line-height:1}.provider-wx .social-icon[data-v-809d50db]{background:#16a34a}.provider-qq .social-icon[data-v-809d50db]{background:#2563eb}.provider-alipay .social-icon[data-v-809d50db]{background:#1677ff}.social-qr-box[data-v-809d50db]{display:flex;flex-direction:column;align-items:center;gap:12px}.social-qr-prompt[data-v-809d50db]{font-size:13px;color:#374151;text-align:center}

View File

@@ -0,0 +1 @@
import{b as W,i as N,c as q,w as F,a as G,_ as H,u as J,d as K}from"./base-xgxQQEpV.js";import{E as O}from"./el-card-CfK866jr.js";import{E as Q}from"./el-button-LKkD3jQh.js";import{A as R,h as T,j as p,o as l,k as i,l as f,B as w,c as V,C as a,n as d,D as X,t as m,g as o,i as Y,z as Z,p as C,s as b,u as ee,x as P}from"./vendor-vue-WbiK4TmU.js";import{_ as se}from"./style-CEbARg1o.js";import"./index-CoYtSGUZ.js";const r={primary:"icon-primary",success:"icon-success",warning:"icon-warning",error:"icon-error",info:"icon-info"},A={[r.primary]:N,[r.success]:G,[r.warning]:F,[r.error]:q,[r.info]:N},te=W({title:{type:String,default:""},subTitle:{type:String,default:""},icon:{type:String,values:["primary","success","warning","info","error"],default:"info"}}),oe=R({name:"ElResult"}),ne=R({...oe,props:te,setup($){const g=$,n=J("result"),c=T(()=>{const s=g.icon,u=s&&r[s]?r[s]:"icon-info",y=A[u]||A["icon-info"];return{class:u,component:y}});return(s,u)=>(l(),p("div",{class:d(a(n).b())},[i("div",{class:d(a(n).e("icon"))},[w(s.$slots,"icon",{},()=>[a(c).component?(l(),V(X(a(c).component),{key:0,class:d(a(c).class)},null,8,["class"])):f("v-if",!0)])],2),s.title||s.$slots.title?(l(),p("div",{key:0,class:d(a(n).e("title"))},[w(s.$slots,"title",{},()=>[i("p",null,m(s.title),1)])],2)):f("v-if",!0),s.subTitle||s.$slots["sub-title"]?(l(),p("div",{key:1,class:d(a(n).e("subtitle"))},[w(s.$slots,"sub-title",{},()=>[i("p",null,m(s.subTitle),1)])],2)):f("v-if",!0),s.$slots.extra?(l(),p("div",{key:2,class:d(a(n).e("extra"))},[w(s.$slots,"extra")],2)):f("v-if",!0)],2))}});var ae=H(ne,[["__file","result.vue"]]);const le=K(ae),re={class:"auth-wrap"},ie={class:"actions"},ce={key:0,class:"countdown app-muted"},ue={__name:"VerifyResultPage",setup($){const g=ee(),n=o(!1),c=o(""),s=o(""),u=o(""),y=o(""),h=o(""),I=o(""),k=o(""),_=o(0);let v=null;function L(){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 U(e){const t=!!e?.success;n.value=t,c.value=e?.title||(t?"验证成功":"验证失败"),s.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),u.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),h.value=e?.secondary_label||(t?"":"返回登录"),I.value=e?.secondary_url||(t?"":"/login"),k.value=e?.redirect_url||(t?"/login":""),_.value=Number(e?.redirect_seconds||(t?5:0))||0}const z=T(()=>!!(h.value&&I.value)),B=T(()=>!!(k.value&&_.value>0));async function E(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await g.push(e)}}function D(){B.value&&(v=window.setInterval(()=>{_.value-=1,_.value<=0&&(window.clearInterval(v),v=null,window.location.href=k.value)},1e3))}return Y(()=>{const e=L();U(e),D()}),Z(()=>{v&&window.clearInterval(v)}),(e,t)=>{const S=Q,M=le,j=O;return l(),p("div",re,[C(j,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:b(()=>[t[2]||(t[2]=i("div",{class:"brand"},[i("div",{class:"brand-title"},"知识管理平台"),i("div",{class:"brand-sub app-muted"},"验证结果")],-1)),C(M,{icon:n.value?"success":"error",title:c.value,"sub-title":s.value,class:"result"},{extra:b(()=>[i("div",ie,[C(S,{type:"primary",onClick:t[0]||(t[0]=x=>E(y.value))},{default:b(()=>[P(m(u.value),1)]),_:1}),z.value?(l(),V(S,{key:0,onClick:t[1]||(t[1]=x=>E(I.value))},{default:b(()=>[P(m(h.value),1)]),_:1})):f("",!0)]),B.value?(l(),p("div",ce,m(_.value)+" 秒后自动跳转... ",1)):f("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},ye=se(ue,[["__scopeId","data-v-c1f8a7e0"]]);export{ye as default};

View File

@@ -1 +1 @@
.el-result{--el-result-padding:40px 30px;--el-result-icon-font-size:64px;--el-result-title-font-size:20px;--el-result-title-margin-top:20px;--el-result-subtitle-margin-top:10px;--el-result-extra-margin-top:30px;align-items:center;box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;padding:var(--el-result-padding);text-align:center}.el-result__icon svg{height:var(--el-result-icon-font-size);width:var(--el-result-icon-font-size)}.el-result__title{margin-top:var(--el-result-title-margin-top)}.el-result__title p{color:var(--el-text-color-primary);font-size:var(--el-result-title-font-size);line-height:1.3;margin:0}.el-result__subtitle{margin-top:var(--el-result-subtitle-margin-top)}.el-result__subtitle p{color:var(--el-text-color-regular);font-size:var(--el-font-size-base);line-height:1.3;margin:0}.el-result__extra{margin-top:var(--el-result-extra-margin-top)}.el-result .icon-primary{--el-result-color:var(--el-color-primary);color:var(--el-result-color)}.el-result .icon-success{--el-result-color:var(--el-color-success);color:var(--el-result-color)}.el-result .icon-warning{--el-result-color:var(--el-color-warning);color:var(--el-result-color)}.el-result .icon-danger{--el-result-color:var(--el-color-danger);color:var(--el-result-color)}.el-result .icon-error{--el-result-color:var(--el-color-error);color:var(--el-result-color)}.el-result .icon-info{--el-result-color:var(--el-color-info);color:var(--el-result-color)}.auth-wrap[data-v-1fc6b081]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-1fc6b081]{width:100%;max-width:520px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-1fc6b081]{margin-bottom:14px}.brand-title[data-v-1fc6b081]{font-size:18px;font-weight:900}.brand-sub[data-v-1fc6b081]{margin-top:4px;font-size:12px}.result[data-v-1fc6b081]{padding:8px 0 2px}.actions[data-v-1fc6b081]{display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap}.countdown[data-v-1fc6b081]{margin-top:10px;text-align:center;font-size:13px} .el-result{--el-result-padding:40px 30px;--el-result-icon-font-size:64px;--el-result-title-font-size:20px;--el-result-title-margin-top:20px;--el-result-subtitle-margin-top:10px;--el-result-extra-margin-top:30px;align-items:center;box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;padding:var(--el-result-padding);text-align:center}.el-result__icon svg{height:var(--el-result-icon-font-size);width:var(--el-result-icon-font-size)}.el-result__title{margin-top:var(--el-result-title-margin-top)}.el-result__title p{color:var(--el-text-color-primary);font-size:var(--el-result-title-font-size);line-height:1.3;margin:0}.el-result__subtitle{margin-top:var(--el-result-subtitle-margin-top)}.el-result__subtitle p{color:var(--el-text-color-regular);font-size:var(--el-font-size-base);line-height:1.3;margin:0}.el-result__extra{margin-top:var(--el-result-extra-margin-top)}.el-result .icon-primary{--el-result-color:var(--el-color-primary);color:var(--el-result-color)}.el-result .icon-success{--el-result-color:var(--el-color-success);color:var(--el-result-color)}.el-result .icon-warning{--el-result-color:var(--el-color-warning);color:var(--el-result-color)}.el-result .icon-danger{--el-result-color:var(--el-color-danger);color:var(--el-result-color)}.el-result .icon-error{--el-result-color:var(--el-color-error);color:var(--el-result-color)}.el-result .icon-info{--el-result-color:var(--el-color-info);color:var(--el-result-color)}.auth-wrap[data-v-c1f8a7e0]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-c1f8a7e0]{width:100%;max-width:520px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-c1f8a7e0]{margin-bottom:14px}.brand-title[data-v-c1f8a7e0]{font-size:18px;font-weight:900}.brand-sub[data-v-c1f8a7e0]{margin-top:4px;font-size:12px}.result[data-v-c1f8a7e0]{padding:8px 0 2px}.actions[data-v-c1f8a7e0]{display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap}.countdown[data-v-c1f8a7e0]{margin-top:10px;text-align:center;font-size:13px}

View File

@@ -1 +0,0 @@
import{b as W,i as N,c as q,w as F,a as G,_ as H,u as J,d as K,E as O}from"./el-button-DWxIvzz-.js";import{E as Q}from"./el-card-DfVpO1U5.js";import{A as R,h as T,j as f,o as a,k as i,l as p,B as w,c as V,C as l,n as d,D as X,t as m,g as n,i as Y,z as Z,q as C,s as b,u as ee,x as P}from"./vendor-vue-DxN60LNb.js";import{_ as se}from"./style-CEbARg1o.js";const r={primary:"icon-primary",success:"icon-success",warning:"icon-warning",error:"icon-error",info:"icon-info"},A={[r.primary]:N,[r.success]:G,[r.warning]:F,[r.error]:q,[r.info]:N},te=W({title:{type:String,default:""},subTitle:{type:String,default:""},icon:{type:String,values:["primary","success","warning","info","error"],default:"info"}}),ne=R({name:"ElResult"}),oe=R({...ne,props:te,setup($){const g=$,o=J("result"),c=T(()=>{const s=g.icon,u=s&&r[s]?r[s]:"icon-info",y=A[u]||A["icon-info"];return{class:u,component:y}});return(s,u)=>(a(),f("div",{class:d(l(o).b())},[i("div",{class:d(l(o).e("icon"))},[w(s.$slots,"icon",{},()=>[l(c).component?(a(),V(X(l(c).component),{key:0,class:d(l(c).class)},null,8,["class"])):p("v-if",!0)])],2),s.title||s.$slots.title?(a(),f("div",{key:0,class:d(l(o).e("title"))},[w(s.$slots,"title",{},()=>[i("p",null,m(s.title),1)])],2)):p("v-if",!0),s.subTitle||s.$slots["sub-title"]?(a(),f("div",{key:1,class:d(l(o).e("subtitle"))},[w(s.$slots,"sub-title",{},()=>[i("p",null,m(s.subTitle),1)])],2)):p("v-if",!0),s.$slots.extra?(a(),f("div",{key:2,class:d(l(o).e("extra"))},[w(s.$slots,"extra")],2)):p("v-if",!0)],2))}});var le=H(oe,[["__file","result.vue"]]);const ae=K(le),re={class:"auth-wrap"},ie={class:"actions"},ce={key:0,class:"countdown app-muted"},ue={__name:"VerifyResultPage",setup($){const g=ee(),o=n(!1),c=n(""),s=n(""),u=n(""),y=n(""),h=n(""),I=n(""),k=n(""),_=n(0);let v=null;function L(){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 U(e){const t=!!e?.success;o.value=t,c.value=e?.title||(t?"验证成功":"验证失败"),s.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),u.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),h.value=e?.secondary_label||(t?"":"返回登录"),I.value=e?.secondary_url||(t?"":"/login"),k.value=e?.redirect_url||(t?"/login":""),_.value=Number(e?.redirect_seconds||(t?5:0))||0}const z=T(()=>!!(h.value&&I.value)),B=T(()=>!!(k.value&&_.value>0));async function E(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await g.push(e)}}function D(){B.value&&(v=window.setInterval(()=>{_.value-=1,_.value<=0&&(window.clearInterval(v),v=null,window.location.href=k.value)},1e3))}return Y(()=>{const e=L();U(e),D()}),Z(()=>{v&&window.clearInterval(v)}),(e,t)=>{const S=O,M=ae,j=Q;return a(),f("div",re,[C(j,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:b(()=>[t[2]||(t[2]=i("div",{class:"brand"},[i("div",{class:"brand-title"},"知识管理平台"),i("div",{class:"brand-sub app-muted"},"验证结果")],-1)),C(M,{icon:o.value?"success":"error",title:c.value,"sub-title":s.value,class:"result"},{extra:b(()=>[i("div",ie,[C(S,{type:"primary",onClick:t[0]||(t[0]=x=>E(y.value))},{default:b(()=>[P(m(u.value),1)]),_:1}),z.value?(a(),V(S,{key:0,onClick:t[1]||(t[1]=x=>E(I.value))},{default:b(()=>[P(m(h.value),1)]),_:1})):p("",!0)]),B.value?(a(),f("div",ce,m(_.value)+" 秒后自动跳转... ",1)):p("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},ve=se(ue,[["__scopeId","data-v-1fc6b081"]]);export{ve as default};

View File

@@ -1 +1 @@
import{p as c}from"./http-CdvgQxJu.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"./http-BoPYlvwK.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};

View File

@@ -1,2 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./LoginPage-D5iXLq7p.js","./vendor-vue-DxN60LNb.js","./style-CEbARg1o.js","./style-BHGuKLUF.css","./LoginPage-DTj5KeC4.css","./RegisterPage-4xFnBJCQ.js","./el-button-DWxIvzz-.js","./el-button-DF1Fi_iE.css","./el-card-DfVpO1U5.js","./el-card-BqOrgVp1.css","./el-alert-DB2IQLpH.js","./http-CdvgQxJu.js","./vendor-axios-B9ygI19o.js","./http-D6B3r8CH.css","./el-alert-B-NgiIln.css","./auth-CX9p6ZYg.js","./password-7ryi82gE.js","./RegisterPage-BOcNcW5D.css","./ResetPasswordPage-lX7l6Nbu.js","./ResetPasswordPage-DybfLMAw.css","./VerifyResultPage-bifpPyoE.js","./VerifyResultPage-efSXaaKI.css","./AppLayout-xWeMM3hH.js","./user-Bl59IefW.js","./el-overlay-C_JJBVfE.js","./el-overlay-Bd56Lw6C.css","./user-B7bO5p8k.css","./settings-Ddo8isuv.js","./isArrayLikeObject-BjIRF-cS.js","./AppLayout-DxoFHO3h.css","./AccountsPage-DnOxRP7e.js","./accounts-3bM7Wy59.js","./accounts-D_6SYB2i.css","./el-select-B0VMg2td.js","./el-select-D_oyzAZN.css","./vendor-realtime-CA1CrNgP.js","./AccountsPage-iiBFNme8.css","./SchedulesPage-TUv7nqYq.js","./el-pagination-BY1uI-wO.js","./el-pagination-B1FwbX1n.css","./SchedulesPage-BIuHs5oJ.css","./ScreenshotsPage-7CRd3Hlo.js","./ScreenshotsPage-30dzddw-.css"])))=>i.map(i=>d[i]);
import{_ as v}from"./style-CEbARg1o.js";import{r as g,c as R,o as y,a as A,b as L,d as w,e as k}from"./vendor-vue-DxN60LNb.js";const V={};function O(p,l){const a=g("RouterView");return y(),R(a)}const T=v(V,[["render",O]]),b="modulepreload",D=function(p,l){return new URL(p,l).href},f={},r=function(l,a,u){let _=Promise.resolve();if(a&&a.length>0){let P=function(e){return Promise.all(e.map(s=>Promise.resolve(s).then(c=>({status:"fulfilled",value:c}),c=>({status:"rejected",reason:c}))))};const n=document.getElementsByTagName("link"),t=document.querySelector("meta[property=csp-nonce]"),h=t?.nonce||t?.getAttribute("nonce");_=P(a.map(e=>{if(e=D(e,u),e in f)return;f[e]=!0;const s=e.endsWith(".css"),c=s?'[rel="stylesheet"]':"";if(u)for(let i=n.length-1;i>=0;i--){const m=n[i];if(m.href===e&&(!s||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${e}"]${c}`))return;const o=document.createElement("link");if(o.rel=s?"stylesheet":b,s||(o.as="script"),o.crossOrigin="",o.href=e,h&&o.setAttribute("nonce",h),document.head.appendChild(o),s)return new Promise((i,m)=>{o.addEventListener("load",i),o.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${e}`)))})}))}function d(n){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=n,window.dispatchEvent(t),!t.defaultPrevented)throw n}return _.then(n=>{for(const t of n||[])t.status==="rejected"&&d(t.reason);return l().catch(d)})},I=()=>r(()=>import("./LoginPage-D5iXLq7p.js"),__vite__mapDeps([0,1,2,3,4]),import.meta.url),S=()=>r(()=>import("./RegisterPage-4xFnBJCQ.js"),__vite__mapDeps([5,6,1,7,8,9,10,11,12,13,14,15,16,2,3,17]),import.meta.url),$=()=>r(()=>import("./ResetPasswordPage-lX7l6Nbu.js"),__vite__mapDeps([18,6,1,7,8,9,10,11,12,13,14,15,16,2,3,19]),import.meta.url),E=()=>r(()=>import("./VerifyResultPage-bifpPyoE.js"),__vite__mapDeps([20,6,1,7,8,9,2,3,21]),import.meta.url),C=()=>r(()=>import("./AppLayout-xWeMM3hH.js"),__vite__mapDeps([22,6,1,7,23,11,12,13,10,14,24,25,26,27,16,2,3,28,29]),import.meta.url),B=()=>r(()=>import("./AccountsPage-DnOxRP7e.js"),__vite__mapDeps([30,6,1,7,24,11,12,13,25,10,14,23,26,31,32,33,34,8,9,27,35,2,3,36]),import.meta.url),N=()=>r(()=>import("./SchedulesPage-TUv7nqYq.js"),__vite__mapDeps([37,6,1,7,24,11,12,13,25,10,14,33,34,23,26,31,32,38,39,8,9,2,3,28,40]),import.meta.url),j=()=>r(()=>import("./ScreenshotsPage-7CRd3Hlo.js"),__vite__mapDeps([41,6,1,7,24,11,12,13,25,38,33,34,39,8,9,2,3,42]),import.meta.url),q=[{path:"/",redirect:"/login"},{path:"/login",name:"login",component:I},{path:"/register",name:"register",component:S},{path:"/reset-password/:token",name:"reset_password",component:$},{path:"/api/verify-email/:token",name:"verify_email",component:E},{path:"/api/verify-bind-email/:token",name:"verify_bind_email",component:E},{path:"/app",component:C,children:[{path:"",redirect:"/app/accounts"},{path:"accounts",name:"accounts",component:B},{path:"schedules",name:"schedules",component:N},{path:"screenshots",name:"screenshots",component:j}]},{path:"/:pathMatch(.*)*",redirect:"/login"}],x=A({history:L(),routes:q});w(T).use(k()).use(x).mount("#app");

View File

@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./LoginPage-BtooAZsk.js","./vendor-vue-WbiK4TmU.js","./SocialLoginButtons-BlVSr6Mm.js","./base-xgxQQEpV.js","./base-CiSqh4F9.css","./el-overlay-hge8bsIn.js","./aria-DLpFpzDe.js","./http-BoPYlvwK.js","./vendor-axios-B9ygI19o.js","./index-CoYtSGUZ.js","./el-overlay-hkg5a9kt.css","./auth-B5cl_nsV.js","./style-CEbARg1o.js","./style-BHGuKLUF.css","./SocialLoginButtons-qO3SCoE7.css","./LoginPage-vCVLchWz.css","./RegisterPage-Cb1mme2j.js","./el-card-CfK866jr.js","./el-card-BqOrgVp1.css","./el-alert-DTUOkrAB.js","./el-button-LKkD3jQh.js","./el-button-BRDnKxwT.css","./el-input-BaZNy9Kg.js","./el-input-D6B3r8CH.css","./el-alert-B-NgiIln.css","./password-7ryi82gE.js","./RegisterPage-LYXwWYc1.css","./ResetPasswordPage-CUOK0fe1.js","./ResetPasswordPage-DAB63ins.css","./SocialBindCallbackPage-DraQ_mks.js","./el-skeleton-item-CD5Idavp.js","./el-skeleton-item-BLY1jEuR.css","./settings-Db4PmPGC.js","./SocialBindCallbackPage-BZgzv_7a.css","./VerifyResultPage-BUSE4fL8.js","./VerifyResultPage-BnGv8vyq.css","./AppLayout-D9A8Va7K.js","./user-BlXB4Zbh.js","./el-empty-B4_NEFfq.js","./el-empty-D4G4LZ50.css","./user-B7bO5p8k.css","./isArrayLikeObject-B5fs56rA.js","./AppLayout-CJKAa2WS.css","./AccountsPage-B7MLZrfr.js","./accounts-DzntEHJR.js","./accounts-D_6SYB2i.css","./el-select-CBs1QjJm.js","./el-select-D_oyzAZN.css","./vendor-realtime-CA1CrNgP.js","./AccountsPage-DKewJ7S7.css","./SchedulesPage-0TKGPmUl.js","./el-pagination-kVJ2XlAP.js","./el-pagination-B1FwbX1n.css","./SchedulesPage-Dxq2ghmQ.css","./ScreenshotsPage-F6GpvKGW.js","./ScreenshotsPage-BhLfAzHf.css"])))=>i.map(i=>d[i]);
import{_ as v}from"./style-CEbARg1o.js";import{r as g,c as R,o as y,a as A,b as L,d as k,e as b}from"./vendor-vue-WbiK4TmU.js";const w={};function V(p,l){const a=g("RouterView");return y(),R(a)}const O=v(w,[["render",V]]),T="modulepreload",D=function(p,l){return new URL(p,l).href},f={},o=function(l,a,u){let _=Promise.resolve();if(a&&a.length>0){let P=function(e){return Promise.all(e.map(s=>Promise.resolve(s).then(c=>({status:"fulfilled",value:c}),c=>({status:"rejected",reason:c}))))};const n=document.getElementsByTagName("link"),t=document.querySelector("meta[property=csp-nonce]"),h=t?.nonce||t?.getAttribute("nonce");_=P(a.map(e=>{if(e=D(e,u),e in f)return;f[e]=!0;const s=e.endsWith(".css"),c=s?'[rel="stylesheet"]':"";if(u)for(let i=n.length-1;i>=0;i--){const m=n[i];if(m.href===e&&(!s||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${e}"]${c}`))return;const r=document.createElement("link");if(r.rel=s?"stylesheet":T,s||(r.as="script"),r.crossOrigin="",r.href=e,h&&r.setAttribute("nonce",h),document.head.appendChild(r),s)return new Promise((i,m)=>{r.addEventListener("load",i),r.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${e}`)))})}))}function d(n){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=n,window.dispatchEvent(t),!t.defaultPrevented)throw n}return _.then(n=>{for(const t of n||[])t.status==="rejected"&&d(t.reason);return l().catch(d)})},I=()=>o(()=>import("./LoginPage-BtooAZsk.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]),import.meta.url),S=()=>o(()=>import("./RegisterPage-Cb1mme2j.js"),__vite__mapDeps([16,3,1,4,17,18,19,20,9,21,22,7,8,6,23,24,11,25,12,13,26]),import.meta.url),C=()=>o(()=>import("./ResetPasswordPage-CUOK0fe1.js"),__vite__mapDeps([27,3,1,4,17,18,19,20,9,21,22,7,8,6,23,24,11,25,12,13,28]),import.meta.url),$=()=>o(()=>import("./SocialBindCallbackPage-DraQ_mks.js"),__vite__mapDeps([29,3,1,4,17,18,30,31,11,7,8,32,12,13,33]),import.meta.url),E=()=>o(()=>import("./VerifyResultPage-BUSE4fL8.js"),__vite__mapDeps([34,3,1,4,17,18,20,9,21,12,13,35]),import.meta.url),B=()=>o(()=>import("./AppLayout-D9A8Va7K.js"),__vite__mapDeps([36,3,1,4,37,22,7,8,6,20,9,21,23,19,24,38,5,10,39,40,30,31,11,32,2,12,13,14,25,41,42]),import.meta.url),N=()=>o(()=>import("./AccountsPage-B7MLZrfr.js"),__vite__mapDeps([43,3,1,4,5,6,7,8,9,10,19,20,21,22,23,24,37,38,39,40,44,45,30,31,46,47,17,18,32,48,12,13,49]),import.meta.url),j=()=>o(()=>import("./SchedulesPage-0TKGPmUl.js"),__vite__mapDeps([50,3,1,4,38,22,7,8,6,20,9,21,23,5,10,39,19,24,46,47,37,40,44,45,51,52,17,18,30,31,12,13,41,53]),import.meta.url),q=()=>o(()=>import("./ScreenshotsPage-F6GpvKGW.js"),__vite__mapDeps([54,3,1,4,5,6,7,8,9,10,51,46,38,22,20,21,23,39,47,52,17,18,30,31,12,13,55]),import.meta.url),x=[{path:"/",redirect:"/login"},{path:"/login",name:"login",component:I},{path:"/register",name:"register",component:S},{path:"/reset-password/:token",name:"reset_password",component:C},{path:"/social-bind-callback",name:"social_bind_callback",component:$},{path:"/api/verify-email/:token",name:"verify_email",component:E},{path:"/api/verify-bind-email/:token",name:"verify_bind_email",component:E},{path:"/app",component:B,children:[{path:"",redirect:"/app/accounts"},{path:"accounts",name:"accounts",component:N},{path:"schedules",name:"schedules",component:j},{path:"screenshots",name:"screenshots",component:q}]},{path:"/:pathMatch(.*)*",redirect:"/login"}],M=A({history:L(),routes:x});k(O).use(b()).use(M).mount("#app");

View File

@@ -0,0 +1 @@
class a extends Error{constructor(e){super(e),this.name="ElementPlusError"}}function d(t,e){throw new a(`[${t}] ${e}`)}function E(t,e){}const f="update:modelValue",l="change",b="input",u='a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])',o=t=>typeof Element>"u"?!1:t instanceof Element,c=t=>getComputedStyle(t).position==="fixed"?!1:t.offsetParent!==null,p=t=>Array.from(t.querySelectorAll(u)).filter(e=>i(e)&&c(e)),i=t=>{if(t.tabIndex>0||t.tabIndex===0&&t.getAttribute("tabIndex")!==null)return!0;if(t.tabIndex<0||t.hasAttribute("disabled")||t.getAttribute("aria-disabled")==="true")return!1;switch(t.nodeName){case"A":return!!t.href&&t.rel!=="ignore";case"INPUT":return!(t.type==="hidden"||t.type==="file");case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},A=function(t,e,...n){let s;e.includes("mouse")||e.includes("click")?s="MouseEvents":e.includes("key")?s="KeyboardEvent":s="HTMLEvents";const r=document.createEvent(s);return r.initEvent(e,...n),t.dispatchEvent(r),t},T=(t,e)=>{if(!t||!t.focus)return;let n=!1;o(t)&&!i(t)&&!t.getAttribute("tabindex")&&(t.setAttribute("tabindex","-1"),n=!0),t.focus(e),o(t)&&n&&t.removeAttribute("tabindex")};export{l as C,b as I,f as U,A as a,E as d,T as f,i,p as o,d as t};

View File

@@ -0,0 +1 @@
import{p as s}from"./http-BoPYlvwK.js";async function c(){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 i(a){const{data:t}=await s.post("/register",a);return t}async function r(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}async function e(){const{data:a}=await s.get("/auth/social/config");return a}async function u(a){const{data:t}=await s.post("/auth/social/login-url",a||{});return t}async function l(a){const{data:t}=await s.post("/auth/social/poll",a||{});return t}async function f(a){const{data:t}=await s.post("/auth/social/callback",a||{});return t}export{e as a,u as b,r as c,l as d,c as f,o as g,i as r,f as s};

View File

@@ -1 +0,0 @@
import{p as a}from"./http-CdvgQxJu.js";async function e(){const{data:t}=await a.get("/email/verify-status");return t}async function n(){const{data:t}=await a.post("/generate_captcha",{});return t}async function c(t){const{data:s}=await a.post("/register",t);return s}async function i(t){const{data:s}=await a.post("/reset-password-confirm",t);return s}export{i as c,e as f,n as g,c as r};

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More