news.jsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. import React from 'react';
  2. import { Icon, Button, Spin, message, Table, Input, Modal, DatePicker, Upload, Select, Radio, Tabs } from 'antd';
  3. import { beforeUpload, getBase64 } from '../../../tools';
  4. import theme from 'react-quill/dist/quill.snow.css'
  5. import ReactQuill from 'react-quill'
  6. import moment from 'moment';
  7. import ajax from 'jquery/src/ajax/xhr.js';
  8. import $ from 'jquery/src/ajax';
  9. function uploadImg(s) {
  10. let myform = new FormData();
  11. myform.append('file', document.getElementById("fileToUpload").files[0]);
  12. myform.append('sign', 'news_content_picture');
  13. $.ajax({
  14. method: "post",
  15. dataType: "json",
  16. url: location.origin + "/api/admin/news/upload",
  17. data: myform,
  18. contentType: false,
  19. processData: false,
  20. success: function (data) {
  21. if (data.error && data.error.length) {
  22. message.warning(data.error[0].message);
  23. } else {
  24. let theUrl = data.data;
  25. const cursorPosition = s.quill.getSelection().index
  26. s.quill.insertEmbed(cursorPosition, 'image', globalConfig.avatarHost + '/upload' + theUrl)
  27. s.quill.setSelection(cursorPosition + 1)
  28. };
  29. }
  30. })
  31. }
  32. function fileSelect() {
  33. let uploader = document.getElementById("fileToUpload"), _me = this;
  34. uploader.onchange = () => {
  35. uploadImg(_me);
  36. };
  37. uploader.click();
  38. }
  39. const Avatar = React.createClass({
  40. getInitialState() {
  41. return {
  42. imageUrl: ''
  43. }
  44. },
  45. handleChange(info) {
  46. if (info.file.status === 'done') {
  47. // Get this url from response in real world.
  48. getBase64(info.file.originFileObj, imageUrl => this.setState({ imageUrl }));
  49. this.props.urlData(info.file.response.data);
  50. }
  51. },
  52. componentWillReceiveProps(nextProps) {
  53. if (this.props.visible && !nextProps.visible) {
  54. this.state.imageUrl = null;
  55. };
  56. },
  57. render() {
  58. const imageUrl = this.state.imageUrl;
  59. return (
  60. <Upload
  61. className="avatar-uploader"
  62. name={this.props.name}
  63. showUploadList={false}
  64. action={globalConfig.context + "/api/admin/news/upload"}
  65. data={{ 'sign': this.props.sign }}
  66. beforeUpload={beforeUpload}
  67. onChange={this.handleChange} >
  68. {
  69. (imageUrl || this.props.imageUrl) ?
  70. <img src={imageUrl || this.props.imageUrl} role="presentation" id={this.props.name} /> :
  71. <Icon type="plus" className="avatar-uploader-trigger" />
  72. }
  73. </Upload>
  74. );
  75. }
  76. });
  77. const News = React.createClass({
  78. loadData(pageNo, theType) {
  79. this.setState({
  80. loading: true
  81. });
  82. $.ajax({
  83. method: "get",
  84. dataType: "json",
  85. crossDomain: false,
  86. url: globalConfig.context + '/api/admin/news/list',
  87. data: {
  88. pageNo: pageNo || 1,
  89. pageSize: this.state.pagination.pageSize,
  90. type: theType || this.props.newsType || 0, //新闻类型
  91. title: this.state.searchTitle,
  92. author: this.state.searchAuthor,
  93. startCreateTime: this.state.startDate, //新闻开始时间 yyyy-MM-dd
  94. endCreateTime: this.state.endDate, //新闻结束时间 yyyy-MM-dd
  95. source: this.state.searchSource,
  96. hot: this.state.searchHot, //是否放在首页
  97. },
  98. success: function (data) {
  99. let theArr = [];
  100. if (!data.data || !data.data.list) {
  101. if (data.error && data.error.length) {
  102. message.warning(data.error[0].message);
  103. };
  104. } else {
  105. for (let i = 0; i < data.data.list.length; i++) {
  106. let thisdata = data.data.list[i];
  107. theArr.push({
  108. key: i,
  109. id: thisdata.id,
  110. createTime: thisdata.createTime,
  111. editTime: thisdata.editTime,
  112. title: thisdata.title,
  113. titleImg: thisdata.titleImg,
  114. author: thisdata.author,
  115. type: thisdata.type,
  116. hot: thisdata.hot,
  117. source: thisdata.source,
  118. sourceUrl: thisdata.sourceUrl,
  119. summary: thisdata.summary,
  120. content: thisdata.content,
  121. createTimeFormattedDate: thisdata.createTimeFormattedDate,
  122. });
  123. };
  124. this.state.pagination.current = data.data.pageNo;
  125. this.state.pagination.total = data.data.totalCount;
  126. };
  127. this.setState({
  128. tableData: theArr,
  129. pagination: this.state.pagination
  130. });
  131. }.bind(this),
  132. }).always(function () {
  133. this.setState({
  134. loading: false
  135. });
  136. }.bind(this));
  137. },
  138. loadDetailData() {
  139. this.setState({
  140. loading: true
  141. });
  142. $.ajax({
  143. method: "get",
  144. dataType: "json",
  145. crossDomain: false,
  146. url: globalConfig.context + '/api/admin/news/detail',
  147. data: {
  148. id: this.state.RowData.id //新闻主键ID(数字类型,非字符串)
  149. },
  150. success: function (data) {
  151. let theObj = {};
  152. if (!data.data) {
  153. if (data.error && data.error.length) {
  154. message.warning(data.error[0].message);
  155. };
  156. } else {
  157. theObj = data.data;
  158. };
  159. this.setState({
  160. RowData: theObj,
  161. });
  162. }.bind(this),
  163. }).always(function () {
  164. this.setState({
  165. loading: false
  166. });
  167. }.bind(this));
  168. },
  169. getInitialState() {
  170. return {
  171. loading: false,
  172. visible: false,
  173. selectedRowKeys: [],
  174. selectedRows: [],
  175. keysObj: {},
  176. RowData: {},
  177. pagination: {
  178. defaultCurrent: 1,
  179. defaultPageSize: 10,
  180. showQuickJumper: true,
  181. pageSize: 10,
  182. onChange: function (page) {
  183. this.loadData(page);
  184. }.bind(this),
  185. showTotal: function (total) {
  186. return '共' + total + '条数据';
  187. }
  188. },
  189. columns: [
  190. {
  191. title: '标题',
  192. dataIndex: 'title',
  193. key: 'title',
  194. width: '20%'
  195. }, {
  196. title: '发布时间',
  197. dataIndex: 'createTimeFormattedDate',
  198. key: 'createTimeFormattedDate',
  199. }, {
  200. title: '作者',
  201. dataIndex: 'author',
  202. key: 'author',
  203. }, {
  204. title: '来源',
  205. dataIndex: 'source',
  206. key: 'source',
  207. }, {
  208. title: '简介',
  209. dataIndex: 'summary',
  210. key: 'summary',
  211. width: '50%'
  212. }
  213. ],
  214. tableData: []
  215. };
  216. },
  217. tableRowClick(record, index) {
  218. this.state.RowData = record;
  219. this.loadDetailData();
  220. this.setState({
  221. visible: true
  222. });
  223. },
  224. componentWillMount() {
  225. this.loadData();
  226. },
  227. componentWillReceiveProps(nextProps) {
  228. if (this.props.newsType != nextProps.newsType) {
  229. this.loadData(1, nextProps.newsType);
  230. };
  231. },
  232. handleRichText(value) {
  233. console.log(value);
  234. this.state.RowData.content = value;
  235. this.setState({ RowData: this.state.RowData });
  236. },
  237. submit() {
  238. if (!this.state.RowData.title) {
  239. message.warning('必须填写一个标题!');
  240. return;
  241. };
  242. if (!this.state.RowData.createTime) {
  243. message.warning('必须选择一个发布时间!');
  244. return;
  245. };
  246. if (!this.state.RowData.author) {
  247. message.warning('必须填写一个作者!');
  248. return;
  249. };
  250. this.setState({
  251. loading: true
  252. });
  253. $.ajax({
  254. method: "post",
  255. dataType: "json",
  256. crossDomain: false,
  257. url: globalConfig.context + (this.state.RowData.id ? '/api/admin/news/update' : '/api/admin/news/add'),
  258. data: {
  259. id: this.state.RowData.id || null, //主键,如果不填主键则为新增记录,填主键则为更新
  260. title: this.state.RowData.title,
  261. titleImg: this.state.RowData.titleImg,
  262. author: this.state.RowData.author,
  263. type: this.props.newsType,
  264. hot: this.state.RowData.hot,
  265. source: this.state.RowData.source,
  266. sourceUrl: this.state.RowData.sourceUrl,
  267. summary: this.state.RowData.summary,
  268. content: this.state.RowData.content,
  269. createTimeFormattedDate: this.state.RowData.createTimeFormattedDate,
  270. },
  271. success: function (data) {
  272. if (data.error && data.error.length) {
  273. message.warning(data.error[0].message);
  274. } else {
  275. message.success('保存成功!');
  276. this.setState({ visible: false });
  277. this.loadData();
  278. };
  279. }.bind(this),
  280. }).always(function () {
  281. this.setState({
  282. loading: false
  283. });
  284. }.bind(this));
  285. },
  286. delectRow() {
  287. let deletedIds = [];
  288. for (let idx = 0; idx < this.state.selectedRows.length; idx++) {
  289. let rowItem = this.state.selectedRows[idx];
  290. if (rowItem.id) {
  291. deletedIds.push(rowItem.id)
  292. };
  293. };
  294. this.setState({
  295. selectedRowKeys: [],
  296. loading: deletedIds.length > 0
  297. });
  298. $.ajax({
  299. method: "POST",
  300. dataType: "json",
  301. crossDomain: false,
  302. url: globalConfig.context + "/api/admin/news/delete",
  303. data: {
  304. ids: deletedIds
  305. }
  306. }).done(function (data) {
  307. if (data.error && data.error.length) {
  308. message.warning(data.error[0].message);
  309. } else {
  310. message.success('删除成功!');
  311. };
  312. this.setState({
  313. loading: false,
  314. });
  315. this.loadData();
  316. }.bind(this));
  317. },
  318. reset() {
  319. this.state.searchKey = undefined;
  320. this.loadData();
  321. },
  322. render() {
  323. const rowSelection = {
  324. selectedRowKeys: this.state.selectedRowKeys,
  325. onChange: (selectedRowKeys, selectedRows) => {
  326. this.setState({
  327. selectedRows: selectedRows.slice(-1),
  328. selectedRowKeys: selectedRowKeys.slice(-1)
  329. });
  330. }
  331. };
  332. const hasSelected = this.state.selectedRowKeys.length > 0;
  333. const modules = {
  334. toolbar: {
  335. container: [
  336. [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
  337. [{ 'header': [1, 2, 3, false] }],
  338. ['bold', 'italic', 'underline', 'strike', 'blockquote'], // toggled buttons
  339. // [{ 'header': 1 }, { 'header': 2 }], // custom button values
  340. [{ 'list': 'ordered' }, { 'list': 'bullet' }],
  341. // [{ 'script': 'sub' }, { 'script': 'super' }], // superscript/subscript
  342. [{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
  343. // [{ 'direction': 'rtl' }], // text direction
  344. [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
  345. // [{ 'font': [] }],
  346. [{ 'align': [] }],
  347. ['link', 'image'],
  348. ['clean']
  349. ],
  350. handlers: { 'image': fileSelect }
  351. }
  352. };
  353. const formats = [
  354. 'size', 'header',
  355. 'bold', 'italic', 'underline', 'strike', 'blockquote',
  356. 'list', 'bullet', 'indent',
  357. 'color', 'background',
  358. 'align',
  359. 'link', 'image'
  360. ];
  361. return (
  362. <div className="admin-content" >
  363. <div className="admin-content-title">科技快讯管理</div>
  364. <div className="admin-content-warp">
  365. <div className="admin-content-header">科技快讯列表</div>
  366. <div className="admin-content-search">
  367. <Input placeholder="标题" style={{ width: 200 }}
  368. value={this.state.searchTitle}
  369. onChange={(e) => { this.setState({ searchTitle: e.target.value }); }} />
  370. <Input placeholder="作者" style={{ width: 100 }}
  371. value={this.state.searchAuthor}
  372. onChange={(e) => { this.setState({ searchAuthor: e.target.value }); }} />
  373. <Input placeholder="来源" style={{ width: 100 }}
  374. value={this.state.searchSource}
  375. onChange={(e) => { this.setState({ searchSource: e.target.value }); }} />
  376. <Select placeholder="显示在首页"
  377. style={{ width: 100 }}
  378. value={this.state.searchHot}
  379. onChange={(e) => { this.setState({ searchHot: e }) }}>
  380. <Select.Option value="0" >不显示</Select.Option>
  381. <Select.Option value="1" >显示</Select.Option>
  382. </Select>
  383. <span>发布时间</span>
  384. <DatePicker style={{ width: 110 }}
  385. format="YYYY-MM-DD"
  386. placeholder="选择开始时间"
  387. value={this.state.startDate ? moment(this.state.startDate, "YYYY-MM-DD") : undefined}
  388. onChange={(t, str) => {
  389. this.setState({ startDate: str });
  390. }} />
  391. <span>到</span>
  392. <DatePicker style={{ width: 110 }}
  393. format="YYYY-MM-DD"
  394. placeholder="选择结束时间"
  395. value={this.state.endDate ? moment(this.state.endDate, "YYYY-MM-DD") : undefined}
  396. onChange={(t, str) => {
  397. this.setState({ endDate: str });
  398. }} />
  399. <Button type="primary" onClick={() => { this.loadData() }}>搜索</Button>
  400. <Button onClick={this.reset}>重置</Button>
  401. <Button type='danger' disabled={!hasSelected} onClick={this.delectRow}>
  402. 删除<Icon type="minus" />
  403. </Button>
  404. <Button type="primary" style={{ float: 'right' }}
  405. onClick={() => { this.setState({ RowData: {}, visible: true }) }}>
  406. 添加<Icon type="plus" />
  407. </Button>
  408. </div>
  409. <Spin spinning={this.state.loading}>
  410. <Table className="no-all-select" columns={this.state.columns}
  411. dataSource={this.state.tableData}
  412. pagination={this.state.pagination}
  413. rowSelection={rowSelection}
  414. onRowClick={this.tableRowClick} />
  415. </Spin>
  416. </div>
  417. <Modal title="新闻详情" width={1000} className="news-modal"
  418. visible={this.state.visible}
  419. maskClosable={false}
  420. footer={null}
  421. afterClose={() => { this.state.RowData = {}; }}
  422. onCancel={() => { this.setState({ visible: false }) }} >
  423. <Spin spinning={this.state.loading}>
  424. <div className="clearfix">
  425. <div className="modal-box">
  426. <span className="modal-box-title">新闻标题</span>
  427. <div className="modal-box-detail">
  428. <Input value={this.state.RowData.title}
  429. onChange={(e) => {
  430. this.state.RowData.title = e.target.value;
  431. this.setState({ RowData: this.state.RowData });
  432. }} />
  433. </div>
  434. </div>
  435. <div className="modal-box">
  436. <span className="modal-box-title">简介</span>
  437. <div className="modal-box-detail">
  438. <Input value={this.state.RowData.summary}
  439. onChange={(e) => {
  440. this.state.RowData.summary = e.target.value;
  441. this.setState({ RowData: this.state.RowData });
  442. }} />
  443. </div>
  444. </div>
  445. <div className="modal-box">
  446. <span className="modal-box-title">来源</span>
  447. <div className="modal-box-detail">
  448. <Input value={this.state.RowData.source}
  449. onChange={(e) => {
  450. this.state.RowData.source = e.target.value;
  451. this.setState({ RowData: this.state.RowData });
  452. }} />
  453. </div>
  454. </div>
  455. <div className="modal-box">
  456. <span className="modal-box-title">来源链接</span>
  457. <div className="modal-box-detail">
  458. <Input value={this.state.RowData.sourceUrl}
  459. onChange={(e) => {
  460. this.state.RowData.sourceUrl = e.target.value;
  461. this.setState({ RowData: this.state.RowData });
  462. }} />
  463. </div>
  464. </div>
  465. <div className="modal-box" style={{ position: 'absolute', right: '0', width: '480px' }}>
  466. <span className="modal-box-title">标题图片</span>
  467. <div className="modal-box-detail clearfix" style={{ width: '360px' }}>
  468. <Avatar sign={'news'} visible={this.state.visible}
  469. imageUrl={this.state.RowData.titleImg ? (globalConfig.avatarHost + "/upload" + this.state.RowData.imgurl) : null}
  470. urlData={(url) => { this.state.RowData.titleImg = url }} />
  471. </div>
  472. </div>
  473. </div>
  474. <div className="clearfix">
  475. <div className="modal-box news-box">
  476. <span className="modal-box-title">作者</span>
  477. <div className="modal-box-detail">
  478. <Input value={this.state.RowData.author}
  479. onChange={(e) => {
  480. this.state.RowData.author = e.target.value;
  481. this.setState({ RowData: this.state.RowData });
  482. }} />
  483. </div>
  484. </div>
  485. <div className="modal-box news-box">
  486. <span className="modal-box-title">发布时间</span>
  487. <div className="modal-box-detail">
  488. <DatePicker style={{ width: '120px' }}
  489. format="YYYY-MM-DD"
  490. placeholder="选择发布时间"
  491. value={this.state.RowData.createTimeFormattedDate ? moment(this.state.RowData.createTimeFormattedDate, "YYYY-MM-DD") : undefined}
  492. onChange={(t, str) => {
  493. this.state.RowData.createTimeFormattedDate = str;
  494. this.setState({ RowData: this.state.RowData });
  495. }} />
  496. </div>
  497. </div>
  498. <div className="modal-box news-box">
  499. <span className="modal-box-title">发布在首页</span>
  500. <div className="modal-box-detail">
  501. <Radio.Group value={this.state.RowData.hot} onChange={(e) => {
  502. this.state.RowData.hot = e.target.value;
  503. this.setState({ RowData: this.state.RowData });
  504. }} >
  505. <Radio value={0}>否</Radio>
  506. <Radio value={1}>是</Radio>
  507. </Radio.Group>
  508. </div>
  509. </div>
  510. </div>
  511. <div className="modal-box">
  512. <span className="modal-box-title">新闻内容</span>
  513. </div>
  514. <ReactQuill theme="snow"
  515. value={this.state.RowData.content}
  516. modules={modules}
  517. formats={formats}
  518. onChange={this.handleRichText} />
  519. <input type="file"
  520. name="fileToUpload"
  521. id="fileToUpload"
  522. style={{ "display": "none" }} />
  523. <div className="modal-box">
  524. <Button type="primary" onClick={this.submit}>提交</Button>
  525. <Button type="ghost" onClick={() => { this.setState({ visible: false }) }}>取消</Button>
  526. </div>
  527. </Spin>
  528. </Modal>
  529. </div >
  530. );
  531. }
  532. });
  533. export default News;