update 通知公告强化

This commit is contained in:
2021-07-08 18:39:16 +08:00
parent 9c076b1aba
commit 3fdef68de8
10 changed files with 198 additions and 210 deletions

View File

@@ -76,16 +76,17 @@ namespace Ewide.Core.Service.Notice
if (input.Status != (int)NoticeStatus.DRAFT && input.Status != (int)NoticeStatus.PUBLIC) if (input.Status != (int)NoticeStatus.DRAFT && input.Status != (int)NoticeStatus.PUBLIC)
throw Oops.Oh(ErrorCode.D7000); throw Oops.Oh(ErrorCode.D7000);
var notice = input.Adapt<SysNotice>(); var config = new TypeAdapterConfig().ForType<AddNoticeInput, SysNotice>()
.Map(target => target.Attachments, src => String.Join(",", src.Attachments))
.Config;
var notice = input.Adapt<SysNotice>(config);
var id = System.Guid.NewGuid().ToString().ToLower(); var id = System.Guid.NewGuid().ToString().ToLower();
notice.Id = id; notice.Id = id;
if (input.Attachments!=null)
notice.Attachments = string.Join(",", input.Attachments);
await UpdatePublicInfo(notice); await UpdatePublicInfo(notice);
// 如果是发布,则设置发布时间 // 如果是发布,则设置发布时间
if (input.Status == (int)NoticeStatus.PUBLIC) if (input.Status == (int)NoticeStatus.PUBLIC)
notice.PublicTime = DateTime.Now; notice.PublicTime = DateTime.Now;
var newItem = await notice.InsertAsync(); await notice.InsertAsync();
// 通知到的人 // 通知到的人
var noticeUserIdList = input.NoticeUserIdList; var noticeUserIdList = input.NoticeUserIdList;

View File

@@ -98,6 +98,9 @@
"sysUser:updateInfo", "sysUser:updateInfo",
"sysUser:updatePwd", "sysUser:updatePwd",
"sysUser:updateAvatar", "sysUser:updateAvatar",
"sysUser:checkBindcode",
"sysUser:getPwdRule",
"sysUser:sendCode",
"sysNotice:received", "sysNotice:received",
"sysNotice:unread", "sysNotice:unread",
"sysNotice:detail", "sysNotice:detail",

View File

@@ -7,8 +7,9 @@ export { default as ComponentDynamic } from './component-dynamic'
export { default as Container } from './container' export { default as Container } from './container'
export { default as IconSelector } from './icon-selector' export { default as IconSelector } from './icon-selector'
export { default as Image } from './image' export { default as Image } from './image'
export { default as ModalForm } from './modal-form'
export { default as InputNumberRange } from './form/input-number-range' export { default as InputNumberRange } from './form/input-number-range'
export { default as ModalForm } from './modal-form'
export { default as NoticeDetail } from './notice-detail'
export { default as PhotoPreview } from './photo-preview' export { default as PhotoPreview } from './photo-preview'
export { default as QueryList } from './query-list' export { default as QueryList } from './query-list'
export { default as QueryTable } from './query-table' export { default as QueryTable } from './query-table'

View File

@@ -1,45 +1,34 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Spin, Divider, Modal, Row, Form, Upload } from 'antd' import { Col, Divider, Modal, Row, Spin, Upload } from 'antd'
import { api } from 'common/api'
import { AntIcon } from 'components' import { AntIcon } from 'components'
import { api } from 'common/api'
import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' import { BlobToBase64, GetFileName, PreviewFile } from 'util/file'
import store from 'store'
export default class form extends Component { const { dispatch } = store
export default class index extends Component {
state = { state = {
detailVisible: false, detailVisible: false,
detailLoading: false, detailLoading: false,
detailData: {}, detailData: {},
fileValue: false, fileList: [],
} }
filedu = React.createRef() onOpenDetail = async id => {
/**
* mount后回调
*/
componentDidMount() {
this.props.created && this.props.created(this)
}
async onOpenDetail(id) {
this.setState({ detailLoading: true, detailVisible: true }) this.setState({ detailLoading: true, detailVisible: true })
const { data } = await api.sysNoticeDetail({ id }) const { data } = await api.sysNoticeDetail({ id })
this.setState({ let fileList = []
detailLoading: false,
detailData: data,
fileValue: false,
})
if (data) { if (data) {
const { attachments } = data const { attachments } = data
if (attachments) { if (attachments) {
const fileValue = [] const fileIds = attachments.split(',')
const fileList = attachments.split(',') for (const fileId of fileIds) {
for (const fileId of fileList) {
try { try {
const file = await PreviewFile(fileId) const file = await PreviewFile(fileId)
const base64 = await BlobToBase64(file) const base64 = await BlobToBase64(file)
fileValue.push({ fileList.push({
uid: fileId, uid: fileId,
response: fileId, response: fileId,
name: file.name, name: file.name,
@@ -48,7 +37,7 @@ export default class form extends Component {
}) })
} catch { } catch {
const { data: file } = await api.sysFileInfoDetail({ id: fileId }) const { data: file } = await api.sysFileInfoDetail({ id: fileId })
fileValue.push({ fileList.push({
uid: fileId, uid: fileId,
response: '文件已丢失', response: '文件已丢失',
name: file.fileOriginName, name: file.fileOriginName,
@@ -56,11 +45,23 @@ export default class form extends Component {
}) })
} }
} }
this.setState({
fileValue,
})
} }
} }
dispatch({
type: 'READ_NOTICE',
id,
})
this.setState({
detailLoading: false,
detailData: data,
fileList,
})
}
onCloseDetail() {
this.setState({ detailVisible: false, detailData: {}, fileList: [] })
} }
async onFileDownload(file) { async onFileDownload(file) {
@@ -74,14 +75,16 @@ export default class form extends Component {
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
a.remove() a.remove()
} }
render() { render() {
const { detailLoading, detailVisible, detailData } = this.state const { detailLoading, detailVisible, detailData, fileList } = this.state
return ( return (
<Modal <Modal
width={1000} width={1000}
footer={false} footer={false}
visible={detailVisible} visible={detailVisible}
onCancel={() => this.setState({ detailVisible: false, detailData: {} })} onCancel={() => this.onCloseDetail()}
> >
<Spin spinning={detailLoading} indicator={<AntIcon type="loading" />}> <Spin spinning={detailLoading} indicator={<AntIcon type="loading" />}>
<div className="h3 mt-lg">{detailData.title}</div> <div className="h3 mt-lg">{detailData.title}</div>
@@ -91,21 +94,23 @@ export default class form extends Component {
dangerouslySetInnerHTML={{ __html: detailData.content }} dangerouslySetInnerHTML={{ __html: detailData.content }}
></div> ></div>
<Divider /> <Divider />
{this.state.fileValue && ( {!!fileList.length && (
<Row> <>
<span> <Row align="top">
查看附件 <span className="text-gray mt-sm mr-xs">附件</span>
<Upload <Col flex="1">
fileList={this.state.fileValue} <Upload
showUploadList={{ fileList={fileList}
showDownloadIcon: true, showUploadList={{
}} showDownloadIcon: true,
onDownload={file => this.onFileDownload(file)} }}
></Upload> onDownload={file => this.onFileDownload(file)}
</span> />
</Row> </Col>
</Row>
<Divider />
</>
)} )}
<Divider />
<Row justify="space-between" className="text-gray"> <Row justify="space-between" className="text-gray">
<span>发布人{detailData.publicUserName}</span> <span>发布人{detailData.publicUserName}</span>
<span>发布时间{detailData.publicTime} </span> <span>发布时间{detailData.publicTime} </span>

View File

@@ -78,7 +78,7 @@ export default class form extends Component {
}) })
} }
} }
this.record.Attachments = fileValue this.record.attachments = fileValue
} }
} }
//#endregion //#endregion
@@ -94,6 +94,34 @@ export default class form extends Component {
//加载 BraftEditor 富文本插件 //加载 BraftEditor 富文本插件
this.state.funloader() this.state.funloader()
} }
/**
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
*/
async getData() {
const form = this.form.current
const valid = await form.validateFields()
if (valid) {
const postData = form.getFieldsValue()
if (this.record) {
postData.id = this.record.id
postData.status = 0
} else {
postData.status = 1
}
//#region 从前段转换后端所需格式
postData.attachments = postData.attachments.map(item =>
item.uid.startsWith('rc-upload') ? item.response : item.uid
)
//#endregion
return postData
}
}
//#region 自定义方法
/** /**
* 接受子组件传过来的方法 * 接受子组件传过来的方法
* 等页面加载完毕后调用 * 等页面加载完毕后调用
@@ -131,34 +159,6 @@ export default class form extends Component {
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)
a.remove() a.remove()
} }
/**
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
*/
async getData() {
const form = this.form.current
const valid = await form.validateFields()
if (valid) {
const postData = form.getFieldsValue()
if (this.record) {
postData.id = this.record.id
postData.status = 0
} else {
postData.status = 1
}
//#region 从前段转换后端所需格式
const { Attachments } = postData
for (const key in Attachments) {
Attachments[key] = Attachments[key].response
}
//#endregion
return postData
}
}
//#region 自定义方法
//#endregion //#endregion
render() { render() {
@@ -207,7 +207,7 @@ export default class form extends Component {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="上传附件" label="上传附件"
name="Attachments" name="attachments"
valuePropName="fileList" valuePropName="fileList"
getValueFromEvent={e => { getValueFromEvent={e => {
if (Array.isArray(e)) { if (Array.isArray(e)) {

View File

@@ -20,8 +20,7 @@ const apiAction = {
add: api.sysNoticeAdd, add: api.sysNoticeAdd,
edit: api.sysNoticeEdit, edit: api.sysNoticeEdit,
delete: api.sysNoticeDelete, delete: api.sysNoticeDelete,
Detail: api.sysNoticeDetail, status: api.sysNoticeChangeStatus,
Status: api.sysNoticeChangeStatus,
} }
/** /**
@@ -34,7 +33,7 @@ const name = '通知公告'
* 统一配置权限标识 * 统一配置权限标识
* [必要] * [必要]
*/ */
const authName = '/**/' const authName = 'sysNotice'
export default class index extends Component { export default class index extends Component {
state = { state = {
@@ -99,7 +98,7 @@ export default class index extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
const flag = auth({ [authName]: [['edit'], ['goBack'], ['publish'], ['delete']] }) const flag = auth({ [authName]: [['edit'], ['changeStatus']] })
if (flag) { if (flag) {
this.columns.push({ this.columns.push({
@@ -111,7 +110,7 @@ export default class index extends Component {
{record.status === 1 ? ( {record.status === 1 ? (
<Auth <Auth
key={this.subUniqueKey(record.id, 1)} key={this.subUniqueKey(record.id, 1)}
auth={{ [authName]: 'goBack' }} auth={{ [authName]: 'changeStatus' }}
> >
<Popconfirm <Popconfirm
placement="topRight" placement="topRight"
@@ -133,7 +132,7 @@ export default class index extends Component {
</Auth>, </Auth>,
<Auth <Auth
key={this.subUniqueKey(record.id, 3)} key={this.subUniqueKey(record.id, 3)}
auth={{ [authName]: 'publish' }} auth={{ [authName]: 'changeStatus' }}
> >
<Popconfirm <Popconfirm
placement="topRight" placement="topRight"
@@ -145,7 +144,7 @@ export default class index extends Component {
</Auth>, </Auth>,
<Auth <Auth
key={this.subUniqueKey(record.id, 4)} key={this.subUniqueKey(record.id, 4)}
auth={{ [authName]: 'delete' }} auth={{ [authName]: 'changeStatus' }}
> >
<Popconfirm <Popconfirm
placement="topRight" placement="topRight"
@@ -263,21 +262,21 @@ export default class index extends Component {
* @param {*} id * @param {*} id
*/ */
onDelete(id) { onDelete(id) {
this.onAction(apiAction.Status({ id, status: 3 }), '删除成功') this.onAction(apiAction.status({ id, status: 3 }), '删除成功')
} }
/** /**
* 发布 * 发布
* @param {*} id * @param {*} id
*/ */
onPublish(id) { onPublish(id) {
this.onAction(apiAction.Status({ id, status: 1 }), '发布成功') this.onAction(apiAction.status({ id, status: 1 }), '发布成功')
} }
/** /**
* 撤回 * 撤回
* @param {*} id * @param {*} id
*/ */
onGoBack(id) { onGoBack(id) {
this.onAction(apiAction.Status({ id, status: 2 }), '撤回成功') this.onAction(apiAction.status({ id, status: 2 }), '撤回成功')
} // } //
render() { render() {

View File

@@ -1,19 +1,18 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Card, Form, message as Message, Input, Select } from 'antd' import { Card, Form, Input, Select } from 'antd'
import { Auth, Container, QueryTable } from 'components' import { Auth, Container, NoticeDetail, QueryTable } from 'components'
import { api } from 'common/api' import { api } from 'common/api'
import auth from 'components/authorized/handler' import auth from 'components/authorized/handler'
import { isEqual } from 'lodash' import { isEqual } from 'lodash'
import getDictData from 'util/dic' import getDictData from 'util/dic'
import { toCamelCase } from 'util/format' import { toCamelCase } from 'util/format'
import FormBody from './form'
import { getSearchInfo, QueryType } from 'util/query' import { getSearchInfo, QueryType } from 'util/query'
/** /**
* 统一配置权限标识 * 统一配置权限标识
* [必要] * [必要]
*/ */
const authName = '/**/' const authName = 'sysNotice'
export default class index extends Component { export default class index extends Component {
state = { state = {
@@ -27,8 +26,7 @@ export default class index extends Component {
// 表格实例 // 表格实例
table = React.createRef() table = React.createRef()
// 编辑窗口实例 detail = React.createRef()
editForm = React.createRef()
columns = [ columns = [
{ {
@@ -75,15 +73,15 @@ export default class index extends Component {
*/ */
constructor(props) { constructor(props) {
super(props) super(props)
const flag = auth({ [authName]: [['show']] }) const flag = auth({ [authName]: 'received' })
if (flag) { if (flag) {
this.columns.push({ this.columns.push({
title: '操作', title: '操作',
width: 150, width: 150,
dataIndex: 'actions', dataIndex: 'actions',
render: (text, record) => ( render: (text, record) => (
<Auth auth={{ [authName]: 'show' }}> <Auth auth={{ [authName]: 'received' }}>
<a onClick={() => this.onOpen(this.editForm, record.id)}>查看</a> <a onClick={() => this.detail.current.onOpenDetail(record.id)}>查看</a>
</Auth> </Auth>
), ),
}) })
@@ -179,7 +177,7 @@ export default class index extends Component {
loadData={this.loadData} loadData={this.loadData}
columns={this.columns} columns={this.columns}
query={ query={
<Auth auth={{ [authName]: 'page' }}> <Auth auth={{ [authName]: 'received' }}>
<Form.Item label="关键字" name="title"> <Form.Item label="关键字" name="title">
<Input <Input
autoComplete="off" autoComplete="off"
@@ -213,7 +211,7 @@ export default class index extends Component {
} }
/> />
</Card> </Card>
<FormBody ref={this.editForm} /> <NoticeDetail ref={this.detail} />
</Container> </Container>
) )
} }

View File

@@ -3,6 +3,7 @@ import user from './user'
import layout from './layout' import layout from './layout'
import nav from './nav' import nav from './nav'
import dictData from './dict-data' import dictData from './dict-data'
import notice from './notice'
import business from './business' import business from './business'
const combine = combineReducers({ const combine = combineReducers({
@@ -10,6 +11,7 @@ const combine = combineReducers({
layout, layout,
nav, nav,
dictData, dictData,
notice,
business business
}) })

View File

@@ -0,0 +1,40 @@
const defaultState = {
count: 0,
list: []
}
const layout = (state = defaultState, action) => {
switch (action.type) {
case 'SET_NOTICE_COUNT':
{
const _state = {
...state,
count: action.count
}
return _state
}
case 'SET_NOTICE_LIST':
{
const _state = {
...state,
list: action.list
}
return _state
}
case 'READ_NOTICE':
{
const notice = state.list.find(p => p.id === action.id)
if (notice && !notice.readStatus) {
notice.readStatus = 1
state.count -= 1
}
return {
...state
}
}
default:
return state
}
}
export default layout

View File

@@ -1,28 +1,41 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Badge, Button, Divider, List, Menu, Modal, Popover, Row, Spin, Upload, Tag } from 'antd' import { Badge, Button, List, Popover, Spin, Tag } from 'antd'
import { AntIcon, Image } from 'components' import { AntIcon, Image, NoticeDetail } from 'components'
import { api } from 'common/api' import { api } from 'common/api'
import InfiniteScroll from 'react-infinite-scroller' import InfiniteScroll from 'react-infinite-scroller'
import { BlobToBase64, GetFileName, PreviewFile } from 'util/file'
import moment from 'moment' import moment from 'moment'
import store from 'store'
const { getState, dispatch, subscribe } = store
export default class notice extends Component { export default class notice extends Component {
state = { state = {
count: 0, ...getState('notice'),
list: [],
loading: false, loading: false,
hasMore: true, hasMore: true,
}
detailVisible: false, detail = React.createRef()
detailLoading: false,
detailData: {}, constructor(props) {
fileValue: false, super(props)
this.unsubscribe = subscribe('notice', notice => {
this.setState({ ...notice })
})
} }
async componentDidMount() { async componentDidMount() {
const { data: count } = await api.sysNoticeUnread() const { data: count } = await api.sysNoticeUnread()
this.setState({ count }) dispatch({
type: 'SET_NOTICE_COUNT',
count,
})
}
componentWillUnmount() {
this.unsubscribe()
} }
finish() { finish() {
@@ -48,65 +61,16 @@ export default class notice extends Component {
return this.finish() return this.finish()
} }
this.setState({ dispatch({
type: 'SET_NOTICE_LIST',
list: [...list, ...items], list: [...list, ...items],
})
this.setState({
loading: false, loading: false,
}) })
} }
async onOpenDetail(id) {
this.setState({ detailLoading: true, detailVisible: true })
const { data } = await api.sysNoticeDetail({ id })
this.setState({
detailLoading: false,
detailData: data,
fileValue: false,
})
if (data) {
const { attachments } = data
if (attachments) {
const fileValue = []
const fileList = attachments.split(',')
for (const fileId of fileList) {
try {
const file = await PreviewFile(fileId)
const base64 = await BlobToBase64(file)
fileValue.push({
uid: fileId,
response: fileId,
name: file.name,
url: base64,
status: 'done',
})
} catch {
const { data: file } = await api.sysFileInfoDetail({ id: fileId })
fileValue.push({
uid: fileId,
response: '文件已丢失',
name: file.fileOriginName,
status: 'error',
})
}
}
this.setState({
fileValue,
})
}
}
}
async onFileDownload(file) {
const { data, headers } = await api.sysFileInfoDownload({ id: file.response })
const url = window.URL.createObjectURL(data)
const fileName = GetFileName(headers['content-disposition'])
const a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
window.URL.revokeObjectURL(url)
a.remove()
}
renderList() { renderList() {
const { list, loading, hasMore } = this.state const { list, loading, hasMore } = this.state
return ( return (
@@ -125,20 +89,24 @@ export default class notice extends Component {
avatar={<Image id={item.avatar} type="avatar" />} avatar={<Image id={item.avatar} type="avatar" />}
title={ title={
<> <>
<a onClick={() => this.onOpenDetail(item.id)}> {item.readStatus ? (
<Tag color="#2db7f5">已读</Tag>
) : (
<Tag color="#f50">未读</Tag>
)}
<a
onClick={() =>
this.detail.current.onOpenDetail(item.id)
}
>
{item.title} {item.title}
</a> </a>
<small className="text-normal ml-xs">
{moment(item.createdTime || item.publicTime).fromNow()}
</small>
</> </>
} }
description={ description={
item.readStatus == 0 ? ( <span className="text-normal ml-xs">
<Tag color="#f50">未读</Tag> {moment(item.createdTime || item.publicTime).fromNow()}
) : ( </span>
<Tag color="#2db7f5">已读</Tag>
)
} }
/> />
<div className="ellipsis-3 text-gray">{item.content}</div> <div className="ellipsis-3 text-gray">{item.content}</div>
@@ -152,7 +120,13 @@ export default class notice extends Component {
)} )}
</List> </List>
{!hasMore && ( {!hasMore && (
<Button type="text" block> <Button
type="text"
block
onClick={() =>
window.openContentWindowByMenuName('sys_notice_mgr_received')
}
>
查看全部 查看全部
</Button> </Button>
)} )}
@@ -161,7 +135,7 @@ export default class notice extends Component {
} }
render() { render() {
const { count, detailLoading, detailVisible, detailData } = this.state const { count } = this.state
return ( return (
<Popover <Popover
@@ -177,42 +151,7 @@ export default class notice extends Component {
<AntIcon type="message" /> <AntIcon type="message" />
</Badge> </Badge>
</span> </span>
<NoticeDetail ref={this.detail} />
<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 />
{this.state.fileValue && (
<Row className="text-gray">
<span>
查看附件
<Upload
fileList={this.state.fileValue}
showUploadList={{
showDownloadIcon: true,
}}
onDownload={file => this.onFileDownload(file)}
></Upload>
</span>
</Row>
)}
<Divider />
<Row justify="space-between" className="text-gray">
<span>发布人{detailData.publicUserName}</span>
<span>发布时间{detailData.publicTime} </span>
</Row>
</Spin>
</Modal>
</Popover> </Popover>
) )
} }