Your Name пре 2 година
родитељ
комит
17c33a78d8

+ 2
- 0
src/app.config.js Прегледај датотеку

@@ -3,6 +3,8 @@ export default defineAppConfig({
3 3
   pages: [
4 4
     'pages/home/index',
5 5
     'pages/login/index',
6
+    'pages/issue/list/index',
7
+    'pages/issue/detail/index',
6 8
     'pages/index3/index',
7 9
     'pages/index4/index',
8 10
     'pages/index5/index',

+ 2
- 4
src/app.less Прегледај датотеку

@@ -12,12 +12,10 @@ page {
12 12
   --button-primary-background-color: var(--main-bg-color);
13 13
   // 菊花转主色
14 14
   --loading-spinner-color: var(--main-bg-color);
15
+  // tab 划线颜色
16
+  --tabs-bottom-bar-color: var(--main-bg-color);
15 17
 }
16 18
 
17 19
 view {
18 20
   box-sizing: border-box;
19 21
 }
20
-
21
-.page-wrapper {
22
-
23
-}

+ 60
- 0
src/components/NavLoading/index.jsx Прегледај датотеку

@@ -0,0 +1,60 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import style from './style.module.less';
5
+
6
+export default (props) => {
7
+
8
+  const { loading } = props;
9
+  const [percent, setPercent] = React.useState(0);
10
+  const percentRef = React.useRef(0);
11
+  const tickerRef = React.useRef();
12
+  const startRef = React.useRef(false);
13
+
14
+  React.useEffect(() => {
15
+    if (loading) {
16
+      startRef.current = true;
17
+      
18
+      // 先前进一点, 大约 3s 内走 94%
19
+      const firstS = 94;
20
+      const startS = 4;
21
+      percentRef.current = startS;
22
+      setPercent(percentRef.current);
23
+
24
+      const step = (firstS - startS) / 300;
25
+      tickerRef.current = setInterval(() => {
26
+        percentRef.current += step;
27
+        setPercent(percentRef.current);
28
+
29
+        if (percentRef.current >= 94) {
30
+          clearInterval(tickerRef.current);
31
+          tickerRef.current = null;
32
+        }
33
+      }, 100);
34
+    } else {
35
+      // 还没开始
36
+      if (!startRef.current) return ;
37
+
38
+      // 剩下的在 200ms 内走完
39
+      clearInterval(tickerRef.current);
40
+      const leftS = 100 - percentRef.current;      
41
+      const step = leftS / 20;
42
+      tickerRef.current = setInterval(() => {  
43
+        percentRef.current += step;
44
+        setPercent(percentRef.current);
45
+
46
+        if (percentRef.current >= 100) {
47
+          clearInterval(tickerRef.current);
48
+          tickerRef.current = null;
49
+          startRef.current = false;
50
+        }
51
+      }, 10);
52
+    }
53
+  }, [loading]);
54
+  
55
+  return (
56
+    <View className={`${style['nav-loading']} ${percent >= 100 ? style.hidden : ''}`}>
57
+      <View className={style['nav-loading-bar']} style={{ width: `${percent}%` }}></View>
58
+    </View>
59
+  )
60
+}

+ 22
- 0
src/components/NavLoading/style.module.less Прегледај датотеку

@@ -0,0 +1,22 @@
1
+
2
+.nav-loading {
3
+  position: fixed;
4
+  z-index: 1000;
5
+  top: 0;
6
+  left: 0;
7
+  width: 100%;
8
+  height: 6px;
9
+  background: transparent;
10
+  opacity: 1;
11
+  transition: opacity 200ms;
12
+
13
+  &.hidden {
14
+    opacity: 0;
15
+  }
16
+
17
+  .nav-loading-bar {
18
+    width: 0%;
19
+    height: 100%;
20
+    background-color: var(--main-bg-color);
21
+  }
22
+}

+ 64
- 0
src/components/PowerList/index.jsx Прегледај датотеку

@@ -0,0 +1,64 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { PowerScrollView } from '@antmjs/vantui';
5
+
6
+export default (props) => {
7
+  const { request, params = {}, renderItem } = props;
8
+
9
+  const pageSize = 20;
10
+  const pageNumRef = React.useRef(1);
11
+  const listRef = React.useRef([]);
12
+  const [list, setList] = React.useState([]);
13
+  const [finished, setFinished] = React.useState(true);
14
+  listRef.current = list;
15
+
16
+  const queryData = React.useCallback((options = {}) => {
17
+    return new Promise((resolve, reject) => {
18
+      request({
19
+        pageSize,
20
+        ...params,
21
+        ...options
22
+      }).then((res) => {
23
+        const { records, current, pages } = res;
24
+
25
+        if (current == 1) {
26
+          setList(records || []);
27
+        } else {
28
+          setList(listRef.current.concat(records || []));
29
+        }
30
+
31
+        setFinished(current >= pages);
32
+        resolve();
33
+      }).catch((err) => {
34
+        reject(err);
35
+      });
36
+    });
37
+  }, [request, params]);
38
+
39
+  const onScrollToLower = React.useCallback((event = 0, isRefresh = false) => {
40
+    pageNumRef.current += 1;
41
+    return queryData({pageNum : pageNumRef.current});
42
+  }, [queryData]);
43
+
44
+  React.useEffect(() => {
45
+    pageNumRef.current = 1;
46
+    queryData({pageNum : pageNumRef.current});
47
+  }, [queryData])
48
+  
49
+  return (
50
+    <PowerScrollView
51
+      scrollY
52
+      finishedText="没有更多了"
53
+      refresherEnabled={false}
54
+      current={list.length}
55
+      pageSize={pageSize}
56
+      finished={finished}
57
+      onScrollToLower={onScrollToLower}
58
+    >
59
+      {
60
+        list.map(renderItem)
61
+      }
62
+    </PowerScrollView>
63
+  )
64
+}

+ 26
- 16
src/components/Uploader/index.jsx Прегледај датотеку

@@ -10,13 +10,15 @@ import style from './style.modules.less';
10 10
 export default (props) => {
11 11
   // value 为数组
12 12
   // 单个元素结构为 { url, fileType }
13
-  const { value = [], onChange } = props;
13
+  const { value, disabled = false, onChange } = props;
14 14
   const [loading, setLoading] = React.useState(false);
15 15
 
16 16
   const onAdd = (e) => {
17 17
     e.preventDefault();
18 18
     e.stopPropagation();
19 19
 
20
+    if (disabled) return;
21
+
20 22
     setLoading(true);
21 23
     Taro.chooseMedia({
22 24
       maxDuration: 60,
@@ -25,7 +27,7 @@ export default (props) => {
25 27
         const { tempFiles } = res;
26 28
         uploadFiles(tempFiles).then(resp => {
27 29
           setLoading(false);          
28
-          onChange(value.concat(resp));
30
+          onChange((value || []).concat(resp));
29 31
         }).catch((err) => {
30 32
           console.error(err);
31 33
           setLoading(false);
@@ -53,27 +55,35 @@ export default (props) => {
53 55
   return (
54 56
     <View className={style['uploader-wrapper']}>
55 57
       <View className={style['uploader-box']}>
56
-        <View className={style['uploader-add']}>
57
-          <Image src={icon} onClick={onAdd} />
58
-          {
59
-            loading && (
60
-              <View>
61
-                <Loading color="#fff" />
62
-              </View>
63
-            )
64
-          }
65
-        </View>
66 58
         {
67
-          value.map((item, inx) => (
59
+          !disabled && (
60
+            <View className={style['uploader-add']}>
61
+              <Image src={icon} onClick={onAdd} />
62
+              {
63
+                loading && (
64
+                  <View>
65
+                    <Loading color="#fff" />
66
+                  </View>
67
+                )
68
+              }
69
+            </View>
70
+          )
71
+        }
72
+        {
73
+          (value || []).map((item, inx) => (
68 74
             <View key={item.url} className={style['uploader-item']}>
69 75
               {
70 76
                 item.fileType == 'image' ?
71 77
                   <Image src={item.url} onClick={() => onPreview(inx)} /> :
72 78
                   <Video src={item.url} controls={false} onClick={() => onPreview(inx)} />
73 79
               }
74
-              <View onClick={() => onDelete(item)}>
75
-                <Image src={closeIcon} />
76
-              </View>
80
+              {
81
+                !disabled && (
82
+                  <View onClick={() => onDelete(item)}>
83
+                    <Image src={closeIcon} />
84
+                  </View>
85
+                )
86
+              }
77 87
             </View>
78 88
           ))
79 89
         }

+ 40
- 12
src/components/map/index.jsx Прегледај датотеку

@@ -1,29 +1,57 @@
1 1
 import React from 'react';
2
-import { View, Map, Marker } from '@tarojs/components';
2
+import Taro from '@tarojs/taro';
3
+import { View, Map } from '@tarojs/components';
4
+import { getLocation } from '@/utils/authorize';
3 5
 import iconPath from '@/assets/icons/marker.png';
4 6
 import style from './style.module.less';
5 7
 
6 8
 export default (props) => {
7
-  const { location = '' } = props;
8
-  const {lng, lat} = location.split(',');
9
+  const { location } = props;
10
+
11
+  const [currentPos, setCurPos] = React.useState();
12
+  const [lngLat, setLngLat] = React.useState([,]);
9 13
 
10 14
   const markers = React.useMemo(() => {
11
-    if (lng == undefined || lat == undefined) return [];
12
-  
15
+    if (!location && !currentPos) return [];
16
+    
17
+    let longitude, latitude;
18
+    if (location) {
19
+      const [lng, lat] = location.split(',');
20
+      longitude = lng - 0;
21
+      latitude = lat - 0;
22
+    } else {
23
+      longitude = currentPos.longitude;
24
+      latitude = currentPos.latitude;
25
+    }
26
+
27
+    setLngLat([longitude, latitude]);
28
+
13 29
     return [{
14
-      longitude: lng - 0,
15
-      latitude: lat - 0,
30
+      id: 1,
31
+      longitude,
32
+      latitude,
16 33
       iconPath,
17
-      width: 50,
18
-      height: 58
34
+      width: 17,
35
+      height: 20
19 36
     }];
20
-  }, [lng, lat]);
37
+  }, [location, currentPos]);
38
+  
39
+  React.useEffect(() => {
40
+    getLocation().then((res) => {
41
+      setCurPos(res);
42
+    }).catch((err) => {
43
+      Taro.showToast({
44
+        title: '定位失败',
45
+        icon: 'none',
46
+      })
47
+    });
48
+  }, []);
21 49
   
22 50
   return (
23 51
     <View className={style['map-wrapper']}>
24 52
       <Map
25
-        longitude={lng}
26
-        latitude={lat}
53
+        longitude={lngLat[0]}
54
+        latitude={lngLat[1]}
27 55
         markers={markers}
28 56
       ></Map>
29 57
     </View>

+ 12
- 4
src/layouts/index.jsx Прегледај датотеку

@@ -3,16 +3,17 @@ import Taro from '@tarojs/taro';
3 3
 import { View, Image } from '@tarojs/components';
4 4
 import { useModel } from '@/store';
5 5
 import { Loading } from '@antmjs/vantui';
6
+import NavLoading from '@/components/NavLoading';
6 7
 import Auth from '@/components/Auth';
7 8
 import TabBar from './TabBar';
8 9
 import laySty from './layout.module.less';
9 10
 
10 11
 export default (props) => {
11
-  const { className, style, roles, tabBar = false } = props;
12
+  const { className, style, roles, tabBar = false, loading } = props;
12 13
 
13 14
   const { person, user } = useModel('user');
14 15
 
15
-  const classNames = `${laySty['page-wrapper']} ${className}`;
16
+  const conatinerClass = `${laySty['page-conatiner']} ${tabBar ? laySty['with-tabbar'] : ''} ${className}`;
16 17
 
17 18
   React.useEffect(() => {
18 19
     if (person && !user) {
@@ -26,8 +27,9 @@ export default (props) => {
26 27
   }, [person, user]);
27 28
 
28 29
   return (
29
-    <View className={classNames} style={style}>
30
-      <View className={laySty['page-conatiner']}>
30
+    <View className={laySty['page-wrapper']}>
31
+      <NavLoading loading={loading} />
32
+      <View className={conatinerClass} style={style}>
31 33
         {
32 34
           !person && (
33 35
             <View className={laySty.loading}>
@@ -40,6 +42,12 @@ export default (props) => {
40 42
         <Auth roles={roles}>
41 43
         {props.children}
42 44
         </Auth>
45
+
46
+        {
47
+          !tabBar && (
48
+            <View className={laySty['pdm-space']}></View>
49
+          )
50
+        }
43 51
       </View>
44 52
       {
45 53
         tabBar && <TabBar className={laySty['page-tabbar']} active={tabBar} />

+ 9
- 2
src/layouts/layout.module.less Прегледај датотеку

@@ -8,8 +8,15 @@
8 8
 
9 9
   .page-conatiner {
10 10
     width: 100%;
11
-    height: calc(100% - var(--tabbar-height, 100rpx));
12
-    overflow: hidden;
11
+    height: 100%;
12
+
13
+    &.with-tabbar {
14
+      height: calc(100% - var(--tabbar-height, 100rpx));
15
+    }
16
+
17
+    .pdm-space {
18
+      height: var(--main-space);
19
+    }
13 20
   }
14 21
 
15 22
   .page-tabbar {

+ 49
- 0
src/pages/issue/add/components/Assigned.jsx Прегледај датотеку

@@ -0,0 +1,49 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Button } from '@antmjs/vantui';
5
+import { putTaIssue } from '@/services/taissue';
6
+import { warn } from '@/utils/message';
7
+import style from './style.module.less';
8
+
9
+export default (props) => {
10
+
11
+  const { issue, formData } = props;
12
+
13
+  const [loading, setLoading] = React.useState(false);
14
+  
15
+  // 新增问题单
16
+  const onSubmit = () => {
17
+    try {
18
+      warn(!formData.addr, '请填写地址')
19
+      warn(!formData.locId, '请选择点位')
20
+      warn(!formData.content, '请填写问题描述')
21
+      warn(!formData.typeId, '请选择问题分类')
22
+      warn(!formData.attachList || formData.attachList.length < 1, '请上传照片')
23
+    } catch (e) {
24
+      return;
25
+    }
26
+
27
+    setLoading(true)
28
+    putTaIssue(issue.issueId, { ...issue, formData }).then(() => {
29
+      setLoading(false);
30
+      Taro.navigateBack({delta: 1});
31
+    }).catch(() => {
32
+      setLoading(false);
33
+    })
34
+  }
35
+  
36
+  return (
37
+    <View class={style['assigned-btn-wrapper']}>
38
+      <View>
39
+        <Button square plain type="danger" loading={loading} onClick={onSubmit}>删除</Button>
40
+      </View>
41
+      <View>
42
+        <Button block square plain hairline type="info" loading={loading} onClick={onSubmit}>打回</Button>
43
+      </View>
44
+      <View>          
45
+        <Button block square type="primary" loading={loading} onClick={onSubmit}>交办</Button>
46
+      </View>
47
+    </View>
48
+  )
49
+}

+ 40
- 0
src/pages/issue/add/components/Edit.jsx Прегледај датотеку

@@ -0,0 +1,40 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Button } from '@antmjs/vantui';
5
+import { putTaIssue } from '@/services/taissue';
6
+import { warn } from '@/utils/message';
7
+
8
+export default (props) => {
9
+
10
+  const { issue, formData } = props;
11
+
12
+  const [loading, setLoading] = React.useState(false);
13
+  
14
+  // 新增问题单
15
+  const onSubmit = () => {
16
+    try {
17
+      warn(!formData.addr, '请填写地址')
18
+      warn(!formData.locId, '请选择点位')
19
+      warn(!formData.content, '请填写问题描述')
20
+      warn(!formData.typeId, '请选择问题分类')
21
+      warn(!formData.attachList || formData.attachList.length < 1, '请上传照片')
22
+    } catch (e) {
23
+      return;
24
+    }
25
+
26
+    setLoading(true)
27
+    putTaIssue(issue.issueId, { ...issue, formData }).then(() => {
28
+      setLoading(false);
29
+      Taro.navigateBack({delta: 1});
30
+    }).catch(() => {
31
+      setLoading(false);
32
+    })
33
+  }
34
+  
35
+  return (
36
+    <View style={{ padding: '0 1em' }}>
37
+      <Button block type="primary" loading={loading} onClick={onSubmit}>修改</Button>
38
+    </View>
39
+  )
40
+}

+ 126
- 0
src/pages/issue/add/components/Form.jsx Прегледај датотеку

@@ -0,0 +1,126 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Button, Notify, Field, Cell, CellGroup } from '@antmjs/vantui';
5
+import LocType from '@/components/locType';
6
+import IssueType from '@/components/issueType';
7
+import Map from '@/components/map';
8
+import Uploader from '@/components/Uploader/index';
9
+import mapIcon from '@/assets/icons/marker.png';
10
+
11
+export default (props) => {
12
+  const { issue, renderAction } = props;
13
+
14
+  const [formData, setFormData] = React.useState({
15
+    typeId: undefined,
16
+    typeName: undefined,
17
+    locId: undefined,
18
+    locName: undefined,
19
+    location: undefined,
20
+    addr: undefined,
21
+    content: undefined,
22
+    attachList: [],
23
+  });
24
+  const [showLocType, setShowLocType] = React.useState(false);
25
+  const [showIssueType, setShowIssueType] = React.useState(false);
26
+  
27
+  const onLocTypeChange = (_, it) => {
28
+    setFormData({
29
+      ...formData,
30
+      locId: it.typeId,
31
+      locName: it.name,
32
+    });
33
+    setShowLocType(false);
34
+  }
35
+
36
+  const onIssueTypeChange = (_, it) => {
37
+    setFormData({
38
+      ...formData,
39
+      typeId: it.typeId,
40
+      typeName: it.name,
41
+    });
42
+    setShowIssueType(false);
43
+  }
44
+
45
+  const onFieldChange = (field, value) => {
46
+    setFormData({
47
+      ...formData,
48
+      [field]: value,
49
+    })
50
+  }
51
+
52
+  React.useEffect(() => {
53
+    if (issue) {
54
+      setFormData(issue);
55
+    }
56
+  }, [issue]);
57
+  
58
+  return (
59
+    <View>
60
+      <LocType
61
+        show={showLocType}
62
+        value={formData.addr}
63
+        onCancel={() => setShowLocType(false)}
64
+        onChange={onLocTypeChange}
65
+      />
66
+
67
+      <IssueType
68
+        show={showIssueType}
69
+        value={formData.typeName}
70
+        onCancel={() => setShowIssueType(false)}
71
+        onChange={onIssueTypeChange}
72
+      />
73
+
74
+      <Map location={formData.location} />
75
+
76
+      <CellGroup>
77
+        <Cell
78
+          isLink
79
+          title="点位"
80
+          value={formData.locName}
81
+          onClick={() => setShowLocType(true)}
82
+        />
83
+        <Field
84
+          value={formData.addr}
85
+          leftIcon={mapIcon}
86
+          placeholder="请输入地址"
87
+          onChange={e => onFieldChange('addr', e.detail)}
88
+        />
89
+      </CellGroup>
90
+
91
+      <CellGroup style={{marginTop: '20px'}}>
92
+
93
+        <Cell
94
+          isLink
95
+          title="问题分类"
96
+          style={{marginTop: '20px'}}
97
+          value={formData.typeName}
98
+          onClick={() => setShowIssueType(true)}
99
+        />
100
+        <Cell title="问题描述" border={false} />
101
+
102
+        <Field
103
+          type="textarea"
104
+          placeholder="请输入问题描述"
105
+          autosize={{ minHeight: '120px' }}
106
+          value={formData.content}
107
+          onChange={e => onFieldChange('content', e.detail)}
108
+        />
109
+      </CellGroup>
110
+            
111
+      <CellGroup style={{marginTop: '20px'}}>        
112
+        <Cell title="拍照或视频" border={false} />
113
+
114
+        <Cell
115
+          renderTitle={
116
+            <Uploader value={formData.attachList} onChange={e => onFieldChange('attachList',e)} />
117
+          }
118
+        />
119
+      </CellGroup>
120
+
121
+      <View style={{margin: '20px auto'}}>
122
+        {renderAction(formData)}
123
+      </View>
124
+    </View>
125
+  )
126
+}

+ 40
- 0
src/pages/issue/add/components/Save.jsx Прегледај датотеку

@@ -0,0 +1,40 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Button } from '@antmjs/vantui';
5
+import { postTaIssue } from '@/services/taissue';
6
+import { warn } from '@/utils/message';
7
+
8
+export default (props) => {
9
+
10
+  const { formData } = props;
11
+
12
+  const [loading, setLoading] = React.useState(false);
13
+  
14
+  // 新增问题单
15
+  const onSubmit = () => {
16
+    try {
17
+      warn(!formData.addr, '请填写地址')
18
+      warn(!formData.locId, '请选择点位')
19
+      warn(!formData.content, '请填写问题描述')
20
+      warn(!formData.typeId, '请选择问题分类')
21
+      warn(!formData.attachList || formData.attachList.length < 1, '请上传照片')
22
+    } catch (e) {
23
+      return;
24
+    }
25
+
26
+    setLoading(true)
27
+    postTaIssue(formData).then(() => {
28
+      setLoading(false);
29
+      Taro.navigateBack({delta: 1});
30
+    }).catch(() => {
31
+      setLoading(false);
32
+    })
33
+  }
34
+  
35
+  return (
36
+    <View style={{ padding: '0 1em' }}>
37
+      <Button block type="primary" loading={loading} onClick={onSubmit}>提交</Button>
38
+    </View>
39
+  )
40
+}

+ 19
- 0
src/pages/issue/add/components/style.module.less Прегледај датотеку

@@ -0,0 +1,19 @@
1
+
2
+
3
+.assigned-btn-wrapper {
4
+  display: flex;
5
+  background: #fff;
6
+  padding: 10px;
7
+  
8
+  & > view {
9
+    flex: 1;
10
+
11
+    &:first-child {
12
+      flex: 2;
13
+    }
14
+  
15
+    & + view {
16
+      margin-left: 10px;
17
+    }
18
+  }
19
+}

+ 30
- 142
src/pages/issue/add/index.jsx Прегледај датотеку

@@ -1,156 +1,44 @@
1 1
 import React from 'react';
2 2
 import Taro from '@tarojs/taro';
3
-import { View } from '@tarojs/components';
4
-import { Button, Notify, Field, Cell, CellGroup } from '@antmjs/vantui';
3
+import { Block } from '@tarojs/components';
5 4
 import Page from '@/layouts/index';
6
-import LocType from '@/components/locType';
7
-import IssueType from '@/components/issueType';
8
-import Map from '@/components/map';
9
-import Uploader from '@/components/Uploader/index';
10
-import { getLocation } from '@/utils/authorize';
11
-import { ROLE_INSPECTOR } from '@/utils/user';
12
-import mapIcon from '@/assets/icons/marker.png';
13
-import { postTaIssue } from '@/services/taissue';
14
-import { warn } from '@/utils/message';
5
+import { ROLE_INSPECTOR, ROLE_MANAGER } from '@/utils/user';
6
+import { getTaIssueById } from '@/services/taissue';
7
+import IssueForm from '../components/Issue';
8
+import Save from './components/Save';
9
+import Edit from './components/Edit';
15 10
 
16 11
 export default (props) => {
17 12
 
18
-  const [formData, setFormData] = React.useState({
19
-    typeId: undefined,
20
-    typeName: undefined,
21
-    locId: undefined,
22
-    locName: undefined,
23
-    location: undefined,
24
-    addr: undefined,
25
-    content: undefined,
26
-    attachList: [],
27
-  });
28
-  const [showLocType, setShowLocType] = React.useState(false);
29
-  const [showIssueType, setShowIssueType] = React.useState(false);
30
-  const [loading, setLoading] = React.useState(false);
31
-
32
-  const onLocTypeChange = (_, it) => {
33
-    setFormData({
34
-      ...formData,
35
-      locId: it.typeId,
36
-      locName: it.name,
37
-    });
38
-    setShowLocType(false);
39
-  }
40
-
41
-  const onIssueTypeChange = (_, it) => {
42
-    setFormData({
43
-      ...formData,
44
-      typeId: it.typeId,
45
-      typeName: it.name,
46
-    });
47
-    setShowIssueType(false);
48
-  }
13
+  const router = Taro.useRouter();
14
+  const { id, act } = router.params;
49 15
 
50
-  const onFieldChange = (field, value) => {
51
-    setFormData({
52
-      ...formData,
53
-      [field]: value,
54
-    })
55
-  }
56
-
57
-  const onSubmit = () => {
58
-    try {
59
-      warn(!formData.addr, '请填写地址')
60
-      warn(!formData.locId, '请选择点位')
61
-      warn(!formData.content, '请填写问题描述')
62
-      warn(!formData.typeId, '请选择问题分类')
63
-      warn(!formData.attachList || formData.attachList.length < 1, '请上传照片')
64
-    } catch (e) {
65
-      return;
16
+  const [loading, setLoading] = React.useState(false);
17
+  const [issue, setIssue] = React.useState();
18
+
19
+  React.useEffect(() => {
20
+    if (id) {
21
+      setLoading(true)
22
+      getTaIssueById(id).then(res => {
23
+        setLoading(false);
24
+        setIssue(res);
25
+      }).catch(() => {
26
+        setLoading(false);
27
+      });
66 28
     }
67
-
68
-    setLoading(true)
69
-    postTaIssue(formData).then(() => {
70
-      setLoading(false);
71
-      Taro.navigateBack({delta: 1});
72
-    }).catch(() => {
73
-      setLoading(false);
74
-    })
75
-  }
76
-  
77
-  React.useMemo(() => {
78
-    getLocation().then((res) => {
79
-      onFieldChange('location', `${res.longitude},${res.latitude}`);
80
-    }).catch((err) => {
81
-      Notify.show({
82
-        message: '获取位置失败, 请退出重试',
83
-        type: 'warning',
84
-      })
85
-    });
86
-  }, []);
29
+  }, [id]);
87 30
 
88 31
   return (
89
-    <Page roles={[ROLE_INSPECTOR]}>
90
-      <LocType
91
-        show={showLocType}
92
-        value={formData.addr}
93
-        onCancel={() => setShowLocType(false)}
94
-        onChange={onLocTypeChange}
95
-      />
96
-
97
-      <IssueType
98
-        show={showIssueType}
99
-        value={formData.typeName}
100
-        onCancel={() => setShowIssueType(false)}
101
-        onChange={onIssueTypeChange}
102
-      />
103
-
104
-      <Map location={formData.location} />
105
-
106
-      <Field
107
-        value={formData.addr}
108
-        leftIcon={mapIcon}
109
-        placeholder="请输入地址"
110
-        onChange={e => onFieldChange('addr', e.detail)}
32
+    <Page roles={[ROLE_INSPECTOR, ROLE_MANAGER]}>
33
+      <IssueForm
34
+        issue={issue}
35
+        renderAction={(formData) => (
36
+          <Block>
37
+            {!id && <Save formData={formData} />}
38
+            {id && !act && <Edit issue={issue} formData={formData} />}
39
+          </Block>
40
+        )}
111 41
       />
112
-
113
-      <CellGroup style={{marginTop: '20px'}}>
114
-        <Cell
115
-          isLink
116
-          title="点位"
117
-          value={formData.locName}
118
-          onClick={() => setShowLocType(true)}
119
-        />
120
-
121
-        <Cell title="问题描述" border={false} />
122
-
123
-        <Field
124
-          type="textarea"
125
-          placeholder="请输入问题描述"
126
-          autosize={{ minHeight: '120px' }}
127
-          value={formData.content}
128
-          onChange={e => onFieldChange('content', e.detail)}
129
-        />
130
-      </CellGroup>
131
-      
132
-      <Cell
133
-        isLink
134
-        title="问题分类"
135
-        style={{marginTop: '20px'}}
136
-        value={formData.typeName}
137
-        onClick={() => setShowIssueType(true)}
138
-      />
139
-
140
-      
141
-      <CellGroup style={{marginTop: '20px'}}>        
142
-        <Cell title="拍照" border={false} />
143
-
144
-        <Cell
145
-          renderTitle={
146
-            <Uploader value={formData.attachList} onChange={e => onFieldChange('attachList',e)} />
147
-          }
148
-        />
149
-      </CellGroup>
150
-
151
-      <View style={{margin: '20px auto', padding: '0 1em'}}>
152
-        <Button block type="primary" loading={loading} onClick={onSubmit}>提交</Button>
153
-      </View>
154 42
     </Page>
155 43
   )
156 44
 }

+ 59
- 0
src/pages/issue/components/Card/index.jsx Прегледај датотеку

@@ -0,0 +1,59 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import dayjs from 'dayjs';
4
+import { View, ScrollView, RichText, Image, Text } from '@tarojs/components';
5
+import { getIssueStatus } from '@/utils/biz';
6
+import icon from '@/assets/icons/marker.png';
7
+import style from './style.module.less';
8
+
9
+const colors = [
10
+  ['rgba(130, 176, 254, 0.08)', 'rgba(52, 121, 237, 1)'], // 未交办
11
+  ['rgba(51, 218, 156, 0.08)', 'rgba(26, 117, 101, 1)'], // 已交办
12
+  ['rgba(251, 157, 75, 0.08)', 'rgba(232, 116, 16, 1)'], // 已办结
13
+  ['rgba(255, 245, 245, 1)', 'rgba(255, 76, 76, 1)'], // 已逾期
14
+  ['transparent', 'rgba(159, 159, 159, 1)'], // 已打回
15
+]
16
+
17
+export default (props) => {
18
+
19
+  const { detail, onClick } = props;
20
+
21
+  const [
22
+    content,
23
+    createDate,
24
+    styleColor,
25
+    statusTxt,
26
+  ] = React.useMemo(() => {
27
+    if (!detail) return [];
28
+
29
+    const {value : bizStatus = 0, label : statusText} = getIssueStatus(detail);
30
+
31
+    return [
32
+      (detail.content || '').replace('\r\n', '<br>').replace('\n', '<br>'),
33
+      dayjs(detail.createDate).format('YYYY-MM-DD HH:mm'),
34
+      colors[bizStatus],
35
+      statusText,
36
+    ];
37
+  }, [detail]);
38
+  
39
+  return (
40
+    <View className={style['issue-card-wrapper']} onClick={onClick}>
41
+      <View className={style['issue-card-header']}>
42
+        <View>问题:</View>
43
+        <View>{createDate}</View>
44
+      </View>
45
+      <View className={style['issue-card-body']} style={{ backgroundColor: styleColor[0] }}>
46
+        <ScrollView scrollY style={{ height: '100%' }}>
47
+          <RichText nodes={content} />
48
+        </ScrollView>
49
+      </View>
50
+      <View className={style['issue-card-footer']}>
51
+        <View>
52
+          <Image src={icon} />
53
+          <Text>{detail?.addr || ''}</Text>
54
+        </View>
55
+        <View style={{ color: styleColor[1] }}>{statusTxt}</View>
56
+      </View>
57
+    </View>
58
+  )
59
+}

+ 64
- 0
src/pages/issue/components/Card/style.module.less Прегледај датотеку

@@ -0,0 +1,64 @@
1
+
2
+.issue-card-wrapper {
3
+  width: calc(100% - var(--main-space) * 2);
4
+  margin: var(--main-space);
5
+  background: #FFFFFF;
6
+  box-shadow: 0px 18px 22px 1px rgba(0,0,0,0.06);
7
+  border-radius: 8px;
8
+  padding: 20px 20px 40px 20px;
9
+
10
+  .issue-card-header {
11
+    display: flex;
12
+    align-items: center;
13
+    justify-content: space-between;
14
+
15
+    & > view {
16
+      flex: 1;
17
+      font-size: 24px;
18
+      line-height: 42px;
19
+      color: #202020;
20
+
21
+      &:last-child {
22
+        flex: none;
23
+        font-size: 20px;
24
+        color: #222;
25
+      }
26
+    }
27
+  }
28
+
29
+  .issue-card-body {
30
+    padding: 20px;
31
+    font-size: 24px;
32
+    color: #333;
33
+    line-height: 54px;
34
+    height: 252px;
35
+    margin-top: calc(var(--main-space) / 2);
36
+  }
37
+
38
+  .issue-card-footer {
39
+    display: flex;
40
+    align-items: center;
41
+    justify-content: space-between;
42
+    
43
+    font-size: 24px;
44
+    line-height: 32px;
45
+    color: #202020;
46
+    margin-top: var(--main-space);
47
+
48
+    image {
49
+      display: inline-block;
50
+      width: 25px;
51
+      height: 29px;
52
+      margin-right: 10px;
53
+      vertical-align: middle;
54
+    }
55
+    
56
+    & > view {
57
+      flex: 1;
58
+
59
+      &:last-child {
60
+        flex: none;
61
+      }
62
+    }
63
+  }
64
+}

+ 153
- 0
src/pages/issue/components/Issue/index.jsx Прегледај датотеку

@@ -0,0 +1,153 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Button, Notify, Field, Cell, CellGroup } from '@antmjs/vantui';
5
+import LocType from '@/components/locType';
6
+import IssueType from '@/components/issueType';
7
+import Map from '@/components/map';
8
+import Uploader from '@/components/Uploader/index';
9
+import { getIssueStatus } from '@/utils/biz';
10
+import mapIcon from '@/assets/icons/marker.png';
11
+
12
+export default (props) => {
13
+  const { issue, readOnly, renderFields, renderAction } = props;
14
+
15
+  const [formData, setFormData] = React.useState({
16
+    typeId: undefined,
17
+    typeName: undefined,
18
+    locId: undefined,
19
+    locName: undefined,
20
+    location: undefined,
21
+    addr: undefined,
22
+    content: undefined,
23
+    attachList: [],
24
+  });
25
+  const [showLocType, setShowLocType] = React.useState(false);
26
+  const [showIssueType, setShowIssueType] = React.useState(false);
27
+  const [bizStatus, setBizStatus] = React.useState();
28
+  
29
+  const onLocTypeChange = (_, it) => {
30
+    setFormData({
31
+      ...formData,
32
+      locId: it.typeId,
33
+      locName: it.name,
34
+    });
35
+    setShowLocType(false);
36
+  }
37
+
38
+  const onIssueTypeChange = (_, it) => {
39
+    setFormData({
40
+      ...formData,
41
+      typeId: it.typeId,
42
+      typeName: it.name,
43
+    });
44
+    setShowIssueType(false);
45
+  }
46
+
47
+  const onFieldChange = (field, value) => {
48
+    setFormData({
49
+      ...formData,
50
+      [field]: value,
51
+    })
52
+  }
53
+
54
+  React.useEffect(() => {
55
+    if (issue) {
56
+      setFormData(issue);
57
+      setBizStatus(getIssueStatus(issue));
58
+    }
59
+  }, [issue]);
60
+  
61
+  return (
62
+    <View>
63
+      <LocType
64
+        show={showLocType}
65
+        value={formData.addr}
66
+        onCancel={() => setShowLocType(false)}
67
+        onChange={onLocTypeChange}
68
+      />
69
+
70
+      <IssueType
71
+        show={showIssueType}
72
+        value={formData.typeName}
73
+        onCancel={() => setShowIssueType(false)}
74
+        onChange={onIssueTypeChange}
75
+      />
76
+
77
+      <Map location={formData.location} />
78
+
79
+      <CellGroup>
80
+        <Cell
81
+          title="点位"
82
+          isLink={!readOnly}
83
+          value={formData.locName}
84
+          onClick={() => !readOnly && setShowLocType(true)}
85
+        />
86
+        <Field
87
+          placeholder="请输入地址"
88
+          value={formData.addr}
89
+          leftIcon={mapIcon}
90
+          readonly={readOnly}
91
+          onChange={e => onFieldChange('addr', e.detail)}
92
+        />
93
+      </CellGroup>
94
+
95
+      {
96
+        readOnly && bizStatus && (
97
+          <CellGroup style={{marginTop: '20px'}}>
98
+            <Cell
99
+              title="状态"
100
+              value={bizStatus.label}
101
+            />
102
+          </CellGroup>
103
+        )
104
+      }
105
+
106
+      <CellGroup style={{marginTop: '20px'}}>
107
+        <Cell
108
+          title="问题分类"
109
+          isLink={!readOnly}
110
+          style={{marginTop: '20px'}}
111
+          value={formData.typeName}
112
+          onClick={() => !readOnly && setShowIssueType(true)}
113
+        />
114
+        <Cell title="问题描述" border={false} />
115
+
116
+        <Field
117
+          type="textarea"
118
+          placeholder="请输入问题描述"
119
+          readonly={readOnly}
120
+          autosize={{ minHeight: '120px' }}
121
+          value={formData.content}
122
+          onChange={e => onFieldChange('content', e.detail)}
123
+        />
124
+      </CellGroup>
125
+            
126
+      <CellGroup style={{marginTop: '20px'}}>        
127
+        <Cell title="拍照或视频" border={false} />
128
+
129
+        <Cell
130
+          renderTitle={
131
+            <Uploader
132
+              value={formData.attachList}
133
+              disabled={readOnly}
134
+              onChange={e => onFieldChange('attachList',e)}
135
+            />
136
+          }
137
+        />
138
+      </CellGroup>
139
+
140
+      {
141
+        renderFields ? renderFields(formData, setFormData) : null
142
+      }
143
+
144
+      {
145
+        renderAction ? (
146
+          <View style={{margin: '20px auto'}}>
147
+            {renderAction(formData)}
148
+          </View>
149
+        ) : null
150
+      }
151
+    </View>
152
+  )
153
+}

+ 4
- 0
src/pages/issue/detail/index.config.js Прегледај датотеку

@@ -0,0 +1,4 @@
1
+// eslint-disable-next-line no-undef
2
+export default definePageConfig({
3
+  navigationBarTitleText: '问题单详情'
4
+})

+ 51
- 0
src/pages/issue/detail/index.jsx Прегледај датотеку

@@ -0,0 +1,51 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View, RichText } from '@tarojs/components';
4
+import { Button, Notify, Field, Cell, CellGroup } from '@antmjs/vantui';
5
+import Page from '@/layouts/index';
6
+import Map from '@/components/map';
7
+import Uploader from '@/components/Uploader/index';
8
+import { ROLE_INSPECTOR } from '@/utils/user';
9
+import mapIcon from '@/assets/icons/marker.png';
10
+import { getTaIssueById } from '@/services/taissue';
11
+import IssueDetail from '../components/Issue';
12
+import style from './style.module.less';
13
+
14
+export default (props) => {
15
+
16
+  const router = Taro.useRouter();
17
+  const { id } = router.params;
18
+  
19
+  const [issue, setIssue] = React.useState(false);
20
+  const [loading, setLoading] = React.useState(false);
21
+
22
+  // const [
23
+  //   content,
24
+  //   bizStatus
25
+  // ] = React.useMemo(() => {
26
+  //   if (!formData || !formData.issueId) return [];
27
+
28
+  //   return [
29
+  //     (formData.content || '').replace('\r\n', '<br>').replace('\n', '<br>'),
30
+  //     getIssueStatus(formData).label,
31
+  //   ]
32
+  // }, [formData])
33
+
34
+  React.useEffect(() => {
35
+    if (id) {
36
+      setLoading(true)
37
+      getTaIssueById(id).then(res => {
38
+        setLoading(false);
39
+        setIssue(res);
40
+      }).catch(() => {
41
+        setLoading(false);
42
+      });
43
+    }
44
+  }, [id]);
45
+
46
+  return (
47
+    <Page roles={[ROLE_INSPECTOR]} loading={loading}>
48
+      <IssueDetail readOnly issue={issue} />
49
+    </Page>
50
+  )
51
+}

+ 10
- 0
src/pages/issue/detail/style.module.less Прегледај датотеку

@@ -0,0 +1,10 @@
1
+
2
+.issue-content {
3
+  padding: 0 24px;
4
+  padding-left: 60px;
5
+  rich-text {
6
+    line-height: 70px;
7
+    font-size: 24px;
8
+    color: #202020;
9
+  }
10
+}

+ 4
- 0
src/pages/issue/list/index.config.js Прегледај датотеку

@@ -0,0 +1,4 @@
1
+// eslint-disable-next-line no-undef
2
+export default definePageConfig({
3
+  navigationBarTitleText: '我的上报'
4
+})

+ 85
- 0
src/pages/issue/list/index.jsx Прегледај датотеку

@@ -0,0 +1,85 @@
1
+import React from 'react';
2
+import Taro from '@tarojs/taro';
3
+import { View } from '@tarojs/components';
4
+import { Tab, Tabs } from '@antmjs/vantui';
5
+import Page from '@/layouts/index';
6
+import PowerList from '@/components/PowerList';
7
+import { getTaIssue } from '@/services/taissue';
8
+import { ROLE_INSPECTOR } from '@/utils/user';
9
+import Card from '../components/Card';
10
+
11
+export default (props) => {
12
+
13
+  const onClick = (item, act) => {
14
+    if (act) {
15
+      Taro.navigateTo({
16
+        url: `/pages/issue/detail/index?id=${item.issueId}&act=${act}`
17
+      })
18
+    } else {
19
+      Taro.navigateTo({
20
+        url: `/pages/issue/detail/index?id=${item.issueId}`
21
+      })
22
+    }
23
+  }
24
+  
25
+  return (
26
+    <Page roles={[ROLE_INSPECTOR]}>
27
+      <Tabs sticky>
28
+        <Tab title="全部">
29
+          <PowerList
30
+            request={getTaIssue}
31
+            params={{mine: true}}
32
+            renderItem={(item) => (
33
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item, 'assigned')} />
34
+            )}
35
+          />
36
+        </Tab>
37
+        <Tab title="未交办">
38
+          <PowerList
39
+            request={getTaIssue}
40
+            params={{bizStatus: 'start', mine: true}}
41
+            renderItem={(item) => (
42
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item)} />
43
+            )}
44
+          />
45
+        </Tab>
46
+        <Tab title="已交办">
47
+          <PowerList
48
+            request={getTaIssue}
49
+            params={{bizStatus: 'start', mine: true}}
50
+            renderItem={(item) => (
51
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item)} />
52
+            )}
53
+          />
54
+        </Tab>
55
+        <Tab title="已办结">
56
+          <PowerList
57
+            request={getTaIssue}
58
+            params={{bizStatus: 'end', mine: true}}
59
+            renderItem={(item) => (
60
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item)} />
61
+            )}
62
+          />
63
+        </Tab>
64
+        <Tab title="已逾期">
65
+          <PowerList
66
+            request={getTaIssue}
67
+            params={{bizStatus: 'expired', mine: true}}
68
+            renderItem={(item) => (
69
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item)} />
70
+            )}
71
+          />
72
+        </Tab>
73
+        <Tab title="已打回">
74
+          <PowerList
75
+            request={getTaIssue}
76
+            params={{bizStatus: 'reject', mine: true}}
77
+            renderItem={(item) => (
78
+              <Card key={item.issueId} detail={item} onClick={() => onClick(item)} />
79
+            )}
80
+          />
81
+        </Tab>
82
+      </Tabs>
83
+    </Page>
84
+  )
85
+}

+ 9
- 1
src/pages/login/components/Form.jsx Прегледај датотеку

@@ -14,6 +14,7 @@ export default (props) => {
14 14
   const form = Form.useForm();
15 15
   const [accErr, setAccErr] = React.useState();
16 16
   const [pwdErr, setPwdErr] = React.useState();
17
+  const [loading, setLoading] = React.useState(false);
17 18
 
18 19
   const onFinish = (errs, res) => {
19 20
     if (!res.account) {
@@ -29,7 +30,13 @@ export default (props) => {
29 30
       setPwdErr();
30 31
     }
31 32
 
32
-    signin(res).then(onSuccess);
33
+    setLoading(true);
34
+    signin(res).then(() => {
35
+      onSuccess();
36
+      setLoading(false);
37
+    }).catch(err => {
38
+      setLoading(false);
39
+    })
33 40
   }
34 41
 
35 42
   const onForgetPwd = () => {
@@ -73,6 +80,7 @@ export default (props) => {
73 80
             block
74 81
             type="primary"
75 82
             formType="submit"
83
+            loading={loading}
76 84
           >
77 85
             登录
78 86
           </Button>

+ 1
- 1
src/pages/login/index.jsx Прегледај датотеку

@@ -25,7 +25,7 @@ export default (props) => {
25 25
   }
26 26
 
27 27
   return (
28
-    <Page className="index">
28
+    <Page>
29 29
       <View className="login-box">
30 30
         <Head />
31 31
         <Form onSuccess={onSuccess} />

+ 32
- 0
src/utils/biz.js Прегледај датотеку

@@ -0,0 +1,32 @@
1
+import dayjs from 'dayjs';
2
+
3
+export function getIssueStatus(taIssue) {
4
+  if (taIssue.processNode === 'start') {
5
+    return {
6
+      value: 0,
7
+      label: '未交办',
8
+    }
9
+  } else if (taIssue.processNode === 'assigned') {
10
+    return {
11
+      value: 1,
12
+      label: '已交办',
13
+    };
14
+  } else if (taIssue.processNode === 'end') {
15
+    return {
16
+      value: 2,
17
+      label: '已办结',
18
+    };
19
+  } else if (taIssue.processNode !== 'end' && taIssue.expireDate <= dayjs().format('YYYY-MM-DD')) {
20
+    return {
21
+      value: 3,
22
+      label: '已逾期',
23
+    };
24
+  } else if (taIssue.processNode !== 'end' && taIssue.processStatus === 'reject') {
25
+    return {
26
+      value: 4,
27
+      label: '已打回',
28
+    };
29
+  }
30
+
31
+  return {};
32
+}

+ 4
- 0
src/utils/user.js Прегледај датотеку

@@ -8,6 +8,9 @@ export const ROLE_MANAGER = 'manager';
8 8
 // 单位人员
9 9
 export const ROLE_ORG_USER = 'org_user';
10 10
 
11
+// 单位管理员
12
+export const ROLE_ORG_MANAGER = 'org_manager';
13
+
11 14
 // 查询员
12 15
 export const ROLE_QUERY_PERSON = 'query_person';
13 16
 
@@ -19,6 +22,7 @@ export const ROLES = {
19 22
   [ROLE_INSPECTOR]: '督察员',
20 23
   [ROLE_MANAGER]: '管理员',
21 24
   [ROLE_ORG_USER]: '单位人员',
25
+  [ROLE_ORG_MANAGER]: '单位管理员',
22 26
   [ROLE_QUERY_PERSON]: '查询员',
23 27
   [ROLE_CITIZEN]: '市民',
24 28
 }