refactor: remove passkey login

This commit is contained in:
237899745
2026-05-27 22:32:42 +08:00
parent 89cb98233f
commit 0443c976fc
105 changed files with 410 additions and 2505 deletions

View File

@@ -20,31 +20,6 @@ export async function logout() {
return data return data
} }
export async function fetchAdminPasskeys() {
const { data } = await api.get('/admin/passkeys')
return data
}
export async function createAdminPasskeyOptions(payload = {}) {
const { data } = await api.post('/admin/passkeys/register/options', payload)
return data
}
export async function createAdminPasskeyVerify(payload = {}) {
const { data } = await api.post('/admin/passkeys/register/verify', payload)
return data
}
export async function deleteAdminPasskey(passkeyId) {
const { data } = await api.delete(`/admin/passkeys/${passkeyId}`)
return data
}
export async function reportAdminPasskeyClientError(payload = {}) {
const { data } = await api.post('/admin/passkeys/client-error', payload)
return data
}
export async function fetchAdminSocialBindings() { export async function fetchAdminSocialBindings() {
const { data } = await api.get('/admin/social-bindings') const { data } = await api.get('/admin/social-bindings')
return data return data

View File

@@ -5,32 +5,19 @@ import QrcodeVue from 'qrcode.vue'
import { import {
createAdminSocialLoginUrl, createAdminSocialLoginUrl,
createAdminPasskeyOptions,
createAdminPasskeyVerify,
fetchAdminSocialBindings, fetchAdminSocialBindings,
deleteAdminPasskey,
fetchAdminPasskeys,
logout, logout,
pollAdminSocialLogin, pollAdminSocialLogin,
reportAdminPasskeyClientError,
unbindAdminSocial, unbindAdminSocial,
updateAdminPassword, updateAdminPassword,
updateAdminUsername, updateAdminUsername,
} from '../api/admin' } from '../api/admin'
import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey'
const username = ref('') const username = ref('')
const currentPassword = ref('') const currentPassword = ref('')
const password = ref('') const password = ref('')
const confirmPassword = ref('') const confirmPassword = ref('')
const submitting = ref(false) const submitting = ref(false)
const passkeyLoading = ref(false)
const passkeyAddLoading = ref(false)
const passkeyDeviceName = ref('')
const passkeyItems = ref([])
const passkeyRegisterOptions = ref(null)
const passkeyRegisterOptionsAt = ref(0)
const PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS = 240000
const socialBindingsLoading = ref(false) const socialBindingsLoading = ref(false)
const socialBindLoadingProvider = ref('') const socialBindLoadingProvider = ref('')
const socialBindings = ref([]) const socialBindings = ref([])
@@ -148,113 +135,6 @@ async function savePassword() {
} }
} }
async function loadPasskeys() {
passkeyLoading.value = true
try {
const data = await fetchAdminPasskeys()
passkeyItems.value = Array.isArray(data?.items) ? data.items : []
if (passkeyItems.value.length < 3) {
await prefetchPasskeyRegisterOptions()
} else {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
} catch {
passkeyItems.value = []
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
} finally {
passkeyLoading.value = false
}
}
function getCachedPasskeyRegisterOptions() {
if (!passkeyRegisterOptions.value) return null
if (Date.now() - Number(passkeyRegisterOptionsAt.value || 0) > PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS) return null
return passkeyRegisterOptions.value
}
async function prefetchPasskeyRegisterOptions() {
try {
const res = await createAdminPasskeyOptions({})
passkeyRegisterOptions.value = res
passkeyRegisterOptionsAt.value = Date.now()
} catch {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
}
async function addPasskey() {
if (!isPasskeyAvailable()) {
ElMessage.error('当前浏览器或环境不支持Passkey需 HTTPS')
return
}
if (passkeyItems.value.length >= 3) {
ElMessage.error('最多可绑定3台设备')
return
}
passkeyAddLoading.value = true
try {
let optionsRes = getCachedPasskeyRegisterOptions()
if (!optionsRes) {
optionsRes = await createAdminPasskeyOptions({})
}
const credential = await createPasskey(optionsRes?.publicKey || {})
await createAdminPasskeyVerify({ credential, device_name: passkeyDeviceName.value.trim() })
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
passkeyDeviceName.value = ''
ElMessage.success('Passkey设备添加成功')
await loadPasskeys()
} catch (e) {
try {
await reportAdminPasskeyClientError({
stage: 'register',
source: 'admin-settings',
name: e?.name || '',
message: e?.message || '',
code: e?.code || '',
user_agent: navigator.userAgent || '',
})
} catch {
// ignore report failure
}
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
await prefetchPasskeyRegisterOptions()
const data = e?.response?.data
const message =
data?.error ||
getPasskeyClientErrorMessage(e, 'Passkey注册')
ElMessage.error(message)
} finally {
passkeyAddLoading.value = false
}
}
async function removePasskey(item) {
try {
await ElMessageBox.confirm(`确定删除设备「${item?.device_name || '未命名设备'}」吗?`, '删除Passkey设备', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
await deleteAdminPasskey(item.id)
ElMessage.success('设备已删除')
await loadPasskeys()
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '删除失败')
}
}
function socialBindRedirectUri() { function socialBindRedirectUri() {
const url = new URL(window.location.href) const url = new URL(window.location.href)
url.pathname = '/yuyx/admin-social-bind-callback' url.pathname = '/yuyx/admin-social-bind-callback'
@@ -380,7 +260,6 @@ async function unbindSocial(item) {
} }
onMounted(() => { onMounted(() => {
loadPasskeys()
loadSocialBindings() loadSocialBindings()
}) })
@@ -443,46 +322,6 @@ onBeforeUnmount(() => {
<div class="help">建议使用更强密码至少8位且包含字母与数字</div> <div class="help">建议使用更强密码至少8位且包含字母与数字</div>
</el-card> </el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<h3 class="section-title">Passkey设备</h3>
<el-alert
type="info"
:closable="false"
title="最多可绑定3台设备可用于管理员无密码登录。"
show-icon
class="help-alert"
/>
<el-form inline>
<el-form-item label="设备备注">
<el-input
v-model="passkeyDeviceName"
placeholder="例如值班iPhone / 办公Mac"
maxlength="40"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="passkeyAddLoading" @click="addPasskey">添加Passkey设备</el-button>
</el-form-item>
</el-form>
<div v-loading="passkeyLoading">
<el-empty v-if="passkeyItems.length === 0" description="暂无Passkey设备" />
<el-table v-else :data="passkeyItems" size="small" style="width: 100%">
<el-table-column prop="device_name" label="设备备注" min-width="160" />
<el-table-column prop="credential_id_preview" label="凭据ID" min-width="180" />
<el-table-column prop="last_used_at" label="最近使用" min-width="140" />
<el-table-column prop="created_at" label="创建时间" min-width="140" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="danger" text @click="removePasskey(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card"> <el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<div class="section-head"> <div class="section-head">
<h3 class="section-title">快捷登录绑定</h3> <h3 class="section-title">快捷登录绑定</h3>

View File

@@ -1,130 +0,0 @@
function ensurePublicKeyOptions(options) {
if (!options || typeof options !== 'object') {
throw new Error('Passkey参数无效')
}
return options.publicKey && typeof options.publicKey === 'object' ? options.publicKey : options
}
function base64UrlToUint8Array(base64url) {
const value = String(base64url || '')
const padding = '='.repeat((4 - (value.length % 4)) % 4)
const base64 = (value + padding).replace(/-/g, '+').replace(/_/g, '/')
const raw = window.atob(base64)
const bytes = new Uint8Array(raw.length)
for (let i = 0; i < raw.length; i += 1) {
bytes[i] = raw.charCodeAt(i)
}
return bytes
}
function uint8ArrayToBase64Url(input) {
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : new Uint8Array(input || [])
let binary = ''
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i])
}
return window
.btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '')
}
function toCreationOptions(rawOptions) {
const options = ensurePublicKeyOptions(rawOptions)
const normalized = {
...options,
challenge: base64UrlToUint8Array(options.challenge),
user: {
...options.user,
id: base64UrlToUint8Array(options.user?.id),
},
}
if (Array.isArray(options.excludeCredentials)) {
normalized.excludeCredentials = options.excludeCredentials.map((item) => ({
...item,
id: base64UrlToUint8Array(item.id),
}))
}
return normalized
}
function serializeCredential(credential) {
if (!credential) return null
const response = credential.response || {}
const output = {
id: credential.id,
rawId: uint8ArrayToBase64Url(credential.rawId),
type: credential.type,
authenticatorAttachment: credential.authenticatorAttachment || undefined,
response: {},
}
if (response.clientDataJSON) {
output.response.clientDataJSON = uint8ArrayToBase64Url(response.clientDataJSON)
}
if (response.attestationObject) {
output.response.attestationObject = uint8ArrayToBase64Url(response.attestationObject)
}
if (response.authenticatorData) {
output.response.authenticatorData = uint8ArrayToBase64Url(response.authenticatorData)
}
if (response.signature) {
output.response.signature = uint8ArrayToBase64Url(response.signature)
}
if (response.userHandle) {
output.response.userHandle = uint8ArrayToBase64Url(response.userHandle)
} else {
output.response.userHandle = null
}
if (typeof response.getTransports === 'function') {
output.response.transports = response.getTransports() || []
}
return output
}
export function isPasskeyAvailable() {
return typeof window !== 'undefined' && window.isSecureContext && !!window.PublicKeyCredential && !!navigator.credentials
}
function isMiuiBrowser() {
const ua = String(window?.navigator?.userAgent || '')
return /MiuiBrowser|XiaoMi\/MiuiBrowser/i.test(ua)
}
export function getPasskeyClientErrorMessage(error, actionLabel = 'Passkey操作') {
const name = String(error?.name || '').trim()
const message = String(error?.message || '').trim()
if (name === 'NotAllowedError') {
return `${actionLabel}未完成(可能已取消、超时或设备未响应)`
}
if (name === 'NotReadableError') {
if (/credential manager/i.test(message) && isMiuiBrowser()) {
return '当前小米浏览器与系统凭据管理器兼容性较差,请改用系统 Chrome 或 Edge 后重试。'
}
if (/credential manager/i.test(message)) {
return '系统凭据管理器返回异常,请确认已设置系统锁屏并改用系统 Chrome/Edge 后重试。'
}
return message || `${actionLabel}失败(设备读取异常)`
}
if (name === 'SecurityError') {
return '当前环境安全策略不满足 Passkey 要求,请确认使用 HTTPS 且证书有效。'
}
return message || `${actionLabel}失败`
}
export async function createPasskey(rawOptions) {
const publicKey = toCreationOptions(rawOptions)
const credential = await navigator.credentials.create({ publicKey })
return serializeCredential(credential)
}

View File

@@ -15,16 +15,6 @@ export async function login(payload) {
return data return data
} }
export async function passkeyLoginOptions(payload) {
const { data } = await publicApi.post('/passkeys/login/options', payload)
return data
}
export async function passkeyLoginVerify(payload) {
const { data } = await publicApi.post('/passkeys/login/verify', payload)
return data
}
export async function register(payload) { export async function register(payload) {
const { data } = await publicApi.post('/register', payload) const { data } = await publicApi.post('/register', payload)
return data return data

View File

@@ -45,31 +45,6 @@ export async function fetchKdocsStatus() {
return data return data
} }
export async function fetchUserPasskeys() {
const { data } = await publicApi.get('/user/passkeys')
return data
}
export async function createUserPasskeyOptions(payload) {
const { data } = await publicApi.post('/user/passkeys/register/options', payload)
return data
}
export async function createUserPasskeyVerify(payload) {
const { data } = await publicApi.post('/user/passkeys/register/verify', payload)
return data
}
export async function deleteUserPasskey(passkeyId) {
const { data } = await publicApi.delete(`/user/passkeys/${passkeyId}`)
return data
}
export async function reportUserPasskeyClientError(payload) {
const { data } = await publicApi.post('/user/passkeys/client-error', payload || {})
return data
}
export async function fetchSocialBindings() { export async function fetchSocialBindings() {
const { data } = await publicApi.get('/user/social-bindings') const { data } = await publicApi.get('/user/social-bindings')
return data return data

View File

@@ -13,15 +13,10 @@ import {
bindSocial, bindSocial,
bindEmail, bindEmail,
changePassword, changePassword,
createUserPasskeyOptions,
createUserPasskeyVerify,
deleteUserPasskey,
fetchEmailNotify, fetchEmailNotify,
fetchUserPasskeys,
fetchUserEmail, fetchUserEmail,
fetchKdocsSettings, fetchKdocsSettings,
fetchSocialBindings, fetchSocialBindings,
reportUserPasskeyClientError,
unbindSocial, unbindSocial,
unbindEmail, unbindEmail,
updateKdocsSettings, updateKdocsSettings,
@@ -29,7 +24,6 @@ import {
} from '../api/settings' } from '../api/settings'
import SocialLoginButtons from '../components/SocialLoginButtons.vue' import SocialLoginButtons from '../components/SocialLoginButtons.vue'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { createPasskey, getPasskeyClientErrorMessage, isPasskeyAvailable } from '../utils/passkey'
import { validateStrongPassword } from '../utils/password' import { validateStrongPassword } from '../utils/password'
const route = useRoute() const route = useRoute()
@@ -129,13 +123,6 @@ const passwordForm = reactive({
const kdocsLoading = ref(false) const kdocsLoading = ref(false)
const kdocsSaving = ref(false) const kdocsSaving = ref(false)
const kdocsUnitValue = ref('') const kdocsUnitValue = ref('')
const passkeyLoading = ref(false)
const passkeyAddLoading = ref(false)
const passkeyDeviceName = ref('')
const passkeyItems = ref([])
const passkeyRegisterOptions = ref(null)
const passkeyRegisterOptionsAt = ref(0)
const PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS = 240000
const socialConfig = ref({ enabled: false, providers: [] }) const socialConfig = ref({ enabled: false, providers: [] })
const socialBindings = ref([]) const socialBindings = ref([])
@@ -273,7 +260,7 @@ async function openSettings() {
} }
async function loadSettings() { async function loadSettings() {
await Promise.all([loadEmailInfo(), loadEmailNotify(), loadKdocsSettings(), loadPasskeys(), loadSocialBindings()]) await Promise.all([loadEmailInfo(), loadEmailNotify(), loadKdocsSettings(), loadSocialBindings()])
} }
function socialBindRedirectUri() { function socialBindRedirectUri() {
@@ -397,113 +384,6 @@ async function saveKdocsSettings() {
} }
} }
async function loadPasskeys() {
passkeyLoading.value = true
try {
const data = await fetchUserPasskeys()
passkeyItems.value = Array.isArray(data?.items) ? data.items : []
if (passkeyItems.value.length < 3) {
await prefetchPasskeyRegisterOptions()
} else {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
} catch {
passkeyItems.value = []
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
} finally {
passkeyLoading.value = false
}
}
function getCachedPasskeyRegisterOptions() {
if (!passkeyRegisterOptions.value) return null
if (Date.now() - Number(passkeyRegisterOptionsAt.value || 0) > PASSKEY_OPTIONS_PREFETCH_MAX_AGE_MS) return null
return passkeyRegisterOptions.value
}
async function prefetchPasskeyRegisterOptions() {
try {
const res = await createUserPasskeyOptions({})
passkeyRegisterOptions.value = res
passkeyRegisterOptionsAt.value = Date.now()
} catch {
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
}
}
async function onAddPasskey() {
if (!isPasskeyAvailable()) {
ElMessage.error('当前浏览器或环境不支持Passkey需 HTTPS')
return
}
if (passkeyItems.value.length >= 3) {
ElMessage.error('最多可绑定3台设备')
return
}
passkeyAddLoading.value = true
try {
let optionsRes = getCachedPasskeyRegisterOptions()
if (!optionsRes) {
optionsRes = await createUserPasskeyOptions({})
}
const credential = await createPasskey(optionsRes?.publicKey || {})
await createUserPasskeyVerify({ credential, device_name: passkeyDeviceName.value.trim() })
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
passkeyDeviceName.value = ''
ElMessage.success('Passkey设备添加成功')
await loadPasskeys()
} catch (e) {
try {
await reportUserPasskeyClientError({
stage: 'register',
source: 'user-settings',
name: e?.name || '',
message: e?.message || '',
code: e?.code || '',
user_agent: navigator.userAgent || '',
})
} catch {
// ignore report failure
}
passkeyRegisterOptions.value = null
passkeyRegisterOptionsAt.value = 0
await prefetchPasskeyRegisterOptions()
const data = e?.response?.data
const message =
data?.error ||
getPasskeyClientErrorMessage(e, 'Passkey注册')
ElMessage.error(message)
} finally {
passkeyAddLoading.value = false
}
}
async function onDeletePasskey(item) {
try {
await ElMessageBox.confirm(`确定删除设备「${item?.device_name || '未命名设备'}」吗?`, '删除Passkey设备', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
await deleteUserPasskey(item.id)
ElMessage.success('设备已删除')
await loadPasskeys()
} catch (e) {
const data = e?.response?.data
ElMessage.error(data?.error || '删除失败')
}
}
async function onBindEmail() { async function onBindEmail() {
const email = bindEmailValue.value.trim().toLowerCase() const email = bindEmailValue.value.trim().toLowerCase()
if (!email) { if (!email) {
@@ -877,47 +757,6 @@ async function dismissAnnouncementPermanently() {
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Passkey设备" name="passkeys">
<div class="settings-section" v-loading="passkeyLoading">
<el-alert
type="info"
:closable="false"
title="最多可绑定3台设备用于无密码登录。"
show-icon
class="settings-alert"
/>
<el-form inline>
<el-form-item label="设备备注">
<el-input
v-model="passkeyDeviceName"
placeholder="例如我的iPhone / 办公Mac"
maxlength="40"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="passkeyAddLoading" @click="onAddPasskey">
添加Passkey设备
</el-button>
</el-form-item>
</el-form>
<el-empty v-if="passkeyItems.length === 0" description="暂无Passkey设备" />
<el-table v-else :data="passkeyItems" size="small" style="width: 100%">
<el-table-column prop="device_name" label="设备备注" min-width="160" />
<el-table-column prop="credential_id_preview" label="凭据ID" min-width="180" />
<el-table-column prop="last_used_at" label="最近使用" min-width="140" />
<el-table-column prop="created_at" label="创建时间" min-width="140" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="danger" text @click="onDeletePasskey(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="快捷登录" name="social"> <el-tab-pane label="快捷登录" name="social">
<div class="settings-section" v-loading="socialBindingsLoading || socialBindLoading"> <div class="settings-section" v-loading="socialBindingsLoading || socialBindLoading">
<el-empty v-if="!socialConfig.enabled" description="暂未启用快捷登录" /> <el-empty v-if="!socialConfig.enabled" description="暂未启用快捷登录" />

View File

@@ -13,7 +13,6 @@ const needCaptcha = ref(false)
const captchaImage = ref('') const captchaImage = ref('')
const captchaSession = ref('') const captchaSession = ref('')
const loading = ref(false) const loading = ref(false)
const passkeyLoading = ref(false)
const emailEnabled = ref(false) const emailEnabled = ref(false)
const registerVerifyEnabled = ref(false) const registerVerifyEnabled = ref(false)
@@ -111,90 +110,11 @@ async function apiRequest(path, options = {}) {
const fetchEmailVerifyStatus = () => apiRequest('/email/verify-status') const fetchEmailVerifyStatus = () => apiRequest('/email/verify-status')
const generateCaptcha = () => apiRequest('/generate_captcha', { method: 'POST', body: {} }) const generateCaptcha = () => apiRequest('/generate_captcha', { method: 'POST', body: {} })
const loginRequest = (payload) => apiRequest('/login', { method: 'POST', body: payload || {} }) const loginRequest = (payload) => apiRequest('/login', { method: 'POST', body: payload || {} })
const passkeyLoginOptions = (payload) => apiRequest('/passkeys/login/options', { method: 'POST', body: payload || {} })
const passkeyLoginVerify = (payload) => apiRequest('/passkeys/login/verify', { method: 'POST', body: payload || {} })
const resendVerifyEmail = (payload) => apiRequest('/resend-verify-email', { method: 'POST', body: payload || {} }) const resendVerifyEmail = (payload) => apiRequest('/resend-verify-email', { method: 'POST', body: payload || {} })
const forgotPassword = (payload) => apiRequest('/forgot-password', { method: 'POST', body: payload || {} }) const forgotPassword = (payload) => apiRequest('/forgot-password', { method: 'POST', body: payload || {} })
const fetchSocialConfig = () => apiRequest('/auth/social/config') const fetchSocialConfig = () => apiRequest('/auth/social/config')
const socialCallbackRequest = (payload) => apiRequest('/auth/social/callback', { method: 'POST', body: payload || {} }) const socialCallbackRequest = (payload) => apiRequest('/auth/social/callback', { method: 'POST', body: payload || {} })
function base64UrlToUint8Array(base64url) {
const value = String(base64url || '')
const padding = '='.repeat((4 - (value.length % 4)) % 4)
const base64 = (value + padding).replace(/-/g, '+').replace(/_/g, '/')
const raw = window.atob(base64)
const bytes = new Uint8Array(raw.length)
for (let i = 0; i < raw.length; i += 1) {
bytes[i] = raw.charCodeAt(i)
}
return bytes
}
function uint8ArrayToBase64Url(input) {
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : new Uint8Array(input || [])
let binary = ''
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i])
}
return window
.btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '')
}
function normalizePublicKeyOptions(options) {
if (!options || typeof options !== 'object') {
throw new Error('Passkey参数无效')
}
return options.publicKey && typeof options.publicKey === 'object' ? options.publicKey : options
}
function toRequestOptions(rawOptions) {
const options = normalizePublicKeyOptions(rawOptions)
const normalized = {
...options,
challenge: base64UrlToUint8Array(options.challenge),
}
if (Array.isArray(options.allowCredentials)) {
normalized.allowCredentials = options.allowCredentials.map((item) => ({
...item,
id: base64UrlToUint8Array(item.id),
}))
}
return normalized
}
function serializeCredential(credential) {
const response = credential?.response || {}
const output = {
id: credential?.id,
rawId: uint8ArrayToBase64Url(credential?.rawId),
type: credential?.type,
authenticatorAttachment: credential?.authenticatorAttachment || undefined,
response: {},
}
if (response.clientDataJSON) output.response.clientDataJSON = uint8ArrayToBase64Url(response.clientDataJSON)
if (response.authenticatorData) output.response.authenticatorData = uint8ArrayToBase64Url(response.authenticatorData)
if (response.signature) output.response.signature = uint8ArrayToBase64Url(response.signature)
if (response.userHandle) {
output.response.userHandle = uint8ArrayToBase64Url(response.userHandle)
} else {
output.response.userHandle = null
}
return output
}
function isPasskeyAvailable() {
return typeof window !== 'undefined' && window.isSecureContext && !!window.PublicKeyCredential && !!navigator.credentials
}
async function authenticateWithPasskey(rawOptions) {
const publicKey = toRequestOptions(rawOptions)
const credential = await navigator.credentials.get({ publicKey })
return serializeCredential(credential)
}
async function loadVerifyStatus() { async function loadVerifyStatus() {
if (verifyStatusLoaded.value) return if (verifyStatusLoaded.value) return
try { try {
@@ -370,33 +290,6 @@ async function onSubmit() {
} }
} }
async function onPasskeyLogin() {
clearNotice()
const username = form.username.trim()
if (!isPasskeyAvailable()) {
setNotice('error', '当前浏览器或环境不支持Passkey需 HTTPS')
return
}
passkeyLoading.value = true
try {
const optionsRes = await passkeyLoginOptions(username ? { username } : {})
const credential = await authenticateWithPasskey(optionsRes?.publicKey || {})
await passkeyLoginVerify(username ? { username, credential } : { credential })
setNotice('success', 'Passkey 登录成功,正在跳转...')
redirectAfterLogin()
} catch (e) {
const data = e?.response?.data
const message =
data?.error ||
(e?.name === 'NotAllowedError' ? 'Passkey验证未完成可能取消、超时或设备未响应' : e?.message || 'Passkey登录失败')
setNotice('error', message)
} finally {
passkeyLoading.value = false
}
}
async function openForgot() { async function openForgot() {
await loadVerifyStatus() await loadVerifyStatus()
forgotOpen.value = true forgotOpen.value = true
@@ -568,9 +461,6 @@ onMounted(async () => {
<button type="button" class="btn-login" :disabled="loading || socialCallbackLoading" @click="onSubmit"> <button type="button" class="btn-login" :disabled="loading || socialCallbackLoading" @click="onSubmit">
{{ loading || socialCallbackLoading ? '登录中...' : '登录系统' }} {{ loading || socialCallbackLoading ? '登录中...' : '登录系统' }}
</button> </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 v-if="socialConfig.enabled" class="social-login-area">
<div class="divider"><span>快捷登录</span></div> <div class="divider"><span>快捷登录</span></div>
@@ -820,23 +710,6 @@ onMounted(async () => {
transition: transform 0.15s, filter 0.15s; transition: transform 0.15s, filter 0.15s;
} }
.btn-passkey {
width: 100%;
height: 42px;
margin-top: 10px;
border-radius: 10px;
border: 1px solid rgba(17, 24, 39, 0.14);
background: #f8fafc;
color: #0f172a;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.btn-passkey:hover:not(:disabled) {
background: #f1f5f9;
}
.social-login-area { .social-login-area {
margin-top: 14px; margin-top: 14px;
} }
@@ -859,7 +732,6 @@ onMounted(async () => {
background: rgba(17, 24, 39, 0.12); background: rgba(17, 24, 39, 0.12);
} }
.btn-passkey:disabled,
.btn-login:disabled, .btn-login:disabled,
.btn-ghost:disabled, .btn-ghost:disabled,
.captcha-refresh:disabled { .captcha-refresh:disabled {

View File

@@ -1,153 +0,0 @@
function ensurePublicKeyOptions(options) {
if (!options || typeof options !== 'object') {
throw new Error('Passkey参数无效')
}
return options.publicKey && typeof options.publicKey === 'object' ? options.publicKey : options
}
function base64UrlToUint8Array(base64url) {
const value = String(base64url || '')
const padding = '='.repeat((4 - (value.length % 4)) % 4)
const base64 = (value + padding).replace(/-/g, '+').replace(/_/g, '/')
const raw = window.atob(base64)
const bytes = new Uint8Array(raw.length)
for (let i = 0; i < raw.length; i += 1) {
bytes[i] = raw.charCodeAt(i)
}
return bytes
}
function uint8ArrayToBase64Url(input) {
const bytes = input instanceof ArrayBuffer ? new Uint8Array(input) : new Uint8Array(input || [])
let binary = ''
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i])
}
return window
.btoa(binary)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '')
}
function toCreationOptions(rawOptions) {
const options = ensurePublicKeyOptions(rawOptions)
const normalized = {
...options,
challenge: base64UrlToUint8Array(options.challenge),
user: {
...options.user,
id: base64UrlToUint8Array(options.user?.id),
},
}
if (Array.isArray(options.excludeCredentials)) {
normalized.excludeCredentials = options.excludeCredentials.map((item) => ({
...item,
id: base64UrlToUint8Array(item.id),
}))
}
return normalized
}
function toRequestOptions(rawOptions) {
const options = ensurePublicKeyOptions(rawOptions)
const normalized = {
...options,
challenge: base64UrlToUint8Array(options.challenge),
}
if (Array.isArray(options.allowCredentials)) {
normalized.allowCredentials = options.allowCredentials.map((item) => ({
...item,
id: base64UrlToUint8Array(item.id),
}))
}
return normalized
}
function serializeCredential(credential) {
if (!credential) return null
const response = credential.response || {}
const output = {
id: credential.id,
rawId: uint8ArrayToBase64Url(credential.rawId),
type: credential.type,
authenticatorAttachment: credential.authenticatorAttachment || undefined,
response: {},
}
if (response.clientDataJSON) {
output.response.clientDataJSON = uint8ArrayToBase64Url(response.clientDataJSON)
}
if (response.attestationObject) {
output.response.attestationObject = uint8ArrayToBase64Url(response.attestationObject)
}
if (response.authenticatorData) {
output.response.authenticatorData = uint8ArrayToBase64Url(response.authenticatorData)
}
if (response.signature) {
output.response.signature = uint8ArrayToBase64Url(response.signature)
}
if (response.userHandle) {
output.response.userHandle = uint8ArrayToBase64Url(response.userHandle)
} else {
output.response.userHandle = null
}
if (typeof response.getTransports === 'function') {
output.response.transports = response.getTransports() || []
}
return output
}
export function isPasskeyAvailable() {
return typeof window !== 'undefined' && window.isSecureContext && !!window.PublicKeyCredential && !!navigator.credentials
}
function isMiuiBrowser() {
const ua = String(window?.navigator?.userAgent || '')
return /MiuiBrowser|XiaoMi\/MiuiBrowser/i.test(ua)
}
export function getPasskeyClientErrorMessage(error, actionLabel = 'Passkey操作') {
const name = String(error?.name || '').trim()
const message = String(error?.message || '').trim()
if (name === 'NotAllowedError') {
return `${actionLabel}未完成(可能已取消、超时或设备未响应)`
}
if (name === 'NotReadableError') {
if (/credential manager/i.test(message) && isMiuiBrowser()) {
return '当前小米浏览器与系统凭据管理器兼容性较差,请改用系统 Chrome 或 Edge 后重试。'
}
if (/credential manager/i.test(message)) {
return '系统凭据管理器返回异常,请确认已设置系统锁屏并改用系统 Chrome/Edge 后重试。'
}
return message || `${actionLabel}失败(设备读取异常)`
}
if (name === 'SecurityError') {
return '当前环境安全策略不满足 Passkey 要求,请确认使用 HTTPS 且证书有效。'
}
return message || `${actionLabel}失败`
}
export async function createPasskey(rawOptions) {
const publicKey = toCreationOptions(rawOptions)
const credential = await navigator.credentials.create({ publicKey })
return serializeCredential(credential)
}
export async function authenticateWithPasskey(rawOptions) {
const publicKey = toRequestOptions(rawOptions)
const credential = await navigator.credentials.get({ publicKey })
return serializeCredential(credential)
}

6
app.py
View File

@@ -212,7 +212,7 @@ def enforce_csrf_protection():
return return
if request.path.startswith("/static/"): if request.path.startswith("/static/"):
return return
# 登录挑战相关路由豁免 CSRF会话尚未建立前需要可用 # 登录和第三方回调相关路由豁免 CSRF会话尚未建立前需要可用
csrf_exempt_paths = { csrf_exempt_paths = {
"/yuyx/api/login", "/yuyx/api/login",
"/api/login", "/api/login",
@@ -221,10 +221,6 @@ def enforce_csrf_protection():
"/api/auth/social/login-url", "/api/auth/social/login-url",
"/api/auth/social/poll", "/api/auth/social/poll",
"/api/auth/social/callback", "/api/auth/social/callback",
"/yuyx/api/passkeys/login/options",
"/yuyx/api/passkeys/login/verify",
"/api/passkeys/login/options",
"/api/passkeys/login/verify",
} }
if request.path in csrf_exempt_paths: if request.path in csrf_exempt_paths:
return return

View File

@@ -80,15 +80,6 @@ from db.feedbacks import (
get_user_feedbacks, get_user_feedbacks,
reply_feedback, reply_feedback,
) )
from db.passkeys import (
count_passkeys,
create_passkey,
delete_passkey,
get_passkey_by_credential_id,
get_passkey_by_id,
list_passkeys,
update_passkey_usage,
)
from db.schedules import ( from db.schedules import (
clean_old_schedule_logs, clean_old_schedule_logs,
create_schedule_execution_log, create_schedule_execution_log,

View File

@@ -909,31 +909,7 @@ def _migrate_to_v20(conn):
def _migrate_to_v21(conn): def _migrate_to_v21(conn):
"""迁移到版本21 - Passkey 认证设备表""" """迁移到版本21 - 旧认证设备功能已移除,保留版本号兼容历史数据库。"""
cursor = conn.cursor()
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS passkeys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner_type TEXT NOT NULL,
owner_id INTEGER NOT NULL,
device_name TEXT NOT NULL,
credential_id TEXT UNIQUE NOT NULL,
public_key TEXT NOT NULL,
sign_count INTEGER DEFAULT 0,
transports TEXT DEFAULT '',
aaguid TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
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)"
)
conn.commit() conn.commit()

View File

@@ -1,173 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import sqlite3
import db_pool
from db.utils import get_cst_now_str
_OWNER_TYPES = {"user", "admin"}
def _normalize_owner_type(owner_type: str) -> str:
normalized = str(owner_type or "").strip().lower()
if normalized not in _OWNER_TYPES:
raise ValueError(f"invalid owner_type: {owner_type}")
return normalized
def list_passkeys(owner_type: str, owner_id: int) -> list[dict]:
owner = _normalize_owner_type(owner_type)
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT id, owner_type, owner_id, device_name, credential_id, transports,
sign_count, aaguid, created_at, last_used_at
FROM passkeys
WHERE owner_type = ? AND owner_id = ?
ORDER BY datetime(created_at) DESC, id DESC
""",
(owner, int(owner_id)),
)
return [dict(row) for row in cursor.fetchall()]
def count_passkeys(owner_type: str, owner_id: int) -> int:
owner = _normalize_owner_type(owner_type)
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT COUNT(*) AS count FROM passkeys WHERE owner_type = ? AND owner_id = ?",
(owner, int(owner_id)),
)
row = cursor.fetchone()
if not row:
return 0
try:
return int(row["count"] or 0)
except Exception:
try:
return int(row[0] or 0)
except Exception:
return 0
def get_passkey_by_credential_id(credential_id: str) -> dict | None:
credential = str(credential_id or "").strip()
if not credential:
return None
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT id, owner_type, owner_id, device_name, credential_id, public_key,
sign_count, transports, aaguid, created_at, last_used_at
FROM passkeys
WHERE credential_id = ?
""",
(credential,),
)
row = cursor.fetchone()
return dict(row) if row else None
def get_passkey_by_id(owner_type: str, owner_id: int, passkey_id: int) -> dict | None:
owner = _normalize_owner_type(owner_type)
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT id, owner_type, owner_id, device_name, credential_id, public_key,
sign_count, transports, aaguid, created_at, last_used_at
FROM passkeys
WHERE id = ? AND owner_type = ? AND owner_id = ?
""",
(int(passkey_id), owner, int(owner_id)),
)
row = cursor.fetchone()
return dict(row) if row else None
def create_passkey(
owner_type: str,
owner_id: int,
*,
credential_id: str,
public_key: str,
sign_count: int,
device_name: str,
transports: str = "",
aaguid: str = "",
) -> int | None:
owner = _normalize_owner_type(owner_type)
now = get_cst_now_str()
with db_pool.get_db() as conn:
cursor = conn.cursor()
try:
cursor.execute(
"""
INSERT INTO passkeys (
owner_type,
owner_id,
device_name,
credential_id,
public_key,
sign_count,
transports,
aaguid,
created_at,
last_used_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
owner,
int(owner_id),
str(device_name or "").strip(),
str(credential_id or "").strip(),
str(public_key or "").strip(),
int(sign_count or 0),
str(transports or "").strip(),
str(aaguid or "").strip(),
now,
now,
),
)
conn.commit()
return int(cursor.lastrowid)
except sqlite3.IntegrityError:
return None
def update_passkey_usage(passkey_id: int, new_sign_count: int) -> bool:
now = get_cst_now_str()
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"""
UPDATE passkeys
SET sign_count = ?,
last_used_at = ?
WHERE id = ?
""",
(int(new_sign_count or 0), now, int(passkey_id)),
)
conn.commit()
return cursor.rowcount > 0
def delete_passkey(owner_type: str, owner_id: int, passkey_id: int) -> bool:
owner = _normalize_owner_type(owner_type)
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"DELETE FROM passkeys WHERE id = ? AND owner_type = ? AND owner_id = ?",
(int(passkey_id), owner, int(owner_id)),
)
conn.commit()
return cursor.rowcount > 0

View File

@@ -74,25 +74,6 @@ def ensure_schema(conn) -> None:
""" """
) )
# Passkey 认证设备表(用户/管理员)
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS passkeys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner_type TEXT NOT NULL,
owner_id INTEGER NOT NULL,
device_name TEXT NOT NULL,
credential_id TEXT UNIQUE NOT NULL,
public_key TEXT NOT NULL,
sign_count INTEGER DEFAULT 0,
transports TEXT DEFAULT '',
aaguid TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
# ==================== 安全防护:威胁检测相关表 ==================== # ==================== 安全防护:威胁检测相关表 ====================
# 威胁事件日志表 # 威胁事件日志表
@@ -448,8 +429,6 @@ def ensure_schema(conn) -> None:
cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_status_created_at ON users(status, created_at)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_status_created_at ON users(status, created_at)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_fingerprints_user ON login_fingerprints(user_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_fingerprints_user ON login_fingerprints(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_ips_user ON login_ips(user_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_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_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_login_bindings_provider_uid ON social_login_bindings(provider, social_uid)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_admin_social_login_bindings_admin ON admin_social_login_bindings(admin_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_admin_social_login_bindings_admin ON admin_social_login_bindings(admin_id)")

View File

@@ -10,7 +10,6 @@ requests==2.32.3
python-dotenv==1.0.0 python-dotenv==1.0.0
beautifulsoup4==4.12.2 beautifulsoup4==4.12.2
cryptography>=41.0.0 cryptography>=41.0.0
webauthn>=2.7.1
Pillow>=10.0.0 Pillow>=10.0.0
playwright==1.42.0 playwright==1.42.0
eventlet==0.36.1 eventlet==0.36.1

View File

@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations from __future__ import annotations
import json
import os import os
import stat import stat
import tempfile import tempfile
@@ -17,19 +16,6 @@ from routes.admin_api import admin_api_bp
from routes.decorators import admin_required from routes.decorators import admin_required
from services.accounts_service import load_user_accounts from services.accounts_service import load_user_accounts
from services.checkpoints import get_checkpoint_mgr from services.checkpoints import get_checkpoint_mgr
from services.passkeys import (
MAX_PASSKEYS_PER_OWNER,
encode_credential_id,
get_credential_transports,
get_expected_origins,
get_rp_id,
is_challenge_valid,
make_authentication_options,
make_registration_options,
normalize_device_name,
verify_authentication,
verify_registration,
)
from services.state import ( from services.state import (
safe_get_user_accounts_snapshot, safe_get_user_accounts_snapshot,
safe_verify_and_consume_captcha, safe_verify_and_consume_captcha,
@@ -46,8 +32,6 @@ from services.tasks import submit_account_task
logger = get_logger("app") logger = get_logger("app")
config = get_config() config = get_config()
_ADMIN_PASSKEY_LOGIN_SESSION_KEY = "admin_passkey_login_state"
_ADMIN_PASSKEY_REGISTER_SESSION_KEY = "admin_passkey_register_state"
def _admin_reauth_required() -> bool: def _admin_reauth_required() -> bool:
@@ -63,26 +47,6 @@ def _require_admin_reauth():
return None return None
def _parse_credential_payload(data: dict) -> dict | None:
credential = data.get("credential")
if isinstance(credential, dict):
return credential
if isinstance(credential, str):
try:
parsed = json.loads(credential)
return parsed if isinstance(parsed, dict) else None
except Exception:
return None
return None
def _truncate_text(value, max_len: int = 300) -> str:
text = str(value or "").strip()
if len(text) > max_len:
return f"{text[:max_len]}..."
return text
@admin_api_bp.route("/debug-config", methods=["GET"]) @admin_api_bp.route("/debug-config", methods=["GET"])
@admin_required @admin_required
def debug_config(): def debug_config():
@@ -107,169 +71,6 @@ def debug_config():
) )
@admin_api_bp.route("/passkeys/login/options", methods=["POST"])
@require_ip_not_locked
def admin_passkey_login_options():
"""管理员 Passkey 登录:获取 assertion challenge。"""
data = request.get_json(silent=True) or {}
username = str(data.get("username", "") or "").strip()
client_ip = get_rate_limit_ip()
mode = "named" if username else "discoverable"
username_key = f"admin-passkey:{username}" if username else "admin-passkey:discoverable"
is_locked, remaining = check_login_ip_user_locked(client_ip, username_key)
if is_locked:
wait_hint = f"{remaining // 60 + 1}分钟" if remaining >= 60 else f"{remaining}"
return jsonify({"error": f"账号短时锁定,请{wait_hint}后再试"}), 429
allowed, error_msg = check_ip_request_rate(client_ip, "login")
if not allowed:
return jsonify({"error": error_msg}), 429
allowed, error_msg = check_login_rate_limits(client_ip, username_key)
if not allowed:
return jsonify({"error": error_msg}), 429
admin_id = 0
allow_credential_ids = []
if mode == "named":
admin_row = database.get_admin_by_username(username)
if not admin_row:
record_login_failure(client_ip, username_key)
return jsonify({"error": "账号或Passkey不可用"}), 400
admin_id = int(admin_row["id"])
passkeys = database.list_passkeys("admin", admin_id)
if not passkeys:
record_login_failure(client_ip, username_key)
return jsonify({"error": "该管理员尚未绑定Passkey"}), 400
allow_credential_ids = [str(item.get("credential_id") or "").strip() for item in passkeys if item.get("credential_id")]
try:
rp_id = get_rp_id(request)
expected_origins = get_expected_origins(request)
except Exception as e:
logger.warning(f"[passkey] 管理员登录 options 失败(mode={mode}, username={username or '-'}) : {e}")
return jsonify({"error": "Passkey配置异常请联系管理员"}), 500
options = make_authentication_options(rp_id=rp_id, allow_credential_ids=allow_credential_ids)
challenge = str(options.get("challenge") or "").strip()
if not challenge:
return jsonify({"error": "生成Passkey挑战失败"}), 500
session[_ADMIN_PASSKEY_LOGIN_SESSION_KEY] = {
"mode": mode,
"username": username,
"admin_id": int(admin_id),
"challenge": challenge,
"rp_id": rp_id,
"expected_origins": expected_origins,
"username_key": username_key,
"created_at": time.time(),
}
session.modified = True
return jsonify({"publicKey": options})
@admin_api_bp.route("/passkeys/login/verify", methods=["POST"])
@require_ip_not_locked
def admin_passkey_login_verify():
"""管理员 Passkey 登录:校验 assertion 并登录。"""
data = request.get_json(silent=True) or {}
request_username = str(data.get("username", "") or "").strip()
credential = _parse_credential_payload(data)
if not credential:
return jsonify({"error": "Passkey参数缺失"}), 400
state = session.get(_ADMIN_PASSKEY_LOGIN_SESSION_KEY) or {}
if not state:
return jsonify({"error": "Passkey挑战不存在或已过期请重试"}), 400
if not is_challenge_valid(state.get("created_at")):
session.pop(_ADMIN_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey挑战已过期请重试"}), 400
mode = str(state.get("mode") or "named")
if mode not in {"named", "discoverable"}:
session.pop(_ADMIN_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey状态异常请重试"}), 400
expected_username = str(state.get("username") or "").strip()
username = expected_username
if mode == "named":
if not expected_username:
session.pop(_ADMIN_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey状态异常请重试"}), 400
if request_username and request_username != expected_username:
return jsonify({"error": "用户名与挑战不匹配,请重试"}), 400
else:
username = request_username
client_ip = get_rate_limit_ip()
username_key = str(state.get("username_key") or "").strip() or (
f"admin-passkey:{expected_username}" if mode == "named" else "admin-passkey:discoverable"
)
is_locked, remaining = check_login_ip_user_locked(client_ip, username_key)
if is_locked:
wait_hint = f"{remaining // 60 + 1}分钟" if remaining >= 60 else f"{remaining}"
return jsonify({"error": f"账号短时锁定,请{wait_hint}后再试"}), 429
credential_id = str(credential.get("id") or credential.get("rawId") or "").strip()
if not credential_id:
return jsonify({"error": "Passkey参数无效"}), 400
passkey = database.get_passkey_by_credential_id(credential_id)
if not passkey:
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey不存在或已删除"}), 401
if str(passkey.get("owner_type") or "") != "admin":
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey不属于管理员账号"}), 401
if mode == "named" and int(passkey.get("owner_id") or 0) != int(state.get("admin_id") or 0):
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey与管理员账号不匹配"}), 401
try:
_, verified = verify_authentication(
credential=credential,
expected_challenge=str(state.get("challenge") or ""),
expected_rp_id=str(state.get("rp_id") or ""),
expected_origins=list(state.get("expected_origins") or []),
credential_public_key=str(passkey.get("public_key") or ""),
credential_current_sign_count=int(passkey.get("sign_count") or 0),
)
verified_credential_id = encode_credential_id(verified.credential_id)
if verified_credential_id != str(passkey.get("credential_id") or ""):
raise ValueError("credential_id mismatch")
except Exception as e:
logger.warning(f"[passkey] 管理员登录验签失败(mode={mode}, username={expected_username or request_username or '-'}) : {e}")
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey验证失败"}), 401
admin_id = int(passkey.get("owner_id") or 0)
admin_row = database.get_admin_by_id(admin_id)
if not admin_row:
return jsonify({"error": "管理员账号不存在"}), 401
admin_username = str(admin_row.get("username") or "").strip() or username or f"admin-{admin_id}"
database.update_passkey_usage(int(passkey["id"]), int(verified.new_sign_count))
clear_login_failures(client_ip, username_key)
admin_login_key = f"admin-passkey:{admin_username}"
if admin_login_key and admin_login_key != username_key:
clear_login_failures(client_ip, admin_login_key)
session.pop(_ADMIN_PASSKEY_LOGIN_SESSION_KEY, None)
session.pop("admin_id", None)
session.pop("admin_username", None)
session["admin_id"] = admin_id
session["admin_username"] = admin_username
session["admin_reauth_until"] = time.time() + int(config.ADMIN_REAUTH_WINDOW_SECONDS)
session.permanent = True
session.modified = True
return jsonify({"success": True, "redirect": "/yuyx/admin", "username": admin_username})
@admin_api_bp.route("/login", methods=["POST"]) @admin_api_bp.route("/login", methods=["POST"])
@require_ip_not_locked @require_ip_not_locked
def admin_login(): def admin_login():
@@ -358,164 +159,6 @@ def admin_logout():
return jsonify({"success": True}) return jsonify({"success": True})
@admin_api_bp.route("/admin/passkeys", methods=["GET"])
@admin_required
def list_admin_passkeys():
admin_id = int(session.get("admin_id") or 0)
rows = database.list_passkeys("admin", admin_id)
items = []
for row in rows:
credential_id = str(row.get("credential_id") or "")
preview = f"{credential_id[:8]}...{credential_id[-6:]}" if len(credential_id) > 16 else credential_id
items.append(
{
"id": int(row.get("id")),
"device_name": str(row.get("device_name") or ""),
"credential_id_preview": preview,
"created_at": row.get("created_at"),
"last_used_at": row.get("last_used_at"),
"transports": str(row.get("transports") or ""),
}
)
return jsonify({"items": items, "limit": MAX_PASSKEYS_PER_OWNER})
@admin_api_bp.route("/admin/passkeys/register/options", methods=["POST"])
@admin_required
def admin_passkey_register_options():
admin_id = int(session.get("admin_id") or 0)
admin_username = str(session.get("admin_username") or "").strip() or f"admin-{admin_id}"
count = database.count_passkeys("admin", admin_id)
if count >= MAX_PASSKEYS_PER_OWNER:
return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400
data = request.get_json(silent=True) or {}
device_name = normalize_device_name(data.get("device_name"))
existing = database.list_passkeys("admin", admin_id)
exclude_credential_ids = [str(item.get("credential_id") or "").strip() for item in existing if item.get("credential_id")]
try:
rp_id = get_rp_id(request)
expected_origins = get_expected_origins(request)
options = make_registration_options(
rp_id=rp_id,
rp_name="知识管理平台",
user_name=admin_username,
user_display_name=admin_username,
user_id_bytes=f"admin:{admin_id}".encode("utf-8"),
exclude_credential_ids=exclude_credential_ids,
)
except Exception as e:
logger.warning(f"[passkey] 管理员注册 options 失败(admin_id={admin_id}): {e}")
return jsonify({"error": "生成Passkey挑战失败"}), 500
challenge = str(options.get("challenge") or "").strip()
if not challenge:
return jsonify({"error": "生成Passkey挑战失败"}), 500
session[_ADMIN_PASSKEY_REGISTER_SESSION_KEY] = {
"admin_id": admin_id,
"challenge": challenge,
"rp_id": rp_id,
"expected_origins": expected_origins,
"device_name": device_name,
"created_at": time.time(),
}
session.modified = True
return jsonify({"publicKey": options, "limit": MAX_PASSKEYS_PER_OWNER})
@admin_api_bp.route("/admin/passkeys/register/verify", methods=["POST"])
@admin_required
def admin_passkey_register_verify():
admin_id = int(session.get("admin_id") or 0)
state = session.get(_ADMIN_PASSKEY_REGISTER_SESSION_KEY) or {}
if not state:
return jsonify({"error": "Passkey挑战不存在或已过期请重试"}), 400
if int(state.get("admin_id") or 0) != admin_id:
return jsonify({"error": "Passkey挑战与当前管理员不匹配"}), 400
if not is_challenge_valid(state.get("created_at")):
session.pop(_ADMIN_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"error": "Passkey挑战已过期请重试"}), 400
data = request.get_json(silent=True) or {}
credential = _parse_credential_payload(data)
if not credential:
return jsonify({"error": "Passkey参数缺失"}), 400
count = database.count_passkeys("admin", admin_id)
if count >= MAX_PASSKEYS_PER_OWNER:
session.pop(_ADMIN_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400
try:
verified = verify_registration(
credential=credential,
expected_challenge=str(state.get("challenge") or ""),
expected_rp_id=str(state.get("rp_id") or ""),
expected_origins=list(state.get("expected_origins") or []),
)
except Exception as e:
logger.warning(f"[passkey] 管理员注册验签失败(admin_id={admin_id}): {e}")
return jsonify({"error": "Passkey验证失败请重试"}), 400
device_name = normalize_device_name(data.get("device_name") if "device_name" in data else state.get("device_name"))
created_id = database.create_passkey(
"admin",
admin_id,
credential_id=encode_credential_id(verified.credential_id),
public_key=encode_credential_id(verified.credential_public_key),
sign_count=int(verified.sign_count or 0),
device_name=device_name,
transports=get_credential_transports(credential),
aaguid=str(verified.aaguid or ""),
)
if not created_id:
return jsonify({"error": "该Passkey已绑定或保存失败"}), 400
session.pop(_ADMIN_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"success": True, "id": int(created_id)})
@admin_api_bp.route("/admin/passkeys/<int:passkey_id>", methods=["DELETE"])
@admin_required
def delete_admin_passkey(passkey_id):
admin_id = int(session.get("admin_id") or 0)
ok = database.delete_passkey("admin", admin_id, int(passkey_id))
if ok:
return jsonify({"success": True})
return jsonify({"error": "设备不存在或已删除"}), 404
@admin_api_bp.route("/admin/passkeys/client-error", methods=["POST"])
@admin_required
def report_admin_passkey_client_error():
"""上报管理员端浏览器 Passkey 失败详情,便于排查兼容性问题。"""
data = request.get_json(silent=True) or {}
error_name = _truncate_text(data.get("name"), 120)
error_message = _truncate_text(data.get("message"), 400)
error_code = _truncate_text(data.get("code"), 120)
ua = _truncate_text(data.get("user_agent") or request.headers.get("User-Agent", ""), 300)
stage = _truncate_text(data.get("stage"), 80)
source = _truncate_text(data.get("source"), 80)
admin_id = int(session.get("admin_id") or 0)
logger.warning(
"[passkey][client-error][admin] admin_id=%s stage=%s source=%s name=%s code=%s message=%s ua=%s",
admin_id,
stage or "-",
source or "-",
error_name or "-",
error_code or "-",
error_message or "-",
ua or "-",
)
return jsonify({"success": True})
@admin_api_bp.route("/admin/reauth", methods=["POST"]) @admin_api_bp.route("/admin/reauth", methods=["POST"])
@admin_required @admin_required
def admin_reauth(): def admin_reauth():

View File

@@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import base64 import base64
import json
import random import random
import secrets import secrets
import threading import threading
@@ -21,15 +20,6 @@ from flask_login import login_required, login_user, logout_user
from routes.pages import render_app_spa_or_legacy from routes.pages import render_app_spa_or_legacy
from services.accounts_service import load_user_accounts from services.accounts_service import load_user_accounts
from services.models import User from services.models import User
from services.passkeys import (
encode_credential_id,
get_expected_origins,
get_rp_id,
is_challenge_valid,
make_authentication_options,
normalize_device_name,
verify_authentication,
)
from services.social_login import parse_providers, provider_label from services.social_login import parse_providers, provider_label
from services.state import ( from services.state import (
check_ip_request_rate, check_ip_request_rate,
@@ -61,7 +51,6 @@ _CAPTCHA_FONT_PATHS = [
] ]
_CAPTCHA_FONT = None _CAPTCHA_FONT = None
_CAPTCHA_FONT_LOCK = threading.Lock() _CAPTCHA_FONT_LOCK = threading.Lock()
_USER_PASSKEY_LOGIN_SESSION_KEY = "user_passkey_login_state"
def _get_json_payload() -> dict: def _get_json_payload() -> dict:
@@ -206,19 +195,6 @@ def _send_login_security_alert_if_needed(user: dict, username: str, client_ip: s
logger.warning(f"发送登录安全提醒失败: user_id={user.get('id')}, error={e}") logger.warning(f"发送登录安全提醒失败: user_id={user.get('id')}, error={e}")
def _parse_credential_payload(data: dict) -> dict | None:
credential = data.get("credential")
if isinstance(credential, dict):
return credential
if isinstance(credential, str):
try:
parsed = json.loads(credential)
return parsed if isinstance(parsed, dict) else None
except Exception:
return None
return None
@api_auth_bp.route("/api/register", methods=["POST"]) @api_auth_bp.route("/api/register", methods=["POST"])
@require_ip_not_locked @require_ip_not_locked
def register(): def register():
@@ -586,166 +562,6 @@ def generate_captcha():
return jsonify({"error": "验证码服务暂不可用请联系管理员安装PIL库"}), 503 return jsonify({"error": "验证码服务暂不可用请联系管理员安装PIL库"}), 503
@api_auth_bp.route("/api/passkeys/login/options", methods=["POST"])
@require_ip_not_locked
def user_passkey_login_options():
"""用户 Passkey 登录:获取 assertion challenge。"""
data = _get_json_payload()
username = str(data.get("username", "") or "").strip()
client_ip = get_rate_limit_ip()
mode = "named" if username else "discoverable"
username_key = f"passkey:{username}" if username else "passkey:discoverable"
is_locked, remaining = check_login_ip_user_locked(client_ip, username_key)
if is_locked:
wait_hint = f"{remaining // 60 + 1}分钟" if remaining >= 60 else f"{remaining}"
return jsonify({"error": f"账号短时锁定,请{wait_hint}后再试"}), 429
allowed, error_msg = check_ip_request_rate(client_ip, "login")
if not allowed:
return jsonify({"error": error_msg}), 429
allowed, error_msg = check_login_rate_limits(client_ip, username_key)
if not allowed:
return jsonify({"error": error_msg}), 429
user_id = 0
allow_credential_ids = []
if mode == "named":
user = database.get_user_by_username(username)
if not user or user.get("status") != "approved":
record_login_failure(client_ip, username_key)
return jsonify({"error": "账号或Passkey不可用"}), 400
user_id = int(user["id"])
passkeys = database.list_passkeys("user", user_id)
if not passkeys:
record_login_failure(client_ip, username_key)
return jsonify({"error": "该账号尚未绑定Passkey"}), 400
allow_credential_ids = [str(item.get("credential_id") or "").strip() for item in passkeys if item.get("credential_id")]
try:
rp_id = get_rp_id(request)
expected_origins = get_expected_origins(request)
except Exception as e:
logger.warning(f"[passkey] 生成登录 challenge 失败(mode={mode}, username={username or '-'}) : {e}")
return jsonify({"error": "Passkey配置异常请联系管理员"}), 500
options = make_authentication_options(rp_id=rp_id, allow_credential_ids=allow_credential_ids)
challenge = str(options.get("challenge") or "").strip()
if not challenge:
return jsonify({"error": "生成Passkey挑战失败"}), 500
session[_USER_PASSKEY_LOGIN_SESSION_KEY] = {
"mode": mode,
"username": username,
"user_id": int(user_id),
"challenge": challenge,
"rp_id": rp_id,
"expected_origins": expected_origins,
"username_key": username_key,
"created_at": time.time(),
}
session.modified = True
return jsonify({"publicKey": options})
@api_auth_bp.route("/api/passkeys/login/verify", methods=["POST"])
@require_ip_not_locked
def user_passkey_login_verify():
"""用户 Passkey 登录:校验 assertion 并登录。"""
data = _get_json_payload()
request_username = str(data.get("username", "") or "").strip()
credential = _parse_credential_payload(data)
if not credential:
return jsonify({"error": "Passkey参数缺失"}), 400
state = session.get(_USER_PASSKEY_LOGIN_SESSION_KEY) or {}
if not state:
return jsonify({"error": "Passkey挑战不存在或已过期请重试"}), 400
if not is_challenge_valid(state.get("created_at")):
session.pop(_USER_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey挑战已过期请重试"}), 400
mode = str(state.get("mode") or "named")
if mode not in {"named", "discoverable"}:
session.pop(_USER_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey状态异常请重试"}), 400
expected_username = str(state.get("username") or "").strip()
username = expected_username
if mode == "named":
if not expected_username:
session.pop(_USER_PASSKEY_LOGIN_SESSION_KEY, None)
return jsonify({"error": "Passkey状态异常请重试"}), 400
if request_username and request_username != expected_username:
return jsonify({"error": "用户名与挑战不匹配,请重试"}), 400
else:
username = request_username
client_ip = get_rate_limit_ip()
username_key = str(state.get("username_key") or "").strip() or (
f"passkey:{expected_username}" if mode == "named" else "passkey:discoverable"
)
is_locked, remaining = check_login_ip_user_locked(client_ip, username_key)
if is_locked:
wait_hint = f"{remaining // 60 + 1}分钟" if remaining >= 60 else f"{remaining}"
return jsonify({"error": f"账号短时锁定,请{wait_hint}后再试"}), 429
credential_id = str(credential.get("id") or credential.get("rawId") or "").strip()
if not credential_id:
return jsonify({"error": "Passkey参数无效"}), 400
passkey = database.get_passkey_by_credential_id(credential_id)
if not passkey:
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey不存在或已删除"}), 401
if str(passkey.get("owner_type") or "") != "user":
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey不属于用户账号"}), 401
if mode == "named" and int(passkey.get("owner_id") or 0) != int(state.get("user_id") or 0):
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey与账号不匹配"}), 401
try:
parsed_credential, verified = verify_authentication(
credential=credential,
expected_challenge=str(state.get("challenge") or ""),
expected_rp_id=str(state.get("rp_id") or ""),
expected_origins=list(state.get("expected_origins") or []),
credential_public_key=str(passkey.get("public_key") or ""),
credential_current_sign_count=int(passkey.get("sign_count") or 0),
)
verified_credential_id = encode_credential_id(verified.credential_id)
if verified_credential_id != str(passkey.get("credential_id") or ""):
raise ValueError("credential_id mismatch")
except Exception as e:
logger.warning(f"[passkey] 用户登录验签失败(mode={mode}, username={expected_username or request_username or '-'}) : {e}")
record_login_failure(client_ip, username_key)
return jsonify({"error": "Passkey验证失败"}), 401
user_id = int(passkey.get("owner_id") or 0)
user = database.get_user_by_id(user_id)
if not user or user.get("status") != "approved":
return jsonify({"error": "账号不可用"}), 401
database.update_passkey_usage(int(passkey["id"]), int(verified.new_sign_count))
clear_login_failures(client_ip, username_key)
user_login_key = f"passkey:{str(user.get('username') or '').strip()}"
if user_login_key and user_login_key != username_key:
clear_login_failures(client_ip, user_login_key)
session.pop(_USER_PASSKEY_LOGIN_SESSION_KEY, None)
user_obj = User(user_id)
login_user(user_obj)
load_user_accounts(user_id)
resolved_username = str(user.get("username") or "").strip() or username or f"user-{user_id}"
_send_login_security_alert_if_needed(user=user, username=resolved_username, client_ip=client_ip)
return jsonify({"success": True, "credential_id": parsed_credential.id, "username": resolved_username})
@api_auth_bp.route("/api/login", methods=["POST"]) @api_auth_bp.route("/api/login", methods=["POST"])
@require_ip_not_locked @require_ip_not_locked
def login(): def login():

View File

@@ -2,9 +2,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations from __future__ import annotations
import json
import time
import database import database
import email_service import email_service
from app_logger import get_logger from app_logger import get_logger
@@ -12,24 +9,12 @@ from app_security import get_rate_limit_ip, require_ip_not_locked, validate_emai
from flask import Blueprint, jsonify, request, session from flask import Blueprint, jsonify, request, session
from flask_login import current_user, login_required from flask_login import current_user, login_required
from routes.pages import render_app_spa_or_legacy from routes.pages import render_app_spa_or_legacy
from services.passkeys import (
MAX_PASSKEYS_PER_OWNER,
encode_credential_id,
get_credential_transports,
get_expected_origins,
get_rp_id,
is_challenge_valid,
make_registration_options,
normalize_device_name,
verify_registration,
)
from services.state import check_email_rate_limit, check_ip_request_rate, safe_iter_task_status_items from services.state import check_email_rate_limit, check_ip_request_rate, safe_iter_task_status_items
from services.tasks import get_task_scheduler from services.tasks import get_task_scheduler
logger = get_logger("app") logger = get_logger("app")
api_user_bp = Blueprint("api_user", __name__) api_user_bp = Blueprint("api_user", __name__)
_USER_PASSKEY_REGISTER_SESSION_KEY = "user_passkey_register_state"
def _get_current_user_record(): def _get_current_user_record():
@@ -61,26 +46,6 @@ def _coerce_binary_flag(value, *, field_label: str):
return value, None return value, None
def _parse_credential_payload(data: dict) -> dict | None:
credential = data.get("credential")
if isinstance(credential, dict):
return credential
if isinstance(credential, str):
try:
parsed = json.loads(credential)
return parsed if isinstance(parsed, dict) else None
except Exception:
return None
return None
def _truncate_text(value, max_len: int = 300) -> str:
text = str(value or "").strip()
if len(text) > max_len:
return f"{text[:max_len]}..."
return text
def _check_bind_email_rate_limits(email: str): def _check_bind_email_rate_limits(email: str):
client_ip = get_rate_limit_ip() client_ip = get_rate_limit_ip()
allowed, error_msg = check_ip_request_rate(client_ip, "email") allowed, error_msg = check_ip_request_rate(client_ip, "email")
@@ -409,176 +374,6 @@ def update_user_email_notify():
return jsonify({"error": "更新失败"}), 500 return jsonify({"error": "更新失败"}), 500
@api_user_bp.route("/api/user/passkeys", methods=["GET"])
@login_required
def list_user_passkeys():
"""获取当前用户绑定的 Passkey 设备列表。"""
rows = database.list_passkeys("user", int(current_user.id))
items = []
for row in rows:
credential_id = str(row.get("credential_id") or "")
preview = ""
if credential_id:
preview = f"{credential_id[:8]}...{credential_id[-6:]}" if len(credential_id) > 16 else credential_id
items.append(
{
"id": int(row.get("id")),
"device_name": str(row.get("device_name") or ""),
"credential_id_preview": preview,
"created_at": row.get("created_at"),
"last_used_at": row.get("last_used_at"),
"transports": str(row.get("transports") or ""),
}
)
return jsonify({"items": items, "limit": MAX_PASSKEYS_PER_OWNER})
@api_user_bp.route("/api/user/passkeys/register/options", methods=["POST"])
@login_required
def user_passkey_register_options():
"""当前登录用户创建 Passkey下发 registration challenge。"""
user, error_response = _get_current_user_or_404()
if error_response:
return error_response
count = database.count_passkeys("user", int(current_user.id))
if count >= MAX_PASSKEYS_PER_OWNER:
return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400
data = request.get_json(silent=True) or {}
device_name = normalize_device_name(data.get("device_name"))
existing = database.list_passkeys("user", int(current_user.id))
exclude_credential_ids = [str(item.get("credential_id") or "").strip() for item in existing if item.get("credential_id")]
try:
rp_id = get_rp_id(request)
expected_origins = get_expected_origins(request)
except Exception as e:
logger.warning(f"[passkey] 用户注册 options 失败(user_id={current_user.id}): {e}")
return jsonify({"error": "Passkey配置异常请联系管理员"}), 500
try:
options = make_registration_options(
rp_id=rp_id,
rp_name="知识管理平台",
user_name=str(user.get("username") or f"user-{current_user.id}"),
user_display_name=str(user.get("username") or f"user-{current_user.id}"),
user_id_bytes=f"user:{int(current_user.id)}".encode("utf-8"),
exclude_credential_ids=exclude_credential_ids,
)
except Exception as e:
logger.warning(f"[passkey] 用户注册 options 构建失败(user_id={current_user.id}): {e}")
return jsonify({"error": "生成Passkey挑战失败"}), 500
challenge = str(options.get("challenge") or "").strip()
if not challenge:
return jsonify({"error": "生成Passkey挑战失败"}), 500
session[_USER_PASSKEY_REGISTER_SESSION_KEY] = {
"user_id": int(current_user.id),
"challenge": challenge,
"rp_id": rp_id,
"expected_origins": expected_origins,
"device_name": device_name,
"created_at": time.time(),
}
session.modified = True
return jsonify({"publicKey": options, "limit": MAX_PASSKEYS_PER_OWNER})
@api_user_bp.route("/api/user/passkeys/register/verify", methods=["POST"])
@login_required
def user_passkey_register_verify():
"""当前登录用户创建 Passkey校验 attestation 并落库。"""
state = session.get(_USER_PASSKEY_REGISTER_SESSION_KEY) or {}
if not state:
return jsonify({"error": "Passkey挑战不存在或已过期请重试"}), 400
if int(state.get("user_id") or 0) != int(current_user.id):
return jsonify({"error": "Passkey挑战与当前用户不匹配"}), 400
if not is_challenge_valid(state.get("created_at")):
session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"error": "Passkey挑战已过期请重试"}), 400
data = request.get_json(silent=True) or {}
credential = _parse_credential_payload(data)
if not credential:
return jsonify({"error": "Passkey参数缺失"}), 400
count = database.count_passkeys("user", int(current_user.id))
if count >= MAX_PASSKEYS_PER_OWNER:
session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400
try:
verified = verify_registration(
credential=credential,
expected_challenge=str(state.get("challenge") or ""),
expected_rp_id=str(state.get("rp_id") or ""),
expected_origins=list(state.get("expected_origins") or []),
)
except Exception as e:
logger.warning(f"[passkey] 用户注册验签失败(user_id={current_user.id}): {e}")
return jsonify({"error": "Passkey验证失败请重试"}), 400
credential_id = encode_credential_id(verified.credential_id)
public_key = encode_credential_id(verified.credential_public_key)
transports = get_credential_transports(credential)
device_name = normalize_device_name(data.get("device_name") if "device_name" in data else state.get("device_name"))
aaguid = str(verified.aaguid or "")
created_id = database.create_passkey(
"user",
int(current_user.id),
credential_id=credential_id,
public_key=public_key,
sign_count=int(verified.sign_count or 0),
device_name=device_name,
transports=transports,
aaguid=aaguid,
)
if not created_id:
return jsonify({"error": "该Passkey已绑定或保存失败"}), 400
session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
return jsonify({"success": True, "id": int(created_id), "device_name": device_name})
@api_user_bp.route("/api/user/passkeys/<int:passkey_id>", methods=["DELETE"])
@login_required
def delete_user_passkey(passkey_id):
"""删除当前用户绑定的 Passkey 设备。"""
ok = database.delete_passkey("user", int(current_user.id), int(passkey_id))
if ok:
return jsonify({"success": True})
return jsonify({"error": "设备不存在或已删除"}), 404
@api_user_bp.route("/api/user/passkeys/client-error", methods=["POST"])
@login_required
def report_user_passkey_client_error():
"""上报浏览器端 Passkey 失败详情,便于排查兼容性问题。"""
data = request.get_json(silent=True) or {}
error_name = _truncate_text(data.get("name"), 120)
error_message = _truncate_text(data.get("message"), 400)
error_code = _truncate_text(data.get("code"), 120)
ua = _truncate_text(data.get("user_agent") or request.headers.get("User-Agent", ""), 300)
stage = _truncate_text(data.get("stage"), 80)
source = _truncate_text(data.get("source"), 80)
logger.warning(
"[passkey][client-error][user] user_id=%s stage=%s source=%s name=%s code=%s message=%s ua=%s",
current_user.id,
stage or "-",
source or "-",
error_name or "-",
error_code or "-",
error_message or "-",
ua or "-",
)
return jsonify({"success": True})
@api_user_bp.route("/api/run_stats", methods=["GET"]) @api_user_bp.route("/api/run_stats", methods=["GET"])
@login_required @login_required
def get_run_stats(): def get_run_stats():

View File

@@ -1,193 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import json
import time
from typing import Any
from flask import Request
from webauthn import (
generate_authentication_options,
generate_registration_options,
verify_authentication_response,
verify_registration_response,
)
from webauthn.helpers import (
base64url_to_bytes,
bytes_to_base64url,
options_to_json,
parse_authentication_credential_json,
parse_registration_credential_json,
)
from webauthn.helpers.structs import (
AuthenticatorSelectionCriteria,
PublicKeyCredentialHint,
PublicKeyCredentialDescriptor,
ResidentKeyRequirement,
UserVerificationRequirement,
)
MAX_PASSKEYS_PER_OWNER = 3
CHALLENGE_TTL_SECONDS = 300
DEVICE_NAME_MAX_LENGTH = 40
def normalize_device_name(value: Any) -> str:
text = str(value or "").strip()
if not text:
return "未命名设备"
if len(text) > DEVICE_NAME_MAX_LENGTH:
text = text[:DEVICE_NAME_MAX_LENGTH]
return text
def is_challenge_valid(created_at: Any, *, now_ts: float | None = None) -> bool:
try:
created_ts = float(created_at)
except Exception:
return False
if now_ts is None:
now_ts = time.time()
return created_ts > 0 and (now_ts - created_ts) <= CHALLENGE_TTL_SECONDS
def get_rp_id(request: Request) -> str:
forwarded_host = str(request.headers.get("X-Forwarded-Host", "") or "").split(",", 1)[0].strip()
host = forwarded_host or str(request.host or "").strip()
host = host.split(":", 1)[0].strip().lower()
if not host:
raise ValueError("无法确定 RP ID")
return host
def get_expected_origins(request: Request) -> list[str]:
host = str(request.host or "").strip()
if not host:
raise ValueError("无法确定 Origin")
forwarded_proto = str(request.headers.get("X-Forwarded-Proto", "") or "").split(",", 1)[0].strip().lower()
scheme = forwarded_proto if forwarded_proto in {"http", "https"} else str(request.scheme or "https").lower()
origin = f"{scheme}://{host}"
return [origin]
def encode_credential_id(raw_credential_id: bytes) -> str:
return bytes_to_base64url(raw_credential_id)
def decode_credential_id(credential_id: str) -> bytes:
return base64url_to_bytes(str(credential_id or ""))
def _to_public_key_options_json(options) -> dict[str, Any]:
return json.loads(options_to_json(options))
def make_registration_options(
*,
rp_id: str,
rp_name: str,
user_name: str,
user_display_name: str,
user_id_bytes: bytes,
exclude_credential_ids: list[str],
) -> dict[str, Any]:
exclude_credentials = [
PublicKeyCredentialDescriptor(id=decode_credential_id(credential_id))
for credential_id in (exclude_credential_ids or [])
if credential_id
]
authenticator_selection = AuthenticatorSelectionCriteria(
resident_key=ResidentKeyRequirement.PREFERRED,
require_resident_key=False,
user_verification=UserVerificationRequirement.PREFERRED,
)
options = generate_registration_options(
rp_id=rp_id,
rp_name=rp_name,
user_name=user_name,
user_display_name=user_display_name,
user_id=user_id_bytes,
timeout=120000,
authenticator_selection=authenticator_selection,
exclude_credentials=exclude_credentials,
hints=[
PublicKeyCredentialHint.CLIENT_DEVICE,
PublicKeyCredentialHint.HYBRID,
],
)
return _to_public_key_options_json(options)
def make_authentication_options(
*,
rp_id: str,
allow_credential_ids: list[str] | None = None,
) -> dict[str, Any]:
allow_credentials = [
PublicKeyCredentialDescriptor(id=decode_credential_id(credential_id))
for credential_id in (allow_credential_ids or [])
if credential_id
]
allow_credentials_value = allow_credentials if allow_credentials else None
options = generate_authentication_options(
rp_id=rp_id,
timeout=120000,
allow_credentials=allow_credentials_value,
user_verification=UserVerificationRequirement.PREFERRED,
)
return _to_public_key_options_json(options)
def verify_registration(
*,
credential: dict[str, Any],
expected_challenge: str,
expected_rp_id: str,
expected_origins: list[str],
):
parsed = parse_registration_credential_json(credential)
return verify_registration_response(
credential=parsed,
expected_challenge=base64url_to_bytes(expected_challenge),
expected_rp_id=expected_rp_id,
expected_origin=expected_origins,
require_user_verification=True,
)
def verify_authentication(
*,
credential: dict[str, Any],
expected_challenge: str,
expected_rp_id: str,
expected_origins: list[str],
credential_public_key: str,
credential_current_sign_count: int,
):
parsed = parse_authentication_credential_json(credential)
verified = verify_authentication_response(
credential=parsed,
expected_challenge=base64url_to_bytes(expected_challenge),
expected_rp_id=expected_rp_id,
expected_origin=expected_origins,
credential_public_key=base64url_to_bytes(credential_public_key),
credential_current_sign_count=int(credential_current_sign_count or 0),
require_user_verification=True,
)
return parsed, verified
def get_credential_transports(credential: dict[str, Any]) -> str:
response = credential.get("response") if isinstance(credential, dict) else None
transports = response.get("transports") if isinstance(response, dict) else None
if isinstance(transports, list):
normalized = sorted({str(item).strip() for item in transports if str(item).strip()})
return ",".join(normalized)
return ""

View File

@@ -3,8 +3,8 @@
"file": "assets/MetricGrid-BR486o_b.css", "file": "assets/MetricGrid-BR486o_b.css",
"src": "_MetricGrid-BR486o_b.css" "src": "_MetricGrid-BR486o_b.css"
}, },
"_MetricGrid-kv-nSROj.js": { "_MetricGrid-VHNT01i6.js": {
"file": "assets/MetricGrid-kv-nSROj.js", "file": "assets/MetricGrid-VHNT01i6.js",
"name": "MetricGrid", "name": "MetricGrid",
"imports": [ "imports": [
"index.html", "index.html",
@@ -14,36 +14,36 @@
"assets/MetricGrid-BR486o_b.css" "assets/MetricGrid-BR486o_b.css"
] ]
}, },
"_admin-VsbfHbbH.js": { "_admin-DcqTfJCB.js": {
"file": "assets/admin-VsbfHbbH.js", "file": "assets/admin-DcqTfJCB.js",
"name": "admin", "name": "admin",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_email-CgUCpCe3.js": { "_email-CoLcTI83.js": {
"file": "assets/email-CgUCpCe3.js", "file": "assets/email-CoLcTI83.js",
"name": "email", "name": "email",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_system-CeiBEEoE.js": { "_system-CARqmgIY.js": {
"file": "assets/system-CeiBEEoE.js", "file": "assets/system-CARqmgIY.js",
"name": "system", "name": "system",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_tasks-C6JkguA6.js": { "_tasks-BFgZ7F4T.js": {
"file": "assets/tasks-C6JkguA6.js", "file": "assets/tasks-BFgZ7F4T.js",
"name": "tasks", "name": "tasks",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_users-D9XvGIoE.js": { "_users-DuxohAiS.js": {
"file": "assets/users-D9XvGIoE.js", "file": "assets/users-DuxohAiS.js",
"name": "users", "name": "users",
"imports": [ "imports": [
"index.html" "index.html"
@@ -80,7 +80,7 @@
"name": "vendor-vue" "name": "vendor-vue"
}, },
"index.html": { "index.html": {
"file": "assets/index-6ynv0Z9Y.js", "file": "assets/index-zJym-Cg7.js",
"name": "index", "name": "index",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
@@ -107,13 +107,13 @@
] ]
}, },
"src/pages/AdminSocialBindCallbackPage.vue": { "src/pages/AdminSocialBindCallbackPage.vue": {
"file": "assets/AdminSocialBindCallbackPage-BsLZg3f-.js", "file": "assets/AdminSocialBindCallbackPage-C0b0Vr3s.js",
"name": "AdminSocialBindCallbackPage", "name": "AdminSocialBindCallbackPage",
"src": "src/pages/AdminSocialBindCallbackPage.vue", "src": "src/pages/AdminSocialBindCallbackPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_admin-VsbfHbbH.js", "_admin-DcqTfJCB.js",
"index.html", "index.html",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
@@ -124,7 +124,7 @@
] ]
}, },
"src/pages/AnnouncementsPage.vue": { "src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-BcIVG51R.js", "file": "assets/AnnouncementsPage-CDXDBA1J.js",
"name": "AnnouncementsPage", "name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue", "src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -140,14 +140,14 @@
] ]
}, },
"src/pages/EmailPage.vue": { "src/pages/EmailPage.vue": {
"file": "assets/EmailPage-B1uMhyWi.js", "file": "assets/EmailPage-CPa_VcxD.js",
"name": "EmailPage", "name": "EmailPage",
"src": "src/pages/EmailPage.vue", "src": "src/pages/EmailPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_email-CgUCpCe3.js", "_email-CoLcTI83.js",
"index.html", "index.html",
"_MetricGrid-kv-nSROj.js", "_MetricGrid-VHNT01i6.js",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
@@ -158,13 +158,13 @@
] ]
}, },
"src/pages/FeedbacksPage.vue": { "src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-CG9FZytm.js", "file": "assets/FeedbacksPage-Dfxcbeo8.js",
"name": "FeedbacksPage", "name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue", "src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-kv-nSROj.js", "_MetricGrid-VHNT01i6.js",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
@@ -175,13 +175,13 @@
] ]
}, },
"src/pages/LogsPage.vue": { "src/pages/LogsPage.vue": {
"file": "assets/LogsPage-Ct-BSxV6.js", "file": "assets/LogsPage-Dyx_Pdm0.js",
"name": "LogsPage", "name": "LogsPage",
"src": "src/pages/LogsPage.vue", "src": "src/pages/LogsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-D9XvGIoE.js", "_users-DuxohAiS.js",
"_tasks-C6JkguA6.js", "_tasks-BFgZ7F4T.js",
"index.html", "index.html",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
@@ -193,17 +193,17 @@
] ]
}, },
"src/pages/ReportPage.vue": { "src/pages/ReportPage.vue": {
"file": "assets/ReportPage-2jS10KoG.js", "file": "assets/ReportPage-nz6X9nYS.js",
"name": "ReportPage", "name": "ReportPage",
"src": "src/pages/ReportPage.vue", "src": "src/pages/ReportPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"index.html", "index.html",
"_email-CgUCpCe3.js", "_email-CoLcTI83.js",
"_tasks-C6JkguA6.js", "_tasks-BFgZ7F4T.js",
"_system-CeiBEEoE.js", "_system-CARqmgIY.js",
"_MetricGrid-kv-nSROj.js", "_MetricGrid-VHNT01i6.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-misc-DszMq72k.js", "_vendor-misc-DszMq72k.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
@@ -213,13 +213,13 @@
] ]
}, },
"src/pages/SecurityPage.vue": { "src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-93lfkhLF.js", "file": "assets/SecurityPage-WZIr3v_6.js",
"name": "SecurityPage", "name": "SecurityPage",
"src": "src/pages/SecurityPage.vue", "src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-kv-nSROj.js", "_MetricGrid-VHNT01i6.js",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
@@ -230,29 +230,29 @@
] ]
}, },
"src/pages/SettingsPage.vue": { "src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-BbHyIZsy.js", "file": "assets/SettingsPage-CjBdzgUX.js",
"name": "SettingsPage", "name": "SettingsPage",
"src": "src/pages/SettingsPage.vue", "src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-misc-DszMq72k.js", "_vendor-misc-DszMq72k.js",
"_admin-VsbfHbbH.js", "_admin-DcqTfJCB.js",
"index.html", "index.html",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/SettingsPage-CjIQQfeg.css" "assets/SettingsPage-Wd9F5VCe.css"
] ]
}, },
"src/pages/SystemPage.vue": { "src/pages/SystemPage.vue": {
"file": "assets/SystemPage-D9T-fhw-.js", "file": "assets/SystemPage-6eR7PzW1.js",
"name": "SystemPage", "name": "SystemPage",
"src": "src/pages/SystemPage.vue", "src": "src/pages/SystemPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_system-CeiBEEoE.js", "_system-CARqmgIY.js",
"index.html", "index.html",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",
@@ -264,12 +264,12 @@
] ]
}, },
"src/pages/UsersPage.vue": { "src/pages/UsersPage.vue": {
"file": "assets/UsersPage-2-Mno2hz.js", "file": "assets/UsersPage-CFbejgdN.js",
"name": "UsersPage", "name": "UsersPage",
"src": "src/pages/UsersPage.vue", "src": "src/pages/UsersPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-D9XvGIoE.js", "_users-DuxohAiS.js",
"index.html", "index.html",
"_vendor-element-CIudPaVX.js", "_vendor-element-CIudPaVX.js",
"_vendor-vue-CVxSw_oJ.js", "_vendor-vue-CVxSw_oJ.js",

View File

@@ -1 +1 @@
import{ax as w,r as y,o as g,aj as l,n as v,q as f,L as d,E as h,t as k,J as x}from"./vendor-vue-CVxSw_oJ.js";import{i as S}from"./admin-VsbfHbbH.js";import{_ as C}from"./index-6ynv0Z9Y.js";import{a as n}from"./vendor-element-CIudPaVX.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-DszMq72k.js";const b={class:"callback-wrap"},B={class:"callback-text"},P={__name:"AdminSocialBindCallbackPage",setup(q){const o=w(),t=y("正在完成绑定");return g(async()=>{const c=String(window.location.hash||"").split("?")[1]||"",e=new URLSearchParams(window.location.search||c),a=String(e.get("provider")||e.get("type")||"").trim().toLowerCase(),r=String(e.get("code")||"").trim(),p=String(o.query?.provider||o.query?.type||"").trim().toLowerCase(),m=String(o.query?.code||"").trim(),s=a||p,i=r||m;if(!s||!i){n.error("快捷登录回调参数不完整"),window.location.replace("/yuyx/admin#/settings");return}try{await S(s,{provider:s,code:i}),n.success("管理员快捷登录已绑定"),window.location.replace("/yuyx/admin#/settings")}catch(u){const _=u?.response?.data;t.value=_?.error||"快捷登录绑定失败",n.error(t.value),window.setTimeout(()=>{window.location.replace("/yuyx/admin#/settings")},1200)}}),(c,e)=>{const a=l("el-skeleton"),r=l("el-card");return f(),v("div",b,[d(r,{shadow:"never",class:"callback-card"},{default:h(()=>[d(a,{rows:3,animated:""}),k("div",B,x(t.value),1)]),_:1})])}}},T=C(P,[["__scopeId","data-v-647766e7"]]);export{T as default}; import{ax as w,r as y,o as g,aj as l,n as v,q as f,L as d,E as h,t as k,J as x}from"./vendor-vue-CVxSw_oJ.js";import{d as S}from"./admin-DcqTfJCB.js";import{_ as C}from"./index-zJym-Cg7.js";import{a as n}from"./vendor-element-CIudPaVX.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-DszMq72k.js";const b={class:"callback-wrap"},B={class:"callback-text"},P={__name:"AdminSocialBindCallbackPage",setup(q){const o=w(),t=y("正在完成绑定");return g(async()=>{const c=String(window.location.hash||"").split("?")[1]||"",e=new URLSearchParams(window.location.search||c),a=String(e.get("provider")||e.get("type")||"").trim().toLowerCase(),r=String(e.get("code")||"").trim(),p=String(o.query?.provider||o.query?.type||"").trim().toLowerCase(),m=String(o.query?.code||"").trim(),s=a||p,i=r||m;if(!s||!i){n.error("快捷登录回调参数不完整"),window.location.replace("/yuyx/admin#/settings");return}try{await S(s,{provider:s,code:i}),n.success("管理员快捷登录已绑定"),window.location.replace("/yuyx/admin#/settings")}catch(u){const _=u?.response?.data;t.value=_?.error||"快捷登录绑定失败",n.error(t.value),window.setTimeout(()=>{window.location.replace("/yuyx/admin#/settings")},1200)}}),(c,e)=>{const a=l("el-skeleton"),r=l("el-card");return f(),v("div",b,[d(r,{shadow:"never",class:"callback-card"},{default:h(()=>[d(a,{rows:3,animated:""}),k("div",B,x(t.value),1)]),_:1})])}}},T=C(P,[["__scopeId","data-v-647766e7"]]);export{T as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-e6d9cfda]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-e6d9cfda]{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-e6d9cfda]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.section-head[data-v-e6d9cfda]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}.section-head .section-title[data-v-e6d9cfda]{margin-bottom:0}.help[data-v-e6d9cfda]{margin-top:10px;font-size:12px;color:var(--app-muted)}.help-alert[data-v-e6d9cfda]{margin-bottom:12px}.social-list[data-v-e6d9cfda]{display:flex;flex-direction:column;gap:10px}.social-row[data-v-e6d9cfda]{min-height:58px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--app-border);border-radius:10px;background:#f8fafcb8}.social-provider[data-v-e6d9cfda]{display:flex;align-items:center;gap:10px;min-width:0}.social-icon[data-v-e6d9cfda]{width:30px;height:30px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:#fff;font-size:13px;font-weight:800}.provider-wx[data-v-e6d9cfda]{background:#16a34a}.provider-qq[data-v-e6d9cfda]{background:#2563eb}.provider-alipay[data-v-e6d9cfda]{background:#1677ff}.social-info[data-v-e6d9cfda]{min-width:0;display:flex;flex-direction:column;gap:2px}.social-info strong[data-v-e6d9cfda]{font-size:14px}.social-info span[data-v-e6d9cfda]{max-width:min(52vw,360px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--app-muted)}.social-actions[data-v-e6d9cfda]{flex:0 0 auto}.social-qr-box[data-v-e6d9cfda]{display:flex;flex-direction:column;align-items:center;gap:12px}.social-qr-prompt[data-v-e6d9cfda]{font-size:13px;color:#374151;text-align:center}@media(max-width:640px){.social-row[data-v-e6d9cfda]{align-items:flex-start;flex-direction:column}.social-actions[data-v-e6d9cfda]{width:100%;display:flex;justify-content:flex-end}}

View File

@@ -0,0 +1 @@
.page-stack[data-v-b14d3c4a]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-b14d3c4a]{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-b14d3c4a]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.section-head[data-v-b14d3c4a]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}.section-head .section-title[data-v-b14d3c4a]{margin-bottom:0}.help[data-v-b14d3c4a]{margin-top:10px;font-size:12px;color:var(--app-muted)}.help-alert[data-v-b14d3c4a]{margin-bottom:12px}.social-list[data-v-b14d3c4a]{display:flex;flex-direction:column;gap:10px}.social-row[data-v-b14d3c4a]{min-height:58px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--app-border);border-radius:10px;background:#f8fafcb8}.social-provider[data-v-b14d3c4a]{display:flex;align-items:center;gap:10px;min-width:0}.social-icon[data-v-b14d3c4a]{width:30px;height:30px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:#fff;font-size:13px;font-weight:800}.provider-wx[data-v-b14d3c4a]{background:#16a34a}.provider-qq[data-v-b14d3c4a]{background:#2563eb}.provider-alipay[data-v-b14d3c4a]{background:#1677ff}.social-info[data-v-b14d3c4a]{min-width:0;display:flex;flex-direction:column;gap:2px}.social-info strong[data-v-b14d3c4a]{font-size:14px}.social-info span[data-v-b14d3c4a]{max-width:min(52vw,360px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--app-muted)}.social-actions[data-v-b14d3c4a]{flex:0 0 auto}.social-qr-box[data-v-b14d3c4a]{display:flex;flex-direction:column;align-items:center;gap:12px}.social-qr-prompt[data-v-b14d3c4a]{font-size:13px;color:#374151;text-align:center}@media(max-width:640px){.social-row[data-v-b14d3c4a]{align-items:flex-start;flex-direction:column}.social-actions[data-v-b14d3c4a]{width:100%;display:flex;justify-content:flex-end}}

View File

@@ -1,4 +1,4 @@
import{f as Fe,a as He,u as me,b as Oe,t as je}from"./system-CeiBEEoE.js";import{a as ge,_ as Ge,g as Se,h as Ye,i as Je,u as fe,j as Pe,p as We}from"./index-6ynv0Z9Y.js";import{E as _e,a as d}from"./vendor-element-CIudPaVX.js";import{r as s,c as de,l as Xe,R as Ze,o as el,aj as m,ap as ll,F as al,q as y,n as b,t as n,L as l,E as t,I as v,K as ol,a3 as tl,J as A,G as T,y as sl}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-DszMq72k.js";async function nl(){const{data:k}=await ge.get("/proxy/config");return k}async function ul(k){const{data:_}=await ge.post("/proxy/config",k);return _}async function il(k){const{data:_}=await ge.post("/proxy/test",k);return _}const dl={class:"page-stack"},rl={class:"config-grid"},cl={class:"row-actions"},vl={class:"row-actions"},pl={class:"row-actions"},ml={key:0,class:"help"},fl={class:"row-actions"},_l={class:"section-head"},gl={class:"status-inline app-muted"},yl={key:0,class:"status-dots","aria-hidden":"true"},bl={class:"kdocs-inline"},kl={class:"kdocs-range"},Vl={class:"row-actions"},xl={key:0,class:"help"},wl={key:1,class:"help"},Sl={class:"kdocs-qr"},Pl=["src"],Cl={__name:"SystemPage",setup(k){const _=s(!1),B=s(2),q=s(1),M=s(3),z=s(120),L=s(!1),g=s(""),R=s(3),$=s(!1),F=s(10),H=s(7),I=s(!1),h=s("https://www.spacezs.cn/connect.php"),K=s(""),V=s(""),O=s(""),j=s(!1),x=s(["wx"]),re=s(!1),ce=s(!1),Ce=[{label:"QQ",value:"qq"},{label:"微信",value:"wx"},{label:"支付宝",value:"alipay"}],G=s(!1),Y=s(""),J=s(""),W=s(""),X=s(0),Z=s("A"),ee=s("D"),le=s(0),ae=s(0),oe=s(!1),te=s(""),ye=Se({maxAgeMs:600*1e3}),c=s(ye||{}),w=s(!1),S=s(""),ve=s(!1),P=s(!1),C=s(!1),U=s(!1),N=s(!ye),se=s("");let ne=null;const be=de(()=>P.value||C.value||U.value),pe=de(()=>N.value||P.value||ve.value),ue=de(()=>{if(pe.value)return"检测中";const o=c.value||{};return o?.logged_in===!0||o?.last_login_ok===!0?"已登录":o?.logged_in===!1||o?.last_login_ok===!1||o?.login_required===!0?"未登录":o?.last_error?"异常":"未知"}),Ue=de(()=>pe.value?"is-checking":ue.value==="已登录"?"is-online":ue.value==="未登录"?"is-offline":ue.value==="异常"?"is-error":"is-unknown");function r(o){if(!o){se.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});se.value=`${o} (${e})`}async function Ae(){_.value=!0;try{const[e,i,u]=await Promise.all([Fe(),nl(),He()]);B.value=e.max_concurrent_global??2,q.value=e.max_concurrent_per_account??1,M.value=e.max_screenshot_concurrent??3,z.value=e.db_slow_query_ms??120,$.value=(e.auto_approve_enabled??0)===1,F.value=e.auto_approve_hourly_limit??10,H.value=e.auto_approve_vip_days??7,L.value=(i.proxy_enabled??0)===1,g.value=i.proxy_api_url||"",R.value=i.proxy_expire_minutes??3,G.value=(e.kdocs_enabled??0)===1,Y.value=e.kdocs_doc_url||"",J.value=e.kdocs_default_unit||"",W.value=e.kdocs_sheet_name||"",X.value=e.kdocs_sheet_index??0,Z.value=(e.kdocs_unit_column||"A").toUpperCase(),ee.value=(e.kdocs_image_column||"D").toUpperCase(),le.value=e.kdocs_row_start??0,ae.value=e.kdocs_row_end??0,oe.value=(e.kdocs_admin_notify_enabled??0)===1,te.value=e.kdocs_admin_notify_email||"",I.value=(u.social_login_enabled??0)===1,h.value=u.social_login_endpoint||"https://www.spacezs.cn/connect.php",K.value=u.social_login_appid||"",V.value="",O.value=u.social_login_appkey_masked||"",j.value=!!u.social_login_appkey_configured,x.value=Array.isArray(u.social_login_providers)&&u.social_login_providers.length?u.social_login_providers:["wx"]}catch{}finally{_.value=!1}const o=Se({maxAgeMs:600*1e3});o&&(c.value=o,N.value=!1),Le()}async function Le(){if(!(N.value||P.value)){N.value=!0;try{const o=await We({force:!1,maxAgeMs:6e4,silent:!0,live:0});c.value=o||{}}catch{}finally{N.value=!1}}}async function Ie(){const o={max_concurrent_global:Number(B.value),max_concurrent_per_account:Number(q.value),max_screenshot_concurrent:Number(M.value),db_slow_query_ms:Number(z.value)};try{await _e.confirm(`确定更新并发配置吗? import{f as Fe,a as He,u as me,b as Oe,t as je}from"./system-CARqmgIY.js";import{a as ge,_ as Ge,g as Se,h as Ye,i as Je,u as fe,j as Pe,p as We}from"./index-zJym-Cg7.js";import{E as _e,a as d}from"./vendor-element-CIudPaVX.js";import{r as s,c as de,l as Xe,R as Ze,o as el,aj as m,ap as ll,F as al,q as y,n as b,t as n,L as l,E as t,I as v,K as ol,a3 as tl,J as A,G as T,y as sl}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-DszMq72k.js";async function nl(){const{data:k}=await ge.get("/proxy/config");return k}async function ul(k){const{data:_}=await ge.post("/proxy/config",k);return _}async function il(k){const{data:_}=await ge.post("/proxy/test",k);return _}const dl={class:"page-stack"},rl={class:"config-grid"},cl={class:"row-actions"},vl={class:"row-actions"},pl={class:"row-actions"},ml={key:0,class:"help"},fl={class:"row-actions"},_l={class:"section-head"},gl={class:"status-inline app-muted"},yl={key:0,class:"status-dots","aria-hidden":"true"},bl={class:"kdocs-inline"},kl={class:"kdocs-range"},Vl={class:"row-actions"},xl={key:0,class:"help"},wl={key:1,class:"help"},Sl={class:"kdocs-qr"},Pl=["src"],Cl={__name:"SystemPage",setup(k){const _=s(!1),B=s(2),q=s(1),M=s(3),z=s(120),L=s(!1),g=s(""),R=s(3),$=s(!1),F=s(10),H=s(7),I=s(!1),h=s("https://www.spacezs.cn/connect.php"),K=s(""),V=s(""),O=s(""),j=s(!1),x=s(["wx"]),re=s(!1),ce=s(!1),Ce=[{label:"QQ",value:"qq"},{label:"微信",value:"wx"},{label:"支付宝",value:"alipay"}],G=s(!1),Y=s(""),J=s(""),W=s(""),X=s(0),Z=s("A"),ee=s("D"),le=s(0),ae=s(0),oe=s(!1),te=s(""),ye=Se({maxAgeMs:600*1e3}),c=s(ye||{}),w=s(!1),S=s(""),ve=s(!1),P=s(!1),C=s(!1),U=s(!1),N=s(!ye),se=s("");let ne=null;const be=de(()=>P.value||C.value||U.value),pe=de(()=>N.value||P.value||ve.value),ue=de(()=>{if(pe.value)return"检测中";const o=c.value||{};return o?.logged_in===!0||o?.last_login_ok===!0?"已登录":o?.logged_in===!1||o?.last_login_ok===!1||o?.login_required===!0?"未登录":o?.last_error?"异常":"未知"}),Ue=de(()=>pe.value?"is-checking":ue.value==="已登录"?"is-online":ue.value==="未登录"?"is-offline":ue.value==="异常"?"is-error":"is-unknown");function r(o){if(!o){se.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});se.value=`${o} (${e})`}async function Ae(){_.value=!0;try{const[e,i,u]=await Promise.all([Fe(),nl(),He()]);B.value=e.max_concurrent_global??2,q.value=e.max_concurrent_per_account??1,M.value=e.max_screenshot_concurrent??3,z.value=e.db_slow_query_ms??120,$.value=(e.auto_approve_enabled??0)===1,F.value=e.auto_approve_hourly_limit??10,H.value=e.auto_approve_vip_days??7,L.value=(i.proxy_enabled??0)===1,g.value=i.proxy_api_url||"",R.value=i.proxy_expire_minutes??3,G.value=(e.kdocs_enabled??0)===1,Y.value=e.kdocs_doc_url||"",J.value=e.kdocs_default_unit||"",W.value=e.kdocs_sheet_name||"",X.value=e.kdocs_sheet_index??0,Z.value=(e.kdocs_unit_column||"A").toUpperCase(),ee.value=(e.kdocs_image_column||"D").toUpperCase(),le.value=e.kdocs_row_start??0,ae.value=e.kdocs_row_end??0,oe.value=(e.kdocs_admin_notify_enabled??0)===1,te.value=e.kdocs_admin_notify_email||"",I.value=(u.social_login_enabled??0)===1,h.value=u.social_login_endpoint||"https://www.spacezs.cn/connect.php",K.value=u.social_login_appid||"",V.value="",O.value=u.social_login_appkey_masked||"",j.value=!!u.social_login_appkey_configured,x.value=Array.isArray(u.social_login_providers)&&u.social_login_providers.length?u.social_login_providers:["wx"]}catch{}finally{_.value=!1}const o=Se({maxAgeMs:600*1e3});o&&(c.value=o,N.value=!1),Le()}async function Le(){if(!(N.value||P.value)){N.value=!0;try{const o=await We({force:!1,maxAgeMs:6e4,silent:!0,live:0});c.value=o||{}}catch{}finally{N.value=!1}}}async function Ie(){const o={max_concurrent_global:Number(B.value),max_concurrent_per_account:Number(q.value),max_screenshot_concurrent:Number(M.value),db_slow_query_ms:Number(z.value)};try{await _e.confirm(`确定更新并发配置吗?
全局并发数: ${o.max_concurrent_global} 全局并发数: ${o.max_concurrent_global}
单账号并发数: ${o.max_concurrent_per_account} 单账号并发数: ${o.max_concurrent_per_account}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{a as t}from"./index-zJym-Cg7.js";async function c(n){const{data:a}=await t.put("/admin/username",{new_username:n});return a}async function d(n={}){const a=String(n.currentPassword||""),s=String(n.newPassword||""),{data:i}=await t.put("/admin/password",{current_password:a,new_password:s});return i}async function r(){const{data:n}=await t.post("/logout");return n}async function e(){const{data:n}=await t.get("/admin/social-bindings");return n}async function u(n={}){const{data:a}=await t.post("/admin/social-login-url",n);return a}async function l(n={}){const{data:a}=await t.post("/admin/social-poll",n);return a}async function m(n,a={}){const{data:s}=await t.post(`/admin/social-bindings/${encodeURIComponent(n)}/callback`,a);return s}async function w(n){const{data:a}=await t.delete(`/admin/social-bindings/${encodeURIComponent(n)}`);return a}export{d as a,w as b,u as c,m as d,e as f,r as l,l as p,c as u};

View File

@@ -1 +0,0 @@
import{a as s}from"./index-6ynv0Z9Y.js";async function e(a){const{data:n}=await s.put("/admin/username",{new_username:a});return n}async function r(a={}){const n=String(a.currentPassword||""),t=String(a.newPassword||""),{data:i}=await s.put("/admin/password",{current_password:n,new_password:t});return i}async function c(){const{data:a}=await s.post("/logout");return a}async function d(){const{data:a}=await s.get("/admin/passkeys");return a}async function u(a={}){const{data:n}=await s.post("/admin/passkeys/register/options",a);return n}async function m(a={}){const{data:n}=await s.post("/admin/passkeys/register/verify",a);return n}async function p(a){const{data:n}=await s.delete(`/admin/passkeys/${a}`);return n}async function l(a={}){const{data:n}=await s.post("/admin/passkeys/client-error",a);return n}async function w(){const{data:a}=await s.get("/admin/social-bindings");return a}async function y(a={}){const{data:n}=await s.post("/admin/social-login-url",a);return n}async function f(a={}){const{data:n}=await s.post("/admin/social-poll",a);return n}async function g(a,n={}){const{data:t}=await s.post(`/admin/social-bindings/${encodeURIComponent(a)}/callback`,n);return t}async function k(a){const{data:n}=await s.delete(`/admin/social-bindings/${encodeURIComponent(a)}`);return n}export{w as a,r as b,u as c,m as d,p as e,d as f,k as g,y as h,g as i,c as l,f as p,l as r,e as u};

View File

@@ -1 +1 @@
import{c as s,a as e}from"./index-6ynv0Z9Y.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-zJym-Cg7.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

View File

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

View File

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

View File

@@ -1 +1 @@
import{a as t}from"./index-6ynv0Z9Y.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-zJym-Cg7.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title> <title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-6ynv0Z9Y.js"></script> <script type="module" crossorigin src="./assets/index-zJym-Cg7.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-vue-CVxSw_oJ.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-vue-CVxSw_oJ.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-misc-DszMq72k.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-misc-DszMq72k.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-element-CIudPaVX.js"> <link rel="modulepreload" crossorigin href="./assets/vendor-element-CIudPaVX.js">

View File

@@ -1,14 +1,14 @@
{ {
"_SocialLoginButtons-BlVSr6Mm.js": { "_SocialLoginButtons-BaFXslgf.js": {
"file": "assets/SocialLoginButtons-BlVSr6Mm.js", "file": "assets/SocialLoginButtons-BaFXslgf.js",
"name": "SocialLoginButtons", "name": "SocialLoginButtons",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_auth-B5cl_nsV.js", "_auth-CuW_jyJD.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js" "_http-BDcxFXLM.js"
], ],
"css": [ "css": [
"assets/SocialLoginButtons-qO3SCoE7.css" "assets/SocialLoginButtons-qO3SCoE7.css"
@@ -18,60 +18,67 @@
"file": "assets/SocialLoginButtons-qO3SCoE7.css", "file": "assets/SocialLoginButtons-qO3SCoE7.css",
"src": "_SocialLoginButtons-qO3SCoE7.css" "src": "_SocialLoginButtons-qO3SCoE7.css"
}, },
"_accounts-D_6SYB2i.css": { "_accounts-DqlHDq0H.css": {
"file": "assets/accounts-D_6SYB2i.css", "file": "assets/accounts-DqlHDq0H.css",
"src": "_accounts-D_6SYB2i.css" "src": "_accounts-DqlHDq0H.css"
}, },
"_accounts-DzntEHJR.js": { "_accounts-HALpNswY.js": {
"file": "assets/accounts-DzntEHJR.js", "file": "assets/accounts-HALpNswY.js",
"name": "accounts", "name": "accounts",
"imports": [ "imports": [
"_http-BoPYlvwK.js" "_vendor-vue-Da_zwKNU.js",
"_base-C_0HtztH.js",
"_el-input-nl0Ylqa_.js",
"_aria-DLpFpzDe.js",
"_el-button-xGNUoXVX.js",
"_index-D04QrwME.js",
"_user-DIrCtqzm.js",
"_http-BDcxFXLM.js"
], ],
"css": [ "css": [
"assets/accounts-D_6SYB2i.css" "assets/accounts-DqlHDq0H.css"
] ]
}, },
"_aria-DLpFpzDe.js": { "_aria-DLpFpzDe.js": {
"file": "assets/aria-DLpFpzDe.js", "file": "assets/aria-DLpFpzDe.js",
"name": "aria" "name": "aria"
}, },
"_auth-B5cl_nsV.js": { "_auth-CuW_jyJD.js": {
"file": "assets/auth-B5cl_nsV.js", "file": "assets/auth-CuW_jyJD.js",
"name": "auth", "name": "auth",
"imports": [ "imports": [
"_http-BoPYlvwK.js" "_http-BDcxFXLM.js"
]
},
"_base-C_0HtztH.js": {
"file": "assets/base-C_0HtztH.js",
"name": "base",
"imports": [
"_vendor-vue-Da_zwKNU.js"
],
"css": [
"assets/base-CiSqh4F9.css"
] ]
}, },
"_base-CiSqh4F9.css": { "_base-CiSqh4F9.css": {
"file": "assets/base-CiSqh4F9.css", "file": "assets/base-CiSqh4F9.css",
"src": "_base-CiSqh4F9.css" "src": "_base-CiSqh4F9.css"
}, },
"_base-xgxQQEpV.js": {
"file": "assets/base-xgxQQEpV.js",
"name": "base",
"imports": [
"_vendor-vue-WbiK4TmU.js"
],
"css": [
"assets/base-CiSqh4F9.css"
]
},
"_el-alert-B-NgiIln.css": { "_el-alert-B-NgiIln.css": {
"file": "assets/el-alert-B-NgiIln.css", "file": "assets/el-alert-B-NgiIln.css",
"src": "_el-alert-B-NgiIln.css" "src": "_el-alert-B-NgiIln.css"
}, },
"_el-alert-DTUOkrAB.js": { "_el-alert-BgJljmz-.js": {
"file": "assets/el-alert-DTUOkrAB.js", "file": "assets/el-alert-BgJljmz-.js",
"name": "el-alert", "name": "el-alert",
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_index-CoYtSGUZ.js" "_index-D04QrwME.js"
], ],
"css": [ "css": [
"assets/el-alert-B-NgiIln.css" "assets/el-alert-B-NgiIln.css"
@@ -81,13 +88,13 @@
"file": "assets/el-button-BRDnKxwT.css", "file": "assets/el-button-BRDnKxwT.css",
"src": "_el-button-BRDnKxwT.css" "src": "_el-button-BRDnKxwT.css"
}, },
"_el-button-LKkD3jQh.js": { "_el-button-xGNUoXVX.js": {
"file": "assets/el-button-LKkD3jQh.js", "file": "assets/el-button-xGNUoXVX.js",
"name": "el-button", "name": "el-button",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_vendor-vue-WbiK4TmU.js" "_vendor-vue-Da_zwKNU.js"
], ],
"css": [ "css": [
"assets/el-button-BRDnKxwT.css" "assets/el-button-BRDnKxwT.css"
@@ -97,65 +104,44 @@
"file": "assets/el-card-BqOrgVp1.css", "file": "assets/el-card-BqOrgVp1.css",
"src": "_el-card-BqOrgVp1.css" "src": "_el-card-BqOrgVp1.css"
}, },
"_el-card-CfK866jr.js": { "_el-card-cnxuvbL3.js": {
"file": "assets/el-card-CfK866jr.js", "file": "assets/el-card-cnxuvbL3.js",
"name": "el-card", "name": "el-card",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_vendor-vue-WbiK4TmU.js" "_vendor-vue-Da_zwKNU.js"
], ],
"css": [ "css": [
"assets/el-card-BqOrgVp1.css" "assets/el-card-BqOrgVp1.css"
] ]
}, },
"_el-empty-B4_NEFfq.js": {
"file": "assets/el-empty-B4_NEFfq.js",
"name": "el-empty",
"imports": [
"_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-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": { "_el-input-D6B3r8CH.css": {
"file": "assets/el-input-D6B3r8CH.css", "file": "assets/el-input-D6B3r8CH.css",
"src": "_el-input-D6B3r8CH.css" "src": "_el-input-D6B3r8CH.css"
}, },
"_el-overlay-hge8bsIn.js": { "_el-input-nl0Ylqa_.js": {
"file": "assets/el-overlay-hge8bsIn.js", "file": "assets/el-input-nl0Ylqa_.js",
"name": "el-input",
"imports": [
"_base-C_0HtztH.js",
"_vendor-vue-Da_zwKNU.js",
"_http-BDcxFXLM.js",
"_aria-DLpFpzDe.js",
"_el-button-xGNUoXVX.js"
],
"css": [
"assets/el-input-D6B3r8CH.css"
]
},
"_el-overlay-ckkTzDcK.js": {
"file": "assets/el-overlay-ckkTzDcK.js",
"name": "el-overlay", "name": "el-overlay",
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_index-CoYtSGUZ.js" "_index-D04QrwME.js"
], ],
"css": [ "css": [
"assets/el-overlay-hkg5a9kt.css" "assets/el-overlay-hkg5a9kt.css"
@@ -169,94 +155,106 @@
"file": "assets/el-pagination-B1FwbX1n.css", "file": "assets/el-pagination-B1FwbX1n.css",
"src": "_el-pagination-B1FwbX1n.css" "src": "_el-pagination-B1FwbX1n.css"
}, },
"_el-pagination-kVJ2XlAP.js": { "_el-pagination-D16TMO1B.js": {
"file": "assets/el-pagination-kVJ2XlAP.js", "file": "assets/el-pagination-D16TMO1B.js",
"name": "el-pagination", "name": "el-pagination",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_el-select-CBs1QjJm.js", "_el-select-BADfKG7m.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js" "_index-D04QrwME.js"
], ],
"css": [ "css": [
"assets/el-pagination-B1FwbX1n.css" "assets/el-pagination-B1FwbX1n.css"
] ]
}, },
"_el-select-CBs1QjJm.js": { "_el-popper-BrfLRiIr.css": {
"file": "assets/el-select-CBs1QjJm.js", "file": "assets/el-popper-BrfLRiIr.css",
"name": "el-select", "src": "_el-popper-BrfLRiIr.css"
},
"_el-popper-_4NhtSRX.js": {
"file": "assets/el-popper-_4NhtSRX.js",
"name": "el-popper",
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_base-C_0HtztH.js",
"_el-empty-B4_NEFfq.js", "_vendor-vue-Da_zwKNU.js",
"_base-xgxQQEpV.js", "_el-input-nl0Ylqa_.js",
"_el-overlay-ckkTzDcK.js",
"_http-BDcxFXLM.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_el-input-BaZNy9Kg.js", "_index-D04QrwME.js",
"_index-CoYtSGUZ.js", "_el-button-xGNUoXVX.js"
"_http-BoPYlvwK.js",
"_el-button-LKkD3jQh.js",
"_el-overlay-hge8bsIn.js"
], ],
"css": [ "css": [
"assets/el-select-D_oyzAZN.css" "assets/el-popper-BrfLRiIr.css"
] ]
}, },
"_el-select-D_oyzAZN.css": { "_el-select-B0XIb2QK.css": {
"file": "assets/el-select-D_oyzAZN.css", "file": "assets/el-select-B0XIb2QK.css",
"src": "_el-select-D_oyzAZN.css" "src": "_el-select-B0XIb2QK.css"
},
"_el-select-BADfKG7m.js": {
"file": "assets/el-select-BADfKG7m.js",
"name": "el-select",
"imports": [
"_vendor-vue-Da_zwKNU.js",
"_el-popper-_4NhtSRX.js",
"_http-BDcxFXLM.js",
"_base-C_0HtztH.js",
"_aria-DLpFpzDe.js",
"_el-input-nl0Ylqa_.js",
"_index-D04QrwME.js",
"_el-button-xGNUoXVX.js",
"_el-overlay-ckkTzDcK.js"
],
"css": [
"assets/el-select-B0XIb2QK.css"
]
}, },
"_el-skeleton-item-BLY1jEuR.css": { "_el-skeleton-item-BLY1jEuR.css": {
"file": "assets/el-skeleton-item-BLY1jEuR.css", "file": "assets/el-skeleton-item-BLY1jEuR.css",
"src": "_el-skeleton-item-BLY1jEuR.css" "src": "_el-skeleton-item-BLY1jEuR.css"
}, },
"_el-skeleton-item-CD5Idavp.js": { "_el-skeleton-item-cWa5ANvD.js": {
"file": "assets/el-skeleton-item-CD5Idavp.js", "file": "assets/el-skeleton-item-cWa5ANvD.js",
"name": "el-skeleton-item", "name": "el-skeleton-item",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_vendor-vue-WbiK4TmU.js" "_vendor-vue-Da_zwKNU.js"
], ],
"css": [ "css": [
"assets/el-skeleton-item-BLY1jEuR.css" "assets/el-skeleton-item-BLY1jEuR.css"
] ]
}, },
"_http-BoPYlvwK.js": { "_http-BDcxFXLM.js": {
"file": "assets/http-BoPYlvwK.js", "file": "assets/http-BDcxFXLM.js",
"name": "http", "name": "http",
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
] ]
}, },
"_index-CoYtSGUZ.js": { "_index-D04QrwME.js": {
"file": "assets/index-CoYtSGUZ.js", "file": "assets/index-D04QrwME.js",
"name": "index", "name": "index",
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_vendor-vue-WbiK4TmU.js" "_vendor-vue-Da_zwKNU.js"
]
},
"_isArrayLikeObject-B5fs56rA.js": {
"file": "assets/isArrayLikeObject-B5fs56rA.js",
"name": "isArrayLikeObject",
"imports": [
"_el-input-BaZNy9Kg.js",
"_base-xgxQQEpV.js",
"_el-empty-B4_NEFfq.js"
] ]
}, },
"_password-7ryi82gE.js": { "_password-7ryi82gE.js": {
"file": "assets/password-7ryi82gE.js", "file": "assets/password-7ryi82gE.js",
"name": "password" "name": "password"
}, },
"_settings-Db4PmPGC.js": { "_settings-C8OWd3zp.js": {
"file": "assets/settings-Db4PmPGC.js", "file": "assets/settings-C8OWd3zp.js",
"name": "settings", "name": "settings",
"imports": [ "imports": [
"_http-BoPYlvwK.js" "_http-BDcxFXLM.js"
] ]
}, },
"_style-BHGuKLUF.css": { "_style-BHGuKLUF.css": {
@@ -270,26 +268,25 @@
"assets/style-BHGuKLUF.css" "assets/style-BHGuKLUF.css"
] ]
}, },
"_user-B7bO5p8k.css": { "_user-B5lTGWdM.css": {
"file": "assets/user-B7bO5p8k.css", "file": "assets/user-B5lTGWdM.css",
"src": "_user-B7bO5p8k.css" "src": "_user-B5lTGWdM.css"
}, },
"_user-BlXB4Zbh.js": { "_user-DIrCtqzm.js": {
"file": "assets/user-BlXB4Zbh.js", "file": "assets/user-DIrCtqzm.js",
"name": "user", "name": "user",
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_base-C_0HtztH.js",
"_base-xgxQQEpV.js", "_el-alert-BgJljmz-.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_vendor-vue-Da_zwKNU.js",
"_el-popper-_4NhtSRX.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_index-CoYtSGUZ.js", "_http-BDcxFXLM.js"
"_el-alert-DTUOkrAB.js",
"_el-empty-B4_NEFfq.js",
"_http-BoPYlvwK.js"
], ],
"css": [ "css": [
"assets/user-B7bO5p8k.css" "assets/user-B5lTGWdM.css"
] ]
}, },
"_vendor-axios-B9ygI19o.js": { "_vendor-axios-B9ygI19o.js": {
@@ -300,18 +297,18 @@
"file": "assets/vendor-realtime-CA1CrNgP.js", "file": "assets/vendor-realtime-CA1CrNgP.js",
"name": "vendor-realtime" "name": "vendor-realtime"
}, },
"_vendor-vue-WbiK4TmU.js": { "_vendor-vue-Da_zwKNU.js": {
"file": "assets/vendor-vue-WbiK4TmU.js", "file": "assets/vendor-vue-Da_zwKNU.js",
"name": "vendor-vue" "name": "vendor-vue"
}, },
"index.html": { "index.html": {
"file": "assets/app-CV_JALyE.js", "file": "assets/app-D7SWy-KG.js",
"name": "app", "name": "app",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-WbiK4TmU.js" "_vendor-vue-Da_zwKNU.js"
], ],
"dynamicImports": [ "dynamicImports": [
"src/pages/LoginPage.vue", "src/pages/LoginPage.vue",
@@ -326,78 +323,77 @@
] ]
}, },
"login.html": { "login.html": {
"file": "assets/login-rQcRwu0T.js", "file": "assets/login-C88J0b5r.js",
"name": "login", "name": "login",
"src": "login.html", "src": "login.html",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"src/pages/LoginPage.vue", "src/pages/LoginPage.vue",
"_SocialLoginButtons-BlVSr6Mm.js", "_SocialLoginButtons-BaFXslgf.js",
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_auth-B5cl_nsV.js" "_auth-CuW_jyJD.js"
] ]
}, },
"src/layouts/AppLayout.vue": { "src/layouts/AppLayout.vue": {
"file": "assets/AppLayout-D9A8Va7K.js", "file": "assets/AppLayout-8mkxrTVV.js",
"name": "AppLayout", "name": "AppLayout",
"src": "src/layouts/AppLayout.vue", "src": "src/layouts/AppLayout.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_user-BlXB4Zbh.js", "_user-DIrCtqzm.js",
"_el-empty-B4_NEFfq.js", "_el-alert-BgJljmz-.js",
"_el-alert-DTUOkrAB.js", "_el-popper-_4NhtSRX.js",
"_el-skeleton-item-CD5Idavp.js", "_el-skeleton-item-cWa5ANvD.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_auth-B5cl_nsV.js", "_auth-CuW_jyJD.js",
"_settings-Db4PmPGC.js", "_settings-C8OWd3zp.js",
"_SocialLoginButtons-BlVSr6Mm.js", "_SocialLoginButtons-BaFXslgf.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_isArrayLikeObject-B5fs56rA.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
"assets/AppLayout-CJKAa2WS.css" "assets/AppLayout-C0FaVSZn.css"
] ]
}, },
"src/pages/AccountsPage.vue": { "src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-B7MLZrfr.js", "file": "assets/AccountsPage-DZM5eF8A.js",
"name": "AccountsPage", "name": "AccountsPage",
"src": "src/pages/AccountsPage.vue", "src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_el-alert-DTUOkrAB.js", "_el-alert-BgJljmz-.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_user-BlXB4Zbh.js", "_accounts-HALpNswY.js",
"_accounts-DzntEHJR.js", "_el-popper-_4NhtSRX.js",
"_el-empty-B4_NEFfq.js", "_el-skeleton-item-cWa5ANvD.js",
"_el-skeleton-item-CD5Idavp.js", "_user-DIrCtqzm.js",
"_el-select-CBs1QjJm.js", "_el-select-BADfKG7m.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_settings-Db4PmPGC.js", "_settings-C8OWd3zp.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_vendor-realtime-CA1CrNgP.js", "_vendor-realtime-CA1CrNgP.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -405,44 +401,44 @@
] ]
}, },
"src/pages/LoginPage.vue": { "src/pages/LoginPage.vue": {
"file": "assets/LoginPage-BtooAZsk.js", "file": "assets/LoginPage-N6sdjwkY.js",
"name": "LoginPage", "name": "LoginPage",
"src": "src/pages/LoginPage.vue", "src": "src/pages/LoginPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_SocialLoginButtons-BlVSr6Mm.js", "_SocialLoginButtons-BaFXslgf.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_vendor-axios-B9ygI19o.js", "_vendor-axios-B9ygI19o.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_auth-B5cl_nsV.js" "_auth-CuW_jyJD.js"
], ],
"css": [ "css": [
"assets/LoginPage-vCVLchWz.css" "assets/LoginPage-CSaMrhQm.css"
] ]
}, },
"src/pages/RegisterPage.vue": { "src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-Cb1mme2j.js", "file": "assets/RegisterPage-Bypz6ilN.js",
"name": "RegisterPage", "name": "RegisterPage",
"src": "src/pages/RegisterPage.vue", "src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_el-alert-DTUOkrAB.js", "_el-alert-BgJljmz-.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_auth-B5cl_nsV.js", "_auth-CuW_jyJD.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -450,23 +446,23 @@
] ]
}, },
"src/pages/ResetPasswordPage.vue": { "src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage-CUOK0fe1.js", "file": "assets/ResetPasswordPage-Znm7wIOo.js",
"name": "ResetPasswordPage", "name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue", "src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_el-alert-DTUOkrAB.js", "_el-alert-BgJljmz-.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_auth-B5cl_nsV.js", "_auth-CuW_jyJD.js",
"_password-7ryi82gE.js", "_password-7ryi82gE.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -474,29 +470,28 @@
] ]
}, },
"src/pages/SchedulesPage.vue": { "src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-0TKGPmUl.js", "file": "assets/SchedulesPage-vAAprGPM.js",
"name": "SchedulesPage", "name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue", "src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-empty-B4_NEFfq.js", "_el-popper-_4NhtSRX.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_el-alert-DTUOkrAB.js", "_el-alert-BgJljmz-.js",
"_el-select-CBs1QjJm.js", "_el-select-BADfKG7m.js",
"_user-BlXB4Zbh.js", "_accounts-HALpNswY.js",
"_accounts-DzntEHJR.js", "_el-input-nl0Ylqa_.js",
"_el-input-BaZNy9Kg.js", "_el-pagination-D16TMO1B.js",
"_el-pagination-kVJ2XlAP.js", "_el-card-cnxuvbL3.js",
"_el-card-CfK866jr.js", "_user-DIrCtqzm.js",
"_el-skeleton-item-CD5Idavp.js", "_el-skeleton-item-cWa5ANvD.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_isArrayLikeObject-B5fs56rA.js", "_index-D04QrwME.js",
"_index-CoYtSGUZ.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -504,25 +499,25 @@
] ]
}, },
"src/pages/ScreenshotsPage.vue": { "src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-F6GpvKGW.js", "file": "assets/ScreenshotsPage-DrfiqfWk.js",
"name": "ScreenshotsPage", "name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue", "src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-overlay-hge8bsIn.js", "_el-overlay-ckkTzDcK.js",
"_el-pagination-kVJ2XlAP.js", "_el-pagination-D16TMO1B.js",
"_el-empty-B4_NEFfq.js", "_el-popper-_4NhtSRX.js",
"_el-select-CBs1QjJm.js", "_el-select-BADfKG7m.js",
"_el-input-BaZNy9Kg.js", "_el-input-nl0Ylqa_.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_el-skeleton-item-CD5Idavp.js", "_el-skeleton-item-cWa5ANvD.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_aria-DLpFpzDe.js", "_aria-DLpFpzDe.js",
"_index-CoYtSGUZ.js", "_index-D04QrwME.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -530,19 +525,19 @@
] ]
}, },
"src/pages/SocialBindCallbackPage.vue": { "src/pages/SocialBindCallbackPage.vue": {
"file": "assets/SocialBindCallbackPage-DraQ_mks.js", "file": "assets/SocialBindCallbackPage-BXLD-LiQ.js",
"name": "SocialBindCallbackPage", "name": "SocialBindCallbackPage",
"src": "src/pages/SocialBindCallbackPage.vue", "src": "src/pages/SocialBindCallbackPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_el-skeleton-item-CD5Idavp.js", "_el-skeleton-item-cWa5ANvD.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_auth-B5cl_nsV.js", "_auth-CuW_jyJD.js",
"_settings-Db4PmPGC.js", "_settings-C8OWd3zp.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_http-BoPYlvwK.js", "_http-BDcxFXLM.js",
"_vendor-axios-B9ygI19o.js" "_vendor-axios-B9ygI19o.js"
], ],
"css": [ "css": [
@@ -550,17 +545,17 @@
] ]
}, },
"src/pages/VerifyResultPage.vue": { "src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-BUSE4fL8.js", "file": "assets/VerifyResultPage-Du3cLyZ2.js",
"name": "VerifyResultPage", "name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue", "src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_base-xgxQQEpV.js", "_base-C_0HtztH.js",
"_el-card-CfK866jr.js", "_el-card-cnxuvbL3.js",
"_el-button-LKkD3jQh.js", "_el-button-xGNUoXVX.js",
"_vendor-vue-WbiK4TmU.js", "_vendor-vue-Da_zwKNU.js",
"_style-CEbARg1o.js", "_style-CEbARg1o.js",
"_index-CoYtSGUZ.js" "_index-D04QrwME.js"
], ],
"css": [ "css": [
"assets/VerifyResultPage-BnGv8vyq.css" "assets/VerifyResultPage-BnGv8vyq.css"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +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}; import"./base-C_0HtztH.js";import{E as R}from"./el-card-cnxuvbL3.js";import{E as F,a as L,b as M}from"./el-alert-BgJljmz-.js";import{E as U}from"./el-input-nl0Ylqa_.js";import{E as j}from"./el-button-xGNUoXVX.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-Da_zwKNU.js";import{c as W}from"./auth-CuW_jyJD.js";import{v as X}from"./password-7ryi82gE.js";import{_ as Y}from"./style-CEbARg1o.js";import{E as v}from"./http-BDcxFXLM.js";import"./aria-DLpFpzDe.js";import"./index-D04QrwME.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};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +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}; import"./base-C_0HtztH.js";import{E as l}from"./el-card-cnxuvbL3.js";import{E as p}from"./el-skeleton-item-cWa5ANvD.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-Da_zwKNU.js";import{s as w}from"./auth-CuW_jyJD.js";import{b as h}from"./settings-C8OWd3zp.js";import{_ as S}from"./style-CEbARg1o.js";import{E as t}from"./http-BDcxFXLM.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};

View File

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

View File

@@ -1 +0,0 @@
.el-checkbox-group{font-size:0;line-height:0}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
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};

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./LoginPage-N6sdjwkY.js","./vendor-vue-Da_zwKNU.js","./SocialLoginButtons-BaFXslgf.js","./base-C_0HtztH.js","./base-CiSqh4F9.css","./el-overlay-ckkTzDcK.js","./aria-DLpFpzDe.js","./http-BDcxFXLM.js","./vendor-axios-B9ygI19o.js","./index-D04QrwME.js","./el-overlay-hkg5a9kt.css","./auth-CuW_jyJD.js","./style-CEbARg1o.js","./style-BHGuKLUF.css","./SocialLoginButtons-qO3SCoE7.css","./LoginPage-CSaMrhQm.css","./RegisterPage-Bypz6ilN.js","./el-card-cnxuvbL3.js","./el-card-BqOrgVp1.css","./el-alert-BgJljmz-.js","./el-button-xGNUoXVX.js","./el-button-BRDnKxwT.css","./el-input-nl0Ylqa_.js","./el-input-D6B3r8CH.css","./el-alert-B-NgiIln.css","./password-7ryi82gE.js","./RegisterPage-LYXwWYc1.css","./ResetPasswordPage-Znm7wIOo.js","./ResetPasswordPage-DAB63ins.css","./SocialBindCallbackPage-BXLD-LiQ.js","./el-skeleton-item-cWa5ANvD.js","./el-skeleton-item-BLY1jEuR.css","./settings-C8OWd3zp.js","./SocialBindCallbackPage-BZgzv_7a.css","./VerifyResultPage-Du3cLyZ2.js","./VerifyResultPage-BnGv8vyq.css","./AppLayout-8mkxrTVV.js","./user-DIrCtqzm.js","./el-popper-_4NhtSRX.js","./el-popper-BrfLRiIr.css","./user-B5lTGWdM.css","./AppLayout-C0FaVSZn.css","./AccountsPage-DZM5eF8A.js","./accounts-HALpNswY.js","./accounts-DqlHDq0H.css","./el-select-BADfKG7m.js","./el-select-B0XIb2QK.css","./vendor-realtime-CA1CrNgP.js","./AccountsPage-DKewJ7S7.css","./SchedulesPage-vAAprGPM.js","./el-pagination-D16TMO1B.js","./el-pagination-B1FwbX1n.css","./SchedulesPage-Dxq2ghmQ.css","./ScreenshotsPage-DrfiqfWk.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-Da_zwKNU.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-N6sdjwkY.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-Bypz6ilN.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-Znm7wIOo.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-BXLD-LiQ.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-Du3cLyZ2.js"),__vite__mapDeps([34,3,1,4,17,18,20,9,21,12,13,35]),import.meta.url),B=()=>o(()=>import("./AppLayout-8mkxrTVV.js"),__vite__mapDeps([36,3,1,4,37,19,20,9,21,22,7,8,6,23,24,38,5,10,39,40,30,31,11,32,2,12,13,14,25,41]),import.meta.url),N=()=>o(()=>import("./AccountsPage-DZM5eF8A.js"),__vite__mapDeps([42,3,1,4,5,6,7,8,9,10,19,20,21,22,23,24,43,37,38,39,40,44,30,31,45,46,17,18,32,47,12,13,48]),import.meta.url),j=()=>o(()=>import("./SchedulesPage-vAAprGPM.js"),__vite__mapDeps([49,3,1,4,38,22,7,8,6,20,9,21,23,5,10,39,19,24,45,46,43,37,40,44,50,51,17,18,30,31,12,13,52]),import.meta.url),q=()=>o(()=>import("./ScreenshotsPage-DrfiqfWk.js"),__vite__mapDeps([53,3,1,4,5,6,7,8,9,10,50,45,38,22,20,21,23,39,46,51,17,18,30,31,12,13,54]),import.meta.url),x=[{path:"/",redirect:"/login"},{path:"/login",name:"login",component:I},{path:"/register",name:"register",component:S},{path:"/reset-password/:token",name:"reset_password",component:C},{path:"/social-bind-callback",name:"social_bind_callback",component:$},{path:"/api/verify-email/:token",name:"verify_email",component:E},{path:"/api/verify-bind-email/:token",name:"verify_bind_email",component:E},{path:"/app",component:B,children:[{path:"",redirect:"/app/accounts"},{path:"accounts",name:"accounts",component:N},{path:"schedules",name:"schedules",component:j},{path:"screenshots",name:"screenshots",component:q}]},{path:"/:pathMatch(.*)*",redirect:"/login"}],M=A({history:L(),routes:x});k(O).use(b()).use(M).mount("#app");

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{b as u,t as h,aK as C,u as v,_ as c,d as m}from"./base-xgxQQEpV.js";import{A as p,j as r,o as t,l as n,k as S,n as o,C as a,B as l,x as i,t as f,U as b}from"./vendor-vue-WbiK4TmU.js";const g=u({header:{type:String,default:""},footer:{type:String,default:""},bodyStyle:{type:h([String,Object,Array]),default:""},headerClass:String,bodyClass:String,footerClass:String,shadow:{type:String,values:["always","hover","never"],default:void 0}}),w=p({name:"ElCard"}),$=p({...w,props:g,setup(B){const y=C("card"),s=v("card");return(e,E)=>{var d;return t(),r("div",{class:o([a(s).b(),a(s).is(`${e.shadow||((d=a(y))==null?void 0:d.shadow)||"always"}-shadow`)])},[e.$slots.header||e.header?(t(),r("div",{key:0,class:o([a(s).e("header"),e.headerClass])},[l(e.$slots,"header",{},()=>[i(f(e.header),1)])],2)):n("v-if",!0),S("div",{class:o([a(s).e("body"),e.bodyClass]),style:b(e.bodyStyle)},[l(e.$slots,"default")],6),e.$slots.footer||e.footer?(t(),r("div",{key:1,class:o([a(s).e("footer"),e.footerClass])},[l(e.$slots,"footer",{},()=>[i(f(e.footer),1)])],2)):n("v-if",!0)],2)}}});var k=c($,[["__file","card.vue"]]);const V=m(k);export{V as E}; import{b as u,g as h,aG as C,u as v,_ as c,d as m}from"./base-C_0HtztH.js";import{A as p,j as r,o as t,l as n,k as g,n as o,C as a,B as l,x as i,t as f,U as S}from"./vendor-vue-Da_zwKNU.js";const b=u({header:{type:String,default:""},footer:{type:String,default:""},bodyStyle:{type:h([String,Object,Array]),default:""},headerClass:String,bodyClass:String,footerClass:String,shadow:{type:String,values:["always","hover","never"],default:void 0}}),w=p({name:"ElCard"}),$=p({...w,props:b,setup(B){const y=C("card"),s=v("card");return(e,E)=>{var d;return t(),r("div",{class:o([a(s).b(),a(s).is(`${e.shadow||((d=a(y))==null?void 0:d.shadow)||"always"}-shadow`)])},[e.$slots.header||e.header?(t(),r("div",{key:0,class:o([a(s).e("header"),e.headerClass])},[l(e.$slots,"header",{},()=>[i(f(e.header),1)])],2)):n("v-if",!0),g("div",{class:o([a(s).e("body"),e.bodyClass]),style:S(e.bodyStyle)},[l(e.$slots,"default")],6),e.$slots.footer||e.footer?(t(),r("div",{key:1,class:o([a(s).e("footer"),e.footerClass])},[l(e.$slots,"footer",{},()=>[i(f(e.footer),1)])],2)):n("v-if",!0)],2)}}});var k=c($,[["__file","card.vue"]]);const V=m(k);export{V as E};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{b,t as S,_ as h,u as N,a_ as P,s as $,J as E,d as I,z as T}from"./base-xgxQQEpV.js";import{A as p,j as u,o as i,c as w,l as B,C as t,n as f,X as v,g as j,i as C,L as V,Z as z,B as _,F as m,a4 as g,p as L,O as y,aj as O}from"./vendor-vue-WbiK4TmU.js";const F=b({animated:Boolean,count:{type:Number,default:1},rows:{type:Number,default:3},loading:{type:Boolean,default:!0},throttle:{type:S([Number,Object])}}),R=b({variant:{type:String,values:["circle","rect","h1","h3","text","caption","p","image","button"],default:"text"}}),A=p({name:"ElSkeletonItem"}),H=p({...A,props:R,setup(l){const e=N("skeleton");return(r,s)=>(i(),u("div",{class:f([t(e).e("item"),t(e).e(r.variant)])},[r.variant==="image"?(i(),w(t(P),{key:0})):B("v-if",!0)],2))}});var c=h(H,[["__file","skeleton-item.vue"]]);const J=(l,e=0)=>{if(e===0)return l;const r=v(e)&&!!e.initVal,s=j(r);let o=null;const a=n=>{if(E(n)){s.value=l.value;return}o&&clearTimeout(o),o=setTimeout(()=>{s.value=l.value},n)},d=n=>{n==="leading"?$(e)?a(e):a(e.leading):v(e)?a(e.trailing):s.value=!1};return C(()=>d("leading")),V(()=>l.value,n=>{d(n?"leading":"trailing")}),s},M=p({name:"ElSkeleton"}),U=p({...M,props:F,setup(l,{expose:e}){const r=l,s=N("skeleton"),o=J(z(r,"loading"),r.throttle);return e({uiLoading:o}),(a,d)=>t(o)?(i(),u("div",y({key:0,class:[t(s).b(),t(s).is("animated",a.animated)]},a.$attrs),[(i(!0),u(m,null,g(a.count,n=>(i(),u(m,{key:n},[t(o)?_(a.$slots,"template",{key:n},()=>[L(c,{class:f(t(s).is("first")),variant:"p"},null,8,["class"]),(i(!0),u(m,null,g(a.rows,k=>(i(),w(c,{key:k,class:f([t(s).e("paragraph"),t(s).is("last",k===a.rows&&a.rows>1)]),variant:"p"},null,8,["class"]))),128))]):B("v-if",!0)],64))),128))],16)):_(a.$slots,"default",O(y({key:1},a.$attrs)))}});var X=h(U,[["__file","skeleton.vue"]]);const D=I(X,{SkeletonItem:c});T(c);export{D as E};

View File

@@ -0,0 +1 @@
import{b,g as S,_ as h,u as N,aX as P,f as $,v as E,d as I,l as T}from"./base-C_0HtztH.js";import{A as p,j as u,o as i,c as w,l as B,C as t,n as m,X as k,g as C,i as V,L as j,Z as L,B as _,F as f,a3 as g,p as O,O as y,af as z}from"./vendor-vue-Da_zwKNU.js";const F=b({animated:Boolean,count:{type:Number,default:1},rows:{type:Number,default:3},loading:{type:Boolean,default:!0},throttle:{type:S([Number,Object])}}),R=b({variant:{type:String,values:["circle","rect","h1","h3","text","caption","p","image","button"],default:"text"}}),X=p({name:"ElSkeletonItem"}),A=p({...X,props:R,setup(l){const e=N("skeleton");return(r,s)=>(i(),u("div",{class:m([t(e).e("item"),t(e).e(r.variant)])},[r.variant==="image"?(i(),w(t(P),{key:0})):B("v-if",!0)],2))}});var c=h(A,[["__file","skeleton-item.vue"]]);const H=(l,e=0)=>{if(e===0)return l;const r=k(e)&&!!e.initVal,s=C(r);let o=null;const a=n=>{if(E(n)){s.value=l.value;return}o&&clearTimeout(o),o=setTimeout(()=>{s.value=l.value},n)},d=n=>{n==="leading"?$(e)?a(e):a(e.leading):k(e)?a(e.trailing):s.value=!1};return V(()=>d("leading")),j(()=>l.value,n=>{d(n?"leading":"trailing")}),s},M=p({name:"ElSkeleton"}),U=p({...M,props:F,setup(l,{expose:e}){const r=l,s=N("skeleton"),o=H(L(r,"loading"),r.throttle);return e({uiLoading:o}),(a,d)=>t(o)?(i(),u("div",y({key:0,class:[t(s).b(),t(s).is("animated",a.animated)]},a.$attrs),[(i(!0),u(f,null,g(a.count,n=>(i(),u(f,{key:n},[t(o)?_(a.$slots,"template",{key:n},()=>[O(c,{class:m(t(s).is("first")),variant:"p"},null,8,["class"]),(i(!0),u(f,null,g(a.rows,v=>(i(),w(c,{key:v,class:m([t(s).e("paragraph"),t(s).is("last",v===a.rows&&a.rows>1)]),variant:"p"},null,8,["class"]))),128))]):B("v-if",!0)],64))),128))],16)):_(a.$slots,"default",z(y({key:1},a.$attrs)))}});var Z=h(U,[["__file","skeleton.vue"]]);const G=I(Z,{SkeletonItem:c});T(c);export{G as E};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{aS as c,X as i}from"./base-xgxQQEpV.js";import{Q as u,V as d,C as a,L as m}from"./vendor-vue-WbiK4TmU.js";const r={prefix:Math.floor(Math.random()*1e4),current:0},I=Symbol("elIdInjection"),p=()=>d()?u(I,r):r,x=t=>{const e=p(),n=c();return i(()=>a(t)||`${n.value}-id-${e.prefix}-${e.current++}`)},C=({from:t,replacement:e,scope:n,version:s,ref:f,type:l="API"},o)=>{m(()=>a(o),j=>{},{immediate:!0})};export{C as a,x as b,p as u};

View File

@@ -0,0 +1 @@
import{aN as c,D as i}from"./base-C_0HtztH.js";import{Q as u,V as d,C as a,L as m}from"./vendor-vue-Da_zwKNU.js";const r={prefix:Math.floor(Math.random()*1e4),current:0},I=Symbol("elIdInjection"),p=()=>d()?u(I,r):r,N=t=>{const e=p(),n=c();return i(()=>a(t)||`${n.value}-id-${e.prefix}-${e.current++}`)},h=({from:t,replacement:e,scope:n,version:s,ref:f,type:l="API"},o)=>{m(()=>a(o),j=>{},{immediate:!0})};export{h as a,N as b,p as u};

View File

@@ -1 +0,0 @@
import{s as W,o as A,i as C}from"./el-input-BaZNy9Kg.js";import{ae as M,e as O,af as j,f as F}from"./base-xgxQQEpV.js";import{t as y}from"./el-empty-B4_NEFfq.js";function z(t,i){return W(A(t,i,C),t+"")}var k=function(){return M.Date.now()},N="Expected a function",_=Math.max,D=Math.min;function B(t,i,u){var f,o,l,s,n,a,d=0,x=!1,c=!1,v=!0;if(typeof t!="function")throw new TypeError(N);i=y(i)||0,O(u)&&(x=!!u.leading,c="maxWait"in u,l=c?_(y(u.maxWait)||0,i):l,v="trailing"in u?!!u.trailing:v);function T(e){var r=f,m=o;return f=o=void 0,d=e,s=t.apply(m,r),s}function I(e){return d=e,n=setTimeout(g,i),x?T(e):s}function p(e){var r=e-a,m=e-d,L=i-r;return c?D(L,l-m):L}function b(e){var r=e-a,m=e-d;return a===void 0||r>=i||r<0||c&&m>=l}function g(){var e=k();if(b(e))return E(e);n=setTimeout(g,p(e))}function E(e){return n=void 0,v&&f?T(e):(f=o=void 0,s)}function R(){n!==void 0&&clearTimeout(n),d=0,f=a=o=n=void 0}function S(){return n===void 0?s:E(k())}function h(){var e=k(),r=b(e);if(f=arguments,o=this,a=e,r){if(n===void 0)return I(a);if(c)return clearTimeout(n),n=setTimeout(g,i),T(a)}return n===void 0&&(n=setTimeout(g,i)),s}return h.cancel=R,h.flush=S,h}function G(t){return j(t)&&F(t)}export{z as b,B as d,G as i};

View File

@@ -0,0 +1 @@
import"./style-CEbARg1o.js";import{d as o}from"./vendor-vue-Da_zwKNU.js";import p from"./LoginPage-N6sdjwkY.js";import"./SocialLoginButtons-BaFXslgf.js";import"./base-C_0HtztH.js";import"./el-overlay-ckkTzDcK.js";import"./aria-DLpFpzDe.js";import"./http-BDcxFXLM.js";import"./vendor-axios-B9ygI19o.js";import"./index-D04QrwME.js";import"./auth-CuW_jyJD.js";o(p).mount("#app");

View File

@@ -1 +0,0 @@
import"./style-CEbARg1o.js";import{d as o}from"./vendor-vue-WbiK4TmU.js";import p from"./LoginPage-BtooAZsk.js";import"./SocialLoginButtons-BlVSr6Mm.js";import"./base-xgxQQEpV.js";import"./el-overlay-hge8bsIn.js";import"./aria-DLpFpzDe.js";import"./http-BoPYlvwK.js";import"./vendor-axios-B9ygI19o.js";import"./index-CoYtSGUZ.js";import"./auth-B5cl_nsV.js";o(p).mount("#app");

View File

@@ -0,0 +1 @@
import{p as t}from"./http-BDcxFXLM.js";async function i(){const{data:a}=await t.get("/user/email");return a}async function c(a){const{data:n}=await t.post("/user/bind-email",a);return n}async function o(){const{data:a}=await t.post("/user/unbind-email",{});return a}async function e(){const{data:a}=await t.get("/user/email-notify");return a}async function u(a){const{data:n}=await t.post("/user/email-notify",a);return n}async function r(a){const{data:n}=await t.post("/user/password",a);return n}async function d(){const{data:a}=await t.get("/user/kdocs");return a}async function f(a){const{data:n}=await t.post("/user/kdocs",a);return n}async function l(){const{data:a}=await t.get("/kdocs/status");return a}async function y(){const{data:a}=await t.get("/user/social-bindings");return a}async function p(a){const{data:n}=await t.post("/user/social-bindings",a||{});return n}async function w(a){const{data:n}=await t.delete(`/user/social-bindings/${encodeURIComponent(a)}`);return n}export{c as a,p as b,u as c,r as d,w as e,f,i as g,y as h,e as i,d as j,l as k,o as u};

View File

@@ -1 +0,0 @@
import{p as t}from"./http-BoPYlvwK.js";async function e(){const{data:a}=await t.get("/user/email");return a}async function i(a){const{data:s}=await t.post("/user/bind-email",a);return s}async function r(){const{data:a}=await t.post("/user/unbind-email",{});return a}async function c(){const{data:a}=await t.get("/user/email-notify");return a}async function o(a){const{data:s}=await t.post("/user/email-notify",a);return s}async function u(a){const{data:s}=await t.post("/user/password",a);return s}async function d(){const{data:a}=await t.get("/user/kdocs");return a}async function y(a){const{data:s}=await t.post("/user/kdocs",a);return s}async function f(){const{data:a}=await t.get("/kdocs/status");return a}async function p(){const{data:a}=await t.get("/user/passkeys");return a}async function l(a){const{data:s}=await t.post("/user/passkeys/register/options",a);return s}async function w(a){const{data:s}=await t.post("/user/passkeys/register/verify",a);return s}async function g(a){const{data:s}=await t.delete(`/user/passkeys/${a}`);return s}async function k(a){const{data:s}=await t.post("/user/passkeys/client-error",a||{});return s}async function m(){const{data:a}=await t.get("/user/social-bindings");return a}async function b(a){const{data:s}=await t.post("/user/social-bindings",a||{});return s}async function h(a){const{data:s}=await t.delete(`/user/social-bindings/${encodeURIComponent(a)}`);return s}export{i as a,b,o as c,u as d,l as e,w as f,g,h,y as i,e as j,p as k,m as l,c as m,d as n,f as o,k as r,r as u};

View File

@@ -0,0 +1 @@
.el-switch{--el-switch-on-color:var(--el-color-primary);--el-switch-off-color:var(--el-border-color);align-items:center;display:inline-flex;font-size:14px;height:32px;line-height:20px;position:relative;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{color:var(--el-text-color-primary);cursor:pointer;display:inline-block;font-size:14px;font-weight:500;height:20px;transition:var(--el-transition-duration-fast);vertical-align:middle}.el-switch__label.is-active{color:var(--el-color-primary)}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{display:inline-block;font-size:14px;line-height:1}.el-switch__label .el-icon{height:inherit}.el-switch__label .el-icon svg{vertical-align:middle}.el-switch__input{height:0;margin:0;opacity:0;position:absolute;width:0}.el-switch__input:focus-visible~.el-switch__core{outline:2px solid var(--el-switch-on-color);outline-offset:1px}.el-switch__core{align-items:center;background:var(--el-switch-off-color);border:1px solid var(--el-switch-border-color,var(--el-switch-off-color));border-radius:10px;box-sizing:border-box;cursor:pointer;display:inline-flex;height:20px;min-width:40px;outline:none;position:relative;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration)}.el-switch__core .el-switch__inner{align-items:center;display:flex;height:16px;justify-content:center;overflow:hidden;padding:0 4px 0 18px;transition:all var(--el-transition-duration);width:100%}.el-switch__core .el-switch__inner .is-icon,.el-switch__core .el-switch__inner .is-text{color:var(--el-color-white);font-size:12px;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.el-switch__core .el-switch__action{align-items:center;background-color:var(--el-color-white);border-radius:var(--el-border-radius-circle);color:var(--el-switch-off-color);display:flex;height:16px;justify-content:center;left:1px;position:absolute;transition:all var(--el-transition-duration);width:16px}.el-switch.is-checked .el-switch__core{background-color:var(--el-switch-on-color);border-color:var(--el-switch-border-color,var(--el-switch-on-color))}.el-switch.is-checked .el-switch__core .el-switch__action{color:var(--el-switch-on-color);left:calc(100% - 17px)}.el-switch.is-checked .el-switch__core .el-switch__inner{padding:0 18px 0 4px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter-from,.el-switch .label-fade-leave-active{opacity:0}.el-switch--large{font-size:14px;height:40px;line-height:24px}.el-switch--large .el-switch__label{font-size:14px;height:24px}.el-switch--large .el-switch__label *{font-size:14px}.el-switch--large .el-switch__core{border-radius:12px;height:24px;min-width:50px}.el-switch--large .el-switch__core .el-switch__inner{height:20px;padding:0 6px 0 22px}.el-switch--large .el-switch__core .el-switch__action{height:20px;width:20px}.el-switch--large.is-checked .el-switch__core .el-switch__action{left:calc(100% - 21px)}.el-switch--large.is-checked .el-switch__core .el-switch__inner{padding:0 22px 0 6px}.el-switch--small{font-size:12px;height:24px;line-height:16px}.el-switch--small .el-switch__label{font-size:12px;height:16px}.el-switch--small .el-switch__label *{font-size:12px}.el-switch--small .el-switch__core{border-radius:8px;height:16px;min-width:30px}.el-switch--small .el-switch__core .el-switch__inner{height:12px;padding:0 2px 0 14px}.el-switch--small .el-switch__core .el-switch__action{height:12px;width:12px}.el-switch--small.is-checked .el-switch__core .el-switch__action{left:calc(100% - 13px)}.el-switch--small.is-checked .el-switch__core .el-switch__inner{padding:0 14px 0 2px}

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