fuxingfan 4 lat temu
rodzic
commit
a6a377c5e3

+ 70
- 0
estateagents-admin-manager/src/components/Authorized/Secured.jsx Wyświetl plik

@@ -0,0 +1,70 @@
1
+import React from 'react';
2
+import CheckPermissions from './CheckPermissions';
3
+/**
4
+ * 默认不能访问任何页面
5
+ * default is "NULL"
6
+ */
7
+
8
+const Exception403 = () => 403;
9
+
10
+export const isComponentClass = component => {
11
+  if (!component) return false;
12
+  const proto = Object.getPrototypeOf(component);
13
+  if (proto === React.Component || proto === Function.prototype) return true;
14
+  return isComponentClass(proto);
15
+}; // Determine whether the incoming component has been instantiated
16
+// AuthorizedRoute is already instantiated
17
+// Authorized  render is already instantiated, children is no instantiated
18
+// Secured is not instantiated
19
+
20
+const checkIsInstantiation = target => {
21
+  if (isComponentClass(target)) {
22
+    const Target = target;
23
+    return props => <Target {...props} />;
24
+  }
25
+
26
+  if (React.isValidElement(target)) {
27
+    return props => React.cloneElement(target, props);
28
+  }
29
+
30
+  return () => target;
31
+};
32
+/**
33
+ * 用于判断是否拥有权限访问此 view 权限
34
+ * authority 支持传入 string, () => boolean | Promise
35
+ * e.g. 'user' 只有 user 用户能访问
36
+ * e.g. 'user,admin' user 和 admin 都能访问
37
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
38
+ * e.g. Promise  then 能访问   catch不能访问
39
+ * e.g. authority support incoming string, () => boolean | Promise
40
+ * e.g. 'user' only user user can access
41
+ * e.g. 'user, admin' user and admin can access
42
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
43
+ * e.g. Promise then can not access the visit to catch
44
+ * @param {string | function | Promise} authority
45
+ * @param {ReactNode} error 非必需参数
46
+ */
47
+
48
+const authorize = (authority, error) => {
49
+  /**
50
+   * conversion into a class
51
+   * 防止传入字符串时找不到staticContext造成报错
52
+   * String parameters can cause staticContext not found error
53
+   */
54
+  let classError = false;
55
+
56
+  if (error) {
57
+    classError = () => error;
58
+  }
59
+
60
+  if (!authority) {
61
+    throw new Error('authority is required');
62
+  }
63
+
64
+  return function decideAuthority(target) {
65
+    const component = CheckPermissions(authority, target, classError || Exception403);
66
+    return checkIsInstantiation(component);
67
+  };
68
+};
69
+
70
+export default authorize;

+ 134
- 0
estateagents-admin-manager/src/components/Cards/PosterCard.jsx Wyświetl plik

@@ -0,0 +1,134 @@
1
+import React, { useState, useEffect } from 'react';
2
+import { Card, Checkbox, Button, Upload } from 'antd';
3
+import apis from '../../services/apis';
4
+import request from '../../utils/request'
5
+import { uploaderProps } from '../../utils/upload';
6
+import head1 from '../../assets/head1.png';
7
+import head2 from '../../assets/head2.png';
8
+import head3 from '../../assets/head3.png';
9
+import link from '../../assets/link.png';
10
+
11
+const { Meta } = Card;
12
+
13
+/**
14
+ *
15
+ *
16
+ * @param {*} props
17
+ * @returns
18
+ */
19
+const PosterCard = (props) => {
20
+  const [data, setData] = useState([])
21
+  const [indexOneUrl, setIndexOneUrl] = useState('')
22
+  const [indexTwoUrl, setIndexTwoUrl] = useState('')
23
+  const [indexThreeUrl, setIndexThreeUrl] = useState('')
24
+  let resultData = []
25
+
26
+  useEffect(() => {
27
+  }, [])
28
+
29
+  const handleUploadIndexOneSucess = url => {
30
+    console.log(url, "第一张图上传回调");
31
+    setIndexOneUrl(url)
32
+    resultData[0] = url
33
+    resultData[1] = indexTwoUrl
34
+    resultData[2] = indexThreeUrl
35
+    props.onChange(resultData)
36
+  }
37
+
38
+  const handleUploadIndexTwoSucess = url => {
39
+    console.log(url, "第二张图上传回调");
40
+    setIndexTwoUrl(url)
41
+    resultData[0] = indexOneUrl
42
+    resultData[1] = url
43
+    resultData[2] = indexThreeUrl
44
+    props.onChange(resultData)
45
+  }
46
+
47
+  const handleUploadIndexThreeSucess = url => {
48
+    console.log(url, "第三张图上传回调");
49
+    setIndexThreeUrl(url)
50
+    resultData[0] = indexOneUrl
51
+    resultData[1] = indexTwoUrl
52
+    resultData[2] = url
53
+    props.onChange(resultData)
54
+  }
55
+
56
+  return (
57
+
58
+    <div style={{ display: 'flex' }}>
59
+
60
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
61
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
62
+          <img style={{ width: '100%', height: '100%' }} src={indexOneUrl} alt="" />
63
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '38px' }} alt="" />
64
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '79px', color: '#333' }}>小五子</p>
65
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '58px', color: '#333' }}>175 1256 0225</p>
66
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '39px', color: '#666' }}>邀请您参与活动</p>
67
+          <img src={link} style={{ margin: '0', width: '80px', height: '80px', backgroundColor: '#fff', padding: '4px', borderRadius: '6px', position: 'absolute', right: '30px', bottom: '45px' }} alt="" />
68
+          <p style={{ margin: '0', fontSize: '13px', position: 'absolute', right: '30px', bottom: '20px', color: '#666' }}>长按识别更多</p>
69
+
70
+        </div>
71
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
72
+        <p style={{ textAlign: 'center' }}>
73
+          <Upload showUploadList={false}
74
+            {...uploaderProps}
75
+            onSuccess={handleUploadIndexOneSucess}>
76
+            <Button>
77
+              上传图片
78
+          </Button>
79
+          </Upload>
80
+        </p>
81
+      </div>
82
+
83
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
84
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
85
+          <img style={{ width: '100%', height: '100%' }} src={indexTwoUrl} alt="" />
86
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '122px' }} alt="" />
87
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '163px', color: '#333' }}>小五子</p>
88
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '142px', color: '#333' }}>175 1256 0225</p>
89
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '121px', color: '#666' }}>邀请您参与活动</p>
90
+          <div style={{ margin: '0', width: '100%', height: '112px', backgroundColor: '#fff', position: 'absolute', left: '0px', bottom: '0.5px', }}>
91
+            <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '30px', bottom: '58px', color: '#666' }}>长按识别小程序码</p>
92
+            <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '30px', bottom: '31px', color: '#666' }}>进入<span style={{ margin: '0 10px', fontSize: '17px', color: '#000' }}>致云</span>查看详情</p>
93
+            <img src={link} style={{ margin: '0', width: '100px', height: '100px', position: 'absolute', right: '20px', bottom: '6px' }} alt="" />
94
+          </div>
95
+        </div>
96
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
97
+        <p style={{ textAlign: 'center' }}>
98
+          <Upload showUploadList={false}
99
+            {...uploaderProps}
100
+            onSuccess={handleUploadIndexTwoSucess}>
101
+            <Button>
102
+              上传图片
103
+          </Button>
104
+          </Upload></p>
105
+      </div>
106
+
107
+      <div style={{ width: '420px', height: '940px', display: 'inline-block', marginTop: '30px' }}>
108
+        <div style={{ width: '375px', height: '780px', backgroundColor: '#CFCFCF', boxShadow: '0px 0px 16px 6px rgba(0,0,0,0.15)', position: 'relative', margin: '0 auto' }}>
109
+          <img style={{ width: '100%', height: '100%' }} src={indexThreeUrl} alt="" />
110
+          <img src={head1} style={{ width: '66px', height: '66px', position: 'absolute', left: '20px', bottom: '142px' }} alt="" />
111
+          <p style={{ margin: '0', fontSize: '18px', position: 'absolute', left: '96px', bottom: '183px', color: '#333' }}>小五子</p>
112
+          <p style={{ margin: '0', fontSize: '15px', position: 'absolute', left: '96px', bottom: '162px', color: '#333' }}>175 1256 0225</p>
113
+          <p style={{ margin: '0', fontSize: '14px', position: 'absolute', left: '96px', bottom: '141px', color: '#666' }}>邀请您参与活动</p>
114
+          <div style={{ margin: '0', width: '335px', borderRadius: '6px', height: '112px', backgroundColor: '#fff', position: 'absolute', left: '20px', bottom: '20px', }}></div>
115
+          <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '50px', bottom: '78px', color: '#666' }}>长按识别小程序码</p>
116
+          <p style={{ margin: '0', fontSize: '16px', position: 'absolute', left: '50px', bottom: '51px', color: '#666' }}>进入<span style={{ margin: '0 10px', fontSize: '17px', color: '#000' }}>致云</span>查看详情</p>
117
+          <img src={link} style={{ margin: '0', width: '100px', height: '100px', position: 'absolute', right: '50px', bottom: '26px' }} alt="" />
118
+        </div>
119
+        <p style={{ textAlign: 'center', fontSize: '19px', color: '#666', marginTop: '30px' }}>生成海报分享给朋友</p>
120
+        <p style={{ textAlign: 'center' }}>
121
+          <Upload showUploadList={false}
122
+            {...uploaderProps}
123
+            onSuccess={handleUploadIndexThreeSucess}>
124
+            <Button>
125
+              上传图片
126
+          </Button>
127
+          </Upload>
128
+        </p>
129
+      </div>
130
+    </div>
131
+  )
132
+}
133
+export default PosterCard
134
+

+ 166
- 0
estateagents-admin-manager/src/components/CommEditor/index.jsx Wyświetl plik

@@ -0,0 +1,166 @@
1
+import React, { useEffect, useState } from 'react'
2
+import { PageHeader, Form, Button, Spin, Modal, notification } from 'antd'
3
+
4
+const formItemLayout = {
5
+  labelCol: {
6
+    xs: { span: 24 },
7
+    sm: { span: 4 },
8
+  },
9
+  wrapperCol: {
10
+    xs: { span: 24 },
11
+    sm: { span: 12 },
12
+  },
13
+}
14
+
15
+const tailFormItemLayout = {
16
+  wrapperCol: {
17
+    xs: {
18
+      span: 24,
19
+      offset: 0,
20
+    },
21
+    sm: {
22
+      span: 16,
23
+      offset: 6,
24
+    },
25
+  },
26
+}
27
+
28
+export default Form.create({})(props => {
29
+  const [title, updateTitle] = useState('详情' || props.title)
30
+  const [loading, updateLoading] = useState(false)
31
+  const [pageData, updatePageData] = useState(null)
32
+
33
+  const id = props.dataId
34
+  const { getFieldDecorator, setFieldsValue, validateFields, resetFields } = props.form
35
+
36
+  const handleCancel = () => {
37
+    if (props.onCancel) {
38
+      props.onCancel()
39
+    }
40
+  }
41
+
42
+  const { fetchAPI, saveAPI, updateAPI } = props
43
+
44
+  const handleSubmit = (e) => {
45
+    e.preventDefault();
46
+    validateFields((err, values) => {
47
+      if (!err) {
48
+        updateLoading(true)
49
+        if (!id && !props.record) {
50
+          saveAPI({data: values}).then(res => {
51
+            updateLoading(false)
52
+            // 保存成功之后直接跳转到详情
53
+            Modal.success({
54
+              content: '保存信息成功',
55
+              onOk: () => {
56
+                if (props.onSuccess) {
57
+                  props.onSuccess(res)
58
+                }
59
+              }
60
+            })
61
+          }).catch(err => {
62
+            updateLoading(false)
63
+            if (props.onError) {
64
+              props.onError(err)
65
+            }
66
+          })
67
+        } else {
68
+          const pKey = id || props.dateKey(props.record)
69
+
70
+          const data = props.record ? { ...props.record, ...values } : values
71
+          updateAPI({ data, urlData: { id: pKey } }).then(res => {
72
+            updateLoading(false)
73
+            updatePageData(res)
74
+            notification.success({ message: '更新信息成功' })
75
+            if (props.onSuccess) {
76
+              props.onSuccess(res)
77
+            }
78
+          }).catch(err => {
79
+            updateLoading(false)
80
+            if (props.onError) {
81
+              props.onError(err)
82
+            }
83
+          })
84
+        }
85
+      }
86
+    });
87
+  }
88
+
89
+  useEffect(() => {
90
+    if (props.mode !== 'inner') {
91
+      if (!pageData) {
92
+        resetFields()
93
+      } else {
94
+        setFieldsValue(pageData)
95
+      }
96
+    }
97
+  }, [props.mode, pageData])
98
+
99
+  useEffect(() => {
100
+    if (props.mode === 'inner') {
101
+      if (!props.record) {
102
+        resetFields()
103
+      } else {
104
+        setFieldsValue(props.record)
105
+      }
106
+      updatePageData(props.record)
107
+    }
108
+  }, [props.mode, props.record])
109
+
110
+  useEffect(() => {
111
+    if (props.mode !== 'inner' && id) {
112
+      updateLoading(true)
113
+      fetchAPI({ urlData: {id} }).then((res) => {
114
+        updatePageData(res)
115
+        updateLoading(false)
116
+        if (!res) {
117
+          notification.info({ message: '没有查询到数据' })
118
+        } else {
119
+          if (props.getPageTitle) {
120
+            updateTitle(props.getPageTitle(res.typeName))
121
+          }
122
+        }
123
+      }).catch(err => {
124
+        updateLoading(false)
125
+      })
126
+    }
127
+  }, [])
128
+
129
+  const childProps = {
130
+    form: props.form,
131
+    dataSource: pageData
132
+  }
133
+
134
+  const showHeader = props.showHeader !== false
135
+
136
+  return (
137
+    <div>
138
+      {
139
+        showHeader && (
140
+          <PageHeader
141
+            ghost={false}
142
+            backIcon={false}
143
+            title={title}
144
+          ></PageHeader>
145
+        )
146
+      }
147
+      <div style={{marginTop: '16px'}}>
148
+        <Spin spinning={loading} tip="请稍候...">
149
+          <Form {...formItemLayout} onSubmit={handleSubmit} >
150
+            {
151
+              props.render(childProps)
152
+            }
153
+            <Form.Item {...tailFormItemLayout}>
154
+              <Button type="primary" htmlType="submit">
155
+                保存
156
+              </Button>
157
+              <Button onClick={handleCancel} style={{ marginLeft: '100px' }}>
158
+                取消
159
+              </Button>
160
+            </Form.Item>
161
+          </Form>
162
+        </Spin>
163
+      </div>
164
+    </div>
165
+  )
166
+})

+ 22
- 0
estateagents-admin-manager/src/components/CommList/DataTable.jsx Wyświetl plik

@@ -0,0 +1,22 @@
1
+import React, { useState } from 'react'
2
+import { Spin, Table } from 'antd'
3
+
4
+export default props => {
5
+  const [loading, setLoading] = useState(false)
6
+
7
+  const pagination = {
8
+    ...props.pagination,
9
+    onChange: (page, pageSize) => {
10
+      setLoading(true)
11
+      props.onPageChange(page, pageSize).then(() => setLoading(false)).catch(() => setLoading(false))
12
+    }
13
+  }
14
+
15
+  const { onPageChange, ...tableProps } = props
16
+
17
+  return (
18
+    <Spin spinning={loading} delay={300} tip="请稍候...">
19
+      <Table pagination={pagination} {...tableProps} />
20
+    </Spin>
21
+  )
22
+}

+ 40
- 0
estateagents-admin-manager/src/components/CommList/FilterForm.jsx Wyświetl plik

@@ -0,0 +1,40 @@
1
+import React from 'react'
2
+import { Form } from 'antd'
3
+
4
+export default Form.create({})((props) => {
5
+  const handleSubmit = (e) => {
6
+    e.preventDefault();
7
+    props.form.validateFields((err, values) => {
8
+      if (!err) {
9
+        props.onSearch(values)
10
+      }
11
+    });
12
+  }
13
+
14
+  const handleReset = () => {
15
+    props.form.resetFields();
16
+    props.onSearch(undefined)
17
+  }
18
+
19
+  const { getFieldDecorator } = props.form
20
+
21
+  const childProps = {
22
+    form: props.form
23
+  }
24
+
25
+  return (
26
+    <Form layout="inline" onSubmit={e => handleSubmit(e)}>
27
+      {
28
+        props.render(childProps)
29
+      }
30
+      <Form.Item>
31
+        <Button type="primary" htmlType="submit">
32
+          搜索
33
+        </Button>
34
+        <Button style={{ marginLeft: 8 }} onClick={handleReset}>
35
+          重置
36
+          </Button>
37
+      </Form.Item>
38
+    </Form>
39
+  )
40
+})

+ 93
- 0
estateagents-admin-manager/src/components/CommList/index.jsx Wyświetl plik

@@ -0,0 +1,93 @@
1
+import React, { useEffect, useState, useCallback, useRef } from 'react'
2
+import { PageHeader, Form, Input, Button, Icon, Table, Rate, Tag, Spin, notification, Tooltip } from 'antd'
3
+
4
+export default (props) => {
5
+  const defaultPageInfo = { pageNum: 1, pageSize: 10 }
6
+  const [loading, updateLoading] = useState(false)
7
+  const [filterParams, updateFilterParams] = useState({})
8
+  const [pageData, updatePageData] = useState([])
9
+  const [pageNavData, updatePageNavData] = useState({})
10
+
11
+  const getList = (params) => {
12
+    updateLoading(true)
13
+    props.fetchList(params).then(res => {
14
+      updateLoading(false)
15
+      updatePageData(res[0])
16
+      updatePageNavData(res[1])
17
+
18
+      if (!res[1].total) {
19
+        notification.info({ message: '没有查询到数据' })
20
+      }
21
+    }).catch(err => {
22
+      updateLoading(false)
23
+      // do nothing
24
+    })
25
+  }
26
+
27
+  const handleSearch = (params) => {
28
+    updateFilterParams(params || {})
29
+    
30
+    getList({
31
+      ...defaultPageInfo,
32
+      ...(params || {})
33
+    })
34
+  }
35
+
36
+  const handlePageChange = (pageNum, pageSize) => {
37
+    getList({
38
+      pageNum,
39
+      pageSize,
40
+      ...filterParams
41
+    })
42
+  }
43
+
44
+  const dataRef = useRef()
45
+  dataRef.current = pageData
46
+
47
+  useEffect(() => {
48
+    getList(defaultPageInfo)
49
+
50
+    if (props.refreshData) {
51
+      props.refreshData((callback) => {
52
+        const newList = callback(dataRef.current)
53
+        updatePageData(newList)
54
+      })
55
+    }
56
+  }, [])
57
+
58
+  const pagination = {
59
+    ...pageNavData,
60
+    onChange: handlePageChange
61
+  }
62
+
63
+  const showFilterForm = props.showFilterForm !== false
64
+  const tableProp = {
65
+    ...(props.table || {}),
66
+    dataSource: pageData
67
+  }
68
+
69
+  return (
70
+    <div>
71
+      <PageHeader
72
+        ghost={false}
73
+        backIcon={false}
74
+        title={props.title || "列表"}
75
+        extra={[<Button key={1} type="primary" onClick={props.onAdd}>新增</Button>]}
76
+      ></PageHeader>
77
+
78
+      {
79
+        showFilterForm && (
80
+          <FilterForm onSearch={handleSearch} render={props.renderFilter} />
81
+        )
82
+      }
83
+      
84
+      <div style={{marginTop: '16px'}}>
85
+        <Spin spinning={loading} delay={300} tip="请稍候...">
86
+          <Table
87
+            {...tableProp}
88
+            pagination={pagination} />
89
+        </Spin>
90
+      </div>
91
+    </div>
92
+  )
93
+}

+ 71
- 0
estateagents-admin-manager/src/components/CopyBlock/index.jsx Wyświetl plik

@@ -0,0 +1,71 @@
1
+import { Icon, Popover, Typography } from 'antd';
2
+import React, { useRef } from 'react';
3
+import { FormattedMessage } from 'umi-plugin-react/locale';
4
+import { connect } from 'dva';
5
+import { isAntDesignPro } from '@/utils/utils';
6
+import styles from './index.less';
7
+
8
+const firstUpperCase = pathString =>
9
+  pathString
10
+    .replace('.', '')
11
+    .split(/\/|-/)
12
+    .map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()))
13
+    .filter(s => !!s)
14
+    .join(''); // when  click block copy, send block url to  ga
15
+
16
+const onBlockCopy = label => {
17
+  if (!isAntDesignPro()) {
18
+    return;
19
+  }
20
+
21
+  const ga = window && window.ga;
22
+
23
+  if (ga) {
24
+    ga('send', 'event', {
25
+      eventCategory: 'block',
26
+      eventAction: 'copy',
27
+      eventLabel: label,
28
+    });
29
+  }
30
+};
31
+
32
+const BlockCodeView = ({ url }) => {
33
+  const blockUrl = `npx umi block add ${firstUpperCase(url)} --path=${url}`;
34
+  return (
35
+    <div className={styles['copy-block-view']}>
36
+      <Typography.Paragraph
37
+        copyable={{
38
+          text: blockUrl,
39
+          onCopy: () => onBlockCopy(url),
40
+        }}
41
+        style={{
42
+          display: 'flex',
43
+        }}
44
+      >
45
+        <pre>
46
+          <code className={styles['copy-block-code']}>{blockUrl}</code>
47
+        </pre>
48
+      </Typography.Paragraph>
49
+    </div>
50
+  );
51
+};
52
+
53
+export default connect(({ routing }) => ({
54
+  location: routing.location,
55
+}))(({ location }) => {
56
+  const url = location.pathname;
57
+  const divDom = useRef(null);
58
+  return (
59
+    <Popover
60
+      title={<FormattedMessage id="app.preview.down.block" defaultMessage="下载此页面到本地项目" />}
61
+      placement="topLeft"
62
+      content={<BlockCodeView url={url} />}
63
+      trigger="click"
64
+      getPopupContainer={dom => (divDom.current ? divDom.current : dom)}
65
+    >
66
+      <div className={styles['copy-block']} ref={divDom}>
67
+        <Icon type="download" />
68
+      </div>
69
+    </Popover>
70
+  );
71
+});

+ 29
- 0
estateagents-admin-manager/src/components/CopyBlock/index.less Wyświetl plik

@@ -0,0 +1,29 @@
1
+.copy-block {
2
+  position: fixed;
3
+  right: 80px;
4
+  bottom: 40px;
5
+  z-index: 99;
6
+  display: flex;
7
+  flex-direction: column;
8
+  align-items: center;
9
+  justify-content: center;
10
+  width: 40px;
11
+  height: 40px;
12
+  font-size: 20px;
13
+  background: #fff;
14
+  border-radius: 40px;
15
+  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14),
16
+    0 1px 10px 0 rgba(0, 0, 0, 0.12);
17
+  cursor: pointer;
18
+}
19
+
20
+.copy-block-view {
21
+  position: relative;
22
+  .copy-block-code {
23
+    display: inline-block;
24
+    margin: 0 0.2em;
25
+    padding: 0.2em 0.4em 0.1em;
26
+    font-size: 85%;
27
+    border-radius: 3px;
28
+  }
29
+}