diff --git a/Api/Ewide.Core/Entity/SysNotice.cs b/Api/Ewide.Core/Entity/SysNotice.cs index b7e6e80..17dcac4 100644 --- a/Api/Ewide.Core/Entity/SysNotice.cs +++ b/Api/Ewide.Core/Entity/SysNotice.cs @@ -72,5 +72,10 @@ namespace Ewide.Core /// [Comment("状态")] public int Status { get; set; } + /// + /// 上传文件ids + /// + [Comment("上传文件id集合")] + public string Attachments { set; get; } } } diff --git a/Api/Ewide.Core/Ewide.Core.xml b/Api/Ewide.Core/Ewide.Core.xml index d504a79..c89a9df 100644 --- a/Api/Ewide.Core/Ewide.Core.xml +++ b/Api/Ewide.Core/Ewide.Core.xml @@ -1223,6 +1223,11 @@ 状态(字典 0草稿 1发布 2撤回 3删除) + + + 上传文件ids + + 通知公告用户表 @@ -5332,6 +5337,11 @@ 通知公告参数 + + + ID + + 标题 @@ -5397,6 +5407,11 @@ 通知到的用户阅读信息集合 + + + 上传文件Id集合 + + 用户Id @@ -5472,6 +5487,11 @@ 通知到的人 + + + 上传文件集合 + + Id diff --git a/Api/Ewide.Core/Service/Notice/Dto/NoticeBase.cs b/Api/Ewide.Core/Service/Notice/Dto/NoticeBase.cs index 48e1985..cf79df0 100644 --- a/Api/Ewide.Core/Service/Notice/Dto/NoticeBase.cs +++ b/Api/Ewide.Core/Service/Notice/Dto/NoticeBase.cs @@ -7,6 +7,10 @@ namespace Ewide.Core.Service /// public class NoticeBase { + /// + /// ID + /// + public string Id { set; get; } /// /// 标题 /// diff --git a/Api/Ewide.Core/Service/Notice/Dto/NoticeDetailOutput.cs b/Api/Ewide.Core/Service/Notice/Dto/NoticeDetailOutput.cs index 032e9ac..c81ac94 100644 --- a/Api/Ewide.Core/Service/Notice/Dto/NoticeDetailOutput.cs +++ b/Api/Ewide.Core/Service/Notice/Dto/NoticeDetailOutput.cs @@ -17,6 +17,11 @@ namespace Ewide.Core.Service /// 通知到的用户阅读信息集合 /// public List NoticeUserReadInfoList { get; set; } + + /// + /// 上传文件Id集合 + /// + public string Attachments { set; get; } } public class NoticeUserRead diff --git a/Api/Ewide.Core/Service/Notice/Dto/NoticeInput.cs b/Api/Ewide.Core/Service/Notice/Dto/NoticeInput.cs index aae3c42..08e7752 100644 --- a/Api/Ewide.Core/Service/Notice/Dto/NoticeInput.cs +++ b/Api/Ewide.Core/Service/Notice/Dto/NoticeInput.cs @@ -65,6 +65,11 @@ namespace Ewide.Core.Service /// [Required(ErrorMessage = "通知到的人不能为空")] public override List NoticeUserIdList { get; set; } + + /// + /// 上传文件集合 + /// + public List Attachments { set; get; } } public class DeleteNoticeInput diff --git a/Api/Ewide.Core/Service/Notice/SysNoticeService.cs b/Api/Ewide.Core/Service/Notice/SysNoticeService.cs index 9825efd..dba5165 100644 --- a/Api/Ewide.Core/Service/Notice/SysNoticeService.cs +++ b/Api/Ewide.Core/Service/Notice/SysNoticeService.cs @@ -55,7 +55,7 @@ namespace Ewide.Core.Service.Notice { var searchValue = !string.IsNullOrEmpty(input.SearchValue?.Trim()); var notices = await _sysNoticeRep.DetachedEntities - .Where(searchValue, u => EF.Functions.Like(u.Title, $"%{input.SearchValue.Trim()}%") || + .Where(searchValue, u => EF.Functions.Like(u.Title, $"%{input.SearchValue.Trim()}%") || EF.Functions.Like(u.Content, $"%{input.SearchValue.Trim()}%")) .Where(input.Type > 0, u => u.Type == input.Type) .Where(u => u.Status != (int)NoticeStatus.DELETED) @@ -79,6 +79,8 @@ namespace Ewide.Core.Service.Notice var notice = input.Adapt(); var id = System.Guid.NewGuid().ToString().ToLower(); notice.Id = id; + if (input.Attachments!=null) + notice.Attachments = string.Join(",", input.Attachments); await UpdatePublicInfo(notice); // 如果是发布,则设置发布时间 if (input.Status == (int)NoticeStatus.PUBLIC) @@ -123,12 +125,14 @@ namespace Ewide.Core.Service.Notice throw Oops.Oh(ErrorCode.D7002); var notice = input.Adapt(); + if (input.Attachments != null) + notice.Attachments = string.Join(",", input.Attachments); if (input.Status == (int)NoticeStatus.PUBLIC) { notice.PublicTime = DateTime.Now; await UpdatePublicInfo(notice); } - await notice.UpdateAsync(); + await notice.UpdateAsync(true); // 通知到的人 var noticeUserIdList = input.NoticeUserIdList; @@ -212,11 +216,11 @@ namespace Ewide.Core.Service.Notice { var sql = @"SELECT SN.*, -SU.Avatar +SU.Avatar,SNU.ReadStatus FROM sys_notice SN LEFT JOIN sys_notice_user SNU ON SN.Id = SNU.NoticeId LEFT JOIN sys_user SU ON SN.PublicUserId = SU.Id -WHERE SNU.UserId = @UserId AND SN.Status <> @Status"; +WHERE SNU.UserId = @UserId AND SN.Status = @Status"; var data = await _dapperRepository.QueryPageDataDynamic( sql, @@ -224,11 +228,16 @@ WHERE SNU.UserId = @UserId AND SN.Status <> @Status"; new { _userManager.UserId, - Status = (int)NoticeStatus.DELETED + Status = (int)NoticeStatus.PUBLIC + }, + new[] + { + "Type","Title" } ); - data.Items = data.Items.Select(p => { + data.Items = data.Items.Select(p => + { var r = p.Adapt(); r.Content = Regex.Replace(r.Content, @"<\/?.+?\/?>", "").Replace("\r\n", ""); return r; @@ -245,7 +254,8 @@ WHERE SNU.UserId = @UserId AND SN.Status <> @Status"; [HttpGet("/sysNotice/unread")] public async Task GetUnreadCount() { - return await _sysNoticeUserRep.Where(u => u.UserId == _userManager.UserId && u.ReadStatus == (int)NoticeUserStatus.UNREAD).CountAsync(); + var noticeList = await _sysNoticeRep.Where(u => u.Status != (int)NoticeStatus.DELETED).Select(s => s.Id).ToListAsync(); + return await _sysNoticeUserRep.Where(u => u.UserId == _userManager.UserId && noticeList.Contains(u.NoticeId) && u.ReadStatus == (int)NoticeUserStatus.UNREAD).CountAsync(); } /// diff --git a/web-react/src/components/form/braft-editor/index.jsx b/web-react/src/components/form/braft-editor/index.jsx index 510c247..957dd3a 100644 --- a/web-react/src/components/form/braft-editor/index.jsx +++ b/web-react/src/components/form/braft-editor/index.jsx @@ -1,20 +1,19 @@ import React, { Component } from 'react' -import { Col, Input, InputNumber, Row } from 'antd' -import { AntIcon } from 'components' import BraftEditor from 'braft-editor' import 'braft-editor/dist/index.css' export default class index extends Component { state = { - editorState: BraftEditor.createEditorState(this.props.value), // 设置编辑器初始内容 + editorState: '', outputHTML: '', } + /** * mount后回调 */ componentDidMount() { - // 3秒后更改编辑器内容 - setTimeout(this.setEditorContentAsync, 2000) + this.isLivinig = true + this.toParent() } componentWillUnmount() { this.isLivinig = false @@ -33,23 +32,26 @@ export default class index extends Component { } setEditorContentAsync = () => { - const { placeholder, value, onChange } = this.props this.isLivinig && this.setState({ - editorState: BraftEditor.createEditorState(value), + editorState: BraftEditor.createEditorState(this.props.value), }) } - render() { - const { editorState, outputHTML } = this.state - //localStorage.setItem('props', JSON.stringify(this.props)) + //给父控件 调用这个方法 因为只有父控件才能掌握页面加载完的时间 + toParent = () => { + this.props.parent.getChildrenMsg(this.setEditorContentAsync) + } + render() { + const { editorState } = this.state const controls = ['bold', 'italic', 'underline', 'text-color', 'separator'] + return ( ) } diff --git a/web-react/src/pages/system/notice/form.jsx b/web-react/src/pages/system/notice/form.jsx index 190f4cd..b4ae8fc 100644 --- a/web-react/src/pages/system/notice/form.jsx +++ b/web-react/src/pages/system/notice/form.jsx @@ -1,11 +1,11 @@ import React, { Component } from 'react' -import { Form, Spin, Input, Radio, Select } from 'antd' +import { Form, Spin, Input, Radio, Select, Upload, Button } from 'antd' import { api } from 'common/api' import { AntIcon, BraftEditor } from 'components' import getDictData from 'util/dic' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' import { cloneDeep } from 'lodash' -// import BraftEditor from 'braft-editor' -// import 'braft-editor/dist/index.css' + const initialValues = {} export default class form extends Component { @@ -19,6 +19,7 @@ export default class form extends Component { noticeType: [], noticeStatus: [], }, + funloader: Object, } // 表单实例 @@ -32,8 +33,6 @@ export default class form extends Component { */ componentDidMount() { this.props.created && this.props.created(this) - this.isLivinig = true - // 3秒后更改编辑器内容 } /** @@ -43,6 +42,7 @@ export default class form extends Component { * @param {*} params */ async fillData(params) { + this.record = cloneDeep(params.record) //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 if (params.id) { this.record = (await api.sysNoticeDetail({ id: params.id })).data @@ -51,6 +51,36 @@ export default class form extends Component { data: { items: userList }, } = await this.onLoadUser() const codes = await getDictData('notice_status', 'notice_type') + + if (this.record) { + const { attachments } = this.record + 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.record.Attachments = fileValue + } + } //#endregion this.form.current.setFieldsValue(this.record) @@ -61,11 +91,46 @@ export default class form extends Component { }, codes, }) + //加载 BraftEditor 富文本插件 + this.state.funloader() + } + /** + * 接受子组件传过来的方法 + * 等页面加载完毕后调用 + */ + getChildrenMsg = funLoad => { + this.setState({ + funloader: funLoad, + }) } async onLoadUser() { const data = await api.getUserPage() return data } + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + 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() + } /** * 获取数据 * 可以对postData进行数据结构调整 @@ -74,14 +139,21 @@ export default class form extends Component { */ async getData() { const form = this.form.current - + console.log(this.record) 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 } @@ -132,9 +204,33 @@ export default class form extends Component { }, ]} > - + + + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={() => false} + onDownload={file => this.onFileDownload(file)} + > + + - - {codes.noticeType.map(item => ( {item.value} @@ -238,7 +305,7 @@ export default class index extends Component { } operator={ - + record.id} auth={{ [authName]: 'add' }}> + ), + }, + ]} ref={this.addForm} onSuccess={() => this.table.current.onReloadData()} > diff --git a/web-react/src/pages/system/noticeReceived/form.jsx b/web-react/src/pages/system/noticeReceived/form.jsx new file mode 100644 index 0000000..e4d73c9 --- /dev/null +++ b/web-react/src/pages/system/noticeReceived/form.jsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react' +import { Spin, Divider, Modal, Row } from 'antd' +import { api } from 'common/api' +import { AntIcon } from 'components' + +export default class form extends Component { + state = { + detailVisible: false, + detailLoading: false, + detailData: {}, + } + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + async onOpenDetail(id) { + this.setState({ detailLoading: true, detailVisible: true }) + const { data } = await api.sysNoticeDetail({ id }) + this.setState({ + detailLoading: false, + detailData: data, + }) + } + + render() { + const { detailLoading, detailVisible, detailData } = this.state + return ( + this.setState({ detailVisible: false, detailData: {} })} + > + }> +
{detailData.title}
+ +
+ + + 发布人:{detailData.publicUserName} + 发布时间:{detailData.publicTime} + +
+
+ ) + } +} diff --git a/web-react/src/pages/system/noticeReceived/index.jsx b/web-react/src/pages/system/noticeReceived/index.jsx new file mode 100644 index 0000000..3c5452f --- /dev/null +++ b/web-react/src/pages/system/noticeReceived/index.jsx @@ -0,0 +1,211 @@ +import React, { Component } from 'react' +import { Card, Form, message as Message, Input, Select } from 'antd' +import { Auth, Container, QueryTable } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' +import { getSearchInfo, QueryType } from 'util/query' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = '/**/' + +export default class index extends Component { + state = { + codes: { + noticeStatus: [], + noticeType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '标题', + dataIndex: 'title', + }, + { + title: '发布人', + dataIndex: 'publicUserName', + }, + { + title: '发布时间', + dataIndex: 'createdTime', + }, + { + title: '发布单位', + dataIndex: 'publicOrgName', + width: 200, + }, + { + title: '类型', + dataIndex: 'type', + render: text => this.bindCodeValue(text, 'notice_type'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + const flag = auth({ [authName]: [['show']] }) + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + this.onOpen(this.editForm, record.id)}>查看 + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('notice_status', 'notice_type').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + type: QueryType.Equal, + title: QueryType.Like, + }, + }) + + const { data } = await api.sysNoticeReceived({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.onOpenDetail(id) + } + + render() { + const { codes } = this.state + return ( + +
+ + + + + + + + +
+ } + /> + + + + ) + } +}