mirror of https://github.com/openspug/spug
# 继承推送服务
parent
cfe33658e8
commit
d125112cd9
|
@ -4,8 +4,10 @@
|
|||
from django.views.generic import View
|
||||
from libs import json_response, JsonParser, Argument, auth
|
||||
from libs.spug import Notification
|
||||
from libs.push import get_contacts
|
||||
from apps.alarm.models import Alarm, Group, Contact
|
||||
from apps.monitor.models import Detection
|
||||
from apps.setting.utils import AppSetting
|
||||
import json
|
||||
|
||||
|
||||
|
@ -55,8 +57,20 @@ class GroupView(View):
|
|||
class ContactView(View):
|
||||
@auth('alarm.contact.view|alarm.group.view')
|
||||
def get(self, request):
|
||||
contacts = Contact.objects.all()
|
||||
return json_response(contacts)
|
||||
form, error = JsonParser(
|
||||
Argument('with_push', required=False),
|
||||
).parse(request.GET)
|
||||
if error is None:
|
||||
response = []
|
||||
if form.with_push:
|
||||
push_key = AppSetting.get('spug_push_key')
|
||||
if push_key:
|
||||
response = get_contacts(push_key)
|
||||
|
||||
for item in Contact.objects.all():
|
||||
response.append(item.to_dict())
|
||||
return json_response(response)
|
||||
return json_response(error=error)
|
||||
|
||||
@auth('alarm.contact.add|alarm.contact.edit')
|
||||
def post(self, request):
|
||||
|
|
|
@ -37,8 +37,8 @@ class DetectionView(View):
|
|||
).parse(request.body)
|
||||
if error is None:
|
||||
if set(form.notify_mode).intersection(['1', '2', '4']):
|
||||
if not AppSetting.get_default('spug_key'):
|
||||
return json_response(error='报警方式 微信、短信、邮件需要配置调用凭据(系统设置/基本设置),请配置后再启用该报警方式。')
|
||||
if not AppSetting.get_default('spug_key') and not AppSetting.get_default('spug_push_key'):
|
||||
return json_response(error='报警方式 微信、短信、邮件需要配置调用凭据(系统设置/基本设置)或推送服务(系统设置/推送服务设置),请配置后再启用该报警方式。')
|
||||
|
||||
form.targets = json.dumps(form.targets)
|
||||
form.notify_grp = json.dumps(form.notify_grp)
|
||||
|
|
|
@ -16,6 +16,7 @@ KEYS_DEFAULT = {
|
|||
'mail_service': {},
|
||||
'private_key': None,
|
||||
'public_key': None,
|
||||
'spug_push_key': None,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,5 +12,6 @@ urlpatterns = [
|
|||
url(r'^ldap_test/$', ldap_test),
|
||||
url(r'^email_test/$', email_test),
|
||||
url(r'^mfa/$', MFAView.as_view()),
|
||||
url(r'^about/$', get_about)
|
||||
url(r'^about/$', get_about),
|
||||
url(r'^balance/$', get_push_balance),
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@ from libs import JsonParser, Argument, json_response, auth
|
|||
from libs.utils import generate_random_str
|
||||
from libs.mail import Mail
|
||||
from libs.spug import send_login_wx_code
|
||||
from libs.push import get_balance
|
||||
from libs.mixins import AdminView
|
||||
from apps.setting.utils import AppSetting
|
||||
from apps.setting.models import Setting, KEYS_DEFAULT
|
||||
|
@ -36,7 +37,8 @@ class SettingView(AdminView):
|
|||
class MFAView(AdminView):
|
||||
def get(self, request):
|
||||
if not request.user.wx_token:
|
||||
return json_response(error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。')
|
||||
return json_response(
|
||||
error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。')
|
||||
code = generate_random_str(6)
|
||||
send_login_wx_code(request.user.wx_token, code)
|
||||
cache.set(f'{request.user.username}:code', code, 300)
|
||||
|
@ -105,7 +107,8 @@ def email_test(request):
|
|||
@auth('admin')
|
||||
def mfa_test(request):
|
||||
if not request.user.wx_token:
|
||||
return json_response(error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。')
|
||||
return json_response(
|
||||
error='检测到当前账户未配置微信Token,请配置后再尝试启用MFA认证,否则可能造成系统无法正常登录。')
|
||||
code = generate_random_str(6)
|
||||
send_login_wx_code(request.user.wx_token, code)
|
||||
cache.set(f'{request.user.username}:code', code, 300)
|
||||
|
@ -120,3 +123,12 @@ def get_about(request):
|
|||
'spug_version': settings.SPUG_VERSION,
|
||||
'django_version': django.get_version()
|
||||
})
|
||||
|
||||
|
||||
@auth('admin')
|
||||
def get_push_balance(request):
|
||||
token = AppSetting.get_default('spug_push_key')
|
||||
if not token:
|
||||
return json_response(error='请先配置推送服务绑定账户')
|
||||
res = get_balance(token)
|
||||
return json_response(res)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
# Copyright: (c) <spug.dev@gmail.com>
|
||||
# Released under the AGPL-3.0 License.
|
||||
import requests
|
||||
|
||||
push_server = 'https://push.spug.cc'
|
||||
|
||||
|
||||
def get_balance(token):
|
||||
res = requests.get(f'{push_server}/spug/balance/', json={'token': token})
|
||||
if res.status_code != 200:
|
||||
raise Exception(f'status code: {res.status_code}')
|
||||
res = res.json()
|
||||
if res.get('error'):
|
||||
raise Exception(res['error'])
|
||||
return res['data']
|
||||
|
||||
|
||||
def get_contacts(token):
|
||||
try:
|
||||
res = requests.post(f'{push_server}/spug/contacts/', json={'token': token})
|
||||
res = res.json()
|
||||
if res['data']:
|
||||
return res['data']
|
||||
except Exception:
|
||||
return []
|
|
@ -6,11 +6,23 @@ from apps.setting.utils import AppSetting
|
|||
from apps.notify.models import Notify
|
||||
from libs.mail import Mail
|
||||
from libs.utils import human_datetime
|
||||
from libs.push import push_server
|
||||
from functools import partial
|
||||
import requests
|
||||
import json
|
||||
|
||||
spug_server = 'https://api.spug.cc'
|
||||
notify_source = 'monitor'
|
||||
make_no_spug_key_notify = partial(
|
||||
Notify.make_monitor_notify,
|
||||
'发送报警信息失败',
|
||||
'未配置报警服务调用凭据,请在系统管理/系统设置/基本设置/调用凭据中配置。'
|
||||
)
|
||||
make_no_push_key_notify = partial(
|
||||
Notify.make_monitor_notify,
|
||||
'发送报警信息失败',
|
||||
'未绑定推送服务,请在系统管理/系统设置/推送服务设置中绑定推送助手账户。'
|
||||
)
|
||||
|
||||
|
||||
def send_login_wx_code(wx_token, code):
|
||||
|
@ -33,7 +45,7 @@ class Notification:
|
|||
self.message = message
|
||||
self.duration = duration
|
||||
self.spug_key = AppSetting.get_default('spug_key')
|
||||
self.u_ids = []
|
||||
self.spug_push_key = AppSetting.get_default('spug_push_key')
|
||||
|
||||
@staticmethod
|
||||
def handle_request(url, data, mode=None):
|
||||
|
@ -62,7 +74,7 @@ class Notification:
|
|||
|
||||
def monitor_by_wx(self, users):
|
||||
if not self.spug_key:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/基本设置/调用凭据中配置。')
|
||||
make_no_spug_key_notify()
|
||||
return
|
||||
data = {
|
||||
'token': self.spug_key,
|
||||
|
@ -99,7 +111,7 @@ class Notification:
|
|||
}
|
||||
self.handle_request(f'{spug_server}/apis/notify/mail/', data, 'spug')
|
||||
else:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未配置报警服务调用凭据,请在系统管理/系统设置/报警服务设置中配置。')
|
||||
make_no_spug_key_notify()
|
||||
|
||||
def monitor_by_dd(self, users):
|
||||
texts = [
|
||||
|
@ -144,30 +156,82 @@ class Notification:
|
|||
for url in users:
|
||||
self.handle_request(url, data, 'wx')
|
||||
|
||||
def monitor_by_spug_push(self, targets):
|
||||
if not self.spug_push_key:
|
||||
make_no_push_key_notify()
|
||||
return
|
||||
data = {
|
||||
'token': self.spug_push_key,
|
||||
'targets': list(targets),
|
||||
'dataset': {
|
||||
'title': self.title,
|
||||
'target': self.target,
|
||||
'message': self.message,
|
||||
'duration': self.duration,
|
||||
'event': self.event
|
||||
}
|
||||
}
|
||||
self.handle_request(f'{push_server}/spug/message/', data, 'spug')
|
||||
|
||||
def dispatch_monitor(self, modes):
|
||||
self.u_ids = sum([json.loads(x.contacts) for x in Group.objects.filter(id__in=self.grp)], [])
|
||||
u_ids, push_ids = [], []
|
||||
for item in Group.objects.filter(id__in=self.grp):
|
||||
for x in json.loads(item.contacts):
|
||||
if isinstance(x, str) and '_' in x:
|
||||
push_ids.append(x)
|
||||
else:
|
||||
u_ids.append(x)
|
||||
|
||||
targets = set()
|
||||
for mode in modes:
|
||||
if mode == '1':
|
||||
users = set(x.wx_token for x in Contact.objects.filter(id__in=self.u_ids, wx_token__isnull=False))
|
||||
wx_mp_ids = set(x for x in push_ids if x.startswith('wx_mp_'))
|
||||
targets.update(wx_mp_ids)
|
||||
users = set(x.wx_token for x in Contact.objects.filter(id__in=u_ids, wx_token__isnull=False))
|
||||
if not users:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的微信Token。')
|
||||
if not wx_mp_ids:
|
||||
Notify.make_monitor_notify(
|
||||
'发送报警信息失败',
|
||||
'未找到可用的通知对象,请确保设置了相关报警联系人的微信Token。'
|
||||
)
|
||||
continue
|
||||
self.monitor_by_wx(users)
|
||||
elif mode == '2':
|
||||
sms_ids = set(x for x in push_ids if x.startswith('sms_'))
|
||||
targets.update(sms_ids)
|
||||
elif mode == '3':
|
||||
users = set(x.ding for x in Contact.objects.filter(id__in=self.u_ids, ding__isnull=False))
|
||||
users = set(x.ding for x in Contact.objects.filter(id__in=u_ids, ding__isnull=False))
|
||||
if not users:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的钉钉。')
|
||||
Notify.make_monitor_notify(
|
||||
'发送报警信息失败',
|
||||
'未找到可用的通知对象,请确保设置了相关报警联系人的钉钉。'
|
||||
)
|
||||
continue
|
||||
self.monitor_by_dd(users)
|
||||
elif mode == '4':
|
||||
users = set(x.email for x in Contact.objects.filter(id__in=self.u_ids, email__isnull=False))
|
||||
mail_ids = set(x for x in push_ids if x.startswith('mail_'))
|
||||
targets.update(mail_ids)
|
||||
users = set(x.email for x in Contact.objects.filter(id__in=u_ids, email__isnull=False))
|
||||
if not users:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的邮件地址。')
|
||||
if not mail_ids:
|
||||
Notify.make_monitor_notify(
|
||||
'发送报警信息失败',
|
||||
'未找到可用的通知对象,请确保设置了相关报警联系人的邮件地址。'
|
||||
)
|
||||
continue
|
||||
self.monitor_by_email(users)
|
||||
elif mode == '5':
|
||||
users = set(x.qy_wx for x in Contact.objects.filter(id__in=self.u_ids, qy_wx__isnull=False))
|
||||
users = set(x.qy_wx for x in Contact.objects.filter(id__in=u_ids, qy_wx__isnull=False))
|
||||
if not users:
|
||||
Notify.make_monitor_notify('发送报警信息失败', '未找到可用的通知对象,请确保设置了相关报警联系人的企业微信。')
|
||||
Notify.make_monitor_notify(
|
||||
'发送报警信息失败',
|
||||
'未找到可用的通知对象,请确保设置了相关报警联系人的企业微信。'
|
||||
)
|
||||
continue
|
||||
self.monitor_by_qy_wx(users)
|
||||
elif mode == '6':
|
||||
voice_ids = set(x for x in push_ids if x.startswith('voice_'))
|
||||
targets.update(voice_ids)
|
||||
|
||||
if targets:
|
||||
self.monitor_by_spug_push(targets)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
|
||||
function Link(props) {
|
||||
return (
|
||||
<a target="_blank" rel="noopener noreferrer" href={props.href}>{props.title}</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default Link
|
|
@ -16,6 +16,7 @@ import TableCard from './TableCard';
|
|||
import Breadcrumb from './Breadcrumb';
|
||||
import AppSelector from './AppSelector';
|
||||
import NotFound from './NotFound';
|
||||
import Link from './Link';
|
||||
|
||||
export {
|
||||
StatisticsCard,
|
||||
|
@ -31,4 +32,5 @@ export {
|
|||
Breadcrumb,
|
||||
AppSelector,
|
||||
NotFound,
|
||||
Link,
|
||||
}
|
||||
|
|
|
@ -3,16 +3,24 @@
|
|||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Modal, Form, Input, Transfer, message } from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {observer} from 'mobx-react';
|
||||
import {Modal, Form, Input, Transfer, Spin, message} from 'antd';
|
||||
import http from 'libs/http';
|
||||
import store from './store';
|
||||
import contactStore from '../contact/store';
|
||||
|
||||
export default observer(function () {
|
||||
const [form] = Form.useForm();
|
||||
const [contacts, setContacts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fetching, setFetching] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFetching(true)
|
||||
http.get('/api/alarm/contact/?with_push=1')
|
||||
.then(res => setContacts(res))
|
||||
.finally(() => setFetching(false))
|
||||
}, []);
|
||||
|
||||
function handleSubmit() {
|
||||
setLoading(true);
|
||||
|
@ -42,14 +50,16 @@ export default observer(function () {
|
|||
<Form.Item name="desc" label="备注信息">
|
||||
<Input.TextArea placeholder="请输入备注信息"/>
|
||||
</Form.Item>
|
||||
<Spin spinning={fetching}>
|
||||
<Form.Item required name="contacts" valuePropName="targetKeys" label="选择联系人">
|
||||
<Transfer
|
||||
rowKey={item => item.id}
|
||||
titles={['已有联系人', '已选联系人']}
|
||||
listStyle={{width: 199}}
|
||||
dataSource={contactStore.records}
|
||||
dataSource={contacts}
|
||||
render={item => item.name}/>
|
||||
</Form.Item>
|
||||
</Spin>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
|
|
|
@ -11,7 +11,6 @@ import { Action, TableCard, AuthButton } from 'components';
|
|||
import { http, hasPermission } from 'libs';
|
||||
import store from './store';
|
||||
import contactStore from '../contact/store';
|
||||
import lds from 'lodash';
|
||||
|
||||
@observer
|
||||
class ComTable extends React.Component {
|
||||
|
@ -76,8 +75,7 @@ class ComTable extends React.Component {
|
|||
pageSizeOptions: ['10', '20', '50', '100']
|
||||
}}>
|
||||
<Table.Column title="组名称" dataIndex="name"/>
|
||||
<Table.Column ellipsis title="成员" dataIndex="contacts"
|
||||
render={value => value.map(x => lds.get(this.state.contactMap, `${x}.name`)).join(',')}/>
|
||||
<Table.Column ellipsis title="成员" dataIndex="contacts" render={value => `${value.length}个`}/>
|
||||
<Table.Column ellipsis title="描述信息" dataIndex="desc"/>
|
||||
{hasPermission('alarm.group.edit|alarm.group.del') && (
|
||||
<Table.Column title="操作" render={info => (
|
||||
|
|
|
@ -14,9 +14,10 @@ import lds from 'lodash';
|
|||
|
||||
const modeOptions = [
|
||||
{label: '微信', 'value': '1'},
|
||||
{label: '短信', 'value': '2', disabled: true},
|
||||
{label: '钉钉', 'value': '3'},
|
||||
{label: '短信', 'value': '2'},
|
||||
{label: '电话', 'value': '6'},
|
||||
{label: '邮件', 'value': '4'},
|
||||
{label: '钉钉', 'value': '3'},
|
||||
{label: '企业微信', 'value': '5'},
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {observer} from 'mobx-react';
|
||||
import {Form, Input, Button, Spin, message} from 'antd';
|
||||
import {Link} from 'components';
|
||||
import css from './index.module.css';
|
||||
import {http, clsNames} from 'libs';
|
||||
import store from './store';
|
||||
|
||||
export default observer(function () {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [balance, setBalance] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
if (store.settings.spug_push_key) {
|
||||
fetchBalance()
|
||||
}
|
||||
}, []);
|
||||
|
||||
function fetchBalance() {
|
||||
setFetching(true)
|
||||
http.get('/api/setting/balance/')
|
||||
.then(res => setBalance(res))
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
setFetching(false)
|
||||
})
|
||||
}
|
||||
|
||||
function handleBind() {
|
||||
const spug_push_key = store.settings.spug_push_key
|
||||
if (!spug_push_key) return message.error('请输入要绑定的推送助手用户ID')
|
||||
setLoading(true);
|
||||
http.post('/api/setting/', {data: [{key: 'spug_push_key', value: spug_push_key}]})
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
store.fetchSettings();
|
||||
fetchBalance()
|
||||
})
|
||||
.finally(() => store.loading = false)
|
||||
}
|
||||
|
||||
const isVip = true
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={css.title}>推送服务设置</div>
|
||||
<div style={{maxWidth: 340}}>
|
||||
<Form.Item label="推送助手账户绑定" labelCol={{span: 24}} style={{marginTop: 12}}
|
||||
extra={<div>请登录 <Link href="https://push.spug.cc/login" title="推送助手"/>,至个人中心 /
|
||||
个人设置查看用户ID,注意保密该ID请勿泄漏给第三方。</div>}>
|
||||
|
||||
<Input.Group compact>
|
||||
<Input
|
||||
value={store.settings.spug_push_key}
|
||||
onChange={e => store.settings.spug_push_key = e.target.value}
|
||||
style={{width: 'calc(100% - 100px)'}}
|
||||
placeholder="请输入要绑定的推送助手用户ID"/>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{width: 80, marginLeft: 20}}
|
||||
onClick={handleBind}
|
||||
loading={loading}>确定</Button>
|
||||
</Input.Group>
|
||||
{/*<Input.Group compact>*/}
|
||||
{/* <Input bordered={false} style={{width: 'calc(100% - 100px)', paddingLeft: 0}} value="32uu73******64823d"/>*/}
|
||||
{/* <Button style={{width: 80, marginLeft: 20}}>解绑</Button>*/}
|
||||
{/*</Input.Group>*/}
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item style={{marginTop: 24}}
|
||||
extra={<div> 如需充值请至 <Link href="https://push.spug.cc/buy/sms" title="推送助手"/>,具体计费规则及说明请查看推送助手官网。
|
||||
</div>}>
|
||||
<div className={css.statistic}>
|
||||
<Spin spinning={fetching}>
|
||||
<div className={css.body}>
|
||||
<div className={css.item}>
|
||||
<div className={css.title}>短信余额</div>
|
||||
<div className={css.value}>{balance.sms_balance}</div>
|
||||
</div>
|
||||
<div className={css.item}>
|
||||
<div className={css.title}>语音余额</div>
|
||||
<div className={css.value}>{balance.voice_balance}</div>
|
||||
</div>
|
||||
<div className={css.item}>
|
||||
<div className={css.title}>邮件余额</div>
|
||||
<div className={css.value}>{balance.mail_balance}</div>
|
||||
{isVip ? (
|
||||
<div className={clsNames(css.tips, css.active)}>+ 会员免费20封 / 天</div>
|
||||
) : (
|
||||
<div className={css.tips}>会员免费20封 / 天</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={css.item}>
|
||||
<div className={css.title}>微信公众号余额</div>
|
||||
<div className={css.value}>{balance.wx_mp_balance}</div>
|
||||
{isVip ? (
|
||||
<div className={clsNames(css.tips, css.active)}>+ 会员免费100条 / 天</div>
|
||||
) : (
|
||||
<div className={css.tips}>会员免费20封 / 天</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
|
@ -12,6 +12,7 @@ import LDAPSetting from './LDAPSetting';
|
|||
import OpenService from './OpenService';
|
||||
import KeySetting from './KeySetting';
|
||||
import SecuritySetting from './SecuritySetting';
|
||||
import PushSetting from './PushSetting';
|
||||
import About from './About';
|
||||
import styles from './index.module.css';
|
||||
import store from './store';
|
||||
|
@ -49,6 +50,7 @@ class Index extends React.Component {
|
|||
<Menu.Item key="security">安全设置</Menu.Item>
|
||||
<Menu.Item key="ldap">LDAP设置</Menu.Item>
|
||||
<Menu.Item key="key">密钥设置</Menu.Item>
|
||||
<Menu.Item key="push">推送服务设置</Menu.Item>
|
||||
<Menu.Item key="alarm">报警服务设置</Menu.Item>
|
||||
<Menu.Item key="service">开放服务设置</Menu.Item>
|
||||
<Menu.Item key="about">关于</Menu.Item>
|
||||
|
@ -59,6 +61,7 @@ class Index extends React.Component {
|
|||
{selectedKeys[0] === 'security' && <SecuritySetting/>}
|
||||
{selectedKeys[0] === 'ldap' && <LDAPSetting/>}
|
||||
{selectedKeys[0] === 'alarm' && <AlarmSetting/>}
|
||||
{selectedKeys[0] === 'push' && <PushSetting/>}
|
||||
{selectedKeys[0] === 'service' && <OpenService/>}
|
||||
{selectedKeys[0] === 'key' && <KeySetting/>}
|
||||
{selectedKeys[0] === 'about' && <About/>}
|
||||
|
|
|
@ -29,3 +29,89 @@
|
|||
.keyText {
|
||||
font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
.statistic {
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
|
||||
&:nth-child(n+2) {
|
||||
&:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 56px;
|
||||
background: #CCCCCC;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 40px;
|
||||
line-height: 46px;
|
||||
color: #333333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tips {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
font-size: 11px;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-radius: 10px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.active {
|
||||
cursor: initial;
|
||||
background: #f7af40;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.buy {
|
||||
position: absolute;
|
||||
right: 51px;
|
||||
top: 110px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 32px;
|
||||
width: 96px;
|
||||
padding: 0 16px 0 20px;
|
||||
border-radius: 16px;
|
||||
color: #2563fc;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
:global(.iconfont) {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue