update 通知公告显示优化

This commit is contained in:
2021-07-01 22:26:11 +08:00
parent c4c35998a6
commit 850d490a1b
22 changed files with 435 additions and 193 deletions

View File

@@ -151,6 +151,8 @@
flex: 0 0 100%;
width: 100%;
text-align: inherit;
}
}
.yo-form--short {

View File

@@ -21,26 +21,25 @@
display: flex;
}
.ellipsis {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ellipsis-2 {
.ellipsis-line(@line) {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-line-clamp: @line;
}
.ellipsis-2 {
.ellipsis-line(2);
}
.ellipsis-3 {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
.ellipsis-line(3);
}

View File

@@ -94,44 +94,76 @@
transition: @animation-duration-slow;
transition-property: color;
// 特殊工具按钮
.theme-toggle {
position: relative;
overflow: hidden;
width: 20px;
height: 20px;
margin: 7px 0;
border-radius: 50%;
&--real {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #fff;
}
&--imaginary {
position: absolute;
top: 6px;
left: -6px;
width: 18px;
height: 18px;
transform: @animation-duration-slow transform;
transform: rotate(45deg) scaleY(1);
transform-origin: top right;
border-radius: 50%;
background-color: fade(@layout-header-background, 70%);
}
}
}
&:active {
box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05);
}
// 特殊工具按钮
.theme-toggle {
position: relative;
overflow: hidden;
width: 20px;
height: 20px;
margin: 7px 0;
border-radius: 50%;
&--real {
position: relative;
width: 20px;
height: 20px;
transition: @animation-duration-slow background-color;
border-radius: 50%;
background-color: fade(@white, 60%);
&::before {
position: absolute;
top: 5px;
left: 5px;
width: 10px;
height: 10px;
content: '';
transition: @animation-duration-slow transform;
transform: scale(0);
border: 2px solid @layout-header-background;
border-radius: 50%;
}
}
&--imaginary {
position: absolute;
top: 6px;
right: -6px;
width: 18px;
height: 18px;
transition: @animation-duration-slow transform;
transform: rotate(45deg) scaleY(1);
transform-origin: top right;
border-radius: 50%;
background-color: @layout-header-background;
}
}
&:hover {
.theme-toggle {
&--real {
background-color: @white;
&::before {
transform: scale(1);
}
}
&--imaginary {
transform: rotate(45deg) scaleY(0);
}
}
}
}
.ant-select-auto-complete {
margin: (@layout-header-height - 10px - 30px) / 2 @padding-md;

View File

@@ -151,6 +151,8 @@
flex: 0 0 100%;
width: 100%;
text-align: inherit;
}
}
.yo-form--short {

View File

@@ -21,26 +21,25 @@
display: flex;
}
.ellipsis {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ellipsis-2 {
.ellipsis-line(@line) {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-line-clamp: @line;
}
.ellipsis-2 {
.ellipsis-line(2);
}
.ellipsis-3 {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
.ellipsis-line(3);
}

View File

@@ -98,6 +98,72 @@
&:active {
box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05);
}
// 特殊工具按钮
.theme-toggle {
position: relative;
overflow: hidden;
width: 20px;
height: 20px;
margin: 7px 0;
border-radius: 50%;
&--real {
position: relative;
width: 100%;
height: 100%;
transition: @animation-duration-slow background-color;
border-radius: 50%;
background-color: fade(@white, 60%);
&::before {
position: absolute;
top: 5px;
left: 5px;
width: 10px;
height: 10px;
content: '';
transition: @animation-duration-slow transform;
transform: scale(1);
border: 2px solid @layout-header-background;
border-radius: 50%;
}
}
&--imaginary {
position: absolute;
top: 6px;
right: -6px;
width: 18px;
height: 18px;
transition: @animation-duration-slow transform;
transform: rotate(45deg) scaleY(0);
transform-origin: top right;
border-radius: 50%;
background-color: #334454;
}
}
&:hover {
.theme-toggle {
&--real {
background-color: @white;
&::before {
transform: scale(0);
}
}
&--imaginary {
transform: rotate(45deg) scaleY(1);
}
}
}
}
.ant-select-auto-complete {
margin: (@layout-header-height - 10px - 30px) / 2 @padding-md;

View File

@@ -9,6 +9,7 @@ import logManage from './logManage'
import machineManage from './machineManage'
import menuManage from './menuManage'
import noticeManage from './noticeManage'
import noticeReceiveManage from './noticeReceiveManage'
import onlineUserManage from './onlineUserManage'
import orgManage from './orgManage'
import posManage from './posManage'
@@ -31,6 +32,7 @@ const urls = {
...machineManage,
...menuManage,
...noticeManage,
...noticeReceiveManage,
...onlineUserManage,
...orgManage,
...posManage,

View File

@@ -28,20 +28,6 @@ const urls = {
* 修改状态
*/
sysNoticeChangeStatus: ['/sysNotice/changeStatus', 'post'],
/**
* 获取Notice总数
*/
sysNoticeGetCount: ['/NoticeUser/getCount', 'get'],
/**
* 获取Notice详细
*/
sysNoticeInfo: ['/NoticeUser/GetNoticeInfo', 'get'],
/**
* 获取Notice详细ByID
*/
sysNoticeShow: ['/sysNotice/detailById', 'get'],
}
export default urls

View File

@@ -1,8 +1,12 @@
const urls = {
/**
* 获取接收到的通知公告总数
*/
sysNoticeUnread: ['/sysNotice/unread', 'get'],
/**
* 查询我收到的系统通知公告
*/
sysNoticeReceived: ['/sysNotice/received', 'get'],
sysNoticeReceived: ['/sysNotice/received', 'post'],
}
export default urls

View File

@@ -7,7 +7,7 @@ import 'braft-editor/dist/index.css'
export default class index extends Component {
state = {
editorState: BraftEditor.createEditorState(this.props.value), // 设置编辑器初始内容
outputHTML: '<p></p>',
outputHTML: '',
}
/**
* mount后回调
@@ -43,7 +43,7 @@ export default class index extends Component {
const { editorState, outputHTML } = this.state
//localStorage.setItem('props', JSON.stringify(this.props))
const controls = ['bold', 'italic', 'underline', 'text-color', 'separator', 'media']
const controls = ['bold', 'italic', 'underline', 'text-color', 'separator']
return (
<BraftEditor
value={editorState}

View File

@@ -1,6 +1,7 @@
export { default as AntIcon } from './ant-icon'
export { default as AuthorityView } from './authority-view'
export { default as Auth } from './authorized'
export { default as BraftEditor } from './form/braft-editor'
export { default as ColorSelector } from './form/color-selector'
export { default as ComponentDynamic } from './component-dynamic'
export { default as Container } from './container'
@@ -13,4 +14,3 @@ export { default as QueryList } from './query-list'
export { default as QueryTable } from './query-table'
export { default as QueryTableActions } from './query-table-actions'
export { default as QueryTreeLayout } from './query-tree-layout'
export { default as BraftEditor } from './form/braft-editor'

View File

@@ -96,13 +96,13 @@ export default class form extends Component {
return (
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<div className="yo-form-group yo-form--fluid">
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题', trigger: 'blur' }]}
>
<Input autoComplete="off" placeholder="请输入标题" className="w-300" />
<Input autoComplete="off" placeholder="请输入标题" />
</Form.Item>
<Form.Item
label="类型"
@@ -120,7 +120,17 @@ export default class form extends Component {
<Form.Item
label="内容"
name="content"
// rules={[{ required: true, message: '请输入内容' }]}
rules={[
{ required: true, message: '请输入内容' },
{
validator: async (field, value) => {
const v = value.replace(/<\/?.+?\/?>/g, '')
if (!v) {
throw Error('请输入内容')
}
},
},
]}
>
<BraftEditor />
</Form.Item>

View File

@@ -28,7 +28,7 @@ const apiAction = {
* 用于弹窗标题
* [必要]
*/
const name = '啥玩意'
const name = '通知公告'
/**
* 统一配置权限标识
@@ -253,6 +253,7 @@ export default class index extends Component {
<Auth auth={{ [authName]: 'add' }}>
<ModalForm
title={`新增${name}`}
width={800}
action={apiAction.add}
ref={this.addForm}
onSuccess={() => this.table.current.onReloadData()}
@@ -264,6 +265,7 @@ export default class index extends Component {
<Auth auth={{ [authName]: 'edit' }}>
<ModalForm
title={`编辑${name}`}
width={800}
action={apiAction.edit}
ref={this.editForm}
onSuccess={() => this.table.current.onReloadData()}

View File

@@ -1,23 +1,19 @@
import React, { Component, useState } from 'react'
import { Layout, Badge, Popover, Menu, Modal } from 'antd'
import { Layout, Badge, Popover, Menu, Modal, Tooltip, Popconfirm } from 'antd'
import { AntIcon, Container } from 'components'
import Logo from '../logo'
import User from './user'
import Search from './search'
import store from 'store'
import { api } from 'common/api'
import Logo from '../logo'
import Search from './search'
import Notice from './notice'
import User from './user'
const { getState, subscribe, dispatch } = store
export default class index extends Component {
state = {
...getState('layout'),
notice: {
count: 0,
data: [],
},
modalVisible: false,
currentNotice: {},
}
constructor(props) {
@@ -28,10 +24,6 @@ export default class index extends Component {
})
}
componentDidMount() {
this.loadNotice()
}
componentWillUnmount() {
this.unsubscribe()
}
@@ -43,27 +35,8 @@ export default class index extends Component {
})
}
async loadNotice() {
const { data } = await api.sysNoticeGetCount()
const items = await api.sysNoticeInfo()
this.setState({
notice: {
count: data,
data: items.data,
},
})
}
async showDetail(params, visible) {
this.setState({ currentNotice: params })
if (visible) {
this.setState({ modalVisible: visible })
} else {
this.setState({ modalVisible: visible })
}
}
render() {
const { allowSiderCollapsed, notice, currentNotice } = this.state
const { allowSiderCollapsed, theme } = this.state
return (
<Layout.Header>
<Container mode="fluid">
@@ -80,57 +53,38 @@ export default class index extends Component {
<Search />
</div>
<div className="header-actions">
<span
className="header-action"
onClick={() => window.realodContentWindow()}
>
<AntIcon type="reload" />
</span>
<Popover
arrowPointAtCenter={true}
overlayClassName="yo-user-popover"
placement="bottomRight"
content={
<Menu selectable={false}>
<Menu.Divider />
{notice.data.map(item => (
<Menu.Item
onClick={() => this.showDetail(item, true)}
key={item.id}
>
{item.title}
</Menu.Item>
))}
</Menu>
}
>
<span className="header-action">
<Badge count={notice.count}>
<AntIcon type="bell" />
</Badge>
</span>
<Modal
title={currentNotice.title}
width={1000}
style={{ top: 120 }}
visible={this.state.modalVisible}
onOk={() => this.showDetail(false)}
onCancel={() => this.showDetail(false)}
style={{ zIndex: 1000 }}
<Tooltip placement="bottom" title="重新加载框架">
<span
className="header-action"
onClick={() => window.realodContentWindow()}
>
<div style={{ textAlign: 'center', fontSize: '30px' }}>
<div
dangerouslySetInnerHTML={{ __html: currentNotice.content }}
></div>
</div>
<div style={{ textAlign: 'right', fontSize: '10px' }}>
<span>发布人{currentNotice.createdUserName}</span>
{' '}
<span>发布时间{currentNotice.createdTime} </span>
</div>
</Modal>
</Popover>
<AntIcon type="reload" />
</span>
</Tooltip>
<Notice />
<Tooltip
placement="bottom"
title={`切换到${{ default: '夜间', dark: '默认' }[theme]}模式`}
>
<Popconfirm
placement="bottomRight"
title="切换模式将刷新整个页面,是否继续?"
onConfirm={() => {
dispatch({
type: 'SET_THEME',
theme: { default: 'dark', dark: 'default' }[theme],
})
window.location.reload()
}}
>
<span className="header-action">
<div className="theme-toggle">
<div className="theme-toggle--real" />
<div className="theme-toggle--imaginary" />
</div>
</span>
</Popconfirm>
</Tooltip>
<User />
</div>
</Container>

View File

@@ -0,0 +1,145 @@
import React, { Component } from 'react'
import { Badge, Divider, List, Menu, Modal, Popover, Row, Spin } from 'antd'
import { AntIcon, Image } from 'components'
import { api } from 'common/api'
import InfiniteScroll from 'react-infinite-scroller'
import moment from 'moment'
export default class notice extends Component {
state = {
count: 0,
list: [],
loading: false,
hasMore: true,
detailVisible: false,
detailLoading: false,
detailData: {},
}
async componentDidMount() {
const { data: count } = await api.sysNoticeUnread()
this.setState({ count })
}
finish() {
this.setState({ loading: false, hasMore: false })
}
async onInfiniteOnLoad(pageIndex) {
this.setState({ loading: true })
const { list } = this.state
if (list.length >= 30) {
return this.finish()
}
const {
data: { items },
} = await api.sysNoticeReceived({
pageIndex,
pageSize: 5,
sortField: 'createdTime',
sortOrder: 'descend',
})
if (!items.length) {
return this.finish()
}
this.setState({
list: [...list, ...items],
loading: false,
})
}
async onOpenDetail(id) {
this.setState({ detailLoading: true, detailVisible: true })
const { data } = await api.sysNoticeDetail({ id })
this.setState({
detailLoading: false,
detailData: data,
})
}
renderList() {
const { list, loading, hasMore } = this.state
return (
<InfiniteScroll
loadMore={pageIndex => this.onInfiniteOnLoad(pageIndex)}
hasMore={!loading && hasMore}
useWindow={false}
threshold={100}
>
<List
itemLayout="vertical"
dataSource={list}
renderItem={item => (
<List.Item key={item.id}>
<List.Item.Meta
avatar={<Image id={item.avatar} type="avatar" />}
title={
<a
className="ellipsis"
onClick={() => this.onOpenDetail(item.id)}
>
{item.title}
</a>
}
description={moment(item.createdTime || item.publicTime).fromNow()}
/>
<div className="ellipsis-3 text-gray">{item.content}</div>
</List.Item>
)}
>
{loading && hasMore && (
<div className="pt-md pb-md text-center">
<Spin indicator={<AntIcon type="loading" />} />
</div>
)}
</List>
</InfiniteScroll>
)
}
render() {
const { count, detailLoading, detailVisible, detailData } = this.state
return (
<Popover
arrowPointAtCenter={true}
placement="bottomRight"
content={this.renderList()}
overlayInnerStyle={{ width: 300, maxHeight: 300, overflowY: 'auto' }}
overlayStyle={{ zIndex: 999 }}
>
<span className="header-action">
<Badge count={count}>
<AntIcon type="bell" />
</Badge>
</span>
<Modal
width={1000}
footer={false}
visible={detailVisible}
onCancel={() => this.setState({ detailVisible: false, detailData: {} })}
>
<Spin spinning={detailLoading} indicator={<AntIcon type="loading" />}>
<div className="h3 mt-lg">{detailData.title}</div>
<Divider />
<div
className="pt-lg pb-lg"
dangerouslySetInnerHTML={{ __html: detailData.content }}
></div>
<Divider />
<Row justify="space-between" className="text-gray">
<span>发布人{detailData.publicUserName}</span>
<span>发布时间{detailData.publicTime} </span>
</Row>
</Spin>
</Modal>
</Popover>
)
}
}