This commit is contained in:
parent
1763b8ef4b
commit
a62987c4b3
@ -233,7 +233,6 @@ const pluginDefinitions = {
|
||||
wechat_push: { label: '微信推送', fields: [
|
||||
field('appId', '应用编号(App ID)'),
|
||||
field('templateId', '消息模板编号'),
|
||||
field('userId', '接收用户编号'),
|
||||
field('webDomain', '网页域名')
|
||||
] }
|
||||
}
|
||||
|
||||
@ -22,7 +22,10 @@ export function usePage() {
|
||||
] : [])
|
||||
] : [])
|
||||
const accountOptions = computed(() => store.hasCapability('plugin.read') && store.hasCapability('plugin.write_own')
|
||||
? [{ value: 'telegram-user', label: 'Telegram 通知', kind: 'account' }]
|
||||
? [
|
||||
{ value: 'telegram-user', label: 'Telegram 通知', kind: 'account' },
|
||||
{ value: 'wechat-user', label: '微信通知', kind: 'account' }
|
||||
]
|
||||
: [])
|
||||
const canReadExternal = () => store.hasCapability('config.read') && store.hasCapability('plugin.read')
|
||||
const externalOptions = computed(() => canReadExternal() ? externalPlugins.value : [])
|
||||
|
||||
@ -37,7 +37,8 @@
|
||||
ref="currentPage"
|
||||
:page-key="selectedSection"
|
||||
/>
|
||||
<TelegramSettings v-else-if="activePage?.kind === 'account'" ref="currentPage" />
|
||||
<TelegramSettings v-else-if="activePage?.value === 'telegram-user'" ref="currentPage" />
|
||||
<WeChatSettings v-else-if="activePage?.value === 'wechat-user'" ref="currentPage" />
|
||||
<section v-else-if="activePage?.kind === 'external'" class="plugin-section">
|
||||
<header class="plugin-section-header"><h3>{{ activePage.label }}</h3></header>
|
||||
<div class="iframe-wrapper">
|
||||
@ -61,6 +62,7 @@ import { usePage } from './PluginSettings.js'
|
||||
import SettingsSubmenu from '../SettingsSubmenu/SettingsSubmenu.vue'
|
||||
import PluginsConfig from '../ConfigSettings/PluginsConfig/PluginsConfig.vue'
|
||||
import TelegramSettings from './TelegramSettings/TelegramSettings.vue'
|
||||
import WeChatSettings from './WeChatSettings/WeChatSettings.vue'
|
||||
|
||||
const {
|
||||
sectionOptions, accountOptions, externalOptions, selectedSection, activePage,
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
.wechat-settings {
|
||||
max-width: 760px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.wechat-header,
|
||||
.wechat-identity,
|
||||
.wechat-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.wechat-header {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.wechat-header h4 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.wechat-header p,
|
||||
.wechat-hint,
|
||||
.wechat-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.wechat-header p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wechat-settings .el-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.wechat-identity {
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.wechat-identity strong {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.wechat-buttons .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.wechat-settings :deep(.el-form) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 20px;
|
||||
}
|
||||
|
||||
.wechat-settings :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wechat-binding {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-align: center;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.wechat-binding img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wechat-binding h4,
|
||||
.wechat-binding p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wechat-dialog-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wechat-settings :deep(.el-form) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,261 @@
|
||||
import { computed, onMounted, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import QRCode from 'qrcode'
|
||||
import { http } from '@/utils/axios'
|
||||
import { isRecentAuthenticationCancelled, requestWithRecentAuthentication } from '@/utils/recentAuthentication'
|
||||
|
||||
const endpoint = '/api/v1/plugin/settings/wechat_push'
|
||||
|
||||
export function useWeChatSettings() {
|
||||
const settings = reactive({ enabled: false, showContent: false })
|
||||
const status = reactive({ bound: false, displayName: '', needsRebind: false, available: false, message: '' })
|
||||
const binding = reactive({ ticket: '', phase: '', displayName: '', expiresAt: 0, image: '' })
|
||||
const ready = ref(false)
|
||||
const working = ref('')
|
||||
const errorMessage = ref('')
|
||||
const bindingError = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const polling = ref(false)
|
||||
const version = ref(0)
|
||||
const savedSettings = ref('')
|
||||
const hasChanges = computed(() => ready.value && savedSettings.value !== JSON.stringify(settings))
|
||||
const bindingLabel = computed(() => status.needsRebind ? '需要重新绑定' : status.bound ? '已绑定' : '未绑定')
|
||||
let disposed = false
|
||||
let generation = 0
|
||||
let pollTimer
|
||||
let expiryTimer
|
||||
|
||||
const checkResponse = (response, fallback) => {
|
||||
if (!response || response.errorNo !== 0) throw new Error(response?.errorMsg || fallback)
|
||||
return response.data
|
||||
}
|
||||
const acceptSettings = data => {
|
||||
if (!data || !Number.isSafeInteger(data.version) || data.version < 0 || typeof data.bound !== 'boolean') {
|
||||
throw new Error('微信通知设置响应不完整,请重新加载')
|
||||
}
|
||||
version.value = data.version
|
||||
settings.enabled = data.enabled === true
|
||||
settings.showContent = data.showContent === true
|
||||
status.bound = data.bound
|
||||
status.needsRebind = data.needsRebind === true
|
||||
status.displayName = typeof data.displayName === 'string' ? data.displayName : ''
|
||||
if (typeof data.available === 'boolean') {
|
||||
status.available = data.available
|
||||
status.message = typeof data.message === 'string' ? data.message : ''
|
||||
}
|
||||
savedSettings.value = JSON.stringify(settings)
|
||||
ready.value = true
|
||||
}
|
||||
const reportSync = data => {
|
||||
if (data.synced === false) {
|
||||
errorMessage.value = '设置已保存,但运行中的推送服务尚未同步,请联系超级管理员。'
|
||||
} else {
|
||||
ElMessage.success('微信通知设置已保存')
|
||||
}
|
||||
}
|
||||
const stopTimers = () => {
|
||||
clearTimeout(pollTimer)
|
||||
clearTimeout(expiryTimer)
|
||||
polling.value = false
|
||||
}
|
||||
const clearBinding = () => {
|
||||
generation++
|
||||
stopTimers()
|
||||
dialogVisible.value = false
|
||||
Object.assign(binding, { ticket: '', phase: '', displayName: '', expiresAt: 0, image: '' })
|
||||
bindingError.value = ''
|
||||
}
|
||||
const bindingRequest = (action, ticket = binding.ticket) => http.post(`${endpoint}/binding`, { action, ticket })
|
||||
const recentRequest = request => requestWithRecentAuthentication(() => {
|
||||
if (disposed) throw new Error('当前页面已关闭')
|
||||
return request()
|
||||
})
|
||||
const confirmDiscard = async () => {
|
||||
if (!hasChanges.value) return true
|
||||
try {
|
||||
await ElMessageBox.confirm('当前通知设置尚未保存,确定放弃这些更改吗?', '未保存的更改', {
|
||||
confirmButtonText: '放弃更改', cancelButtonText: '继续编辑', type: 'warning'
|
||||
})
|
||||
return !disposed
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const load = async () => {
|
||||
if (disposed || working.value || dialogVisible.value) return
|
||||
working.value = 'load'
|
||||
errorMessage.value = ''
|
||||
ready.value = false
|
||||
try {
|
||||
const data = checkResponse(await http.get(`${endpoint}/user`), '读取微信通知设置失败')
|
||||
if (!disposed) acceptSettings(data)
|
||||
} catch (error) {
|
||||
if (!disposed) errorMessage.value = error.message || '读取微信通知设置失败'
|
||||
} finally {
|
||||
working.value = ''
|
||||
}
|
||||
}
|
||||
const expire = () => {
|
||||
generation++
|
||||
stopTimers()
|
||||
binding.phase = 'expired'
|
||||
binding.image = ''
|
||||
bindingError.value = '二维码已过期,请关闭后重新扫码。'
|
||||
}
|
||||
const poll = async () => {
|
||||
if (disposed || polling.value || !dialogVisible.value || !binding.ticket || binding.phase === 'expired') return
|
||||
if (Date.now() >= binding.expiresAt) return expire()
|
||||
clearTimeout(pollTimer)
|
||||
const current = generation
|
||||
polling.value = true
|
||||
bindingError.value = ''
|
||||
try {
|
||||
const data = checkResponse(await bindingRequest('status'), '读取扫码状态失败')
|
||||
if (disposed || current !== generation) return
|
||||
if (!['waiting', 'processing', 'claimed'].includes(data?.phase)) throw new Error('扫码状态异常,请重新绑定')
|
||||
binding.phase = data.phase
|
||||
binding.displayName = typeof data.displayName === 'string' ? data.displayName : ''
|
||||
if (data.phase !== 'claimed') pollTimer = setTimeout(poll, 2000)
|
||||
} catch (error) {
|
||||
if (!disposed && current === generation) bindingError.value = error.message || '扫码状态读取失败,请重试'
|
||||
} finally {
|
||||
if (current === generation) polling.value = false
|
||||
}
|
||||
}
|
||||
const closeBinding = async () => {
|
||||
if (working.value) return false
|
||||
working.value = 'cancel'
|
||||
generation++
|
||||
stopTimers()
|
||||
try {
|
||||
if (binding.ticket) checkResponse(await bindingRequest('cancel'), '取消绑定失败')
|
||||
clearBinding()
|
||||
return true
|
||||
} catch (error) {
|
||||
if (!disposed) bindingError.value = error.message || '取消绑定失败,请重试'
|
||||
return false
|
||||
} finally {
|
||||
working.value = ''
|
||||
}
|
||||
}
|
||||
const beginBinding = async () => {
|
||||
if (!ready.value || !status.available || working.value || disposed || dialogVisible.value) return
|
||||
working.value = 'begin'
|
||||
errorMessage.value = ''
|
||||
let ticket = ''
|
||||
try {
|
||||
if (!await confirmDiscard()) return
|
||||
const data = checkResponse(await recentRequest(() => bindingRequest('begin', '')), '创建绑定请求失败')
|
||||
ticket = typeof data?.ticket === 'string' ? data.ticket : ''
|
||||
if (!/^[A-Za-z0-9_-]{43}$/.test(ticket)) throw new Error('绑定凭据无效,请重新申请')
|
||||
if (disposed) return
|
||||
const url = new URL(data.url)
|
||||
const expiresAt = Date.parse(data.expiresAt)
|
||||
if (url.origin !== 'https://open.weixin.qq.com' || url.pathname !== '/connect/oauth2/authorize' ||
|
||||
url.username || url.password || url.searchParams.get('state') !== ticket ||
|
||||
!Number.isFinite(expiresAt) || expiresAt <= Date.now() || expiresAt > Date.now() + 310000) {
|
||||
throw new Error('微信授权地址或有效期异常,请重新申请')
|
||||
}
|
||||
const image = await QRCode.toDataURL(url.href, { width: 240, margin: 2 })
|
||||
if (disposed) return
|
||||
clearBinding()
|
||||
Object.assign(binding, { ticket, phase: 'waiting', displayName: '', expiresAt, image })
|
||||
dialogVisible.value = true
|
||||
ticket = ''
|
||||
expiryTimer = setTimeout(expire, Math.max(0, expiresAt - Date.now()))
|
||||
await poll()
|
||||
} catch (error) {
|
||||
if (!disposed && !isRecentAuthenticationCancelled(error)) errorMessage.value = error.message || '创建绑定请求失败'
|
||||
} finally {
|
||||
if (/^[A-Za-z0-9_-]{43}$/.test(ticket)) {
|
||||
// 页面关闭或二维码生成失败时,撤销已经创建的请求,不保留授权地址。
|
||||
try { await bindingRequest('cancel', ticket) } catch { /* 请求仍受服务端有效期限制。 */ }
|
||||
}
|
||||
working.value = ''
|
||||
}
|
||||
}
|
||||
const confirmBinding = async () => {
|
||||
if (disposed || working.value || binding.phase !== 'claimed') return
|
||||
if (Date.now() >= binding.expiresAt) return expire()
|
||||
working.value = 'confirm'
|
||||
const ticket = binding.ticket
|
||||
bindingError.value = ''
|
||||
try {
|
||||
const data = checkResponse(await recentRequest(() => bindingRequest('confirm', ticket)), '确认绑定失败')
|
||||
if (disposed) return
|
||||
acceptSettings(data)
|
||||
clearBinding()
|
||||
reportSync(data)
|
||||
} catch (error) {
|
||||
if (!disposed && !isRecentAuthenticationCancelled(error)) bindingError.value = error.message || '确认绑定失败'
|
||||
} finally {
|
||||
working.value = ''
|
||||
}
|
||||
}
|
||||
const submit = async action => {
|
||||
if (disposed || !ready.value || working.value || dialogVisible.value) return
|
||||
working.value = action
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
if (action === 'unbind') {
|
||||
try {
|
||||
await ElMessageBox.confirm('解除后将停止向此微信发送通知,确定继续吗?', '解除本人绑定', {
|
||||
confirmButtonText: '解除绑定', cancelButtonText: '取消', type: 'warning'
|
||||
})
|
||||
} catch { return }
|
||||
}
|
||||
const payload = { ...settings, version: version.value }
|
||||
const response = await recentRequest(() => action === 'test'
|
||||
? http.post(`${endpoint}/test`)
|
||||
: action === 'unbind'
|
||||
? http.delete(`${endpoint}/user`, { data: payload })
|
||||
: http.put(`${endpoint}/user`, payload))
|
||||
if (disposed) return
|
||||
const data = checkResponse(response, '微信通知操作失败')
|
||||
if (action === 'test') ElMessage.success('测试请求已提交,请到微信核对')
|
||||
else {
|
||||
acceptSettings(data)
|
||||
reportSync(data)
|
||||
}
|
||||
} catch (error) {
|
||||
if (!disposed && !isRecentAuthenticationCancelled(error)) errorMessage.value = error.message || '微信通知操作失败'
|
||||
} finally {
|
||||
working.value = ''
|
||||
}
|
||||
}
|
||||
const beforeLeave = async () => {
|
||||
if (working.value) return false
|
||||
if (!await confirmDiscard()) return false
|
||||
if (!dialogVisible.value) return true
|
||||
try {
|
||||
await ElMessageBox.confirm('当前扫码绑定尚未完成,确定取消并离开吗?', '取消绑定', {
|
||||
confirmButtonText: '取消并离开', cancelButtonText: '继续绑定', type: 'warning'
|
||||
})
|
||||
} catch { return false }
|
||||
return closeBinding()
|
||||
}
|
||||
const refresh = async () => {
|
||||
if (!working.value && await confirmDiscard()) await load()
|
||||
}
|
||||
const preventUnload = event => {
|
||||
if (!hasChanges.value && !dialogVisible.value && !working.value) return
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('beforeunload', preventUnload)
|
||||
load()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true
|
||||
const ticket = binding.ticket
|
||||
clearBinding()
|
||||
window.removeEventListener('beforeunload', preventUnload)
|
||||
if (ticket) bindingRequest('cancel', ticket).catch(() => {})
|
||||
})
|
||||
return {
|
||||
settings, status, binding, ready, working, errorMessage, bindingError,
|
||||
dialogVisible, polling, hasChanges, bindingLabel, refresh, submit,
|
||||
beginBinding, confirmBinding, closeBinding, poll, beforeLeave
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<section v-loading="!!working" class="wechat-settings" aria-label="本人微信通知">
|
||||
<header class="wechat-header">
|
||||
<div>
|
||||
<h4>微信通知</h4>
|
||||
<p>绑定本人微信,仅接收当前账号的邮件通知。</p>
|
||||
</div>
|
||||
<el-tag :type="status.needsRebind ? 'warning' : status.bound ? 'success' : 'info'">
|
||||
{{ bindingLabel }}
|
||||
</el-tag>
|
||||
</header>
|
||||
<el-alert v-if="errorMessage" :title="errorMessage" type="error" :closable="false" show-icon />
|
||||
<el-alert v-if="ready && status.message" :title="status.message" type="warning" :closable="false" show-icon />
|
||||
<el-alert
|
||||
v-if="status.needsRebind"
|
||||
title="系统公众号已变更,请重新扫码绑定。原绑定不会收到新通知。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<div class="wechat-identity">
|
||||
<span class="wechat-label">绑定微信</span>
|
||||
<strong>{{ status.bound ? status.displayName || '已绑定的微信账号' : '尚未绑定' }}</strong>
|
||||
<div class="wechat-buttons">
|
||||
<el-button :disabled="!ready || !status.available || !!working" @click="beginBinding">
|
||||
{{ status.bound ? '重新绑定' : '扫码绑定' }}
|
||||
</el-button>
|
||||
<el-button v-if="status.bound" type="danger" plain :disabled="!!working" @click="submit('unbind')">
|
||||
解除绑定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-form label-position="left" label-width="100px" :disabled="!ready || !!working">
|
||||
<el-form-item label="邮件通知">
|
||||
<el-switch
|
||||
v-model="settings.enabled"
|
||||
:disabled="!status.bound || (!settings.enabled && (!status.available || status.needsRebind))"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="包含正文">
|
||||
<el-switch v-model="settings.showContent" :disabled="!status.bound" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<p class="wechat-hint">开启正文后,邮件内容将发送到微信。停用通知不会解除绑定。</p>
|
||||
<footer class="wechat-buttons">
|
||||
<el-button :disabled="!!working" @click="refresh">重新加载</el-button>
|
||||
<el-button
|
||||
:disabled="!ready || !!working || !status.available || !status.bound || status.needsRebind || hasChanges || !settings.enabled"
|
||||
@click="submit('test')"
|
||||
>
|
||||
发送测试消息
|
||||
</el-button>
|
||||
<el-button type="primary" :disabled="!ready || !!working || !hasChanges" @click="submit('save')">
|
||||
保存设置
|
||||
</el-button>
|
||||
</footer>
|
||||
<el-dialog
|
||||
:model-value="dialogVisible"
|
||||
title="绑定本人微信"
|
||||
width="420px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="!working"
|
||||
:show-close="!working"
|
||||
:before-close="closeBinding"
|
||||
>
|
||||
<div class="wechat-binding" aria-live="polite">
|
||||
<el-alert v-if="bindingError" :title="bindingError" type="warning" :closable="false" show-icon />
|
||||
<template v-if="binding.phase === 'claimed'">
|
||||
<h4>请核对微信身份</h4>
|
||||
<strong>{{ binding.displayName || '已授权的微信账号' }}</strong>
|
||||
<p>确认是本人后完成绑定;不是本人请取消。</p>
|
||||
</template>
|
||||
<template v-else-if="binding.phase !== 'expired'">
|
||||
<img v-if="binding.image" :src="binding.image" width="240" height="240" alt="本人微信绑定二维码" />
|
||||
<p>{{ binding.phase === 'processing' ? '正在读取微信授权结果…' : '请用本人微信扫码授权,再回到此处确认。' }}</p>
|
||||
<span class="wechat-hint">二维码有效期为 5 分钟,请勿转发。</span>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="wechat-buttons wechat-dialog-actions">
|
||||
<el-button :disabled="!!working" @click="closeBinding">取消</el-button>
|
||||
<el-button
|
||||
v-if="bindingError && binding.phase !== 'expired'"
|
||||
:disabled="!!working || polling"
|
||||
@click="poll"
|
||||
>
|
||||
重试状态查询
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="binding.phase === 'claimed'"
|
||||
type="primary"
|
||||
:disabled="!!working"
|
||||
@click="confirmBinding"
|
||||
>
|
||||
确认绑定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWeChatSettings } from './WeChatSettings.js'
|
||||
|
||||
const {
|
||||
settings, status, binding, ready, working, errorMessage, bindingError,
|
||||
dialogVisible, polling, hasChanges, bindingLabel, refresh, submit,
|
||||
beginBinding, confirmBinding, closeBinding, poll, beforeLeave
|
||||
} = useWeChatSettings()
|
||||
|
||||
defineExpose({ beforeLeave })
|
||||
</script>
|
||||
|
||||
<style scoped src="./WeChatSettings.css"></style>
|
||||
@ -31,11 +31,14 @@ for (const role of ['super_admin', 'admin', 'user']) {
|
||||
assert.deepEqual(page.sectionOptions.value.map(item => item.value), role === 'super_admin'
|
||||
? ['spam_block', 'telegram_push', 'wechat_push']
|
||||
: role === 'admin' ? ['spam_block'] : [])
|
||||
assert.deepEqual(page.accountOptions.value.map(item => item.value), ['telegram-user'])
|
||||
assert.deepEqual(page.accountOptions.value.map(item => item.value), ['telegram-user', 'wechat-user'])
|
||||
await page.selectSection('telegram-user')
|
||||
assert.equal(page.activePage.value.kind, 'account')
|
||||
await page.selectSection('wechat-user')
|
||||
assert.equal(page.activePage.value.kind, 'account')
|
||||
assert.equal(page.activePage.value.value, 'wechat-user')
|
||||
await page.selectSection('wechat_push')
|
||||
assert.equal(page.selectedSection.value, role === 'super_admin' ? 'wechat_push' : 'telegram-user')
|
||||
assert.equal(page.selectedSection.value, role === 'super_admin' ? 'wechat_push' : 'wechat-user')
|
||||
})
|
||||
}
|
||||
|
||||
@ -59,7 +62,7 @@ test('独立插件配置包含三个运行配置页,停用插件不依赖运
|
||||
assert.equal(page.activePage.value.kind, 'config')
|
||||
assert.equal(page.activePage.value.value, key)
|
||||
}
|
||||
assert.deepEqual(page.accountOptions.value.map(item => item.value), ['telegram-user'])
|
||||
assert.deepEqual(page.accountOptions.value.map(item => item.value), ['telegram-user', 'wechat-user'])
|
||||
})
|
||||
|
||||
test('普通账号只保留本人通知设置,不读取或进入全局配置和管理员插件列表', async () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user