优化了一些问题
This commit is contained in:
1 parent
1d904e6532
commit
780e1eba21
46 files changed
+4074
-3143
No files matched your search
@@ -6,9 +6,6 @@ export const usersApiMethods = {
|
||||
getAuthSettings() { return this.request('/settings/auth') },
|
||||
saveAuthSettings(data) { return this.request('/settings/auth', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
|
||||
getOAuthSettings() { return this.request('/settings/oauth') },
|
||||
saveOAuthSettings(data) { return this.request('/settings/oauth', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
|
||||
getNotificationSettings() { return this.request('/settings/notifications') },
|
||||
saveNotificationSettings(data) { return this.request('/settings/notifications', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
getNotificationOverview(params = {}) {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<template>
|
||||
<n-card class="oauth-provider-card" title="">
|
||||
<!-- 平台信息头部 -->
|
||||
<div class="oauth-provider-header">
|
||||
<div class="oauth-header-left">
|
||||
<span class="oauth-platform-icon">
|
||||
<img v-if="icon" :src="icon" :alt="name" class="oauth-platform-img" />
|
||||
<span v-else class="oauth-platform-text">{{ name.charAt(0) }}</span>
|
||||
</span>
|
||||
<div class="oauth-platform-info">
|
||||
<h4>{{ name }}</h4>
|
||||
<a :href="docUrl" target="_blank" class="oauth-doc-link">
|
||||
前往 {{ name }} 创建应用 ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<n-switch v-model:value="modelValue.enabled" class="oauth-enable-switch">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>停用</template>
|
||||
</n-switch>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域(启用后才展开) -->
|
||||
<div v-if="modelValue.enabled" class="oauth-form-body">
|
||||
<n-form label-placement="top" class="aksrt-form oauth-form-grid">
|
||||
<n-form-item
|
||||
v-for="field in fields"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
>
|
||||
<n-input
|
||||
v-if="!field.readonly"
|
||||
v-model:value="modelValue[field.key]"
|
||||
:type="field.secret ? 'password' : 'text'"
|
||||
:show-password-on-click="field.secret"
|
||||
:placeholder="'输入 ' + field.label"
|
||||
size="large"
|
||||
/>
|
||||
<n-input
|
||||
v-else
|
||||
:value="callbackUrl"
|
||||
readonly
|
||||
size="large"
|
||||
style='font-family: "SF Mono", Consolas, monospace; font-size:13px;'
|
||||
/>
|
||||
<template v-if="field.hint" #feedback>
|
||||
<span class="aksrt-hint-text">{{ field.hint }}</span>
|
||||
</template>
|
||||
<template v-if="field.readonly && !field.hint" #feedback>
|
||||
<span class="aksrt-hint-text">将此地址填写到对应平台的「授权回调域 / 回调 URL」中</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
|
||||
<!-- 未启用时的提示 -->
|
||||
<div v-else class="oauth-disabled-hint">
|
||||
<p>开启开关以配置此平台的 OAuth 凭证</p>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
name: { type: String, required: true },
|
||||
docUrl: { type: String, required: true },
|
||||
fields: { type: Array, required: true },
|
||||
icon: { type: String, default: '' },
|
||||
})
|
||||
|
||||
defineEmits(['update:modelValue'])
|
||||
|
||||
const callbackUrl = computed(() => {
|
||||
const siteUrl = window.aksrtAdminData?.siteUrl || window.location.origin
|
||||
return `${siteUrl}/oauth/callback/`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.oauth-provider-card {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
/* 平台头部 */
|
||||
.oauth-provider-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.oauth-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.oauth-platform-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--aksrt-color-canvas);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.oauth-platform-img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.oauth-platform-text {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--aksrt-color-muted);
|
||||
}
|
||||
|
||||
.oauth-platform-info h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.oauth-doc-link {
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
.oauth-doc-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 启用开关 */
|
||||
.oauth-enable-switch {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 表单体 */
|
||||
.oauth-form-body {
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--aksrt-color-border);
|
||||
}
|
||||
|
||||
.oauth-form-grid :deep(.n-form-item) {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
/* 禁用提示 */
|
||||
.oauth-disabled-hint {
|
||||
text-align: center;
|
||||
padding: 28px 0 10px;
|
||||
}
|
||||
.oauth-disabled-hint p {
|
||||
font-size: 13px;
|
||||
color: var(--aksrt-color-border-strong);
|
||||
}
|
||||
</style>
|
||||
@@ -25,17 +25,28 @@
|
||||
/>
|
||||
|
||||
<!-- Input (text, email, url, password) -->
|
||||
<n-input
|
||||
v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'"
|
||||
:value="modelValue"
|
||||
:placeholder="schema.placeholder || ''"
|
||||
:type="schema.inputType || 'text'"
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<template v-if="schema.prefix" #prefix>{{ schema.prefix }}</template>
|
||||
<template v-if="schema.suffix" #suffix>{{ schema.suffix }}</template>
|
||||
</n-input>
|
||||
<div v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'" class="aksrt-field__input-wrap">
|
||||
<n-input
|
||||
:value="secretDisplayValue"
|
||||
:placeholder="schema.placeholder || ''"
|
||||
:type="schema.inputType || 'text'"
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<template v-if="schema.prefix" #prefix>{{ schema.prefix }}</template>
|
||||
<template v-if="schema.suffix" #suffix>{{ schema.suffix }}</template>
|
||||
</n-input>
|
||||
<n-button
|
||||
v-if="schema.secret && (modelValue === '***' || secretMarkedForClear)"
|
||||
size="small"
|
||||
secondary
|
||||
:type="secretMarkedForClear ? 'warning' : 'error'"
|
||||
@click="toggleSecretClear"
|
||||
>
|
||||
{{ secretMarkedForClear ? '取消清除' : '清除已配置密钥' }}
|
||||
</n-button>
|
||||
<span v-if="secretMarkedForClear" class="aksrt-field__secret-clear-hint">保存后将删除此密钥</span>
|
||||
</div>
|
||||
|
||||
<!-- Textarea -->
|
||||
<n-input
|
||||
@@ -636,6 +647,13 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__AKSRT_CLEAR_SECRET__')
|
||||
const secretDisplayValue = computed(() => secretMarkedForClear.value ? '' : props.modelValue)
|
||||
|
||||
function toggleSecretClear() {
|
||||
emit('update:modelValue', secretMarkedForClear.value ? '***' : '__AKSRT_CLEAR_SECRET__')
|
||||
}
|
||||
|
||||
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
|
||||
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
||||
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
||||
@@ -1014,6 +1032,20 @@ function confirmRepeaterModal() {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__input-wrap {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__input-wrap .n-button {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.aksrt-field__secret-clear-hint {
|
||||
color: var(--aksrt-color-warning, #d97706);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-field__label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -200,7 +200,20 @@ const {
|
||||
}
|
||||
return response
|
||||
},
|
||||
submit: (data) => api.saveSchemaPage(resolvedDomain.value, resolvedSection.value, data),
|
||||
submit: async (data) => {
|
||||
const payload = { ...data }
|
||||
const clearSecrets = []
|
||||
Object.keys(payload).forEach((key) => {
|
||||
if (payload[key] === '__AKSRT_CLEAR_SECRET__') {
|
||||
clearSecrets.push(key)
|
||||
delete payload[key]
|
||||
}
|
||||
})
|
||||
if (clearSecrets.length) payload.__clear_secrets = clearSecrets
|
||||
const response = await api.saveSchemaPage(resolvedDomain.value, resolvedSection.value, payload)
|
||||
clearSecrets.forEach((key) => { data[key] = '' })
|
||||
return response
|
||||
},
|
||||
defaults: defaults.value,
|
||||
onSaved: () => {
|
||||
message.success('设置已保存')
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader :title="title" :desc="desc">
|
||||
<template #actions>
|
||||
<n-space v-if="links.length">
|
||||
<n-button v-for="link in links" :key="link.path" quaternary @click="go(link.path)">
|
||||
{{ link.label }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-card v-if="note" class="aksrt-section-card">
|
||||
<n-alert type="info" :show-icon="false">
|
||||
{{ note }}
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<slot name="before-form" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
|
||||
defineProps({
|
||||
title: { type: String, required: true },
|
||||
desc: { type: String, default: '' },
|
||||
note: { type: String, default: '' },
|
||||
links: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function go(path) {
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@ export const oauthSchema = {
|
||||
key: 'users.oauth',
|
||||
title: '第三方登录',
|
||||
icon: 'fa fas fa-share-alt',
|
||||
description: 'GitHub、Gitee、QQ、Google 等第三方登录配置。',
|
||||
description: 'GitHub、Gitee、QQ、Google、微博和微信第三方登录配置。',
|
||||
defaults: {
|
||||
oauth_s: false,
|
||||
oauth_proxy_mode: 'close',
|
||||
@@ -10,59 +10,42 @@ export const oauthSchema = {
|
||||
oauth_proxy_server_url: '',
|
||||
oauth_proxy_server_providers: ['github', 'gitee', 'qq', 'google'],
|
||||
oauth_proxy_client_providers: ['github', 'gitee', 'qq', 'google'],
|
||||
oauth_proxy_gzh_type: 'service',
|
||||
oauth_bind_type: 'auto',
|
||||
oauth_button_lg: false,
|
||||
weixingzh_priority: false,
|
||||
weixingzh_auto: false,
|
||||
weixingzh_auto_expires: 3600,
|
||||
oauth_github_s: false,
|
||||
oauth_gitee_s: false,
|
||||
oauth_qq_s: false,
|
||||
oauth_weixingzh_s: false,
|
||||
oauth_weixin_s: false,
|
||||
oauth_google_s: false,
|
||||
oauth_weibo_s: false,
|
||||
oauth_baidu_s: false,
|
||||
oauth_alipay_s: false,
|
||||
oauth_weixin_s: false,
|
||||
oauth_weixingzh_s: false,
|
||||
clogin_s: false,
|
||||
clogin_url: '',
|
||||
clogin_id: '',
|
||||
clogin_key: '',
|
||||
clogin_oauth_s: ['qq', 'weixin', 'weibo', 'alipay'],
|
||||
github_appid: '',
|
||||
github_appkey: '',
|
||||
gitee_appid: '',
|
||||
gitee_appkey: '',
|
||||
qq_appid: '',
|
||||
qq_appkey: '',
|
||||
google_client_id: '',
|
||||
google_client_secret: '',
|
||||
weibo_appid: '',
|
||||
weibo_appkey: '',
|
||||
weixin_appid: '',
|
||||
weixin_appsecret: '',
|
||||
weixingzh_gzh_type: 'service',
|
||||
weixingzh_token: '',
|
||||
weixingzh_token_nocheck: false,
|
||||
weixingzh_encoding_aes_key: '',
|
||||
weixingzh_appid: '',
|
||||
weixingzh_appsecret: '',
|
||||
weixingzh_code_qrcode: '',
|
||||
weixingzh_code_keyword: '登录',
|
||||
weixingzh_code_reply: '您的验证码是:%s,有效期 %d 分钟。',
|
||||
weixingzh_code_subscribe_reply: '欢迎关注!回复「%s」获取登录验证码。',
|
||||
weixingzh_subscribe_msg: '',
|
||||
weixingzh_new_subscribe_msg: '',
|
||||
weixingzh_scan_msg: '',
|
||||
weixingzh_auto_reply_text: '',
|
||||
weixingzh_auto_reply_search: '',
|
||||
weixingzh_auto_reply_image: '',
|
||||
weixingzh_auto_reply_voice: '',
|
||||
weixingzh_auto_reply_default: '暂不支持此类型的消息',
|
||||
weixingzh_gzh_menu: '',
|
||||
weibo_appid: '',
|
||||
weibo_appkey: '',
|
||||
baidu_appid: '',
|
||||
baidu_appkey: '',
|
||||
alipay_appid: '',
|
||||
alipay_privatekey: '',
|
||||
alipay_publickey: '',
|
||||
clogin_url: '',
|
||||
clogin_id: '',
|
||||
clogin_key: '',
|
||||
clogin_oauth_s: ['qq', 'weixin', 'weibo'],
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -72,16 +55,13 @@ export const oauthSchema = {
|
||||
fields: [
|
||||
{ key: 'oauth_s', label: '启用第三方登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_proxy_mode', label: '代理登录模式', type: 'radio', default: 'close', options: [{ label: '关闭', value: 'close' }, { label: '服务端', value: 'server' }, { label: '客户端', value: 'client' }] },
|
||||
{ key: 'oauth_proxy_secret', label: '代理 HMAC 密钥', type: 'input', default: '', dependsOn: { key: 'oauth_proxy_mode', not: 'close' } },
|
||||
{ key: 'oauth_proxy_secret', label: '代理 HMAC 密钥', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'oauth_proxy_mode', not: 'close' } },
|
||||
{ key: 'oauth_proxy_server_url', label: '服务端地址', type: 'input', default: '', dependsOn: { key: 'oauth_proxy_mode', value: 'client' } },
|
||||
{ key: 'oauth_proxy_server_providers', label: '服务端允许平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }, { label: '百度', value: 'baidu' }, { label: '支付宝', value: 'alipay' }], dependsOn: { key: 'oauth_proxy_mode', value: 'server' } },
|
||||
{ key: 'oauth_proxy_client_providers', label: '客户端启用平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }, { label: '百度', value: 'baidu' }, { label: '支付宝', value: 'alipay' }], dependsOn: { key: 'oauth_proxy_mode', value: 'client' } },
|
||||
{ key: 'oauth_proxy_gzh_type', label: '代理公众号类型', type: 'radio', default: 'service', options: [{ label: '服务号', value: 'service' }, { label: '订阅号', value: 'subscribe' }], dependsOn: { key: 'oauth_proxy_mode', not: 'close' } },
|
||||
{ key: 'oauth_proxy_server_providers', label: '服务端允许平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }], dependsOn: { key: 'oauth_proxy_mode', value: 'server' } },
|
||||
{ key: 'oauth_proxy_client_providers', label: '客户端启用平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }], dependsOn: { key: 'oauth_proxy_mode', value: 'client' } },
|
||||
{ key: 'oauth_bind_type', label: '绑定方式', type: 'radio', default: 'auto', options: [{ label: '自动绑定', value: 'auto' }, { label: '手动绑定', value: 'manual' }] },
|
||||
{ key: 'oauth_button_lg', label: '大按钮样式', type: 'switch', default: false },
|
||||
{ key: 'weixingzh_priority', label: 'PC端优先微信扫码', type: 'switch', default: false, help: '开启后 PC 端登录框优先显示微信公众号扫码' },
|
||||
{ key: 'weixingzh_auto', label: '微信APP内自动登录', type: 'switch', default: false, help: '在微信内置浏览器中自动使用公众号授权登录' },
|
||||
{ key: 'weixingzh_auto_expires', label: '自动登录频率限制 (秒)', type: 'number', default: 3600, min: 0, max: 86400, dependsOn: { key: 'weixingzh_auto', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -92,12 +72,11 @@ export const oauthSchema = {
|
||||
{ key: 'oauth_github_s', label: 'GitHub登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_gitee_s', label: 'Gitee登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_qq_s', label: 'QQ登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_weixingzh_s', label: '微信公众号登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_weixin_s', label: '微信开放平台登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_google_s', label: 'Google登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_weibo_s', label: '微博登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_baidu_s', label: '百度登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_alipay_s', label: '支付宝登录', type: 'switch', default: false },
|
||||
{ key: 'clogin_s', label: '彩虹聚合登录', type: 'switch', default: false, help: '通过第三方聚合登录平台统一接入 QQ/微信/微博/支付宝等' },
|
||||
{ key: 'oauth_weixin_s', label: '微信开放平台登录', type: 'switch', default: false },
|
||||
{ key: 'oauth_weixingzh_s', label: '微信公众号登录', type: 'switch', default: false },
|
||||
{ key: 'clogin_s', label: '彩虹聚合登录', type: 'switch', default: false, help: '通过第三方聚合登录平台统一接入 QQ、微信和微博' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -106,7 +85,7 @@ export const oauthSchema = {
|
||||
icon: 'fa fab fa-github',
|
||||
fields: [
|
||||
{ key: 'github_appid', label: 'Client ID', type: 'input', default: '' },
|
||||
{ key: 'github_appkey', label: 'Client Secret', type: 'input', default: '' },
|
||||
{ key: 'github_appkey', label: 'Client Secret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -115,7 +94,7 @@ export const oauthSchema = {
|
||||
icon: 'fa fas fa-code-branch',
|
||||
fields: [
|
||||
{ key: 'gitee_appid', label: 'Client ID', type: 'input', default: '' },
|
||||
{ key: 'gitee_appkey', label: 'Client Secret', type: 'input', default: '' },
|
||||
{ key: 'gitee_appkey', label: 'Client Secret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -124,42 +103,16 @@ export const oauthSchema = {
|
||||
icon: 'fa fab fa-qq',
|
||||
fields: [
|
||||
{ key: 'qq_appid', label: 'App ID', type: 'input', default: '' },
|
||||
{ key: 'qq_appkey', label: 'App Key', type: 'input', default: '' },
|
||||
{ key: 'qq_appkey', label: 'App Key', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'weixin',
|
||||
title: '微信开放平台配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
key: 'google',
|
||||
title: 'Google配置',
|
||||
icon: 'fa fab fa-google',
|
||||
fields: [
|
||||
{ key: 'weixin_appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'weixin_appsecret', label: 'AppSecret', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'weixingzh',
|
||||
title: '微信公众号配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'weixingzh_gzh_type', label: '公众号类型', type: 'radio', default: 'service', options: [{ label: '服务号', value: 'service' }, { label: '订阅号', value: 'subscribe' }] },
|
||||
{ key: 'weixingzh_token', label: 'Token', type: 'input', default: '' },
|
||||
{ key: 'weixingzh_token_nocheck', label: '不验证Token', type: 'switch', default: false, help: '开发调试时可跳过 Token 验证' },
|
||||
{ key: 'weixingzh_encoding_aes_key', label: 'EncodingAESKey', type: 'input', default: '' },
|
||||
{ key: 'weixingzh_appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'weixingzh_appsecret', label: 'AppSecret', type: 'input', default: '' },
|
||||
{ key: 'weixingzh_code_qrcode', label: '公众号二维码', type: 'media', default: '', help: '引导用户扫码关注登录的二维码图片' },
|
||||
{ key: 'weixingzh_code_keyword', label: '登录回复关键词', type: 'input', default: '登录', help: '用户在公众号内回复此关键词触发登录验证码' },
|
||||
{ key: 'weixingzh_code_reply', label: '验证码回复模板', type: 'textarea', default: '您的验证码是:%s,有效期 %d 分钟。', help: '%s=验证码,%d=有效分钟数' },
|
||||
{ key: 'weixingzh_code_subscribe_reply', label: '新关注回复模板', type: 'textarea', default: '欢迎关注!回复「%s」获取登录验证码。', help: '%s=登录关键词' },
|
||||
{ key: 'weixingzh_subscribe_msg', label: '关注后回复', type: 'textarea', default: '', help: '用户关注公众号后的自动回复文案' },
|
||||
{ key: 'weixingzh_new_subscribe_msg', label: '新用户关注回复', type: 'textarea', default: '', help: '首次关注用户的专属回复文案,留空使用通用回复' },
|
||||
{ key: 'weixingzh_scan_msg', label: '扫码回复', type: 'textarea', default: '', help: '用户扫码关注公众号后的回复文案' },
|
||||
{ key: 'weixingzh_auto_reply_text', label: '文本自动回复', type: 'textarea', default: '', help: '收到文本消息的自动回复' },
|
||||
{ key: 'weixingzh_auto_reply_search', label: '搜索回复', type: 'textarea', default: '', help: '未匹配关键词时的搜索提示回复' },
|
||||
{ key: 'weixingzh_auto_reply_image', label: '图片回复', type: 'textarea', default: '', help: '收到图片消息的自动回复' },
|
||||
{ key: 'weixingzh_auto_reply_voice', label: '语音回复', type: 'textarea', default: '', help: '收到语音消息的自动回复' },
|
||||
{ key: 'weixingzh_auto_reply_default', label: '默认回复', type: 'textarea', default: '暂不支持此类型的消息', help: '其他未匹配消息类型的兜底回复' },
|
||||
{ key: 'weixingzh_gzh_menu', label: '自定义菜单 JSON', type: 'textarea', default: '', help: '微信公众号自定义菜单 JSON 配置,留空不创建菜单' },
|
||||
{ key: 'google_client_id', label: 'Client ID', type: 'input', default: '' },
|
||||
{ key: 'google_client_secret', label: 'Client Secret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -168,16 +121,32 @@ export const oauthSchema = {
|
||||
icon: 'fa fab fa-weibo',
|
||||
fields: [
|
||||
{ key: 'weibo_appid', label: 'App Key', type: 'input', default: '' },
|
||||
{ key: 'weibo_appkey', label: 'App Secret', type: 'input', default: '' },
|
||||
{ key: 'weibo_appkey', label: 'App Secret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'baidu',
|
||||
title: '百度配置',
|
||||
icon: 'fa fas fa-paw',
|
||||
key: 'weixin',
|
||||
title: '微信开放平台配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'baidu_appid', label: 'API Key', type: 'input', default: '' },
|
||||
{ key: 'baidu_appkey', label: 'Secret Key', type: 'input', default: '' },
|
||||
{ key: 'weixin_appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'weixin_appsecret', label: 'AppSecret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'weixingzh',
|
||||
title: '微信公众号配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'weixingzh_gzh_type', label: '公众号类型', type: 'radio', default: 'service', options: [{ label: '服务号', value: 'service' }, { label: '订阅号', value: 'subscribe' }] },
|
||||
{ key: 'weixingzh_token', label: 'Token', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
{ key: 'weixingzh_appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'weixingzh_appsecret', label: 'AppSecret', type: 'input', inputType: 'password', secret: true, default: '' },
|
||||
{ key: 'weixingzh_code_qrcode', label: '公众号二维码', type: 'media', default: '', help: '引导用户扫码关注登录的二维码图片' },
|
||||
{ key: 'weixingzh_code_keyword', label: '登录回复关键词', type: 'input', default: '登录', help: '用户在公众号内回复此关键词触发登录验证码' },
|
||||
{ key: 'weixingzh_code_reply', label: '验证码回复模板', type: 'textarea', default: '您的验证码是:%s,有效期 %d 分钟。', help: '%s=验证码,%d=有效分钟数' },
|
||||
{ key: 'weixingzh_subscribe_msg', label: '关注后回复', type: 'textarea', default: '', help: '用户关注公众号后的自动回复文案' },
|
||||
{ key: 'weixingzh_scan_msg', label: '扫码回复', type: 'textarea', default: '', help: '用户扫码关注公众号后的回复文案' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -185,27 +154,15 @@ export const oauthSchema = {
|
||||
title: '彩虹聚合登录',
|
||||
icon: 'fa fas fa-cloud',
|
||||
fields: [
|
||||
{ key: 'clogin_url', label: '接口地址', type: 'input', default: '', help: '如 https://xxx.com 不要带 connect.php', dependsOn: { key: 'clogin_s', value: true } },
|
||||
{ key: 'clogin_url', label: '接口地址', type: 'input', default: '', help: '如 https://xxx.com,不要带 connect.php', dependsOn: { key: 'clogin_s', value: true } },
|
||||
{ key: 'clogin_id', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'clogin_s', value: true } },
|
||||
{ key: 'clogin_key', label: 'AppKey', type: 'input', default: '', dependsOn: { key: 'clogin_s', value: true } },
|
||||
{ key: 'clogin_oauth_s', label: '启用平台', type: 'checkbox', default: ['qq', 'weixin', 'weibo', 'alipay'], options: [
|
||||
{ key: 'clogin_key', label: 'AppKey', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'clogin_s', value: true } },
|
||||
{ key: 'clogin_oauth_s', label: '启用平台', type: 'checkbox', default: ['qq', 'weixin', 'weibo'], options: [
|
||||
{ label: 'QQ', value: 'qq' },
|
||||
{ label: '微信', value: 'weixin' },
|
||||
{ label: '微博', value: 'weibo' },
|
||||
{ label: '支付宝', value: 'alipay' },
|
||||
{ label: '百度', value: 'baidu' },
|
||||
], dependsOn: { key: 'clogin_s', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'alipay',
|
||||
title: '支付宝配置',
|
||||
icon: 'fa fas fa-credit-card',
|
||||
fields: [
|
||||
{ key: 'alipay_appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' },
|
||||
{ key: 'alipay_publickey', label: '支付宝公钥', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-ciLine truncated
|
||||
|
||||
/* source: assets/css/main.min.css */
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--single-cover-scale: 35%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacitLine truncated
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--single-cover-scale: 35%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacitLine truncated
|
||||
|
||||
/* source: style.css */
|
||||
/*
|
||||
|
||||
Vendored
+1
-1
@@ -5,4 +5,4 @@
|
||||
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" ("attr(href) ")"}abbr[title]:after{content:" ("attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}*{box-sizing:border-box}:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-Line truncated
|
||||
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:""}.fa-accessible-icon:before{content:""}.fa-accusoft:before{content:""}.fa-acquisitions-incorporated:before{content:""}.fa-ad:before{content:""}.fa-address-book:before{content:""}.fa-address-card:before{content:""}.fa-adjust:before{content:""}.fa-adn:before{content:""}.fa-adversal:before{content:""}.fa-affiliatetheme:before{content:""}.fa-air-freshener:before{content:""}.fa-airbnb:before{content:""}.fa-algolia:before{content:""}.fa-align-center:before{content:""}.fa-align-justify:before{content:""}.fa-align-left:before{content:""}.fa-align-right:before{content:""}.fa-alipay:before{content:""}.fa-allergies:before{content:""}.fa-amazon:before{content:""}.fa-amazon-pay:before{content:""}.fa-ambulance:before{content:""}.fa-american-sign-language-interpreting:before{content:""}.fa-amilia:before{content:""}.fa-anchor:before{content:""}.fa-android:before{content:""}.fa-angellist:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angry:before{content:""}.fa-angrycreative:before{content:""}.fa-angular:before{content:""}.fa-ankh:before{content:""}.fa-app-store:before{content:""}.fa-app-store-ios:before{content:""}.fa-apper:before{content:""}.fa-apple:before{content:""}.fa-apple-alt:before{content:""}.fa-apple-pay:before{content:""}.fa-archive:before{content:""}.fa-archway:before{content:""}.fa-arrow-alt-circle-down:before{content:""}.fa-arrow-alt-circle-left:before{content:""}.fa-arrow-alt-circle-right:before{content:""}.fa-arrow-alt-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-arrow-circle-left:before{content:""}.fa-arrow-circle-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-down:before{content:""}.fLine truncated
|
||||
*/.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:""}.fa-accessible-icon:before{content:""}.fa-accusoft:before{content:""}.fa-acquisitions-incorporated:before{content:""}.fa-ad:before{content:""}.fa-address-book:before{content:""}.fa-address-card:before{content:""}.fa-adjust:before{content:""}.fa-adn:before{content:""}.fa-adversal:before{content:""}.fa-affiliatetheme:before{content:""}.fa-air-freshener:before{content:""}.fa-airbnb:before{content:""}.fa-algolia:before{content:""}.fa-align-center:before{content:""}.fa-align-justify:before{content:""}.fa-align-left:before{content:""}.fa-align-right:before{content:""}.fa-alipay:before{content:""}.fa-allergies:before{content:""}.fa-amazon:before{content:""}.fa-amazon-pay:before{content:""}.fa-ambulance:before{content:""}.fa-american-sign-language-interpreting:before{content:""}.fa-amilia:before{content:""}.fa-anchor:before{content:""}.fa-android:before{content:""}.fa-angellist:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angry:before{content:""}.fa-angrycreative:before{content:""}.fa-angular:before{content:""}.fa-ankh:before{content:""}.fa-app-store:before{content:""}.fa-app-store-ios:before{content:""}.fa-apper:before{content:""}.fa-apple:before{content:""}.fa-apple-alt:before{content:""}.fa-apple-pay:before{content:""}.fa-archive:before{content:""}.fa-archway:before{content:""}.fa-arrow-alt-circle-down:before{content:""}.fa-arrow-alt-circle-left:before{content:""}.fa-arrow-alt-circle-right:before{content:""}.fa-arrow-alt-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-arrow-circle-left:before{content:""}.fa-arrow-circle-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-down:before{content:""}.fLine truncated
|
||||
@@ -5366,10 +5366,6 @@ body:has(.modal.in) .float-btn,
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.social-login-item.button-lg.baidu .icon {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.social-login-item,
|
||||
.social-login-item svg {
|
||||
color: #fff !important;
|
||||
@@ -5389,14 +5385,6 @@ body:has(.modal.in) .float-btn,
|
||||
background: #1296db;
|
||||
}
|
||||
|
||||
.social-login-item.alipay {
|
||||
background: #1296db;
|
||||
}
|
||||
|
||||
.social-login-item.baidu {
|
||||
background: #4e6ef2;
|
||||
}
|
||||
|
||||
.social-login-item.github {
|
||||
background: #505050;
|
||||
}
|
||||
@@ -5446,8 +5434,6 @@ body:has(.modal.in) .float-btn,
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.social-login-item.toggle-radius.baidu,
|
||||
.social-login-item.toggle-radius.alipay,
|
||||
.social-login-item.toggle-radius.github {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--single-cover-scale: 35%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacitLine truncated
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--single-cover-scale: 35%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacitLine truncated
|
||||
@@ -70,13 +70,12 @@
|
||||
.social-login-item.weibo { background: #ea5d5c; }
|
||||
.social-login-item.weixingzh,
|
||||
.social-login-item.weixin { background: #24b76a; }
|
||||
.social-login-item.qq,
|
||||
.social-login-item.alipay { background: #1296db; }
|
||||
.social-login-item.baidu { background: #4e6ef2; }
|
||||
.social-login-item.qq { background: #1296db; }
|
||||
.social-login-item.github { background: #505050; }
|
||||
.social-login-item.gitee { background: #c2292f; }
|
||||
.social-login-item.google { background: var(--main-shadow, rgba(116, 116, 116, .08)); color: var(--main-color, #4e5358); }
|
||||
.auth-qrcode { display: grid; place-items: center; width: 180px; min-height: 180px; margin: 20px auto; background: #fff; color: #38b766; text-align: center; }
|
||||
.auth-qrcode [data-auth-qr-content] { display: grid; place-items: center; width: 100%; min-height: 180px; }
|
||||
.auth-qrcode i { font-size: 46px; }
|
||||
.auth-qrcode span { color: #777; font-size: 12px; }
|
||||
|
||||
|
||||
+438
-89
@@ -5,13 +5,50 @@
|
||||
var config = window.aksrtData || {};
|
||||
var apiRoot = String(config.apiUrl || config.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '');
|
||||
var nonce = config.restNonce || '';
|
||||
var modal = null;
|
||||
var lastModalTrigger = null;
|
||||
var qrStates = [];
|
||||
var oauthErrorMessages = {
|
||||
access_denied: '已取消第三方授权',
|
||||
cancelled: '已取消第三方授权',
|
||||
canceled: '已取消第三方授权',
|
||||
invalid_state: '授权状态已失效,请重新尝试',
|
||||
state_invalid: '授权状态已失效,请重新尝试',
|
||||
state_mismatch: '授权状态已失效,请重新尝试',
|
||||
state_user_mismatch: '当前账号与授权发起账号不一致,请重新尝试',
|
||||
provider_disabled: '该登录方式暂未启用',
|
||||
provider_unavailable: '该登录方式暂时不可用',
|
||||
provider_cancelled: '已取消第三方授权',
|
||||
provider_error: '第三方授权失败,请稍后重试',
|
||||
callback_failed: '第三方授权失败,请稍后重试',
|
||||
code_missing: '授权回调缺少必要信息,请重新尝试',
|
||||
proxy_config: '代理登录配置不可用,请联系管理员',
|
||||
account_match_failed: '第三方账号匹配失败,请重试',
|
||||
login_failed: '第三方登录失败,请重试',
|
||||
bind_failed: '第三方账号绑定失败,请重试',
|
||||
binding_conflict: '该第三方账号已绑定其他账号',
|
||||
account_conflict: '该第三方账号无法绑定到当前账号',
|
||||
already_bound: '该第三方账号已绑定其他账号',
|
||||
timeout: '第三方授权已超时,请重试',
|
||||
};
|
||||
var oauthProviderLabels = {
|
||||
github: 'GitHub',
|
||||
gitee: 'Gitee',
|
||||
qq: 'QQ',
|
||||
google: 'Google',
|
||||
weibo: '微博',
|
||||
weixin: '微信',
|
||||
weixingzh: '微信公众号',
|
||||
};
|
||||
|
||||
function request(path, data) {
|
||||
function request(path, data, options) {
|
||||
options = options || {};
|
||||
return fetch(apiRoot + '/auth/' + path, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
||||
body: JSON.stringify(data),
|
||||
signal: options.signal,
|
||||
}).then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (body) {
|
||||
if (!response.ok || body.success === false) {
|
||||
@@ -37,74 +74,295 @@
|
||||
}
|
||||
|
||||
function notify(text, type) {
|
||||
if (!text) return;
|
||||
if (!text || typeof window.notyf !== 'function') return;
|
||||
window.notyf(escapeHtml(text), type || 'info');
|
||||
}
|
||||
|
||||
function cleanOAuthResult(url) {
|
||||
['oauth_action', 'oauth_provider', 'oauth_error', 'oauth_bound'].forEach(function (key) {
|
||||
url.searchParams.delete(key);
|
||||
});
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState({}, '', url.pathname + url.search + url.hash);
|
||||
}
|
||||
}
|
||||
|
||||
function showOAuthResultToast() {
|
||||
if (!window.URL || document.documentElement.dataset.aksrtOauthResultHandled === 'true') return;
|
||||
var url = new URL(window.location.href);
|
||||
var action = url.searchParams.get('oauth_action');
|
||||
if (['login', 'bind', 'error'].indexOf(action) === -1) return;
|
||||
var userCenterOwnsBind = action === 'bind' && /\/user(?:\/settings)?\/?$/.test(url.pathname) && document.querySelector('[data-identity-root], .settings-section');
|
||||
if (userCenterOwnsBind) return;
|
||||
document.documentElement.dataset.aksrtOauthResultHandled = 'true';
|
||||
|
||||
var provider = url.searchParams.get('oauth_provider') || '';
|
||||
var providerLabel = oauthProviderLabels[provider] || '';
|
||||
var message = '';
|
||||
var type = 'success';
|
||||
if (action === 'login') message = providerLabel ? providerLabel + '登录成功' : '登录成功';
|
||||
if (action === 'bind') message = providerLabel ? providerLabel + '绑定成功' : '第三方账号绑定成功';
|
||||
if (action === 'error') {
|
||||
type = 'danger';
|
||||
message = oauthErrorMessages[url.searchParams.get('oauth_error') || ''] || '第三方授权失败,请重试';
|
||||
}
|
||||
|
||||
notify(message, type);
|
||||
cleanOAuthResult(url);
|
||||
}
|
||||
|
||||
function qrState(root) {
|
||||
if (root._aksrtQrState) return root._aksrtQrState;
|
||||
root._aksrtQrState = {
|
||||
root: root,
|
||||
timer: null,
|
||||
timeout: null,
|
||||
controller: null,
|
||||
inFlight: false,
|
||||
sequence: 0,
|
||||
token: '',
|
||||
startedAt: 0,
|
||||
backoff: 0,
|
||||
terminal: false,
|
||||
};
|
||||
qrStates.push(root._aksrtQrState);
|
||||
return root._aksrtQrState;
|
||||
}
|
||||
|
||||
function qrNodes(root) {
|
||||
var box = root.querySelector('[data-auth-qrcode]');
|
||||
return {
|
||||
box: box,
|
||||
content: box && box.querySelector('[data-auth-qr-content]'),
|
||||
status: box && box.querySelector('[data-auth-qr-status]'),
|
||||
};
|
||||
}
|
||||
|
||||
function setQrStatus(root, message) {
|
||||
var nodes = qrNodes(root);
|
||||
if (nodes.status) nodes.status.textContent = message || '';
|
||||
}
|
||||
|
||||
function setQrRefresh(root, message) {
|
||||
var nodes = qrNodes(root);
|
||||
if (!nodes.box) return;
|
||||
nodes.box.dataset.loading = '0';
|
||||
nodes.box.dataset.loaded = '0';
|
||||
if (nodes.content) nodes.content.innerHTML = '<button type="button" data-auth-refresh-qr>' + escapeHtml(message) + '</button>';
|
||||
setQrStatus(root, message);
|
||||
}
|
||||
|
||||
function isRootActive(root) {
|
||||
if (!root || root.dataset.activeTab !== 'oauth' || document.visibilityState !== 'visible') return false;
|
||||
if (root.dataset.surface !== 'modal') return document.documentElement.contains(root);
|
||||
return !!modal && modal.classList.contains('is-open') && modal.contains(root);
|
||||
}
|
||||
|
||||
function stopQr(root) {
|
||||
var state = qrState(root);
|
||||
state.sequence += 1;
|
||||
if (state.timer) window.clearTimeout(state.timer);
|
||||
if (state.timeout) window.clearTimeout(state.timeout);
|
||||
state.timer = null;
|
||||
state.timeout = null;
|
||||
if (state.controller) {
|
||||
try { state.controller.abort(); } catch (_) {}
|
||||
}
|
||||
state.controller = null;
|
||||
state.inFlight = false;
|
||||
}
|
||||
|
||||
function finishQr(root, status, message) {
|
||||
var state = qrState(root);
|
||||
stopQr(root);
|
||||
state.terminal = true;
|
||||
state.token = '';
|
||||
setQrRefresh(root, message || (status === 'expired' ? '二维码已过期,点击刷新' : '二维码登录已结束,点击刷新'));
|
||||
}
|
||||
|
||||
function qrRequest(root, path, payload) {
|
||||
var state = qrState(root);
|
||||
if (state.inFlight) return Promise.resolve(null);
|
||||
state.inFlight = true;
|
||||
var controller = window.AbortController ? new window.AbortController() : null;
|
||||
var timedOut = false;
|
||||
state.controller = controller;
|
||||
state.timeout = window.setTimeout(function () {
|
||||
timedOut = true;
|
||||
if (controller) controller.abort();
|
||||
}, 15000);
|
||||
|
||||
return request(path, payload, { signal: controller ? controller.signal : undefined }).catch(function (error) {
|
||||
if (timedOut) {
|
||||
var timeoutError = new Error('二维码请求超时');
|
||||
timeoutError.code = 'QR_TIMEOUT';
|
||||
throw timeoutError;
|
||||
}
|
||||
throw error;
|
||||
}).finally(function () {
|
||||
if (state.controller !== controller) return;
|
||||
if (state.timeout) window.clearTimeout(state.timeout);
|
||||
state.timeout = null;
|
||||
state.controller = null;
|
||||
state.inFlight = false;
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleQrPoll(root, delay) {
|
||||
var state = qrState(root);
|
||||
if (!state.token || state.terminal || !isRootActive(root)) return;
|
||||
if (state.startedAt && Date.now() - state.startedAt >= 300000) {
|
||||
finishQr(root, 'timeout', '二维码登录已超时,点击刷新');
|
||||
return;
|
||||
}
|
||||
if (state.timer) window.clearTimeout(state.timer);
|
||||
var wait = delay === 0
|
||||
? 0
|
||||
: Math.min(30000, Math.max(3000, Number(delay) || 3000) * Math.pow(2, Math.min(3, state.backoff)));
|
||||
state.timer = window.setTimeout(function () {
|
||||
state.timer = null;
|
||||
pollQr(root);
|
||||
}, wait);
|
||||
}
|
||||
|
||||
function pollQr(root) {
|
||||
var state = qrState(root);
|
||||
if (!state.token || state.terminal || state.inFlight || !isRootActive(root)) return;
|
||||
qrRequest(root, 'check-qrcode-login', {
|
||||
token: state.token,
|
||||
action: root.dataset.oauthIntent || 'login',
|
||||
redirect_to: root.dataset.redirectTo || window.location.href,
|
||||
}).then(function (result) {
|
||||
if (!result || !isRootActive(root)) return;
|
||||
if (result.needs_binding && result.redirect) {
|
||||
stopQr(root);
|
||||
setQrStatus(root, '需要绑定本地账号,正在跳转');
|
||||
notify('请绑定已有账号或注册新账号', 'info');
|
||||
window.location.assign(result.redirect);
|
||||
return;
|
||||
}
|
||||
if (result.bound && result.redirect) {
|
||||
stopQr(root);
|
||||
setQrStatus(root, '绑定成功,正在跳转');
|
||||
notify('微信公众号绑定成功', 'success');
|
||||
window.location.assign(result.redirect);
|
||||
return;
|
||||
}
|
||||
if (result.logged_in || result.status === 'logged_in') {
|
||||
stopQr(root);
|
||||
setQrStatus(root, '登录成功,正在跳转');
|
||||
notify('登录成功', 'success');
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
return;
|
||||
}
|
||||
if (result.status === 'expired') {
|
||||
finishQr(root, 'expired', '二维码已过期,点击刷新');
|
||||
notify('二维码已过期,请刷新后重试', 'warning');
|
||||
return;
|
||||
}
|
||||
if (['denied', 'cancelled', 'canceled', 'failed', 'error'].indexOf(result.status) !== -1) {
|
||||
finishQr(root, result.status, '二维码登录未完成,点击刷新');
|
||||
notify('二维码登录未完成,请刷新后重试', 'danger');
|
||||
return;
|
||||
}
|
||||
state.backoff = 0;
|
||||
setQrStatus(root, result.message || (result.status === 'scanned' ? '已扫码,请在微信中确认' : '等待扫码确认'));
|
||||
scheduleQrPoll(root, result.poll_interval ? Number(result.poll_interval) * 1000 : 3000);
|
||||
}).catch(function (error) {
|
||||
if (!isRootActive(root) || (error && error.name === 'AbortError')) return;
|
||||
state.backoff = Math.min(3, state.backoff + 1);
|
||||
setQrStatus(root, error && error.code === 'QR_TIMEOUT' ? '状态检查超时,正在重试' : '状态检查失败,正在重试');
|
||||
scheduleQrPoll(root, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function loadQr(root, force) {
|
||||
var nodes = qrNodes(root);
|
||||
if (!nodes.box) return;
|
||||
var state = qrState(root);
|
||||
if (force) {
|
||||
stopQr(root);
|
||||
state.token = '';
|
||||
state.startedAt = 0;
|
||||
state.backoff = 0;
|
||||
state.terminal = false;
|
||||
nodes.box.dataset.loaded = '0';
|
||||
}
|
||||
if (state.inFlight || nodes.box.dataset.loading === '1') return;
|
||||
if (state.token && !state.terminal) {
|
||||
scheduleQrPoll(root, 0);
|
||||
return;
|
||||
}
|
||||
if (nodes.box.dataset.loaded === '1' && state.terminal) return;
|
||||
|
||||
nodes.box.dataset.loading = '1';
|
||||
setQrStatus(root, '正在获取二维码');
|
||||
qrRequest(root, 'qrcode-login', {
|
||||
action: root.dataset.oauthIntent || 'login',
|
||||
redirect_to: root.dataset.redirectTo || window.location.href,
|
||||
}).then(function (result) {
|
||||
if (!result || !isRootActive(root)) return;
|
||||
nodes.box.dataset.loading = '0';
|
||||
nodes.box.dataset.loaded = '1';
|
||||
if (nodes.content) nodes.content.innerHTML = result.qr_html || '';
|
||||
state.token = result.token || '';
|
||||
state.startedAt = Date.now();
|
||||
state.backoff = 0;
|
||||
state.terminal = result.mode !== 'scan' || !state.token;
|
||||
setQrStatus(root, state.terminal ? '请按页面提示完成登录' : '请使用微信扫描二维码');
|
||||
if (!state.terminal) scheduleQrPoll(root, result.poll_interval ? Number(result.poll_interval) * 1000 : 3000);
|
||||
}).catch(function (error) {
|
||||
if (!isRootActive(root) || (error && error.name === 'AbortError')) return;
|
||||
state.terminal = true;
|
||||
setQrRefresh(root, '二维码加载失败,点击重试');
|
||||
notify(error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function activate(root, tab, updateUrl) {
|
||||
var panel = root.querySelector('[data-auth-panel="' + tab + '"]');
|
||||
if (!panel) return;
|
||||
root.querySelectorAll('[data-auth-panel]').forEach(function (item) { item.hidden = item !== panel; });
|
||||
if (root.dataset.activeTab === 'oauth' && tab !== 'oauth') stopQr(root);
|
||||
root.querySelectorAll('[data-auth-panel]').forEach(function (item) {
|
||||
var active = item === panel;
|
||||
item.hidden = !active;
|
||||
item.setAttribute('aria-hidden', active ? 'false' : 'true');
|
||||
});
|
||||
root.querySelectorAll('[data-auth-tab]').forEach(function (item) {
|
||||
item.setAttribute('aria-selected', item.dataset.authTab === tab ? 'true' : 'false');
|
||||
});
|
||||
root.dataset.activeTab = tab;
|
||||
if (updateUrl && root.dataset.surface === 'page' && window.history && window.URL) {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', tab);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
}
|
||||
var focus = panel.querySelector('input:not([type="hidden"])');
|
||||
var focus = panel.querySelector('input:not([type="hidden"]), button, a[href], [tabindex]:not([tabindex="-1"])');
|
||||
if (focus) window.setTimeout(function () { focus.focus(); }, 30);
|
||||
if (tab === 'oauth') loadQr(root);
|
||||
if (tab === 'oauth') loadQr(root, false);
|
||||
}
|
||||
|
||||
function loadQr(root) {
|
||||
var box = root.querySelector('[data-auth-qrcode]');
|
||||
if (!box || box.dataset.loading === '1' || box.dataset.loaded === '1') return;
|
||||
box.dataset.loading = '1';
|
||||
request('qrcode-login', {}).then(function (result) {
|
||||
box.dataset.loading = '0';
|
||||
box.dataset.loaded = '1';
|
||||
box.innerHTML = result.qr_html || '';
|
||||
if (result.mode === 'scan' && result.token) pollQr(box, result.token, 0);
|
||||
}).catch(function (error) {
|
||||
box.dataset.loading = '0';
|
||||
box.dataset.loaded = '0';
|
||||
box.innerHTML = '<button type="button" data-auth-refresh-qr>二维码加载失败,点击重试</button>';
|
||||
notify(error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function pollQr(box, token, count) {
|
||||
if (!document.documentElement.contains(box) || count >= 100) return;
|
||||
window.setTimeout(function () {
|
||||
request('check-qrcode-login', { token: token }).then(function (result) {
|
||||
if (result.logged_in) {
|
||||
notify('登录成功', 'success');
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
return;
|
||||
}
|
||||
if (result.status === 'expired') {
|
||||
box.dataset.loaded = '0';
|
||||
box.innerHTML = '<button type="button" data-auth-refresh-qr>二维码已过期,点击刷新</button>';
|
||||
notify('二维码已过期,请刷新后重试', 'warning');
|
||||
return;
|
||||
}
|
||||
pollQr(box, token, count + 1);
|
||||
}).catch(function () { pollQr(box, token, count + 1); });
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function submitQrCode(form) {
|
||||
var data = formData(form);
|
||||
var button = form.querySelector('button[type="submit"]');
|
||||
if (button) button.disabled = true;
|
||||
request('weixingzh-code', data).then(function (result) {
|
||||
notify('登录成功', 'success');
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
}).catch(function (error) {
|
||||
if (button) button.disabled = false;
|
||||
notify(error.message, 'danger');
|
||||
});
|
||||
}
|
||||
function submitQrCode(form) {
|
||||
var data = formData(form);
|
||||
var root = form.closest('[data-aksrt-auth]');
|
||||
data.action = root ? (root.dataset.oauthIntent || 'login') : 'login';
|
||||
data.redirect_to = root ? (root.dataset.redirectTo || window.location.href) : window.location.href;
|
||||
var button = form.querySelector('button[type="submit"]');
|
||||
if (button) button.disabled = true;
|
||||
request('weixingzh-code', data).then(function (result) {
|
||||
if (result.needs_binding) {
|
||||
notify('请绑定已有账号或注册新账号', 'info');
|
||||
} else if (result.bound) {
|
||||
notify('微信公众号绑定成功', 'success');
|
||||
} else {
|
||||
notify('登录成功', 'success');
|
||||
}
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
}).catch(function (error) {
|
||||
if (button) button.disabled = false;
|
||||
notify(error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function setBusy(form, busy) {
|
||||
var button = form.querySelector('.sign-submit');
|
||||
@@ -189,9 +447,7 @@
|
||||
function submit(form) {
|
||||
var action = form.dataset.authForm;
|
||||
var payload = formData(form);
|
||||
if (action === 'register' && payload.channel !== 'phone') {
|
||||
payload.user_email = payload.target;
|
||||
}
|
||||
if (action === 'register' && payload.channel !== 'phone') payload.user_email = payload.target;
|
||||
setBusy(form, true);
|
||||
request(action, payload).then(function (result) {
|
||||
notify(result.message || '操作成功', 'success');
|
||||
@@ -212,38 +468,82 @@
|
||||
button.textContent = showCode ? '使用账号密码登录' : '使用验证码登录';
|
||||
}
|
||||
|
||||
function setOAuthRedirect(root, redirect) {
|
||||
if (!redirect || !window.URL) return;
|
||||
root.querySelectorAll('a[data-provider][href]').forEach(function (link) {
|
||||
var url;
|
||||
try { url = new URL(link.href, window.location.href); } catch (_) { return; }
|
||||
if (url.origin !== window.location.origin) return;
|
||||
if (!link.dataset.oauthStartHref) link.dataset.oauthStartHref = url.toString();
|
||||
url = new URL(link.dataset.oauthStartHref, window.location.href);
|
||||
url.searchParams.set('redirect_to', redirect);
|
||||
link.href = url.toString();
|
||||
});
|
||||
}
|
||||
|
||||
function initRoot(root) {
|
||||
root.querySelectorAll('input').forEach(function (input) {
|
||||
if (!input.hasAttribute('placeholder')) input.setAttribute('placeholder', ' ');
|
||||
});
|
||||
root.querySelectorAll('form').forEach(function (form) { form.noValidate = true; });
|
||||
var initial = root.dataset.initialTab || 'signin';
|
||||
if (initial === 'oauth' && window.matchMedia && window.matchMedia('(max-width: 767px)').matches) initial = 'signin';
|
||||
activate(root, initial, false);
|
||||
root.querySelectorAll('form').forEach(function (form) { form.noValidate = true; });
|
||||
root.querySelectorAll('[data-auth-tab]').forEach(function (tab) {
|
||||
tab.setAttribute('role', 'tab');
|
||||
var panel = root.querySelector('[data-auth-panel="' + tab.dataset.authTab + '"]');
|
||||
if (panel && panel.id) tab.setAttribute('aria-controls', panel.id);
|
||||
});
|
||||
root.querySelectorAll('[data-password-show]').forEach(function (button) {
|
||||
button.setAttribute('aria-pressed', 'false');
|
||||
if (!button.getAttribute('aria-label')) button.setAttribute('aria-label', '显示密码');
|
||||
});
|
||||
setOAuthRedirect(root, root.dataset.redirectTo || '');
|
||||
var initial = root.dataset.initialTab || 'signin';
|
||||
if (initial === 'oauth' && window.matchMedia && window.matchMedia('(max-width: 767px)').matches) initial = 'signin';
|
||||
var mayActivateQr = initial !== 'oauth' || document.visibilityState === 'visible';
|
||||
if (root.dataset.surface === 'modal' && (!modal || !modal.classList.contains('is-open'))) mayActivateQr = false;
|
||||
if (!mayActivateQr) initial = 'signin';
|
||||
activate(root, initial, false);
|
||||
root.addEventListener('click', function (event) {
|
||||
var tab = event.target.closest('[data-auth-tab]');
|
||||
if (tab) { event.preventDefault(); activate(root, tab.dataset.authTab, true); return; }
|
||||
if (tab) {
|
||||
event.preventDefault();
|
||||
activate(root, tab.dataset.authTab, true);
|
||||
return;
|
||||
}
|
||||
var password = event.target.closest('[data-password-show]');
|
||||
if (password) {
|
||||
var input = password.closest('.floating-input').querySelector('input');
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
password.querySelector('i').className = input.type === 'password' ? 'fa fas fa-eye' : 'fa fas fa-eye-slash';
|
||||
var visible = input.type === 'password';
|
||||
input.type = visible ? 'text' : 'password';
|
||||
password.setAttribute('aria-label', visible ? '隐藏密码' : '显示密码');
|
||||
password.setAttribute('aria-pressed', visible ? 'true' : 'false');
|
||||
password.querySelector('i').className = visible ? 'fa fas fa-eye-slash' : 'fa fas fa-eye';
|
||||
return;
|
||||
}
|
||||
var send = event.target.closest('[data-send-code]');
|
||||
if (send) { event.preventDefault(); sendCode(send); return; }
|
||||
if (send) {
|
||||
event.preventDefault();
|
||||
sendCode(send);
|
||||
return;
|
||||
}
|
||||
var mode = event.target.closest('[data-login-mode]');
|
||||
if (mode) { event.preventDefault(); toggleLoginMode(root, mode); }
|
||||
var refreshQr = event.target.closest('[data-auth-refresh-qr]');
|
||||
if (refreshQr) { event.preventDefault(); loadQr(root); }
|
||||
if (mode) {
|
||||
event.preventDefault();
|
||||
toggleLoginMode(root, mode);
|
||||
return;
|
||||
}
|
||||
var refreshQr = event.target.closest('[data-auth-refresh-qr]');
|
||||
if (refreshQr) {
|
||||
event.preventDefault();
|
||||
loadQr(root, true);
|
||||
}
|
||||
});
|
||||
root.addEventListener('submit', function (event) {
|
||||
var qrForm = event.target.closest('[data-aksrt-weixingzh-code]');
|
||||
if (qrForm) {
|
||||
event.preventDefault();
|
||||
if (validateForm(qrForm)) submitQrCode(qrForm);
|
||||
return;
|
||||
}
|
||||
var qrForm = event.target.closest('[data-aksrt-weixingzh-code]');
|
||||
if (qrForm) {
|
||||
event.preventDefault();
|
||||
if (validateForm(qrForm)) submitQrCode(qrForm);
|
||||
return;
|
||||
}
|
||||
var form = event.target.closest('[data-auth-form]');
|
||||
if (!form) return;
|
||||
event.preventDefault();
|
||||
@@ -252,15 +552,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm(form) {
|
||||
if (!form.checkValidity || form.checkValidity()) return true;
|
||||
var field = form.querySelector(':invalid');
|
||||
notify((field && field.validationMessage) || '请完整填写表单', 'warning');
|
||||
if (field && typeof field.focus === 'function') field.focus();
|
||||
return false;
|
||||
}
|
||||
function validateForm(form) {
|
||||
if (!form.checkValidity || form.checkValidity()) return true;
|
||||
var field = form.querySelector(':invalid');
|
||||
notify((field && field.validationMessage) || '请完整填写表单', 'warning');
|
||||
if (field && typeof field.focus === 'function') field.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
var modal;
|
||||
function authPageUrl(tab, redirect) {
|
||||
var routes = config.routes || {};
|
||||
var base = tab === 'signup' ? routes.register : routes.login;
|
||||
@@ -270,24 +569,37 @@
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function openModal(tab, redirect) {
|
||||
function focusableModalItems() {
|
||||
if (!modal) return [];
|
||||
return Array.prototype.filter.call(modal.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'), function (item) {
|
||||
return item.offsetParent !== null && !item.closest('[hidden]');
|
||||
});
|
||||
}
|
||||
|
||||
function openModal(tab, redirect, trigger) {
|
||||
if (!modal) {
|
||||
window.location.assign(authPageUrl(tab, redirect));
|
||||
return;
|
||||
}
|
||||
lastModalTrigger = trigger || document.activeElement;
|
||||
modal.classList.add('is-open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.classList.add('auth-modal-open');
|
||||
var root = modal.querySelector('[data-aksrt-auth]');
|
||||
if (root) {
|
||||
var targetRedirect = redirect || window.location.href;
|
||||
root.dataset.redirectTo = targetRedirect;
|
||||
root.dataset.oauthIntent = tab === 'oauth' && trigger && trigger.dataset.oauthIntent === 'bind' ? 'bind' : 'login';
|
||||
root.querySelectorAll('[name="redirect_to"]').forEach(function (input) { input.value = targetRedirect; });
|
||||
setOAuthRedirect(root, targetRedirect);
|
||||
activate(root, tab || 'signin', false);
|
||||
var redirectInput = root.querySelector('[name="redirect_to"]');
|
||||
if (redirectInput && redirect) redirectInput.value = redirect;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(storeDismissal) {
|
||||
if (!modal) return;
|
||||
if (!modal || !modal.classList.contains('is-open')) return;
|
||||
var root = modal.querySelector('[data-aksrt-auth]');
|
||||
if (root) stopQr(root);
|
||||
modal.classList.remove('is-open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
document.body.classList.remove('auth-modal-open');
|
||||
@@ -295,9 +607,38 @@
|
||||
var until = Date.now() + Number(modal.dataset.autoExpires) * 3600000;
|
||||
window.localStorage.setItem('aksrtAuthAutoUntil', String(until));
|
||||
}
|
||||
var restore = lastModalTrigger;
|
||||
lastModalTrigger = null;
|
||||
if (restore && document.documentElement.contains(restore) && typeof restore.focus === 'function') restore.focus();
|
||||
}
|
||||
|
||||
function handleModalKeydown(event) {
|
||||
if (!modal || !modal.classList.contains('is-open')) return;
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
closeModal(true);
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'Tab') return;
|
||||
var items = focusableModalItems();
|
||||
if (!items.length) {
|
||||
event.preventDefault();
|
||||
modal.focus();
|
||||
return;
|
||||
}
|
||||
var first = items[0];
|
||||
var last = items[items.length - 1];
|
||||
if (event.shiftKey && (document.activeElement === first || !modal.contains(document.activeElement))) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
showOAuthResultToast();
|
||||
document.querySelectorAll('[data-aksrt-auth]').forEach(initRoot);
|
||||
modal = document.querySelector('[data-auth-modal]');
|
||||
document.addEventListener('click', function (event) {
|
||||
@@ -305,17 +646,25 @@
|
||||
if (!trigger) return;
|
||||
event.preventDefault();
|
||||
var tab = trigger.classList.contains('signup-loader') ? 'signup' : (trigger.classList.contains('qrcode-signin') ? 'oauth' : 'signin');
|
||||
openModal(tab, trigger.dataset.authRedirect || '');
|
||||
openModal(tab, trigger.dataset.authRedirect || window.location.href, trigger);
|
||||
});
|
||||
if (modal) {
|
||||
modal.addEventListener('click', function (event) {
|
||||
if (event.target.closest('[data-auth-close]')) closeModal(true);
|
||||
});
|
||||
document.addEventListener('keydown', function (event) { if (event.key === 'Escape') closeModal(true); });
|
||||
document.addEventListener('keydown', handleModalKeydown);
|
||||
if (modal.dataset.auto === '1' && Number(window.localStorage.getItem('aksrtAuthAutoUntil') || 0) < Date.now()) {
|
||||
window.setTimeout(function () { openModal(modal.querySelector('[data-aksrt-auth]').dataset.initialTab || 'signin'); }, Math.max(1, Number(modal.dataset.autoWait) || 5) * 1000);
|
||||
window.setTimeout(function () {
|
||||
openModal(modal.querySelector('[data-aksrt-auth]').dataset.initialTab || 'signin', window.location.href, null);
|
||||
}, Math.max(1, Number(modal.dataset.autoWait) || 5) * 1000);
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
qrStates.forEach(function (state) {
|
||||
if (document.visibilityState !== 'visible') stopQr(state.root);
|
||||
else if (isRootActive(state.root)) loadQr(state.root, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
@@ -875,25 +875,33 @@
|
||||
data.set('nonce', unbindNonce ? unbindNonce.value : '');
|
||||
data.set('provider', p);
|
||||
data.set('userId', String(userId));
|
||||
fetch(ajaxUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: data.toString()
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.success) {
|
||||
notyf((d.data && d.data.message) || i18n.unbindSuccess || '解除成功', 'success', 5, toastId);
|
||||
setTimeout(function() {
|
||||
window.location.href = tabUrls.settings || new URL('/user/settings/', window.location.origin).toString();
|
||||
}, 1000);
|
||||
} else {
|
||||
notyf((d.data && d.data.message) || i18n.unbindFailed || '解除失败', 'danger', 5, toastId);
|
||||
self.disabled = false;
|
||||
self.textContent = i18n.unbind || '解除';
|
||||
}
|
||||
})
|
||||
fetch(ajaxUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: data.toString()
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json().catch(function() { return {}; }).then(function(payload) {
|
||||
return { ok: response.ok, status: response.status, payload: payload || {} };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
var d = result.payload;
|
||||
if (result.ok && d.success) {
|
||||
notyf((d.data && d.data.message) || d.message || i18n.unbindSuccess || '解除成功', 'success', 5, toastId);
|
||||
setTimeout(function() {
|
||||
window.location.href = tabUrls.settings || new URL('/user/settings/', window.location.origin).toString();
|
||||
}, 1000);
|
||||
} else {
|
||||
var message = result.status === 409
|
||||
? (d.message || (d.data && d.data.message) || i18n.unbindFailed || '解除失败')
|
||||
: ((d.data && d.data.message) || d.message || i18n.unbindFailed || '解除失败');
|
||||
notyf(message, 'danger', 5, toastId);
|
||||
self.disabled = false;
|
||||
self.textContent = i18n.unbind || '解除';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
notyf(i18n.networkError || '网络错误', 'danger', 5, toastId);
|
||||
self.disabled = false;
|
||||
@@ -903,22 +911,57 @@
|
||||
});
|
||||
}
|
||||
|
||||
function initSettingsCallbackToast() {
|
||||
if (document.documentElement.dataset.aksrtSettingsCallbackInitialized === 'true') return;
|
||||
document.documentElement.dataset.aksrtSettingsCallbackInitialized = 'true';
|
||||
var currentUrl = new URL(window.location.href);
|
||||
var provider = currentUrl.searchParams.get('oauth_bound');
|
||||
var isBindingCallback = currentUrl.searchParams.get('oauth_action') === 'bind'
|
||||
|| currentUrl.searchParams.get('tab') === 'settings'
|
||||
|| /\/user\/settings\/?$/.test(currentUrl.pathname);
|
||||
if (!provider || !isBindingCallback) return;
|
||||
function initSettingsCallbackToast() {
|
||||
if (document.documentElement.dataset.aksrtSettingsCallbackInitialized === 'true') return;
|
||||
var currentUrl = new URL(window.location.href);
|
||||
var action = currentUrl.searchParams.get('oauth_action');
|
||||
if (['login', 'bind', 'error'].indexOf(action) === -1) return;
|
||||
if (action !== 'bind' && document.documentElement.dataset.aksrtOauthResultHandled === 'true') return;
|
||||
document.documentElement.dataset.aksrtSettingsCallbackInitialized = 'true';
|
||||
|
||||
notyf(i18n.bindSuccess || '绑定成功', 'success');
|
||||
if (window.history.replaceState) {
|
||||
var cleanUrl = new URL(tabUrls.settings || '/user/settings/', window.location.origin);
|
||||
window.history.replaceState({}, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash);
|
||||
var provider = currentUrl.searchParams.get('oauth_provider') || '';
|
||||
var providerLabels = {
|
||||
github: 'GitHub',
|
||||
gitee: 'Gitee',
|
||||
qq: 'QQ',
|
||||
google: 'Google',
|
||||
weibo: '微博',
|
||||
weixin: '微信',
|
||||
weixingzh: '微信公众号'
|
||||
};
|
||||
var errorMessages = {
|
||||
access_denied: '已取消第三方授权',
|
||||
cancelled: '已取消第三方授权',
|
||||
canceled: '已取消第三方授权',
|
||||
invalid_state: '授权状态已失效,请重新尝试',
|
||||
state_mismatch: '授权状态已失效,请重新尝试',
|
||||
provider_unavailable: '该登录方式暂时不可用',
|
||||
provider_error: '第三方授权失败,请稍后重试',
|
||||
callback_failed: '第三方授权失败,请稍后重试',
|
||||
login_failed: '第三方登录失败,请重试',
|
||||
bind_failed: '第三方账号绑定失败,请重试',
|
||||
account_conflict: '该第三方账号无法绑定到当前账号',
|
||||
already_bound: '该第三方账号已绑定其他账号',
|
||||
timeout: '第三方授权已超时,请重试'
|
||||
};
|
||||
var providerLabel = providerLabels[provider] || '';
|
||||
var message = '';
|
||||
var type = 'success';
|
||||
if (action === 'login') message = providerLabel ? providerLabel + '登录成功' : '登录成功';
|
||||
if (action === 'bind') message = providerLabel ? providerLabel + '绑定成功' : (i18n.bindSuccess || '绑定成功');
|
||||
if (action === 'error') {
|
||||
type = 'danger';
|
||||
message = errorMessages[currentUrl.searchParams.get('oauth_error') || ''] || '第三方授权失败,请重试';
|
||||
}
|
||||
notyf(message, type);
|
||||
|
||||
if (window.history.replaceState) {
|
||||
['oauth_action', 'oauth_provider', 'oauth_error', 'oauth_bound'].forEach(function(key) {
|
||||
currentUrl.searchParams.delete(key);
|
||||
});
|
||||
window.history.replaceState({}, '', currentUrl.pathname + currentUrl.search + currentUrl.hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
|
||||
window.AksrtFrontend.register('aksrt-settings', function(root) {
|
||||
|
||||
Reference in new issue
Block a user