张延森 il y a 4 ans
Parent
révision
3cb82ea214

+ 11
- 0
config/routes.js Voir le fichier

@@ -224,6 +224,17 @@ export default [
224 224
                 component: './property/lifeConsultant/Score',
225 225
                 hideInMenu: true,
226 226
               },
227
+              {
228
+                path: 'message-board',
229
+                name: '在聆听',
230
+                component: './property/messageBoard',
231
+              },
232
+              {
233
+                path: 'message-board/edit',
234
+                name: '留言历史',
235
+                component: './property/messageBoard/Edit',
236
+                hideInMenu: true,
237
+              },
227 238
               {
228 239
                 path: 'message-event',
229 240
                 name: '消息推送',

+ 21
- 17
src/layouts/BlankLayout.jsx Voir le fichier

@@ -1,21 +1,25 @@
1
-import React from 'react';
1
+import React, { useEffect, useRef, useState } from 'react';
2 2
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
3 3
 
4
-const Layout = ({ children }) => (
5
-  <PageHeaderWrapper style={{height:'auto',background:'rgba(240,240,240,1)',paddingTop:'15px'}}>
6
-    <div
7
-      style={{
8
-        backgroundColor: '#fff',
9
-        padding: '32PX 28px',
10
-        border: '1px solid rgba(0, 0, 0, 0.12)',
11
-        boxShadow: '0 1px 4px rgba(0, 21, 41, 0.08)',
12
-        borderRadius: '2px',
13
-        minHeight:'60vh'
14
-      }}
15
-    >
16
-      {children}
17
-    </div>
18
-  </PageHeaderWrapper>
19
-);
4
+const Layout = ({ children }) => {
5
+  const [pageStyle, setPageStyle] = useState({
6
+    backgroundColor: '#fff',
7
+    padding: '32PX 28px',
8
+    border: '1px solid rgba(0, 0, 0, 0.12)',
9
+    boxShadow: '0 1px 4px rgba(0, 21, 41, 0.08)',
10
+    borderRadius: '2px',
11
+    minHeight:'60vh'
12
+  })
13
+
14
+  const domRef = useRef()
15
+
16
+  return (
17
+    <PageHeaderWrapper style={{height:'auto',background:'rgba(240,240,240,1)',paddingTop:'15px'}}>
18
+      <div style={pageStyle} ref={domRef}>
19
+        {children}
20
+      </div>
21
+    </PageHeaderWrapper>
22
+  )
23
+};
20 24
 
21 25
 export default Layout;

+ 101
- 0
src/pages/property/messageBoard/Edit.jsx Voir le fichier

@@ -0,0 +1,101 @@
1
+import { now } from 'lodash'
2
+import React, { useEffect, useState, useRef } from 'react'
3
+import { Row, Col, Button, Input } from 'antd'
4
+import Item from './components/Item'
5
+import { fetch, apis } from '@/utils/request'
6
+
7
+const getReplyList = fetch(apis.messageBoard.detail)
8
+const messageReply = fetch(apis.messageBoard.save)
9
+
10
+export default props => {
11
+  const [pageStyle, setPageStyle] = useState({})
12
+  const [loading, setLoading] = useState(false)
13
+  const [submitting, setSubmitting] = useState(false)
14
+  const [content, setContent] = useState()
15
+  const [listData, setListData] = useState([])
16
+  const [queryParams, setQueryParams] = useState({ pageNum: 1, pageSize: 9999 })
17
+  const pgDom = useRef()
18
+  const imDom = useRef()
19
+
20
+  const msgId = props.location.query.id
21
+
22
+  const scrollToBottom = () => {
23
+    const t = setTimeout(() => {
24
+      imDom.current.scrollTop = imDom.current.scrollHeight
25
+      clearTimeout(t)
26
+    }, 200)
27
+  }
28
+
29
+  const sendMessage = () => {
30
+    setSubmitting(true)
31
+    messageReply({data: {
32
+      msgId,
33
+      content
34
+    }}).then(e => {
35
+      setListData(listData.concat({
36
+        msgId,
37
+        replyId: Math.random().toString(36).substring(2),
38
+        userId: 999,
39
+        content
40
+      }))
41
+      setContent()
42
+      setSubmitting(false)
43
+      scrollToBottom()
44
+    }).then(e => console.error(e) || setSubmitting(false))
45
+  }
46
+
47
+  useEffect(() => {
48
+    setLoading(true)
49
+    getReplyList({params: queryParams, urlData: {id: msgId}}).then(res => {
50
+      const {records, ...pageInfo} = res || {}
51
+      setListData((records || []).reverse())
52
+      setLoading(false)
53
+      scrollToBottom()
54
+    }).catch(() => setLoading(false))
55
+  }, [msgId])
56
+
57
+  useEffect(() => {
58
+    const parent = pgDom.current.offsetParent
59
+    const pageHeight = document.body.offsetHeight
60
+    const domY = pgDom.current.offsetTop
61
+    const parentY = parent.offsetTop
62
+    const height = pageHeight - domY - (domY -  parentY) * 2
63
+    setPageStyle({
64
+      display: 'flex',
65
+      flexDirection: 'column',
66
+      height,
67
+    })
68
+  }, [])
69
+
70
+  return (
71
+    <div ref={pgDom} style={pageStyle}>
72
+      <div style={{flex: 'auto', overflowY: 'auto'}} ref={imDom}>
73
+      {
74
+        listData.map(item => {
75
+          const key = `${item.msgId}-${item.replyId}`
76
+          const right = !!item.userId
77
+          const name = item.userId ? '' : item.nickname
78
+          const avatar = item.userId ? 'https://zhiyun-image.oss-accelerate.aliyuncs.com/xiangsong/logo.png' : item.avatar
79
+
80
+          return (
81
+            <Item
82
+              key={key}
83
+              right={right}
84
+              name={name}
85
+              date={item.createDate}
86
+              avatar={avatar}
87
+              content={item.content}
88
+            />
89
+          )
90
+        })
91
+      }
92
+      </div>
93
+      <div style={{flex: 'none', marginTop: 24}}>
94
+        <div style={{display: 'flex'}}>
95
+          <div style={{flex: 'auto'}}><Input.TextArea value={content} onChange={e => setContent(e.target.value)} /></div>
96
+          <div style={{flex: 'none', marginLeft: 24}}><Button type="primary" loading={submitting} onClick={sendMessage}>提交</Button></div>
97
+        </div>
98
+      </div>
99
+    </div>
100
+  )
101
+}

+ 32
- 0
src/pages/property/messageBoard/components/Item.jsx Voir le fichier

@@ -0,0 +1,32 @@
1
+import React, { useEffect, useState } from 'react'
2
+import moment from 'moment'
3
+import classNames from 'classnames'
4
+import Styles from './style.less'
5
+
6
+export default props => {
7
+  
8
+  const title = `${props.name} ${moment(props.date).format('YYYY-MM-DD HH:mm')}`
9
+
10
+  return (
11
+    <div className={classNames(Styles['message-item'], {[Styles.right]: props.right})}>
12
+      <div className={classNames(Styles['flex-row'], {[Styles.right]: props.right})}>
13
+        <div className={classNames(Styles.header, Styles.gutter)}></div>
14
+        <div className={classNames(Styles.gutter)}>
15
+          {props.right && <span className={Styles.title}>{moment(props.date).format('YYYY-MM-DD HH:mm')}</span>}
16
+          <span className={Styles.title}>{props.name}</span>
17
+          {!props.right && <span className={Styles.title}>{moment(props.date).format('YYYY-MM-DD HH:mm')}</span>}
18
+        </div>
19
+      </div>
20
+      <div className={classNames(Styles['flex-row'], {[Styles.right]: props.right})}>
21
+        <div className={classNames(Styles.header, Styles.gutter)}>
22
+          <img src={props.avatar} className={classNames(Styles.header, Styles.avatar)} alt=""/>
23
+        </div>
24
+        <div className={classNames(Styles.gutter)}>
25
+          <div className={classNames(Styles.poptip, {[Styles.right]: props.right})}>
26
+            {props.content}
27
+          </div>
28
+        </div>
29
+      </div>
30
+    </div>
31
+  )
32
+}

+ 114
- 0
src/pages/property/messageBoard/components/style.less Voir le fichier

@@ -0,0 +1,114 @@
1
+
2
+.message-item {
3
+  position: relative;
4
+  max-width: 50%;
5
+
6
+  & + .message-item {
7
+    margin-top: 24px;
8
+  }
9
+
10
+  &.right {
11
+    margin-left: 50%;
12
+  }
13
+
14
+  .gutter {
15
+    margin: 0 8px;
16
+  }
17
+
18
+  .flex-row {
19
+    display: flex;
20
+    margin: 8px 0;
21
+
22
+    &.right {
23
+      flex-direction: row-reverse;
24
+    }
25
+
26
+    & > div:first-child {
27
+      flex: none;
28
+    }
29
+  }
30
+
31
+  .header {
32
+    min-width: 48px;
33
+    width: 48px;
34
+  }
35
+
36
+  .avatar {
37
+    min-width: 48px;
38
+    width: 48px;
39
+    height: 48px;
40
+    border: 1px solid rgba(0, 0, 0, .1);
41
+  }
42
+
43
+  .title {
44
+    display: inline-block;
45
+    font-size: 0.85em;
46
+    color: #666;
47
+
48
+    & + .title {
49
+      margin-left: 8px;
50
+    }
51
+  }
52
+
53
+  .poptip {
54
+    border: 1px solid #ff7063;
55
+    border-radius: 4px;
56
+    text-align: left;
57
+    padding: 8px;
58
+    position: relative;
59
+
60
+    &::before {
61
+      content: '';
62
+      position: absolute;
63
+      left: -16px;
64
+      right: auto;
65
+      top: 12px;
66
+      border-right: 8px solid #fff;
67
+      border-top: 8px solid transparent;
68
+      border-bottom: 8px solid transparent;
69
+      border-left: 8px solid transparent;
70
+      z-index: 12;
71
+    }
72
+
73
+    &::after {
74
+      content: '';
75
+      position: absolute;
76
+      left: -18px;
77
+      right: auto;
78
+      top: 11px;
79
+      border-right: 9px solid #ff7063;
80
+      border-top: 9px solid transparent;
81
+      border-bottom: 9px solid transparent;
82
+      border-left: 9px solid transparent;
83
+      z-index: 10;
84
+    }
85
+
86
+    &.right {
87
+      &::before {
88
+        content: '';
89
+        position: absolute;
90
+        left: auto;
91
+        right: -16px;
92
+        top: 12px;
93
+        border-left: 8px solid #fff;
94
+        border-top: 8px solid transparent;
95
+        border-bottom: 8px solid transparent;
96
+        border-right: 8px solid transparent;
97
+        z-index: 12;
98
+      }
99
+
100
+      &::after {
101
+        content: '';
102
+        position: absolute;
103
+        left: auto;
104
+        right: -18px;
105
+        top: 11px;
106
+        border-left: 9px solid #ff7063;
107
+        border-top: 9px solid transparent;
108
+        border-bottom: 9px solid transparent;
109
+        border-right: 9px solid transparent;
110
+        z-index: 10;
111
+      }
112
+    }
113
+  }
114
+}

+ 119
- 0
src/pages/property/messageBoard/index.jsx Voir le fichier

@@ -0,0 +1,119 @@
1
+import React, { useEffect, useState } from 'react'
2
+import { Table, Typography, Button, Form, Input, DatePicker } from 'antd'
3
+import { fetchList, apis } from '@/utils/request'
4
+import moment from 'moment'
5
+import NavLink from 'umi/navlink'
6
+import Search from '../components/Search'
7
+import List from '../components/List'
8
+
9
+const getMessageList = fetchList(apis.messageBoard.list)
10
+
11
+const Condition = props => {
12
+  const [dateRange, setDateRange] = useState([])
13
+
14
+  const handleSearch = vals => {
15
+    props.onSearch({
16
+      ...vals,
17
+      startDate: dateRange[0] ? dateRange[0].format('YYYY-MM-DD') : undefined,
18
+      endDate: dateRange[1] ? dateRange[1].format('YYYY-MM-DD') : undefined,
19
+    })
20
+  }
21
+
22
+  const handelReset = vals => {
23
+    setDateRange([])
24
+    props.onReset({
25
+      ...vals,
26
+      startDate: undefined,
27
+      endDate: undefined,
28
+    })
29
+  }
30
+
31
+  return (
32
+    <Search
33
+      onSearch={handleSearch}
34
+      onReset={handelReset}
35
+      render={form => {
36
+        const { getFieldDecorator } = form
37
+
38
+        return (
39
+          <>
40
+            <Form.Item label="名称">
41
+            {
42
+              getFieldDecorator('name')(<Input placeholder="名称" />)
43
+            }
44
+            </Form.Item>
45
+            <Form.Item label="手机">
46
+            {
47
+              getFieldDecorator('phone')(<Input placeholder="手机" />)
48
+            }
49
+            </Form.Item>
50
+            <Form.Item label="留言时间">
51
+              <DatePicker.RangePicker value={dateRange} onChange={v => setDateRange(v)} placeholder={['开始时间', '结束时间']} />
52
+            </Form.Item>
53
+          </>
54
+        )
55
+      }}
56
+    />
57
+  )
58
+}
59
+
60
+export default props => {
61
+  const [loading, setLoading] = useState(false)
62
+  const [listData, setListData] = useState([])
63
+  const [pagination, setPagination] = useState({})
64
+  const [queryParams, setQueryParams] = useState({ pageNum: 1, pageSize: 10 })
65
+
66
+  const handleSearch = vals => {
67
+    setQueryParams({
68
+      ...queryParams,
69
+      ...vals,
70
+      pageNum: 1,
71
+    })
72
+  }
73
+  
74
+  const handlePageChange = (pageNum, pageSize) => {
75
+    setQueryParams({
76
+      ...queryParams,      
77
+      pageNum,
78
+      pageSize,
79
+    })
80
+  }
81
+
82
+  useEffect(() => {
83
+    setLoading(true)
84
+    getMessageList(queryParams).then(res => {
85
+      const [list, pageInfo] = res || {}
86
+      setListData(list)
87
+      setPagination(pageInfo)
88
+      setLoading(false)
89
+    }).catch(() => setLoading(false))
90
+  }, [queryParams])
91
+
92
+
93
+  return (
94
+    <div>
95
+      <Condition onSearch={handleSearch} onReset={handleSearch} />
96
+      <div style={{margin: '24px auto 24px'}}> </div>
97
+      <List dataSource={listData} loading={loading} pagination={pagination} onPageChange={handlePageChange} rowKey={row => `${row.msgId}-${row.replyId}`}>
98
+        <Table.Column title="头像" dataIndex="avatar" key="avatar" render={t => <img src={t} style={{ width: 64, height: 64 }} />} />
99
+        <Table.Column title="昵称" dataIndex="nickname" key="nickname" />
100
+        <Table.Column title="手机" dataIndex="phone" key="phone" />
101
+        <Table.Column title="留言时间" dataIndex="createDate" key="createDate" render={t => t ? moment(t).format('YYYY-MM-DD HH:mm') : ''} />
102
+        <Table.Column title="内容" dataIndex="content" key="content" render={t => <Typography.Text ellipsis>{t}</Typography.Text>} />
103
+        <Table.Column
104
+          title="操作"
105
+          key="action"
106
+          render={(_, row) => {
107
+            return (
108
+              <>
109
+                <NavLink to={`/property/message-board/edit?id=${row.msgId}`}>
110
+                  <Button type="link">详情</Button>
111
+                </NavLink>
112
+              </>
113
+            )
114
+          }}
115
+        />
116
+      </List>
117
+    </div>
118
+  )
119
+}

+ 17
- 0
src/services/apis.js Voir le fichier

@@ -69,6 +69,23 @@ export default {
69 69
       action: 'admin.person.export',
70 70
     }
71 71
   },
72
+  messageBoard: {
73
+    list: {
74
+      url: `${prefix}/message-board`,
75
+      method: 'get',
76
+      action: 'admin.messageBoard.list',
77
+    },
78
+    detail: {
79
+      url: `${prefix}/message-board/:id`,
80
+      method: 'get',
81
+      action: 'admin.messageBoard.detail',
82
+    },
83
+    save: {
84
+      url: `${prefix}/message-board`,
85
+      method: 'post',
86
+      action: 'admin.messageBoard.save',
87
+    }
88
+  },
72 89
   image: {
73 90
     uploadForAnt: {
74 91
       url: `${prefix}/antd/image`,