feat: add Space aggregate login
This commit is contained in:
@@ -20,3 +20,19 @@ export async function executeScheduleNow() {
|
||||
const { data } = await api.post('/schedule/execute', {})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
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 { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
|
||||
import { getCachedKdocsStatus, preloadKdocsStatus, updateCachedKdocsStatus } from '../utils/kdocsStatusCache'
|
||||
@@ -22,6 +22,21 @@ const autoApproveEnabled = ref(false)
|
||||
const autoApproveHourlyLimit = ref(10)
|
||||
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 kdocsDocUrl = ref('')
|
||||
const kdocsDefaultUnit = ref('')
|
||||
@@ -83,9 +98,10 @@ function setKdocsHint(message) {
|
||||
async function loadAll() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [system, proxy] = await Promise.all([
|
||||
const [system, proxy, social] = await Promise.all([
|
||||
fetchSystemConfig(),
|
||||
fetchProxyConfig(),
|
||||
fetchSocialLoginConfig(),
|
||||
])
|
||||
|
||||
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
|
||||
@@ -112,6 +128,16 @@ async function loadAll() {
|
||||
kdocsRowEnd.value = system.kdocs_row_end ?? 0
|
||||
kdocsAdminNotifyEnabled.value = (system.kdocs_admin_notify_enabled ?? 0) === 1
|
||||
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 {
|
||||
// handled by interceptor
|
||||
} 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() {
|
||||
const payload = {
|
||||
kdocs_enabled: kdocsEnabled.value ? 1 : 0,
|
||||
@@ -459,6 +557,43 @@ onMounted(loadAll)
|
||||
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<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) {
|
||||
.config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
10
app-frontend/package-lock.json
generated
10
app-frontend/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"axios": "^1.12.2",
|
||||
"element-plus": "^2.11.3",
|
||||
"pinia": "^3.0.3",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
@@ -1991,6 +1992,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"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": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"axios": "^1.12.2",
|
||||
"element-plus": "^2.11.3",
|
||||
"pinia": "^3.0.3",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
|
||||
@@ -44,3 +44,23 @@ export async function confirmPasswordReset(payload) {
|
||||
const { data } = await publicApi.post('/reset-password-confirm', payload)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -69,3 +69,18 @@ export async function reportUserPasskeyClientError(payload) {
|
||||
const { data } = await publicApi.post('/user/passkeys/client-error', payload || {})
|
||||
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
|
||||
}
|
||||
|
||||
238
app-frontend/src/components/SocialLoginButtons.vue
Normal file
238
app-frontend/src/components/SocialLoginButtons.vue
Normal 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>
|
||||
@@ -7,8 +7,10 @@ import 'element-plus/es/components/message/style/css'
|
||||
import 'element-plus/es/components/message-box/style/css'
|
||||
|
||||
import { fetchActiveAnnouncement, dismissAnnouncement } from '../api/announcements'
|
||||
import { fetchSocialConfig } from '../api/auth'
|
||||
import { fetchMyFeedbacks, submitFeedback } from '../api/feedback'
|
||||
import {
|
||||
bindSocial,
|
||||
bindEmail,
|
||||
changePassword,
|
||||
createUserPasskeyOptions,
|
||||
@@ -18,11 +20,14 @@ import {
|
||||
fetchUserPasskeys,
|
||||
fetchUserEmail,
|
||||
fetchKdocsSettings,
|
||||
fetchSocialBindings,
|
||||
reportUserPasskeyClientError,
|
||||
unbindSocial,
|
||||
unbindEmail,
|
||||
updateKdocsSettings,
|
||||
updateEmailNotify,
|
||||
} from '../api/settings'
|
||||
import SocialLoginButtons from '../components/SocialLoginButtons.vue'
|
||||
import { useUserStore } from '../stores/user'
|
||||
import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey'
|
||||
import { validateStrongPassword } from '../utils/password'
|
||||
@@ -132,6 +137,12 @@ const passkeyRegisterOptions = ref(null)
|
||||
const passkeyRegisterOptionsAt = ref(0)
|
||||
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() {
|
||||
isMobile.value = Boolean(mediaQuery?.matches)
|
||||
if (!isMobile.value) drawerOpen.value = false
|
||||
@@ -262,7 +273,76 @@ async function openSettings() {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -838,6 +918,37 @@ async function dismissAnnouncementPermanently() {
|
||||
</div>
|
||||
</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">
|
||||
<div v-loading="kdocsLoading" class="settings-section">
|
||||
<el-form label-position="top">
|
||||
@@ -1101,6 +1212,43 @@ async function dismissAnnouncementPermanently() {
|
||||
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 {
|
||||
margin-top: 12px;
|
||||
display: grid;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import SocialLoginButtons from '../components/SocialLoginButtons.vue'
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
@@ -43,6 +45,15 @@ const resendError = ref('')
|
||||
|
||||
const showResendLink = computed(() => true)
|
||||
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) {
|
||||
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 resendVerifyEmail = (payload) => apiRequest('/resend-verify-email', { 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) {
|
||||
const value = String(base64url || '')
|
||||
@@ -251,6 +264,70 @@ function redirectAfterLogin() {
|
||||
}, 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() {
|
||||
clearNotice()
|
||||
|
||||
@@ -421,6 +498,8 @@ function goRegister() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSocialConfig()
|
||||
await handleSocialCallback()
|
||||
if (needCaptcha.value) {
|
||||
await refreshLoginCaptcha()
|
||||
}
|
||||
@@ -486,13 +565,24 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-login" :disabled="loading" @click="onSubmit">
|
||||
{{ loading ? '登录中...' : '登录系统' }}
|
||||
<button type="button" class="btn-login" :disabled="loading || socialCallbackLoading" @click="onSubmit">
|
||||
{{ loading || socialCallbackLoading ? '登录中...' : '登录系统' }}
|
||||
</button>
|
||||
<button type="button" class="btn-passkey" :disabled="passkeyLoading" @click="onPasskeyLogin">
|
||||
{{ passkeyLoading ? 'Passkey验证中...' : '使用 Passkey 登录' }}
|
||||
</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">
|
||||
<button type="button" class="link-btn" @click="openForgot">忘记密码?</button>
|
||||
<button v-if="showResendLink" type="button" class="link-btn" @click="openResend">重发验证邮件</button>
|
||||
@@ -747,6 +837,28 @@ onMounted(async () => {
|
||||
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-login:disabled,
|
||||
.btn-ghost:disabled,
|
||||
|
||||
@@ -25,6 +25,26 @@ const errorText = ref('')
|
||||
const successTitle = 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 emailHint = computed(() => (emailVerifyEnabled.value ? '必填,用于账号验证' : '选填,用于找回密码和接收通知'))
|
||||
|
||||
@@ -55,6 +75,35 @@ function clearAlerts() {
|
||||
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() {
|
||||
clearAlerts()
|
||||
|
||||
@@ -104,11 +153,15 @@ async function onSubmit() {
|
||||
email,
|
||||
captcha_session: captchaSession.value,
|
||||
captcha,
|
||||
social_bind_token: pendingSocial.token || undefined,
|
||||
})
|
||||
|
||||
successTitle.value = res?.message || '注册成功'
|
||||
successDesc.value = res?.need_verify ? '请检查您的邮箱(包括垃圾邮件文件夹)' : ''
|
||||
ElMessage.success('注册成功')
|
||||
if (pendingSocial.token) {
|
||||
clearPendingSocialBind()
|
||||
}
|
||||
|
||||
form.username = ''
|
||||
form.password = ''
|
||||
@@ -134,6 +187,7 @@ function goLogin() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadPendingSocialBind()
|
||||
await refreshCaptcha()
|
||||
await loadEmailVerifyStatus()
|
||||
})
|
||||
@@ -158,6 +212,15 @@ onMounted(async () => {
|
||||
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-item label="用户名 *">
|
||||
<el-input v-model="form.username" placeholder="至少3个字符" autocomplete="username" />
|
||||
|
||||
87
app-frontend/src/pages/SocialBindCallbackPage.vue
Normal file
87
app-frontend/src/pages/SocialBindCallbackPage.vue
Normal 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>
|
||||
@@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
const LoginPage = () => import('../pages/LoginPage.vue')
|
||||
const RegisterPage = () => import('../pages/RegisterPage.vue')
|
||||
const ResetPasswordPage = () => import('../pages/ResetPasswordPage.vue')
|
||||
const SocialBindCallbackPage = () => import('../pages/SocialBindCallbackPage.vue')
|
||||
const VerifyResultPage = () => import('../pages/VerifyResultPage.vue')
|
||||
const AppLayout = () => import('../layouts/AppLayout.vue')
|
||||
|
||||
@@ -15,6 +16,7 @@ const routes = [
|
||||
{ path: '/login', name: 'login', component: LoginPage },
|
||||
{ path: '/register', name: 'register', component: RegisterPage },
|
||||
{ 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-bind-email/:token', name: 'verify_bind_email', component: VerifyResultPage },
|
||||
{
|
||||
|
||||
3
app.py
3
app.py
@@ -218,6 +218,9 @@ def enforce_csrf_protection():
|
||||
"/api/login",
|
||||
"/api/auth/login",
|
||||
"/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/verify",
|
||||
"/api/passkeys/login/options",
|
||||
|
||||
22
database.py
22
database.py
@@ -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.users import (
|
||||
approve_user,
|
||||
cleanup_expired_social_pending_binds,
|
||||
create_user,
|
||||
create_social_pending_bind,
|
||||
delete_user,
|
||||
delete_social_login_binding,
|
||||
delete_social_pending_bind,
|
||||
extend_user_vip,
|
||||
find_social_login_binding,
|
||||
find_user_social_login_binding,
|
||||
get_all_users,
|
||||
get_users_count,
|
||||
get_pending_users,
|
||||
get_social_pending_bind,
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
get_user_kdocs_settings,
|
||||
@@ -122,7 +129,10 @@ from db.users import (
|
||||
remove_user_vip,
|
||||
set_default_vip_days,
|
||||
set_user_vip,
|
||||
list_social_login_bindings,
|
||||
update_social_login_binding_profile,
|
||||
update_user_kdocs_settings,
|
||||
upsert_social_login_binding,
|
||||
verify_user,
|
||||
)
|
||||
from db.security import record_login_context
|
||||
@@ -134,7 +144,7 @@ logger = get_logger(__name__)
|
||||
DB_FILE = config.DB_FILE
|
||||
|
||||
# 数据库版本 (用于迁移管理)
|
||||
DB_VERSION = 21
|
||||
DB_VERSION = 22
|
||||
|
||||
|
||||
# ==================== 系统配置缓存(P1 / O-03) ====================
|
||||
@@ -261,6 +271,11 @@ def update_system_config(
|
||||
kdocs_row_start=None,
|
||||
kdocs_row_end=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(
|
||||
@@ -290,6 +305,11 @@ def update_system_config(
|
||||
kdocs_row_start=kdocs_row_start,
|
||||
kdocs_row_end=kdocs_row_end,
|
||||
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:
|
||||
invalidate_system_config_cache()
|
||||
|
||||
20
db/admin.py
20
db/admin.py
@@ -42,6 +42,11 @@ _DEFAULT_SYSTEM_CONFIG = {
|
||||
"kdocs_admin_notify_email": "",
|
||||
"kdocs_row_start": 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 = (
|
||||
@@ -71,6 +76,11 @@ _SYSTEM_CONFIG_UPDATERS = (
|
||||
("kdocs_admin_notify_email", "kdocs_admin_notify_email"),
|
||||
("kdocs_row_start", "kdocs_row_start"),
|
||||
("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_end=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:
|
||||
"""更新系统配置(仅更新DB,不做缓存处理)。"""
|
||||
arg_values = {
|
||||
@@ -345,6 +360,11 @@ def update_system_config(
|
||||
"kdocs_row_start": kdocs_row_start,
|
||||
"kdocs_row_end": kdocs_row_end,
|
||||
"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 = []
|
||||
|
||||
@@ -76,6 +76,7 @@ def _get_migration_steps():
|
||||
(19, _migrate_to_v19),
|
||||
(20, _migrate_to_v20),
|
||||
(21, _migrate_to_v21),
|
||||
(22, _migrate_to_v22),
|
||||
]
|
||||
|
||||
|
||||
@@ -933,3 +934,71 @@ def _migrate_to_v21(conn):
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
46
db/schema.py
46
db/schema.py
@@ -246,11 +246,52 @@ def ensure_schema(conn) -> None:
|
||||
kdocs_admin_notify_email TEXT DEFAULT '',
|
||||
kdocs_row_start 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
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 聚合登录绑定表
|
||||
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(
|
||||
"""
|
||||
@@ -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_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_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_ip ON threat_events(ip)")
|
||||
|
||||
150
db/users.py
150
db/users.py
@@ -297,6 +297,156 @@ def get_user_by_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):
|
||||
normalized_limit = None
|
||||
if limit is not None:
|
||||
|
||||
@@ -10,6 +10,7 @@ def register_blueprints(app) -> None:
|
||||
from routes.api_auth import api_auth_bp
|
||||
from routes.api_schedules import api_schedules_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.health import health_bp
|
||||
from routes.pages import pages_bp
|
||||
@@ -17,6 +18,7 @@ def register_blueprints(app) -> None:
|
||||
app.register_blueprint(pages_bp)
|
||||
app.register_blueprint(health_bp)
|
||||
app.register_blueprint(api_auth_bp)
|
||||
app.register_blueprint(api_social_bp)
|
||||
app.register_blueprint(api_user_bp)
|
||||
app.register_blueprint(api_accounts_bp)
|
||||
app.register_blueprint(api_screenshots_bp)
|
||||
|
||||
@@ -30,6 +30,7 @@ from services.passkeys import (
|
||||
normalize_device_name,
|
||||
verify_authentication,
|
||||
)
|
||||
from services.social_login import parse_providers, provider_label
|
||||
from services.state import (
|
||||
check_ip_request_rate,
|
||||
check_email_rate_limit,
|
||||
@@ -228,6 +229,7 @@ def register():
|
||||
email = data.get("email", "").strip().lower()
|
||||
captcha_session = data.get("captcha_session", "")
|
||||
captcha_code = data.get("captcha", "").strip()
|
||||
social_bind_token = str(data.get("social_bind_token") or "").strip()
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({"error": "用户名和密码不能为空"}), 400
|
||||
@@ -273,6 +275,26 @@ def register():
|
||||
|
||||
user_id = database.create_user(username, password, email)
|
||||
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_vip_days > 0:
|
||||
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)
|
||||
if result["success"]:
|
||||
message = _with_vip_suffix(
|
||||
"注册成功!验证邮件已发送(可直接登录,建议完成邮箱验证)",
|
||||
f"注册成功!验证邮件已发送(可直接登录,建议完成邮箱验证){social_bind_message}",
|
||||
auto_approve_enabled,
|
||||
auto_approve_vip_days,
|
||||
)
|
||||
@@ -289,13 +311,15 @@ def register():
|
||||
|
||||
logger.error(f"注册验证邮件发送失败: {result['error']}")
|
||||
message = _with_vip_suffix(
|
||||
f"注册成功,但验证邮件发送失败({result['error']})。你仍可直接登录",
|
||||
f"注册成功,但验证邮件发送失败({result['error']})。你仍可直接登录{social_bind_message}",
|
||||
auto_approve_enabled,
|
||||
auto_approve_vip_days,
|
||||
)
|
||||
return jsonify({"success": True, "message": message, "need_verify": True})
|
||||
|
||||
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({"error": "用户名已存在"}), 400
|
||||
|
||||
|
||||
333
routes/api_social.py
Normal file
333
routes/api_social.py
Normal 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
306
services/social_login.py
Normal 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)
|
||||
@@ -1,42 +1,42 @@
|
||||
{
|
||||
"_MetricGrid-BnihYB_8.js": {
|
||||
"file": "assets/MetricGrid-BnihYB_8.js",
|
||||
"_MetricGrid-BR486o_b.css": {
|
||||
"file": "assets/MetricGrid-BR486o_b.css",
|
||||
"src": "_MetricGrid-BR486o_b.css"
|
||||
},
|
||||
"_MetricGrid-C3Xjc9mZ.js": {
|
||||
"file": "assets/MetricGrid-C3Xjc9mZ.js",
|
||||
"name": "MetricGrid",
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_vendor-vue-CVxSw_oJ.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/MetricGrid-yP_dkP6X.css"
|
||||
"assets/MetricGrid-BR486o_b.css"
|
||||
]
|
||||
},
|
||||
"_MetricGrid-yP_dkP6X.css": {
|
||||
"file": "assets/MetricGrid-yP_dkP6X.css",
|
||||
"src": "_MetricGrid-yP_dkP6X.css"
|
||||
},
|
||||
"_email-px7YBG2O.js": {
|
||||
"file": "assets/email-px7YBG2O.js",
|
||||
"_email-Mh1SHQbX.js": {
|
||||
"file": "assets/email-Mh1SHQbX.js",
|
||||
"name": "email",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_system-ZDPnxnIu.js": {
|
||||
"file": "assets/system-ZDPnxnIu.js",
|
||||
"_system-CYbWdReq.js": {
|
||||
"file": "assets/system-CYbWdReq.js",
|
||||
"name": "system",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_tasks-Bep0SUyu.js": {
|
||||
"file": "assets/tasks-Bep0SUyu.js",
|
||||
"_tasks-B7oNpIBD.js": {
|
||||
"file": "assets/tasks-B7oNpIBD.js",
|
||||
"name": "tasks",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_users-te9ySk34.js": {
|
||||
"file": "assets/users-te9ySk34.js",
|
||||
"_users-DzDcz9C_.js": {
|
||||
"file": "assets/users-DzDcz9C_.js",
|
||||
"name": "users",
|
||||
"imports": [
|
||||
"index.html"
|
||||
@@ -73,7 +73,7 @@
|
||||
"name": "vendor-vue"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-C1f9ticl.js",
|
||||
"file": "assets/index-DOvMEmc8.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
@@ -95,11 +95,11 @@
|
||||
"src/pages/SettingsPage.vue"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-Dh0BbqTX.css"
|
||||
"assets/index-CPs_XZ2s.css"
|
||||
]
|
||||
},
|
||||
"src/pages/AnnouncementsPage.vue": {
|
||||
"file": "assets/AnnouncementsPage-C6UgwLIT.js",
|
||||
"file": "assets/AnnouncementsPage-Dagm5PzE.js",
|
||||
"name": "AnnouncementsPage",
|
||||
"src": "src/pages/AnnouncementsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -111,52 +111,52 @@
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/AnnouncementsPage-DOwZaaOu.css"
|
||||
"assets/AnnouncementsPage-tpO97PUg.css"
|
||||
]
|
||||
},
|
||||
"src/pages/EmailPage.vue": {
|
||||
"file": "assets/EmailPage-CATruPK6.js",
|
||||
"file": "assets/EmailPage-DiZA9Kx_.js",
|
||||
"name": "EmailPage",
|
||||
"src": "src/pages/EmailPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_email-px7YBG2O.js",
|
||||
"_email-Mh1SHQbX.js",
|
||||
"index.html",
|
||||
"_MetricGrid-BnihYB_8.js",
|
||||
"_MetricGrid-C3Xjc9mZ.js",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
"_vendor-axios-B9ygI19o.js",
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/EmailPage-BmPCDPYC.css"
|
||||
"assets/EmailPage-CTHxGzDv.css"
|
||||
]
|
||||
},
|
||||
"src/pages/FeedbacksPage.vue": {
|
||||
"file": "assets/FeedbacksPage-BAnFKHSL.js",
|
||||
"file": "assets/FeedbacksPage-DrMVqBKf.js",
|
||||
"name": "FeedbacksPage",
|
||||
"src": "src/pages/FeedbacksPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-BnihYB_8.js",
|
||||
"_MetricGrid-C3Xjc9mZ.js",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
"_vendor-axios-B9ygI19o.js",
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/FeedbacksPage-mrXjCiV2.css"
|
||||
"assets/FeedbacksPage-CPmSqIaj.css"
|
||||
]
|
||||
},
|
||||
"src/pages/LogsPage.vue": {
|
||||
"file": "assets/LogsPage-DFPeq0bL.js",
|
||||
"file": "assets/LogsPage-Cy6Q0ave.js",
|
||||
"name": "LogsPage",
|
||||
"src": "src/pages/LogsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-te9ySk34.js",
|
||||
"_tasks-Bep0SUyu.js",
|
||||
"_users-DzDcz9C_.js",
|
||||
"_tasks-B7oNpIBD.js",
|
||||
"index.html",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
@@ -164,48 +164,48 @@
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/LogsPage-D1bozCEo.css"
|
||||
"assets/LogsPage-BUgx3sZr.css"
|
||||
]
|
||||
},
|
||||
"src/pages/ReportPage.vue": {
|
||||
"file": "assets/ReportPage-fzVH-d9u.js",
|
||||
"file": "assets/ReportPage-BMEJM5Hr.js",
|
||||
"name": "ReportPage",
|
||||
"src": "src/pages/ReportPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"index.html",
|
||||
"_email-px7YBG2O.js",
|
||||
"_tasks-Bep0SUyu.js",
|
||||
"_system-ZDPnxnIu.js",
|
||||
"_MetricGrid-BnihYB_8.js",
|
||||
"_email-Mh1SHQbX.js",
|
||||
"_tasks-B7oNpIBD.js",
|
||||
"_system-CYbWdReq.js",
|
||||
"_MetricGrid-C3Xjc9mZ.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
"_vendor-misc-BeoNyvBp.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/ReportPage-BCQBCnjY.css"
|
||||
"assets/ReportPage-IaDpUFfl.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SecurityPage.vue": {
|
||||
"file": "assets/SecurityPage-xwMQfhuh.js",
|
||||
"file": "assets/SecurityPage-yzYEGeTN.js",
|
||||
"name": "SecurityPage",
|
||||
"src": "src/pages/SecurityPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-BnihYB_8.js",
|
||||
"_MetricGrid-C3Xjc9mZ.js",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
"_vendor-axios-B9ygI19o.js",
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/SecurityPage-DN76ndc_.css"
|
||||
"assets/SecurityPage-C2-mJ7eD.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SettingsPage.vue": {
|
||||
"file": "assets/SettingsPage-DRqlQLxJ.js",
|
||||
"file": "assets/SettingsPage-DF5fL8gq.js",
|
||||
"name": "SettingsPage",
|
||||
"src": "src/pages/SettingsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -217,16 +217,16 @@
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/SettingsPage-BAa-Qu3q.css"
|
||||
"assets/SettingsPage-D-iYz1zh.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SystemPage.vue": {
|
||||
"file": "assets/SystemPage-D3eBPCNe.js",
|
||||
"file": "assets/SystemPage-DrM9-RI5.js",
|
||||
"name": "SystemPage",
|
||||
"src": "src/pages/SystemPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_system-ZDPnxnIu.js",
|
||||
"_system-CYbWdReq.js",
|
||||
"index.html",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
@@ -234,16 +234,16 @@
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/SystemPage-BhhEz4Qz.css"
|
||||
"assets/SystemPage-CTs6qr36.css"
|
||||
]
|
||||
},
|
||||
"src/pages/UsersPage.vue": {
|
||||
"file": "assets/UsersPage-DJZUCpfb.js",
|
||||
"file": "assets/UsersPage-RI5S3snx.js",
|
||||
"name": "UsersPage",
|
||||
"src": "src/pages/UsersPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-te9ySk34.js",
|
||||
"_users-DzDcz9C_.js",
|
||||
"index.html",
|
||||
"_vendor-element-B5S5pUKo.js",
|
||||
"_vendor-vue-CVxSw_oJ.js",
|
||||
@@ -251,7 +251,7 @@
|
||||
"_vendor-misc-BeoNyvBp.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/UsersPage-BNDnhJe0.css"
|
||||
"assets/UsersPage-CgYh6JHW.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
File diff suppressed because one or more lines are too long
1
static/admin/assets/AnnouncementsPage-tpO97PUg.css
Normal file
1
static/admin/assets/AnnouncementsPage-tpO97PUg.css
Normal 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}
|
||||
@@ -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}
|
||||
1
static/admin/assets/EmailPage-CTHxGzDv.css
Normal file
1
static/admin/assets/EmailPage-CTHxGzDv.css
Normal 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
1
static/admin/assets/FeedbacksPage-CPmSqIaj.css
Normal file
1
static/admin/assets/FeedbacksPage-CPmSqIaj.css
Normal 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}
|
||||
File diff suppressed because one or more lines are too long
@@ -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}
|
||||
1
static/admin/assets/LogsPage-BUgx3sZr.css
Normal file
1
static/admin/assets/LogsPage-BUgx3sZr.css
Normal 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
@@ -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}
|
||||
@@ -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}}
|
||||
@@ -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
1
static/admin/assets/ReportPage-IaDpUFfl.css
Normal file
1
static/admin/assets/ReportPage-IaDpUFfl.css
Normal file
File diff suppressed because one or more lines are too long
1
static/admin/assets/SecurityPage-C2-mJ7eD.css
Normal file
1
static/admin/assets/SecurityPage-C2-mJ7eD.css
Normal 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}
|
||||
@@ -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}
|
||||
File diff suppressed because one or more lines are too long
@@ -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}
|
||||
1
static/admin/assets/SettingsPage-D-iYz1zh.css
Normal file
1
static/admin/assets/SettingsPage-D-iYz1zh.css
Normal 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}
|
||||
File diff suppressed because one or more lines are too long
@@ -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}}
|
||||
1
static/admin/assets/SystemPage-CTs6qr36.css
Normal file
1
static/admin/assets/SystemPage-CTs6qr36.css
Normal 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
6
static/admin/assets/SystemPage-DrM9-RI5.js
Normal file
6
static/admin/assets/SystemPage-DrM9-RI5.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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}
|
||||
1
static/admin/assets/UsersPage-CgYh6JHW.css
Normal file
1
static/admin/assets/UsersPage-CgYh6JHW.css
Normal 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
@@ -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
1
static/admin/assets/system-CYbWdReq.js
Normal file
1
static/admin/assets/system-CYbWdReq.js
Normal 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};
|
||||
@@ -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};
|
||||
@@ -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};
|
||||
@@ -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};
|
||||
@@ -5,15 +5,16 @@
|
||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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-misc-BeoNyvBp.js">
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-element-B5S5pUKo.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/index-Dh0BbqTX.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CPs_XZ2s.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,111 +1,203 @@
|
||||
{
|
||||
"_accounts-3bM7Wy59.js": {
|
||||
"file": "assets/accounts-3bM7Wy59.js",
|
||||
"name": "accounts",
|
||||
"_SocialLoginButtons-BlVSr6Mm.js": {
|
||||
"file": "assets/SocialLoginButtons-BlVSr6Mm.js",
|
||||
"name": "SocialLoginButtons",
|
||||
"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": [
|
||||
"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": {
|
||||
"file": "assets/accounts-D_6SYB2i.css",
|
||||
"src": "_accounts-D_6SYB2i.css"
|
||||
},
|
||||
"_auth-CX9p6ZYg.js": {
|
||||
"file": "assets/auth-CX9p6ZYg.js",
|
||||
"_accounts-DzntEHJR.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",
|
||||
"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": {
|
||||
"file": "assets/el-alert-B-NgiIln.css",
|
||||
"src": "_el-alert-B-NgiIln.css"
|
||||
},
|
||||
"_el-alert-DB2IQLpH.js": {
|
||||
"file": "assets/el-alert-DB2IQLpH.js",
|
||||
"_el-alert-DTUOkrAB.js": {
|
||||
"file": "assets/el-alert-DTUOkrAB.js",
|
||||
"name": "el-alert",
|
||||
"imports": [
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_http-CdvgQxJu.js"
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_index-CoYtSGUZ.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/el-alert-B-NgiIln.css"
|
||||
]
|
||||
},
|
||||
"_el-button-DF1Fi_iE.css": {
|
||||
"file": "assets/el-button-DF1Fi_iE.css",
|
||||
"src": "_el-button-DF1Fi_iE.css"
|
||||
"_el-button-BRDnKxwT.css": {
|
||||
"file": "assets/el-button-BRDnKxwT.css",
|
||||
"src": "_el-button-BRDnKxwT.css"
|
||||
},
|
||||
"_el-button-DWxIvzz-.js": {
|
||||
"file": "assets/el-button-DWxIvzz-.js",
|
||||
"_el-button-LKkD3jQh.js": {
|
||||
"file": "assets/el-button-LKkD3jQh.js",
|
||||
"name": "el-button",
|
||||
"imports": [
|
||||
"_vendor-vue-DxN60LNb.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-vue-WbiK4TmU.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/el-button-DF1Fi_iE.css"
|
||||
"assets/el-button-BRDnKxwT.css"
|
||||
]
|
||||
},
|
||||
"_el-card-BqOrgVp1.css": {
|
||||
"file": "assets/el-card-BqOrgVp1.css",
|
||||
"src": "_el-card-BqOrgVp1.css"
|
||||
},
|
||||
"_el-card-DfVpO1U5.js": {
|
||||
"file": "assets/el-card-DfVpO1U5.js",
|
||||
"_el-card-CfK866jr.js": {
|
||||
"file": "assets/el-card-CfK866jr.js",
|
||||
"name": "el-card",
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_vendor-vue-DxN60LNb.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_vendor-vue-WbiK4TmU.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/el-card-BqOrgVp1.css"
|
||||
]
|
||||
},
|
||||
"_el-overlay-Bd56Lw6C.css": {
|
||||
"file": "assets/el-overlay-Bd56Lw6C.css",
|
||||
"src": "_el-overlay-Bd56Lw6C.css"
|
||||
},
|
||||
"_el-overlay-C_JJBVfE.js": {
|
||||
"file": "assets/el-overlay-C_JJBVfE.js",
|
||||
"name": "el-overlay",
|
||||
"_el-empty-B4_NEFfq.js": {
|
||||
"file": "assets/el-empty-B4_NEFfq.js",
|
||||
"name": "el-empty",
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_vendor-vue-DxN60LNb.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_el-overlay-hge8bsIn.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_el-button-LKkD3jQh.js"
|
||||
],
|
||||
"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": {
|
||||
"file": "assets/el-pagination-B1FwbX1n.css",
|
||||
"src": "_el-pagination-B1FwbX1n.css"
|
||||
},
|
||||
"_el-pagination-BY1uI-wO.js": {
|
||||
"file": "assets/el-pagination-BY1uI-wO.js",
|
||||
"_el-pagination-kVJ2XlAP.js": {
|
||||
"file": "assets/el-pagination-kVJ2XlAP.js",
|
||||
"name": "el-pagination",
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_el-select-B0VMg2td.js",
|
||||
"_http-CdvgQxJu.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_el-select-CBs1QjJm.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/el-pagination-B1FwbX1n.css"
|
||||
]
|
||||
},
|
||||
"_el-select-B0VMg2td.js": {
|
||||
"file": "assets/el-select-B0VMg2td.js",
|
||||
"_el-select-CBs1QjJm.js": {
|
||||
"file": "assets/el-select-CBs1QjJm.js",
|
||||
"name": "el-select",
|
||||
"imports": [
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_el-overlay-C_JJBVfE.js",
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_http-CdvgQxJu.js"
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_el-overlay-hge8bsIn.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/el-select-D_oyzAZN.css"
|
||||
@@ -115,40 +207,56 @@
|
||||
"file": "assets/el-select-D_oyzAZN.css",
|
||||
"src": "_el-select-D_oyzAZN.css"
|
||||
},
|
||||
"_http-CdvgQxJu.js": {
|
||||
"file": "assets/http-CdvgQxJu.js",
|
||||
"name": "http",
|
||||
"_el-skeleton-item-BLY1jEuR.css": {
|
||||
"file": "assets/el-skeleton-item-BLY1jEuR.css",
|
||||
"src": "_el-skeleton-item-BLY1jEuR.css"
|
||||
},
|
||||
"_el-skeleton-item-CD5Idavp.js": {
|
||||
"file": "assets/el-skeleton-item-CD5Idavp.js",
|
||||
"name": "el-skeleton-item",
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_vendor-vue-WbiK4TmU.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/http-D6B3r8CH.css"
|
||||
"assets/el-skeleton-item-BLY1jEuR.css"
|
||||
]
|
||||
},
|
||||
"_http-D6B3r8CH.css": {
|
||||
"file": "assets/http-D6B3r8CH.css",
|
||||
"src": "_http-D6B3r8CH.css"
|
||||
"_http-BoPYlvwK.js": {
|
||||
"file": "assets/http-BoPYlvwK.js",
|
||||
"name": "http",
|
||||
"imports": [
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
]
|
||||
},
|
||||
"_isArrayLikeObject-BjIRF-cS.js": {
|
||||
"file": "assets/isArrayLikeObject-BjIRF-cS.js",
|
||||
"_index-CoYtSGUZ.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",
|
||||
"imports": [
|
||||
"_http-CdvgQxJu.js",
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-overlay-C_JJBVfE.js"
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-empty-B4_NEFfq.js"
|
||||
]
|
||||
},
|
||||
"_password-7ryi82gE.js": {
|
||||
"file": "assets/password-7ryi82gE.js",
|
||||
"name": "password"
|
||||
},
|
||||
"_settings-Ddo8isuv.js": {
|
||||
"file": "assets/settings-Ddo8isuv.js",
|
||||
"_settings-Db4PmPGC.js": {
|
||||
"file": "assets/settings-Db4PmPGC.js",
|
||||
"name": "settings",
|
||||
"imports": [
|
||||
"_http-CdvgQxJu.js"
|
||||
"_http-BoPYlvwK.js"
|
||||
]
|
||||
},
|
||||
"_style-BHGuKLUF.css": {
|
||||
@@ -166,15 +274,19 @@
|
||||
"file": "assets/user-B7bO5p8k.css",
|
||||
"src": "_user-B7bO5p8k.css"
|
||||
},
|
||||
"_user-Bl59IefW.js": {
|
||||
"file": "assets/user-Bl59IefW.js",
|
||||
"_user-BlXB4Zbh.js": {
|
||||
"file": "assets/user-BlXB4Zbh.js",
|
||||
"name": "user",
|
||||
"imports": [
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_el-overlay-C_JJBVfE.js"
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_http-BoPYlvwK.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/user-B7bO5p8k.css"
|
||||
@@ -188,23 +300,24 @@
|
||||
"file": "assets/vendor-realtime-CA1CrNgP.js",
|
||||
"name": "vendor-realtime"
|
||||
},
|
||||
"_vendor-vue-DxN60LNb.js": {
|
||||
"file": "assets/vendor-vue-DxN60LNb.js",
|
||||
"_vendor-vue-WbiK4TmU.js": {
|
||||
"file": "assets/vendor-vue-WbiK4TmU.js",
|
||||
"name": "vendor-vue"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/app-BmSIJu6s.js",
|
||||
"file": "assets/app-CV_JALyE.js",
|
||||
"name": "app",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-CEbARg1o.js",
|
||||
"_vendor-vue-DxN60LNb.js"
|
||||
"_vendor-vue-WbiK4TmU.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"src/pages/LoginPage.vue",
|
||||
"src/pages/RegisterPage.vue",
|
||||
"src/pages/ResetPasswordPage.vue",
|
||||
"src/pages/SocialBindCallbackPage.vue",
|
||||
"src/pages/VerifyResultPage.vue",
|
||||
"src/layouts/AppLayout.vue",
|
||||
"src/pages/AccountsPage.vue",
|
||||
@@ -213,172 +326,244 @@
|
||||
]
|
||||
},
|
||||
"login.html": {
|
||||
"file": "assets/login-BtMsx-ZC.js",
|
||||
"file": "assets/login-rQcRwu0T.js",
|
||||
"name": "login",
|
||||
"src": "login.html",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-CEbARg1o.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"src/pages/LoginPage.vue"
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"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": {
|
||||
"file": "assets/AppLayout-xWeMM3hH.js",
|
||||
"file": "assets/AppLayout-D9A8Va7K.js",
|
||||
"name": "AppLayout",
|
||||
"src": "src/layouts/AppLayout.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_user-Bl59IefW.js",
|
||||
"_el-overlay-C_JJBVfE.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_settings-Ddo8isuv.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_user-BlXB4Zbh.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-skeleton-item-CD5Idavp.js",
|
||||
"_el-input-BaZNy9Kg.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",
|
||||
"_style-CEbARg1o.js",
|
||||
"_isArrayLikeObject-BjIRF-cS.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_isArrayLikeObject-B5fs56rA.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/AppLayout-DxoFHO3h.css"
|
||||
"assets/AppLayout-CJKAa2WS.css"
|
||||
]
|
||||
},
|
||||
"src/pages/AccountsPage.vue": {
|
||||
"file": "assets/AccountsPage-DnOxRP7e.js",
|
||||
"file": "assets/AccountsPage-B7MLZrfr.js",
|
||||
"name": "AccountsPage",
|
||||
"src": "src/pages/AccountsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-overlay-C_JJBVfE.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_user-Bl59IefW.js",
|
||||
"_accounts-3bM7Wy59.js",
|
||||
"_el-select-B0VMg2td.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_settings-Ddo8isuv.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-overlay-hge8bsIn.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_user-BlXB4Zbh.js",
|
||||
"_accounts-DzntEHJR.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_el-skeleton-item-CD5Idavp.js",
|
||||
"_el-select-CBs1QjJm.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_settings-Db4PmPGC.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_vendor-realtime-CA1CrNgP.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/AccountsPage-iiBFNme8.css"
|
||||
"assets/AccountsPage-DKewJ7S7.css"
|
||||
]
|
||||
},
|
||||
"src/pages/LoginPage.vue": {
|
||||
"file": "assets/LoginPage-D5iXLq7p.js",
|
||||
"file": "assets/LoginPage-BtooAZsk.js",
|
||||
"name": "LoginPage",
|
||||
"src": "src/pages/LoginPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_style-CEbARg1o.js"
|
||||
"_vendor-vue-WbiK4TmU.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": [
|
||||
"assets/LoginPage-DTj5KeC4.css"
|
||||
"assets/LoginPage-vCVLchWz.css"
|
||||
]
|
||||
},
|
||||
"src/pages/RegisterPage.vue": {
|
||||
"file": "assets/RegisterPage-4xFnBJCQ.js",
|
||||
"file": "assets/RegisterPage-Cb1mme2j.js",
|
||||
"name": "RegisterPage",
|
||||
"src": "src/pages/RegisterPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_auth-CX9p6ZYg.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_auth-B5cl_nsV.js",
|
||||
"_password-7ryi82gE.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/RegisterPage-BOcNcW5D.css"
|
||||
"assets/RegisterPage-LYXwWYc1.css"
|
||||
]
|
||||
},
|
||||
"src/pages/ResetPasswordPage.vue": {
|
||||
"file": "assets/ResetPasswordPage-lX7l6Nbu.js",
|
||||
"file": "assets/ResetPasswordPage-CUOK0fe1.js",
|
||||
"name": "ResetPasswordPage",
|
||||
"src": "src/pages/ResetPasswordPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_auth-CX9p6ZYg.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_auth-B5cl_nsV.js",
|
||||
"_password-7ryi82gE.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/ResetPasswordPage-DybfLMAw.css"
|
||||
"assets/ResetPasswordPage-DAB63ins.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SchedulesPage.vue": {
|
||||
"file": "assets/SchedulesPage-TUv7nqYq.js",
|
||||
"file": "assets/SchedulesPage-0TKGPmUl.js",
|
||||
"name": "SchedulesPage",
|
||||
"src": "src/pages/SchedulesPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-overlay-C_JJBVfE.js",
|
||||
"_el-alert-DB2IQLpH.js",
|
||||
"_el-select-B0VMg2td.js",
|
||||
"_user-Bl59IefW.js",
|
||||
"_accounts-3bM7Wy59.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_el-pagination-BY1uI-wO.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_el-overlay-hge8bsIn.js",
|
||||
"_el-alert-DTUOkrAB.js",
|
||||
"_el-select-CBs1QjJm.js",
|
||||
"_user-BlXB4Zbh.js",
|
||||
"_accounts-DzntEHJR.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_el-pagination-kVJ2XlAP.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_el-skeleton-item-CD5Idavp.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_isArrayLikeObject-BjIRF-cS.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_isArrayLikeObject-B5fs56rA.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/SchedulesPage-BIuHs5oJ.css"
|
||||
"assets/SchedulesPage-Dxq2ghmQ.css"
|
||||
]
|
||||
},
|
||||
"src/pages/ScreenshotsPage.vue": {
|
||||
"file": "assets/ScreenshotsPage-7CRd3Hlo.js",
|
||||
"file": "assets/ScreenshotsPage-F6GpvKGW.js",
|
||||
"name": "ScreenshotsPage",
|
||||
"src": "src/pages/ScreenshotsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-overlay-C_JJBVfE.js",
|
||||
"_el-pagination-BY1uI-wO.js",
|
||||
"_el-select-B0VMg2td.js",
|
||||
"_http-CdvgQxJu.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-overlay-hge8bsIn.js",
|
||||
"_el-pagination-kVJ2XlAP.js",
|
||||
"_el-empty-B4_NEFfq.js",
|
||||
"_el-select-CBs1QjJm.js",
|
||||
"_el-input-BaZNy9Kg.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_el-skeleton-item-CD5Idavp.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_http-BoPYlvwK.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_aria-DLpFpzDe.js",
|
||||
"_index-CoYtSGUZ.js",
|
||||
"_vendor-axios-B9ygI19o.js"
|
||||
],
|
||||
"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": {
|
||||
"file": "assets/VerifyResultPage-bifpPyoE.js",
|
||||
"file": "assets/VerifyResultPage-BUSE4fL8.js",
|
||||
"name": "VerifyResultPage",
|
||||
"src": "src/pages/VerifyResultPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_el-button-DWxIvzz-.js",
|
||||
"_el-card-DfVpO1U5.js",
|
||||
"_vendor-vue-DxN60LNb.js",
|
||||
"_style-CEbARg1o.js"
|
||||
"_base-xgxQQEpV.js",
|
||||
"_el-card-CfK866jr.js",
|
||||
"_el-button-LKkD3jQh.js",
|
||||
"_vendor-vue-WbiK4TmU.js",
|
||||
"_style-CEbARg1o.js",
|
||||
"_index-CoYtSGUZ.js"
|
||||
],
|
||||
"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
1
static/app/assets/AppLayout-D9A8Va7K.js
Normal file
1
static/app/assets/AppLayout-D9A8Va7K.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/app/assets/LoginPage-BtooAZsk.js
Normal file
1
static/app/assets/LoginPage-BtooAZsk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/app/assets/LoginPage-vCVLchWz.css
Normal file
1
static/app/assets/LoginPage-vCVLchWz.css
Normal file
File diff suppressed because one or more lines are too long
@@ -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};
|
||||
@@ -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}
|
||||
1
static/app/assets/RegisterPage-Cb1mme2j.js
Normal file
1
static/app/assets/RegisterPage-Cb1mme2j.js
Normal file
File diff suppressed because one or more lines are too long
1
static/app/assets/RegisterPage-LYXwWYc1.css
Normal file
1
static/app/assets/RegisterPage-LYXwWYc1.css
Normal 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}
|
||||
1
static/app/assets/ResetPasswordPage-CUOK0fe1.js
Normal file
1
static/app/assets/ResetPasswordPage-CUOK0fe1.js
Normal 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};
|
||||
1
static/app/assets/ResetPasswordPage-DAB63ins.css
Normal file
1
static/app/assets/ResetPasswordPage-DAB63ins.css
Normal 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}
|
||||
@@ -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}
|
||||
@@ -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};
|
||||
1
static/app/assets/SchedulesPage-0TKGPmUl.js
Normal file
1
static/app/assets/SchedulesPage-0TKGPmUl.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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
1
static/app/assets/ScreenshotsPage-BhLfAzHf.css
Normal file
1
static/app/assets/ScreenshotsPage-BhLfAzHf.css
Normal 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}}
|
||||
1
static/app/assets/ScreenshotsPage-F6GpvKGW.js
Normal file
1
static/app/assets/ScreenshotsPage-F6GpvKGW.js
Normal file
File diff suppressed because one or more lines are too long
1
static/app/assets/SocialBindCallbackPage-BZgzv_7a.css
Normal file
1
static/app/assets/SocialBindCallbackPage-BZgzv_7a.css
Normal 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}
|
||||
1
static/app/assets/SocialBindCallbackPage-DraQ_mks.js
Normal file
1
static/app/assets/SocialBindCallbackPage-DraQ_mks.js
Normal 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};
|
||||
1
static/app/assets/SocialLoginButtons-BlVSr6Mm.js
Normal file
1
static/app/assets/SocialLoginButtons-BlVSr6Mm.js
Normal file
File diff suppressed because one or more lines are too long
1
static/app/assets/SocialLoginButtons-qO3SCoE7.css
Normal file
1
static/app/assets/SocialLoginButtons-qO3SCoE7.css
Normal 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}
|
||||
1
static/app/assets/VerifyResultPage-BUSE4fL8.js
Normal file
1
static/app/assets/VerifyResultPage-BUSE4fL8.js
Normal 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};
|
||||
@@ -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}
|
||||
@@ -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};
|
||||
@@ -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};
|
||||
@@ -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");
|
||||
2
static/app/assets/app-CV_JALyE.js
Normal file
2
static/app/assets/app-CV_JALyE.js
Normal 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");
|
||||
1
static/app/assets/aria-DLpFpzDe.js
Normal file
1
static/app/assets/aria-DLpFpzDe.js
Normal 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};
|
||||
1
static/app/assets/auth-B5cl_nsV.js
Normal file
1
static/app/assets/auth-B5cl_nsV.js
Normal 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};
|
||||
@@ -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};
|
||||
1
static/app/assets/base-CiSqh4F9.css
Normal file
1
static/app/assets/base-CiSqh4F9.css
Normal file
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
Reference in New Issue
Block a user