Selaa lähdekoodia

feat: 新增设备定自检功能模块

1. 新增pages/selfCheck页面作为定自检详情页
2. 在家页面新增针对设备维修中心的定自检组件
3. 注册全局定自检组件并配置路由权限
huoyi 1 kuukausi sitten
vanhempi
commit
45f73ea582

+ 6 - 0
src/pages.json

@@ -321,6 +321,12 @@
321 321
       }
322 322
     },
323 323
     {
324
+      "path": "pages/selfCheck/index",
325
+      "style": {
326
+        "navigationBarTitleText": "定/自检"
327
+      }
328
+    },
329
+    {
324 330
       "path": "pages/seizeStatistics/index",
325 331
       "style": {
326 332
         "navigationBarTitleText": "查获统计"

+ 227 - 0
src/pages/home-new/components/selfCheck.vue

@@ -0,0 +1,227 @@
1
+<template>
2
+    <view class="self-check-container">
3
+        <view class="self-check-header">
4
+            <text class="header-title">定/自检</text>
5
+            <text class="view-all" @click="goToSelfCheckPage('all')">查看全部</text>
6
+        </view>
7
+
8
+        <view class="self-check-content">
9
+            <!-- 已过期定检提醒 -->
10
+            <view class="check-section">
11
+                <view class="section-header">
12
+                    <text class="section-title">已过期定检提醒</text>
13
+                    <text class="view-more" @click="goToSelfCheckPage('expired')">查看更多</text>
14
+                </view>
15
+                <view class="check-item" v-for="(item, index) in expiredItems" :key="index">
16
+                    <view class="item-indicator expired"></view>
17
+                    <view class="item-content">
18
+                        <view class="item-row">
19
+                            <text class="item-label">{{ item.name }}</text>
20
+                            <text class="item-label">{{ item.serialNumber }}</text>
21
+                            <text class="item-date">{{ item.date }}</text>
22
+                        </view>
23
+                        <view class="item-row">
24
+                            <text class="item-label">{{ item.location }}</text>
25
+                            <text class="item-role">{{ item.role }}</text>
26
+                        </view>
27
+                    </view>
28
+                </view>
29
+            </view>
30
+
31
+            <!-- 两周内到期提醒 -->
32
+            <view class="check-section">
33
+                <view class="section-header">
34
+                    <text class="section-title">两周内到期提醒</text>
35
+                    <text class="view-more" @click="goToSelfCheckPage('twoWeeks')">查看更多</text>
36
+                </view>
37
+                <view class="check-item" v-for="(item, index) in twoWeeksItems" :key="index">
38
+                    <view class="item-indicator urgent"></view>
39
+                    <view class="item-content">
40
+                        <view class="item-row">
41
+                            <text class="item-label">{{ item.name }}</text>
42
+                            <text class="item-label">{{ item.serialNumber }}</text>
43
+                            <text class="item-date">{{ item.date }}</text>
44
+                        </view>
45
+                        <view class="item-row">
46
+                            <text class="item-label">{{ item.location }}</text>
47
+                            <text class="item-role">{{ item.role }}</text>
48
+                        </view>
49
+                    </view>
50
+                </view>
51
+            </view>
52
+
53
+            <!-- 一月内检到期提醒 -->
54
+            <view class="check-section">
55
+                <view class="section-header">
56
+                    <text class="section-title">一月内检到期提醒</text>
57
+                    <text class="view-more" @click="goToSelfCheckPage('oneMonth')">查看更多</text>
58
+                </view>
59
+                <view class="check-item" v-for="(item, index) in oneMonthItems" :key="index">
60
+                    <view class="item-indicator normal"></view>
61
+                    <view class="item-content">
62
+                        <view class="item-row">
63
+                            <text class="item-label">{{ item.name }}</text>
64
+                            <text class="item-label">{{ item.serialNumber }}</text>
65
+                            <text class="item-date">{{ item.date }}</text>
66
+                        </view>
67
+                        <view class="item-row">
68
+                            <text class="item-label">{{ item.location }}</text>
69
+                            <text class="item-role">{{ item.role }}</text>
70
+                        </view>
71
+                    </view>
72
+                </view>
73
+            </view>
74
+        </view>
75
+    </view>
76
+</template>
77
+
78
+<script>
79
+export default {
80
+    name: 'SelfCheck',
81
+    data() {
82
+        return {
83
+            // 已过期定检数据
84
+            expiredItems: [
85
+                { name: '设备名称', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' },
86
+                { name: '设备名称', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' }
87
+            ],
88
+            // 两周内到期数据
89
+            twoWeeksItems: [
90
+                { name: '设备名称', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' },
91
+                { name: '设备型号', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' }
92
+            ],
93
+            // 一月内到期数据
94
+            oneMonthItems: [
95
+                { name: '设备型号', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' },
96
+                { name: '设备型号', serialNumber: '设备序列号', location: '设备位置', role: '组长、组员、组员', date: '2026-06-10' }
97
+            ]
98
+        }
99
+    },
100
+    methods: {
101
+        goToSelfCheckPage(tab) {
102
+            uni.navigateTo({
103
+                url: `/pages/selfCheck/index?tab=${tab}`
104
+            });
105
+        }
106
+    }
107
+}
108
+</script>
109
+
110
+<style scoped>
111
+.self-check-container {
112
+    width: 100%;
113
+    padding: 16px;
114
+    background-color: #f5f7fa;
115
+}
116
+
117
+.self-check-header {
118
+    display: flex;
119
+    justify-content: space-between;
120
+    align-items: center;
121
+    margin-bottom: 16px;
122
+}
123
+
124
+.header-title {
125
+    font-size: 24px;
126
+    font-weight: bold;
127
+    color: #333;
128
+}
129
+
130
+.view-all {
131
+    font-size: 16px;
132
+    color: #409eff;
133
+}
134
+
135
+.self-check-content {
136
+    background-color: #fff;
137
+    border-radius: 12px;
138
+    padding: 16px;
139
+}
140
+
141
+.check-section {
142
+    margin-bottom: 24px;
143
+}
144
+
145
+.check-section:last-child {
146
+    margin-bottom: 0;
147
+}
148
+
149
+.section-header {
150
+    display: flex;
151
+    justify-content: space-between;
152
+    align-items: center;
153
+    margin-bottom: 12px;
154
+}
155
+
156
+.section-title {
157
+    font-size: 20px;
158
+    font-weight: bold;
159
+    color: #333;
160
+}
161
+
162
+.view-more {
163
+    font-size: 16px;
164
+    color: #409eff;
165
+}
166
+
167
+.check-item {
168
+    display: flex;
169
+    align-items: flex-start;
170
+    padding: 12px 0;
171
+    border-bottom: 1px solid #f0f0f0;
172
+}
173
+
174
+.check-item:last-child {
175
+    border-bottom: none;
176
+}
177
+
178
+.item-indicator {
179
+    width: 6px;
180
+    border-radius: 3px;
181
+    margin-right: 12px;
182
+    flex-shrink: 0;
183
+}
184
+
185
+.item-indicator.expired {
186
+    background-color: #8c3c34;
187
+    height: 80px;
188
+}
189
+
190
+.item-indicator.urgent {
191
+    background-color: #ff291f;
192
+    height: 80px;
193
+}
194
+
195
+.item-indicator.normal {
196
+    background-color: #b97c45;
197
+    height: 80px;
198
+}
199
+
200
+.item-content {
201
+    flex: 1;
202
+    display: flex;
203
+    flex-direction: column;
204
+    gap: 8px;
205
+}
206
+
207
+.item-row {
208
+    display: flex;
209
+    justify-content: space-between;
210
+    align-items: center;
211
+}
212
+
213
+.item-label {
214
+    font-size: 16px;
215
+    color: #333;
216
+}
217
+
218
+.item-date {
219
+    font-size: 16px;
220
+    color: #333;
221
+}
222
+
223
+.item-role {
224
+    font-size: 16px;
225
+    color: #333;
226
+}
227
+</style>

+ 37 - 28
src/pages/home-new/index.vue

@@ -35,41 +35,47 @@
35 35
             <Notice ref="notice" />
36 36
             <MessagePush ref="messagePush" />
37 37
 
38
-            <!-- 时间范围选择 -->
39
-            <div class="time-range-section">
40
-                <div class="time-tags-container">
41
-                    <div v-for="tag in timeTags" :key="tag.value"
42
-                        :class="['time-tag', { 'active': selectedTimeRange === tag.value, 'time-range-label': tag.isLabel }]"
43
-                        @click="tag.isLabel ? null : handleTimeTagClick(tag)">
44
-                        {{ tag.label }}
45
-                    </div>
46
-
47
-                    <!-- 自定义时间显示 - 紧跟自定义时间标签 -->
48
-                    <div v-if="selectedTimeRange === 'custom'" class="custom-time-inline">
49
-                        <uni-datetime-picker v-model="dateRange" type="daterange" rangeSeparator="至"
50
-                            @change="handleDateRangeChange" class="date-range-picker" :clear-icon="false" />
51
-                        <div class="days-count">共 {{ totalDays }} 天</div>
38
+            <!-- 当 currentDepartment === '设备维修中心' 时显示 SelfCheck 组件 -->
39
+            <SelfCheck v-if="currentDepartment === '设备维修中心'" />
40
+
41
+            <!-- 当 currentDepartment !== '设备维修中心' 时显示原内容 -->
42
+            <template v-else>
43
+                <!-- 时间范围选择 -->
44
+                <div class="time-range-section">
45
+                    <div class="time-tags-container">
46
+                        <div v-for="tag in timeTags" :key="tag.value"
47
+                            :class="['time-tag', { 'active': selectedTimeRange === tag.value, 'time-range-label': tag.isLabel }]"
48
+                            @click="tag.isLabel ? null : handleTimeTagClick(tag)">
49
+                            {{ tag.label }}
50
+                        </div>
51
+
52
+                        <!-- 自定义时间显示 - 紧跟自定义时间标签 -->
53
+                        <div v-if="selectedTimeRange === 'custom'" class="custom-time-inline">
54
+                            <uni-datetime-picker v-model="dateRange" type="daterange" rangeSeparator="至"
55
+                                @change="handleDateRangeChange" class="date-range-picker" :clear-icon="false" />
56
+                            <div class="days-count">共 {{ totalDays }} 天</div>
57
+                        </div>
52 58
                     </div>
53 59
                 </div>
54
-            </div>
55 60
 
56
-            <!-- TypeDetail组件 -->
57
-            <TypeDetail :data-list="typeDetailData" @sort-change="handleSortChange" />
61
+                <!-- TypeDetail组件 -->
62
+                <TypeDetail :data-list="typeDetailData" @sort-change="handleSortChange" />
58 63
 
59
-            <!-- TotalDetail组件 -->
60
-            <TotalDetail :homePageWholeData="homePageWholeData" :start-date="startDate" :end-date="endDate"
61
-                :time-range="selectedTimeRange" :selectedRole="selectedRole" />
64
+                <!-- TotalDetail组件 -->
65
+                <TotalDetail :homePageWholeData="homePageWholeData" :start-date="startDate" :end-date="endDate"
66
+                    :time-range="selectedTimeRange" :selectedRole="selectedRole" />
62 67
 
63 68
 
64
-            <!-- QualityControlAnalysis组件 -->
65
-            <QualityControlAnalysis v-if="role.includes('zhijianke') || role.includes('test')"
66
-                :checkProblemDistributionData="checkProblemDistributionData"
67
-                :accuracyAnalysisData="accuracyAnalysisData" :prohibitedTop3Data="prohibitedTop3Data"
68
-                :concealmentPositionTop1Data="concealmentPositionTop1Data" />
69
+                <!-- QualityControlAnalysis组件 -->
70
+                <QualityControlAnalysis v-if="role.includes('zhijianke') || role.includes('test')"
71
+                    :checkProblemDistributionData="checkProblemDistributionData"
72
+                    :accuracyAnalysisData="accuracyAnalysisData" :prohibitedTop3Data="prohibitedTop3Data"
73
+                    :concealmentPositionTop1Data="concealmentPositionTop1Data" />
69 74
 
70
-            <!-- PerformanceAnalysis组件 -->
71
-            <PerformanceAnalysis v-if="role.includes('zhijianke') || role.includes('test')"
72
-                :performanceData="performanceData" />
75
+                <!-- PerformanceAnalysis组件 -->
76
+                <PerformanceAnalysis v-if="role.includes('zhijianke') || role.includes('test')"
77
+                    :performanceData="performanceData" />
78
+            </template>
73 79
         </div>
74 80
     </view>
75 81
 </template>
@@ -86,6 +92,7 @@ import Notice from "@/pages/home/components/notice.vue";
86 92
 import SelectTag from "@/components/select-tag/select-tag.vue";
87 93
 import TypeDetail from "@/pages/home-new/components/type-detail.vue";
88 94
 import TotalDetail from "@/pages/home-new/components/total-detail.vue";
95
+import SelfCheck from "@/pages/home-new/components/selfCheck.vue";
89 96
 import { getUserProfile, getAppListByRoleId, getAppList } from "@/api/system/user";
90 97
 import { getAttendanceList } from "@/api/attendance/attendance"
91 98
 import { getUnreadCount } from "@/api/check/checkTask"
@@ -104,6 +111,7 @@ export default {
104 111
         SelectTag,
105 112
         TypeDetail,
106 113
         TotalDetail,
114
+        SelfCheck,
107 115
         QualityControlAnalysis,
108 116
         PerformanceAnalysis,
109 117
         uniDatetimePicker,
@@ -111,6 +119,7 @@ export default {
111 119
     },
112 120
     data() {
113 121
         return {
122
+            currentDepartment: '设备维修中心', // 用于控制显示不同内容的变量
114 123
             checkerId: this.$store.state.user.id,
115 124
             userInfo: '',
116 125
             todayCheckInTime: '未打卡',

+ 269 - 0
src/pages/selfCheck/index.vue

@@ -0,0 +1,269 @@
1
+<template>
2
+    <home-container>
3
+        <view class="self-check-page">
4
+            <!-- 标题区域 -->
5
+            <view class="header-section">
6
+                <view class="back-button" @click="goBack">
7
+                    <text class="back-icon">&lt;</text>
8
+                </view>
9
+                <view class="page-title">定/自检</view>
10
+            </view>
11
+
12
+            <!-- 标签页区域 -->
13
+            <view class="tabs-section">
14
+                <view v-for="(tab, index) in tabs" :key="index"
15
+                    :class="['tab-item', { 'active': currentTab === tab.value }]"
16
+                    @click="switchTab(tab.value)">
17
+                    <text class="tab-text">{{ tab.label }}</text>
18
+                </view>
19
+            </view>
20
+
21
+            <!-- 卡片列表区域 -->
22
+            <view class="cards-section">
23
+                <view class="check-card" v-for="(item, index) in filteredItems" :key="index">
24
+                    <view class="card-content">
25
+                        <view class="card-row">
26
+                            <text class="card-label">{{ item.name }}</text>
27
+                            <text class="card-label">{{ item.serialNumber }}</text>
28
+                            <text class="card-date">{{ item.date }}</text>
29
+                        </view>
30
+                        <view class="card-row">
31
+                            <text class="card-label">{{ item.location }}</text>
32
+                            <text class="card-role">{{ item.role }}</text>
33
+                        </view>
34
+                    </view>
35
+                    <view :class="['status-tag', getStatusClass(item.status)]">
36
+                        {{ item.statusText }}
37
+                    </view>
38
+                </view>
39
+            </view>
40
+        </view>
41
+    </home-container>
42
+</template>
43
+
44
+<script>
45
+import HomeContainer from "@/components/HomeContainer.vue";
46
+
47
+export default {
48
+    name: 'SelfCheckPage',
49
+    components: {
50
+        HomeContainer
51
+    },
52
+    data() {
53
+        return {
54
+            currentTab: 'oneMonth',
55
+            tabs: [
56
+                { label: '全部', value: 'all' },
57
+                { label: '两周临期', value: 'twoWeeks' },
58
+                { label: '一月临期', value: 'oneMonth' },
59
+                { label: '已过期', value: 'expired' }
60
+            ],
61
+            allItems: [
62
+                { 
63
+                    name: '设备名称', 
64
+                    serialNumber: '设备序列号', 
65
+                    location: '设备位置', 
66
+                    role: '组长、组员、组员', 
67
+                    date: '2026-06-10', 
68
+                    status: 'expired',
69
+                    statusText: '已过期'
70
+                },
71
+                { 
72
+                    name: '设备型号', 
73
+                    serialNumber: '设备序列号', 
74
+                    location: '设备位置', 
75
+                    role: '组长、组员、组员', 
76
+                    date: '2026-06-10', 
77
+                    status: 'twoWeeks',
78
+                    statusText: '两周临期'
79
+                },
80
+                { 
81
+                    name: '设备型号', 
82
+                    serialNumber: '设备序列号', 
83
+                    location: '设备位置', 
84
+                    role: '组长、组员、组员', 
85
+                    date: '2026-06-10', 
86
+                    status: 'oneMonth',
87
+                    statusText: '一月临期'
88
+                }
89
+            ]
90
+        }
91
+    },
92
+    onLoad(options) {
93
+        // 接收跳转参数
94
+        if (options && options.tab) {
95
+            this.currentTab = options.tab;
96
+        }
97
+    },
98
+    computed: {
99
+        filteredItems() {
100
+            if (this.currentTab === 'all') {
101
+                return this.allItems;
102
+            }
103
+            return this.allItems.filter(item => item.status === this.currentTab);
104
+        }
105
+    },
106
+    methods: {
107
+        goBack() {
108
+            uni.navigateBack();
109
+        },
110
+        switchTab(tabValue) {
111
+            this.currentTab = tabValue;
112
+        },
113
+        getStatusClass(status) {
114
+            switch(status) {
115
+                case 'expired':
116
+                    return 'expired';
117
+                case 'twoWeeks':
118
+                    return 'urgent';
119
+                case 'oneMonth':
120
+                    return 'normal';
121
+                default:
122
+                    return '';
123
+            }
124
+        }
125
+    }
126
+}
127
+</script>
128
+
129
+<style scoped>
130
+.self-check-page {
131
+    width: 100%;
132
+    min-height: 100vh;
133
+    background-color: #fff;
134
+}
135
+
136
+.header-section {
137
+    display: flex;
138
+    align-items: center;
139
+    padding: 20px 16px;
140
+    position: relative;
141
+}
142
+
143
+.back-button {
144
+    width: 40px;
145
+    height: 40px;
146
+    display: flex;
147
+    align-items: center;
148
+    justify-content: center;
149
+    position: absolute;
150
+    left: 16px;
151
+}
152
+
153
+.back-icon {
154
+    font-size: 36px;
155
+    color: #333;
156
+    font-weight: bold;
157
+}
158
+
159
+.page-title {
160
+    flex: 1;
161
+    text-align: center;
162
+    font-size: 36px;
163
+    font-weight: bold;
164
+    color: #333;
165
+}
166
+
167
+.tabs-section {
168
+    display: flex;
169
+    padding: 16px;
170
+    border-bottom: 1px solid #f0f0f0;
171
+}
172
+
173
+.tab-item {
174
+    margin-right: 32px;
175
+    padding: 8px 0;
176
+    position: relative;
177
+}
178
+
179
+.tab-item:last-child {
180
+    margin-right: 0;
181
+}
182
+
183
+.tab-text {
184
+    font-size: 24px;
185
+    color: #999;
186
+}
187
+
188
+.tab-item.active .tab-text {
189
+    color: #409eff;
190
+    font-weight: bold;
191
+}
192
+
193
+.tab-item.active::after {
194
+    content: '';
195
+    position: absolute;
196
+    bottom: 0;
197
+    left: 0;
198
+    right: 0;
199
+    height: 3px;
200
+    background-color: #409eff;
201
+}
202
+
203
+.cards-section {
204
+    padding: 16px;
205
+}
206
+
207
+.check-card {
208
+    background-color: #fff;
209
+    border-radius: 16px;
210
+    padding: 20px;
211
+    margin-bottom: 24px;
212
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
213
+    display: flex;
214
+    justify-content: space-between;
215
+    align-items: flex-start;
216
+}
217
+
218
+.card-content {
219
+    flex: 1;
220
+    display: flex;
221
+    flex-direction: column;
222
+    gap: 16px;
223
+}
224
+
225
+.card-row {
226
+    display: flex;
227
+    justify-content: space-between;
228
+    align-items: center;
229
+}
230
+
231
+.card-label {
232
+    font-size: 24px;
233
+    color: #333;
234
+}
235
+
236
+.card-date {
237
+    font-size: 24px;
238
+    color: #333;
239
+}
240
+
241
+.card-role {
242
+    font-size: 24px;
243
+    color: #333;
244
+}
245
+
246
+.status-tag {
247
+    padding: 8px 20px;
248
+    border-radius: 0 16px 16px 0;
249
+    font-size: 24px;
250
+    font-weight: bold;
251
+    margin: -20px -20px -20px 0;
252
+    align-self: flex-end;
253
+}
254
+
255
+.status-tag.expired {
256
+    background-color: #e8c8c7;
257
+    color: #8c3c34;
258
+}
259
+
260
+.status-tag.urgent {
261
+    background-color: #ffd1d0;
262
+    color: #ff291f;
263
+}
264
+
265
+.status-tag.normal {
266
+    background-color: #e8d4bc;
267
+    color: #b97c45;
268
+}
269
+</style>