Browse Source

feat: 新增质控分析报告和消息推送功能

新增质控分析报告页面及相关组件,包含勤务组织、质控活动、风险隐患和抽问抽答四个分析模块
新增消息推送功能,包括首页消息推送组件和详情页面
新增违禁品查获上报表单中的常见违禁品选项和描述字段
添加相关API接口和静态资源图片
huoyi 5 days ago
parent
commit
13ebfc4870

+ 8 - 0
src/api/home-new/home-new.js

@@ -131,3 +131,11 @@ export function getConcealmentPositionTop1(query) {
131 131
     params: query
132 132
   })
133 133
 }
134
+//获取查获消息推送数据
135
+export function getPushMessage(query) {
136
+  return request({
137
+    url: '/item/seizure/push/message',
138
+    method: 'get',
139
+    params: query
140
+  })
141
+}

+ 212 - 0
src/api/qualityControlAnalysisReport/qualityControlAnalysisReport.js

@@ -0,0 +1,212 @@
1
+import request from '@/utils/request'
2
+
3
+// 质控活动
4
+// 勤务组织相关接口
5
+export function getAnalysisReport(data) {
6
+  return request({
7
+    url: '/system/analysisReport/check',
8
+    method: 'post',
9
+    data: data
10
+  })
11
+}
12
+
13
+// 出勤人次分析
14
+export function getCalculate(data) {
15
+  return request({
16
+    url: '/quality/attendance/calculate',
17
+    method: 'post',
18
+    data: data
19
+  })
20
+}
21
+
22
+// 出勤人次趋势数据
23
+export function getCalculateTrendData(data) {
24
+  return request({
25
+    url: '/quality/attendance/trend-data',
26
+    method: 'post',
27
+    data: data
28
+  })
29
+}
30
+
31
+// 获取资质等级分布饼状图数据
32
+export function getQualificationPieChart(query) {
33
+  return request({
34
+    url: '/statistics/qualification/pie-chart',
35
+    method: 'get',
36
+    params: query
37
+  })
38
+}
39
+
40
+// 获取资质等级分布柱状图数据
41
+export function getQualificationBarChart(query) {
42
+  return request({
43
+    url: '/statistics/qualification/bar-chart',
44
+    method: 'get',
45
+    params: query
46
+  })
47
+}
48
+
49
+// 质控活动相关接口
50
+// 获取物品分类统计
51
+export function getCategoryStats(query) {
52
+  return request({
53
+    url: '/quality/item-category-stats/category-stats',
54
+    method: 'get',
55
+    params: query
56
+  })
57
+}
58
+
59
+// 获取查获时段趋势图
60
+export function getSeizureTimeTrend(query) {
61
+  return request({
62
+    url: '/quality/item-category-stats/seizure-time-trend',
63
+    method: 'get',
64
+    params: query
65
+  })
66
+}
67
+
68
+// 获取隐匿夹带部位分布统计
69
+export function getConcealmentPositionStats(query) {
70
+  return request({
71
+    url: '/quality/item-category-stats/concealment-position-stats',
72
+    method: 'get',
73
+    params: query
74
+  })
75
+}
76
+
77
+// 获取岗位分类统计
78
+export function getPostCategoryStats(query) {
79
+  return request({
80
+    url: '/quality/item-category-stats/post-category-stats',
81
+    method: 'get',
82
+    params: query
83
+  })
84
+}
85
+
86
+// 获取通道排名统计
87
+export function getChannelRankingStats(query) {
88
+  return request({
89
+    url: '/quality/item-category-stats/channel-ranking-stats',
90
+    method: 'get',
91
+    params: query
92
+  })
93
+}
94
+
95
+// 获取各科室查获排名
96
+export function getDepartmentRanking(query) {
97
+  return request({
98
+    url: '/quality/item-category-stats/brigade-ranking',
99
+    method: 'get',
100
+    params: query
101
+  })
102
+}
103
+
104
+// 风险隐患相关接口
105
+// 移交公安数据
106
+export function getPoliceData(params = {}) {
107
+  return request({
108
+    url: '/quality/item-category-stats/police-data',
109
+    method: 'get',
110
+    params
111
+  })
112
+}
113
+
114
+// 移交公安数据统计
115
+export function getPoliceDataStats(params = {}) {
116
+  return request({
117
+    url: '/quality/item-category-stats/police-stats',
118
+    method: 'get',
119
+    params
120
+  })
121
+}
122
+
123
+// X光机漏检数据
124
+export function getXrayMissCheck(params = {}) {
125
+  return request({
126
+    url: '/quality/item-category-stats/xray-miss-check',
127
+    method: 'get',
128
+    params
129
+  })
130
+}
131
+
132
+// X光机漏检人员统计TOP3
133
+export function getXrayMissCheckStats(params = {}) {
134
+  return request({
135
+    url: '/quality/item-category-stats/xray-miss-check-top3',
136
+    method: 'get',
137
+    params
138
+  })
139
+}
140
+
141
+// 异常查获数据
142
+export function getAbnormalSeizureData(params = {}) {
143
+  return request({
144
+    url: '/quality/item-category-stats/abnormal-seizure-data',
145
+    method: 'get',
146
+    params
147
+  })
148
+}
149
+
150
+// 异常查获数据TOP3
151
+export function getAbnormalSeizureStats(params = {}) {
152
+  return request({
153
+    url: '/quality/item-category-stats/abnormal-seizure-data-top3',
154
+    method: 'get',
155
+    params
156
+  })
157
+}
158
+
159
+// 抽问抽答相关接口
160
+// 抽问抽答完成趋势
161
+export function getCompletionTrend(params = {}) {
162
+  return request({
163
+    url: '/v1/cs/app/daily-exam/completion-comparison',
164
+    method: 'get',
165
+    params
166
+  })
167
+}
168
+
169
+// 错题分析 - 总体问题分布
170
+export function getWrongAnalysisOverview(params = {}) {
171
+  return request({
172
+    url: '/v1/cs/app/daily-exam/wrong-analysis/pc-overview',
173
+    method: 'get',
174
+    params
175
+  })
176
+}
177
+
178
+// 错题分析 - 问题分布对比(雷达图)
179
+export function getWrongAnalysisRadar(params = {}) {
180
+  return request({
181
+    url: '/v1/cs/app/daily-exam/wrong-analysis/pc-radar',
182
+    method: 'get',
183
+    params
184
+  })
185
+}
186
+
187
+// 基于时间维度的绩效统计查询趋势图
188
+export function getCalculateByTime(data) {
189
+  return request({
190
+    url: '/performance/dimension/calculate-by-time',
191
+    method: 'post',
192
+    data: data
193
+  })
194
+}
195
+
196
+// 基于时间维度的绩效统计查询列表
197
+export function getCalculateByTimeList(data) {
198
+  return request({
199
+    url: '/performance/dimension/calculate-by-time-list',
200
+    method: 'post',
201
+    data: data
202
+  })
203
+}
204
+
205
+// 使用报告
206
+export function getUsageReport(query) {
207
+  return request({
208
+    url: '/system/usageReport/report',
209
+    method: 'get',
210
+    params: query
211
+  })
212
+}

+ 12 - 0
src/pages.json

@@ -261,6 +261,18 @@
261 261
       }
262 262
     },
263 263
     {
264
+      "path": "pages/messagePush/index",
265
+      "style": {
266
+        "navigationBarTitleText": "消息推送"
267
+      }
268
+    },
269
+    {
270
+      "path": "pages/qualityControlAnalysisReport/index",
271
+      "style": {
272
+        "navigationBarTitleText": "质控分析报告"
273
+      }
274
+    },
275
+    {
264 276
       "path": "pages/questionStatistics/index",
265 277
       "style": {
266 278
         "navigationBarTitleText": "抽问抽答统计"

+ 135 - 0
src/pages/home-new/components/messagePush.vue

@@ -0,0 +1,135 @@
1
+<template>
2
+    <div class="tips" @click="navigateToMessagePush">
3
+        <img alt="" src="@/static/images/messagePush.png">
4
+        <div class="message-container">
5
+            <div class="message-line message-title">近15天违禁品查获情况群体消息推送</div>
6
+            <div class="message-line">{{ prohibitedItemText }}</div>
7
+            <div class="message-line">{{ concealmentPositionText }}</div>
8
+            <div class="message-line">{{ highRiskChannelText }}</div>
9
+        </div>
10
+    </div>
11
+</template>
12
+
13
+<script>
14
+import { getPushMessage } from '@/api/home-new/home-new';
15
+export default {
16
+    name: 'MessagePush',
17
+    data() {
18
+        return {
19
+            concealmentPositionsTop3: [],
20
+            highRiskChannelsTop3: [],
21
+            prohibitedItemsTop3: [],
22
+        }
23
+    },
24
+    computed: {
25
+        prohibitedItemText() {
26
+            if (!this.prohibitedItemsTop3 || this.prohibitedItemsTop3.length === 0) {
27
+                return '移交公安TOP1:火种/打火机';
28
+            }
29
+            const item = this.prohibitedItemsTop3[0];
30
+            const nameOne = item.categoryNameOne || '';
31
+            const nameTwo = item.categoryNameTwo || '';
32
+            return `移交公安TOP1:${nameOne}${nameTwo ? '/' + nameTwo : ''}`;
33
+        },
34
+        concealmentPositionText() {
35
+            if (!this.concealmentPositionsTop3 || this.concealmentPositionsTop3.length === 0) {
36
+                return '藏匿部位TOP1:随身行礼物品';
37
+            }
38
+            const item = this.concealmentPositionsTop3[0];
39
+            const nameOne = item.positionNameOne || '';
40
+            const nameTwo = item.positionNameTwo || '';
41
+            return `藏匿部位TOP1:${nameOne}${nameTwo ? '/' + nameTwo : ''}`;
42
+        },
43
+        highRiskChannelText() {
44
+            if (!this.highRiskChannelsTop3 || this.highRiskChannelsTop3.length === 0) {
45
+                return '高发通道TOP1:T1航站楼/a区域/a01';
46
+            }
47
+            const item = this.highRiskChannelsTop3[0];
48
+            const terminalName = item.terminalName || '';
49
+            const areaName = item.areaName || '';
50
+            const channelName = item.channelName || '';
51
+            let text = '高发通道TOP1:';
52
+            if (terminalName) text += terminalName;
53
+            if (areaName) text += '/' + areaName;
54
+            if (channelName) text += '/' + channelName;
55
+            return text;
56
+        }
57
+    },
58
+    mounted() {
59
+        this.fetchPushMessage();
60
+    },
61
+    methods: {
62
+        // 获取推送消息数据
63
+        async fetchPushMessage() {
64
+            try {
65
+                const response = await getPushMessage();
66
+                console.log('推送消息响应:', response);
67
+                if (response && response.data) {
68
+                    const { concealmentPositionsTop3, highRiskChannelsTop3, prohibitedItemsTop3 } = response.data;
69
+                    this.concealmentPositionsTop3 = concealmentPositionsTop3 || [];
70
+                    this.highRiskChannelsTop3 = highRiskChannelsTop3 || [];
71
+                    this.prohibitedItemsTop3 = prohibitedItemsTop3 || [];
72
+                }
73
+            } catch (error) {
74
+                console.error('获取推送消息失败:', error);
75
+            }
76
+        },
77
+        
78
+        // 跳转到消息推送详情页面
79
+        navigateToMessagePush() {
80
+            uni.navigateTo({
81
+                url: '/pages/messagePush/index'
82
+            });
83
+        }
84
+    }
85
+}
86
+</script>
87
+
88
+<style lang="scss" scoped>
89
+.tips {
90
+    display: flex;
91
+    align-items: center;
92
+    padding: 20rpx 28rpx;
93
+    background: #FFF4F4;
94
+    border-radius: 32rpx;
95
+    font-size: 24rpx;
96
+    color: #222222;
97
+    line-height: 32rpx;
98
+    margin-bottom: 32rpx;
99
+    cursor: pointer;
100
+    transition: all 0.3s ease;
101
+
102
+    img {
103
+        width: 66rpx;
104
+        padding-right: 28rpx;
105
+        border-right: 1px solid rgba(0, 0, 0, 0.1);
106
+        margin-right: 28rpx;
107
+        flex-shrink: 0;
108
+        margin-top: 4rpx;
109
+    }
110
+
111
+    .message-container {
112
+        flex: 1;
113
+        display: flex;
114
+        flex-direction: column;
115
+        gap: 8rpx;
116
+    }
117
+
118
+    .message-line {
119
+        white-space: nowrap;
120
+        overflow: hidden;
121
+        text-overflow: ellipsis;
122
+    }
123
+
124
+    .message-title {
125
+        font-weight: bold;
126
+        font-size: 26rpx;
127
+    }
128
+
129
+    // 鼠标悬停效果
130
+    &:hover {
131
+        background: #FFE8E8;
132
+        transform: scale(1.01);
133
+    }
134
+}
135
+</style>

+ 114 - 0
src/pages/home-new/components/reportCarousel.vue

@@ -0,0 +1,114 @@
1
+<template>
2
+  <view class="report-carousel">
3
+    <swiper class="swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500" circular>
4
+      <!-- 月度报告 -->
5
+      <swiper-item class="swiper-item" @click="navigateToReport('MONTH')">
6
+        <view class="carousel-content">
7
+          <image class="carousel-image" src="/static/images/monthReport.png" mode="aspectFill" />
8
+          <view class="image-tag">{{ monthReportTime }}</view>
9
+        </view>
10
+      </swiper-item>
11
+
12
+      <!-- 季度报告 -->
13
+      <swiper-item class="swiper-item" @click="navigateToReport('QUARTER')">
14
+        <view class="carousel-content">
15
+          <image class="carousel-image" src="/static/images/quarterReport.png" mode="aspectFill" />
16
+          <view class="image-tag">{{ quarterReportTime }}</view>
17
+        </view>
18
+      </swiper-item>
19
+
20
+      <!-- 年度报告 -->
21
+      <swiper-item class="swiper-item" @click="navigateToReport('YEAR')">
22
+        <view class="carousel-content">
23
+          <image class="carousel-image" src="/static/images/yearReport.png" mode="aspectFill" />
24
+          <view class="image-tag">{{ yearReportTime }}</view>
25
+        </view>
26
+      </swiper-item>
27
+    </swiper>
28
+  </view>
29
+</template>
30
+
31
+<script>
32
+export default {
33
+  name: 'ReportCarousel',
34
+  computed: {
35
+    // 月度报告时间 - 上个月(2026年3月)
36
+    monthReportTime() {
37
+      const now = new Date()
38
+      const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
39
+      const year = lastMonth.getFullYear()
40
+      const month = lastMonth.getMonth() + 1
41
+      return `${year}年${month}月`
42
+    },
43
+    // 季度报告时间 - 上一季度(2026年第一季度)  
44
+    quarterReportTime() {
45
+      const now = new Date()
46
+      const currentQuarter = Math.floor((now.getMonth()) / 3) + 1
47
+      const lastQuarter = currentQuarter === 1 ? 4 : currentQuarter - 1
48
+      const year = currentQuarter === 1 ? now.getFullYear() - 1 : now.getFullYear()
49
+      return `${year}年第${lastQuarter}季度`
50
+    },
51
+    // 年度报告时间 - 上一年(2025年)
52
+    yearReportTime() {
53
+      const now = new Date()
54
+      const lastYear = now.getFullYear() - 1
55
+      return `${lastYear}年`
56
+    }
57
+  },
58
+  methods: {
59
+    // 跳转到质控分析报告页面
60
+    navigateToReport(dateRangeQueryType) {
61
+      uni.navigateTo({
62
+        url: `/pages/qualityControlAnalysisReport/index?dateRangeQueryType=${dateRangeQueryType}`
63
+      })
64
+    }
65
+  }
66
+}
67
+</script>
68
+
69
+<style lang="scss" scoped>
70
+.report-carousel {
71
+  margin-bottom: 20rpx;
72
+  // margin: 0 32rpx 32rpx 32rpx;
73
+  border-radius: 24rpx;
74
+  overflow: hidden;
75
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
76
+}
77
+
78
+.swiper {
79
+  height: 300rpx;
80
+}
81
+
82
+.swiper-item {
83
+  display: flex;
84
+  justify-content: center;
85
+  align-items: center;
86
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
87
+}
88
+
89
+.carousel-content {
90
+  position: relative;
91
+  width: 100%;
92
+  height: 100%;
93
+}
94
+
95
+.carousel-image {
96
+  width: 100%;
97
+  height: 100%;
98
+  object-fit: cover;
99
+}
100
+
101
+// 图片左上角标签样式
102
+.image-tag {
103
+  position: absolute;
104
+  top: 0rpx;
105
+  left: 0rpx;
106
+  background-color: #C5E3FF;
107
+  color: #0A46A7;
108
+  padding: 8rpx 16rpx;
109
+  border-radius: 20rpx 0 20rpx 0;
110
+  font-size: 24rpx;
111
+  font-weight: bold;
112
+  z-index: 10;
113
+}
114
+</style>

+ 7 - 1
src/pages/home-new/index.vue

@@ -31,7 +31,9 @@
31 31
         </div>
32 32
 
33 33
         <div class="content">
34
+            <ReportCarousel />
34 35
             <Notice ref="notice" />
36
+            <MessagePush ref="messagePush" />
35 37
 
36 38
             <!-- 时间范围选择 -->
37 39
             <div class="time-range-section">
@@ -78,6 +80,8 @@ import UserInfo from "@/components/UserInfo.vue";
78 80
 import QualityControlAnalysis from "@/pages/home-new/components/qualityControlAnalysis.vue";
79 81
 import PerformanceAnalysis from "@/pages/home-new/components/performanceAnalysis.vue";
80 82
 import HeadTitle from "@/components/HeadTitle.vue";
83
+import ReportCarousel from "@/pages/home-new/components/reportCarousel.vue";
84
+import MessagePush from "@/pages/home-new/components/messagePush.vue";
81 85
 import Notice from "@/pages/home/components/notice.vue";
82 86
 import SelectTag from "@/components/select-tag/select-tag.vue";
83 87
 import TypeDetail from "@/pages/home-new/components/type-detail.vue";
@@ -96,12 +100,14 @@ export default {
96 100
         UserInfo,
97 101
         HomeContainer,
98 102
         Notice,
103
+        MessagePush,
99 104
         SelectTag,
100 105
         TypeDetail,
101 106
         TotalDetail,
102 107
         QualityControlAnalysis,
103 108
         PerformanceAnalysis,
104
-        uniDatetimePicker
109
+        uniDatetimePicker,
110
+        ReportCarousel,
105 111
     },
106 112
     data() {
107 113
         return {

+ 232 - 0
src/pages/messagePush/index.vue

@@ -0,0 +1,232 @@
1
+<template>
2
+  <HomeContainer :customStyle="{ background: 'none', padding: '0' }">
3
+    <!-- 页面标题 -->
4
+    <view class="page-title">近15天违禁品查获情况群体消息推送</view>
5
+    
6
+    <!-- 违禁品TOP3重点关注提示 -->
7
+    <view class="section">
8
+      <view class="section-title">1. 近15天移交公安违禁品TOP3</view>
9
+      <view class="section-description">
10
+        重点关注违禁品提示:需重点关注的违禁品主要包括{{prohibitedItemTips}},各岗位需严格按照安检标准,对相关违禁品进行重点核查,杜绝漏检。
11
+      </view>
12
+      <StatisticTable 
13
+        :columns="prohibitedItemColumns" 
14
+        :data="prohibitedItemData" 
15
+        :blankData="'0'"
16
+        class="statistic-table" />
17
+    </view>
18
+
19
+    <!-- 藏匿部位TOP3检查提示 -->
20
+    <view class="section">
21
+      <view class="section-title">2. 近15天故意隐匿物品藏匿部位TOP3</view>
22
+      <view class="section-description">
23
+        藏匿部位检查提示:需着重注意{{concealmentTips}}的情况,检查时需细致排查,杜绝隐匿夹带。
24
+      </view>
25
+      <StatisticTable 
26
+        :columns="concealmentPositionColumns" 
27
+        :data="concealmentPositionData" 
28
+        :blankData="'0'"
29
+        class="statistic-table" />
30
+    </view>
31
+
32
+    <!-- 高发通道TOP3管控提示 -->
33
+    <view class="section">
34
+      <view class="section-title">3. 近15天移交公安违禁品高发通道TOP3</view>
35
+      <view class="section-description">
36
+        高发通道管控提示:需着重注意{{highRiskChannelTips}}这三个高发通道,需安排经验丰富的员工上岗,重点关注各通道的安检操作规范,加强相关通道的巡视检查频次,确保安检质量,杜绝违规操作和漏检情况。
37
+      </view>
38
+      <StatisticTable 
39
+        :columns="highRiskChannelColumns" 
40
+        :data="highRiskChannelData" 
41
+        :blankData="'0'"
42
+        class="statistic-table" />
43
+    </view>
44
+  </HomeContainer>
45
+</template>
46
+
47
+<script>
48
+import HomeContainer from '@/components/HomeContainer.vue'
49
+import StatisticTable from '@/components/statistic-table/statistic-table.vue'
50
+import { getPushMessage } from '@/api/home-new/home-new'
51
+
52
+export default {
53
+  name: 'MessagePush',
54
+  components: {
55
+    HomeContainer,
56
+    StatisticTable
57
+  },
58
+  data() {
59
+    return {
60
+      // 违禁品表格列配置
61
+      prohibitedItemColumns: [
62
+        { props: 'rank', title: '排名' },
63
+        { props: 'category', title: '违禁品类别(一二级分类合并)' },
64
+        { props: 'count', title: '查获数量(件)' }
65
+      ],
66
+      // 藏匿部位表格列配置
67
+      concealmentPositionColumns: [
68
+        { props: 'rank', title: '排名' },
69
+        { props: 'position', title: '藏匿部位(一二级分类合并)' },
70
+        { props: 'count', title: '隐匿次数(次)' }
71
+      ],
72
+      // 高发通道表格列配置
73
+      highRiskChannelColumns: [
74
+        { props: 'rank', title: '排名' },
75
+        { props: 'channel', title: '通道编号(航站楼+区域)' },
76
+        { props: 'count', title: '移交公安数量(件)' }
77
+      ],
78
+      // 表格数据
79
+      prohibitedItemData: [],
80
+      concealmentPositionData: [],
81
+      highRiskChannelData: [],
82
+      // 原始数据
83
+      prohibitedItemsTop3: [],
84
+      concealmentPositionsTop3: [],
85
+      highRiskChannelsTop3: []
86
+    }
87
+  },
88
+
89
+  async onLoad() {
90
+    await this.fetchPushMessageData()
91
+  },
92
+
93
+  computed: {
94
+    // 违禁品提示文本
95
+    prohibitedItemTips() {
96
+      if (!this.prohibitedItemsTop3 || this.prohibitedItemsTop3.length === 0) {
97
+        return '';
98
+      }
99
+      
100
+      const items = this.prohibitedItemsTop3.slice(0, 3).map(item => {
101
+        const category = `${item.categoryNameOne || ''}${item.categoryNameTwo ? '/' + item.categoryNameTwo : ''}`;
102
+        return category;
103
+      });
104
+      
105
+      return items.join('、');
106
+    },
107
+    
108
+    // 藏匿部位提示文本
109
+    concealmentTips() {
110
+      if (!this.concealmentPositionsTop3 || this.concealmentPositionsTop3.length === 0) {
111
+        return '';
112
+      }
113
+      
114
+      const positions = this.concealmentPositionsTop3.slice(0, 3).map(item => {
115
+        const position = `${item.positionNameOne || ''}${item.positionNameTwo ? '/' + item.positionNameTwo : ''}`;
116
+        return position;
117
+      });
118
+      
119
+      return positions.join('、');
120
+    },
121
+    
122
+    // 高发通道提示文本
123
+    highRiskChannelTips() {
124
+      if (!this.highRiskChannelsTop3 || this.highRiskChannelsTop3.length === 0) {
125
+        return '';
126
+      }
127
+      
128
+      const channels = this.highRiskChannelsTop3.slice(0, 3).map(item => {
129
+        const channel = `${item.terminalName || ''}${item.areaName ? '/' + item.areaName : ''}${item.channelName ? '/' + item.channelName : ''}`;
130
+        return channel;
131
+      });
132
+      
133
+      return channels.join('、');
134
+    }
135
+  },
136
+
137
+  methods: {
138
+    // 获取推送消息数据
139
+    async fetchPushMessageData() {
140
+      try {
141
+        const response = await getPushMessage()
142
+        console.log('推送消息响应:', response)
143
+        if (response && response.data) {
144
+          const { concealmentPositionsTop3, highRiskChannelsTop3, prohibitedItemsTop3 } = response.data
145
+          
146
+          // 保存原始数据用于计算属性
147
+          this.prohibitedItemsTop3 = prohibitedItemsTop3 || []
148
+          this.concealmentPositionsTop3 = concealmentPositionsTop3 || []
149
+          this.highRiskChannelsTop3 = highRiskChannelsTop3 || []
150
+          
151
+          // 处理违禁品数据
152
+          this.prohibitedItemData = this.formatProhibitedItemsData(this.prohibitedItemsTop3)
153
+          
154
+          // 处理藏匿部位数据
155
+          this.concealmentPositionData = this.formatConcealmentPositionsData(this.concealmentPositionsTop3)
156
+          
157
+          // 处理高发通道数据
158
+          this.highRiskChannelData = this.formatHighRiskChannelsData(this.highRiskChannelsTop3)
159
+        }
160
+      } catch (error) {
161
+        console.error('获取推送消息失败:', error)
162
+      }
163
+    },
164
+
165
+    // 格式化违禁品数据
166
+    formatProhibitedItemsData(items) {
167
+      return items.map((item, index) => ({
168
+        rank: `TOP${index + 1}`,
169
+        category: `${item.categoryNameOne || ''}${item.categoryNameTwo ? '/' + item.categoryNameTwo : ''}`,
170
+        count: item.seizureCount || 0
171
+      }))
172
+    },
173
+
174
+    // 格式化藏匿部位数据
175
+    formatConcealmentPositionsData(positions) {
176
+      return positions.map((item, index) => ({
177
+        rank: `TOP${index + 1}`,
178
+        position: `${item.positionNameOne || ''}${item.positionNameTwo ? '/' + item.positionNameTwo : ''}`,
179
+        count: item.concealmentCount || 0
180
+      }))
181
+    },
182
+
183
+    // 格式化高发通道数据
184
+    formatHighRiskChannelsData(channels) {
185
+      return channels.map((item, index) => ({
186
+        rank: `TOP${index + 1}`,
187
+        channel: `${item.terminalName || ''}${item.areaName ? '/' + item.areaName : ''}${item.channelName ? '/' + item.channelName : ''}`,
188
+        count: item.transferToPoliceCount || 0
189
+      }))
190
+    }
191
+  }
192
+}
193
+</script>
194
+
195
+<style lang="scss" scoped>
196
+.page-title {
197
+  font-size: 36rpx;
198
+  font-weight: bold;
199
+  color: #222222;
200
+  text-align: center;
201
+  margin: 32rpx 0 48rpx 0;
202
+  line-height: 48rpx;
203
+}
204
+
205
+.section {
206
+  margin-bottom: 48rpx;
207
+  padding: 0 32rpx;
208
+}
209
+
210
+.section-title {
211
+  font-size: 32rpx;
212
+  font-weight: bold;
213
+  color: #333333;
214
+  margin-bottom: 16rpx;
215
+  line-height: 44rpx;
216
+}
217
+
218
+.section-description {
219
+  font-size: 28rpx;
220
+  color: #666666;
221
+  line-height: 40rpx;
222
+  margin-bottom: 24rpx;
223
+  background: #f8f9fa;
224
+  padding: 24rpx;
225
+  border-radius: 16rpx;
226
+  
227
+}
228
+
229
+.statistic-table {
230
+  margin-top: 16rpx;
231
+}
232
+</style>

File diff suppressed because it is too large
+ 1130 - 0
src/pages/qualityControlAnalysisReport/components/dutyOrganization.vue


+ 817 - 0
src/pages/qualityControlAnalysisReport/components/qaAnalysis.vue

@@ -0,0 +1,817 @@
1
+<template>
2
+  <view class="qa-analysis mobile-section">
3
+    <!-- 抽问抽答标题 -->
4
+    <view class="section-title">
5
+      <text class="title-text">抽问抽答</text>
6
+    </view>
7
+
8
+    <!-- 三个横向均分的内容区域 -->
9
+    <view class="three-panel-layout">
10
+      <!-- 第一个:答题完成趋势 -->
11
+      <view class="panel-item">
12
+        <view class="panel-header">
13
+          <text class="header-text">答题完成趋势</text>
14
+        </view>
15
+
16
+        <!-- 描述卡片 -->
17
+        <view class="describe-card">
18
+          <view class="describe-content">
19
+            {{ completionTrendDescription }}
20
+          </view>
21
+        </view>
22
+
23
+        <!-- 折线图 -->
24
+        <view class="chart-container">
25
+          <view ref="completionTrendChartRef" class="echarts-chart"></view>
26
+        </view>
27
+      </view>
28
+
29
+      <!-- 第二个:错题分布 -->
30
+      <view class="panel-item">
31
+        <view class="panel-header">
32
+          <text class="header-text">错题分布</text>
33
+        </view>
34
+
35
+        <!-- 描述卡片 -->
36
+        <view class="describe-card">
37
+          <view class="describe-content">
38
+            {{ errorDistributionDescription }}
39
+          </view>
40
+        </view>
41
+
42
+        <!-- 饼图 -->
43
+        <view class="chart-container">
44
+          <view ref="errorDistributionChartRef" class="echarts-chart"></view>
45
+        </view>
46
+      </view>
47
+
48
+      <!-- 第三个:各科错题分布对比 -->
49
+      <view class="panel-item" v-show="isStationType">
50
+        <view class="panel-header">
51
+          <text class="header-text">各科错题分布对比</text>
52
+        </view>
53
+
54
+        <!-- 描述卡片 -->
55
+        <view class="describe-card">
56
+          <view class="describe-content">
57
+            {{ departmentComparisonDescription }}
58
+          </view>
59
+        </view>
60
+
61
+        <!-- 雷达图 -->
62
+        <view class="chart-container">
63
+          <view ref="departmentComparisonChartRef" class="echarts-chart"></view>
64
+        </view>
65
+      </view>
66
+    </view>
67
+  </view>
68
+</template>
69
+
70
+<script>
71
+import * as echarts from 'echarts'
72
+import { getCompletionTrend, getWrongAnalysisOverview, getWrongAnalysisRadar } from '@/api/qualityControlAnalysisReport/qualityControlAnalysisReport'
73
+
74
+export default {
75
+  name: 'QaAnalysis',
76
+  props: {
77
+    queryForm: {
78
+      type: Object,
79
+      default: () => ({})
80
+    }
81
+  },
82
+  data() {
83
+    return {
84
+      completionTrendDescription: '',
85
+      errorDistributionDescription: '',
86
+      departmentComparisonDescription: '',
87
+      loading: false,
88
+      completionTrendChart: null,
89
+      errorDistributionChart: null,
90
+      departmentComparisonChart: null
91
+    }
92
+  },
93
+
94
+  computed: {
95
+    // 计算属性:检查是否为STATION类型
96
+    isStationType() {
97
+      const roles = this.$store.state?.user?.roles || []
98
+
99
+
100
+      return roles.includes('zhijianke') || roles.includes('test')
101
+    }
102
+  },
103
+
104
+  mounted() {
105
+    this.loadData()
106
+    this.$nextTick(() => {
107
+      this.initCharts()
108
+    })
109
+  },
110
+
111
+  beforeDestroy() {
112
+    this.disposeCharts()
113
+  },
114
+
115
+  watch: {
116
+    queryForm: {
117
+      handler() {
118
+        this.loadData()
119
+      },
120
+      deep: true
121
+    },
122
+
123
+  },
124
+
125
+  methods: {
126
+    // 初始化图表
127
+    initCharts() {
128
+      // 答题完成趋势折线图
129
+      if (this.$refs.completionTrendChartRef) {
130
+        this.initCompletionTrendChart()
131
+      }
132
+
133
+      // 错题分布饼图
134
+      if (this.$refs.errorDistributionChartRef) {
135
+        this.initErrorDistributionChart()
136
+      }
137
+
138
+      // 各科错题分布对比雷达图
139
+      if (this.$refs.departmentComparisonChartRef) {
140
+        this.initDepartmentComparisonChart()
141
+      }
142
+    },
143
+
144
+    // 初始化答题完成趋势折线图
145
+    initCompletionTrendChart() {
146
+      const completionTrendOptions = {
147
+        tooltip: {
148
+          trigger: 'axis'
149
+        },
150
+        legend: {
151
+          data: []
152
+        },
153
+        grid: {
154
+          left: '10%',
155
+          right: '8%',
156
+          top: '45%',
157
+          bottom: '10%',
158
+          // containLabel: true
159
+        },
160
+        xAxis: {
161
+          type: 'category',
162
+          boundaryGap: false,
163
+          data: []
164
+        },
165
+        yAxis: {
166
+          type: 'value'
167
+        },
168
+        series: []
169
+      }
170
+
171
+      this.completionTrendChart = this.createEChartsInstance(this.$refs.completionTrendChartRef, completionTrendOptions)
172
+    },
173
+
174
+    // 初始化错题分布饼图
175
+    initErrorDistributionChart() {
176
+      const errorDistributionOptions = {
177
+        tooltip: {
178
+          trigger: 'item',
179
+          formatter: '{a} <br/>{b}: {c} ({d}%)'
180
+        },
181
+        legend: {
182
+          orient: 'horizontal',
183
+
184
+          top: 0
185
+        },
186
+        series: [
187
+          {
188
+            name: '错题分布',
189
+            type: 'pie',
190
+            radius: ['40%', '70%'],
191
+            avoidLabelOverlap: false,
192
+            itemStyle: {
193
+              borderRadius: 10,
194
+              borderColor: '#fff',
195
+              borderWidth: 2
196
+            },
197
+            emphasis: {
198
+              label: {
199
+                show: true,
200
+                fontSize: 12,
201
+                fontWeight: 'bold'
202
+              }
203
+            },
204
+            label: {
205
+              show: true,
206
+              formatter: '{b}\n{c} ({d}%)',
207
+              fontSize: 12,
208
+              fontWeight: 'normal'
209
+            },
210
+            labelLine: {
211
+              show: true,
212
+              length: 10,
213
+              length2: 20
214
+            },
215
+            data: []
216
+          }
217
+        ]
218
+      }
219
+
220
+      this.errorDistributionChart = this.createEChartsInstance(this.$refs.errorDistributionChartRef, errorDistributionOptions)
221
+    },
222
+
223
+    // 初始化各科错题分布对比雷达图
224
+    initDepartmentComparisonChart() {
225
+      const departmentComparisonOptions = {
226
+        tooltip: {
227
+          trigger: 'item'
228
+        },
229
+        legend: {
230
+          show: true,
231
+          orient: 'horizontal',
232
+          top: 0
233
+        },
234
+        // grid: {
235
+          
236
+     
237
+        //   top: '30%',
238
+         
239
+          
240
+        // },
241
+        radar: {
242
+          indicator: [],
243
+          radius: '50%', // 缩小雷达图半径
244
+          center: ['50%', '65%'] // 调整雷达图位置
245
+        },
246
+        series: [
247
+          {
248
+            type: 'radar',
249
+            data: [],
250
+            areaStyle: {}
251
+          }
252
+        ]
253
+      }
254
+
255
+      this.departmentComparisonChart = this.createEChartsInstance(this.$refs.departmentComparisonChartRef, departmentComparisonOptions)
256
+    },
257
+
258
+    // 创建ECharts实例(适配uni-app环境)
259
+    createEChartsInstance(chartRef, options) {
260
+      if (!chartRef) return null
261
+
262
+      // 获取DOM元素
263
+      const chartDom = chartRef.$el || chartRef
264
+      if (!chartDom) return null
265
+
266
+      // 创建ECharts实例
267
+      const chartInstance = echarts.init(chartDom)
268
+
269
+      // 设置初始选项
270
+      if (options) {
271
+        chartInstance.setOption(options)
272
+      }
273
+
274
+      return chartInstance
275
+    },
276
+
277
+    // 销毁图表
278
+    disposeCharts() {
279
+      if (this.completionTrendChart) {
280
+        this.completionTrendChart.dispose()
281
+        this.completionTrendChart = null
282
+      }
283
+      if (this.errorDistributionChart) {
284
+        this.errorDistributionChart.dispose()
285
+        this.errorDistributionChart = null
286
+      }
287
+      if (this.departmentComparisonChart) {
288
+        this.departmentComparisonChart.dispose()
289
+        this.departmentComparisonChart = null
290
+      }
291
+    },
292
+
293
+    // 加载数据
294
+    async loadData() {
295
+      if (this.loading) return
296
+
297
+      this.loading = true
298
+      try {
299
+        // 检查是否为站类型
300
+        this.checkStationType()
301
+
302
+        // 加载答题完成趋势数据
303
+        await this.fetchCompletionTrendData()
304
+
305
+        // 加载错题分布数据
306
+        await this.fetchWrongAnalysisOverviewData()
307
+
308
+        // 如果是站类型,加载各科错题分布对比数据
309
+        if (this.isStationType) {
310
+          await this.fetchWrongAnalysisRadarData()
311
+        }
312
+
313
+      } catch (error) {
314
+        console.error('加载抽问抽答数据失败:', error)
315
+        // 模拟数据作为备选
316
+        this.setMockData()
317
+      } finally {
318
+        this.loading = false
319
+      }
320
+    },
321
+
322
+    // 检查是否为站类型(已废弃,使用computed属性替代)
323
+    checkStationType() {
324
+      // 此方法已废弃,角色判断现在通过computed属性实现
325
+    },
326
+
327
+    // 处理查询参数
328
+    processQueryParams() {
329
+      const processedParams = { ...this.queryForm }
330
+
331
+
332
+
333
+      return {
334
+        ...processedParams,
335
+        scopeType: processedParams.scopedType == 'TEAMS' ? 'TEAM' : processedParams.scopedType,
336
+        scopeId: processedParams.scopedId,
337
+        yearOnYear: '',
338
+        chainRatio: ''
339
+      }
340
+    },
341
+
342
+    // 获取答题完成趋势数据
343
+    async fetchCompletionTrendData() {
344
+      try {
345
+        const processedParams = this.processQueryParams()
346
+        const response = await getCompletionTrend(processedParams)
347
+
348
+        if (response.code === 200) {
349
+          const { processedData, xAxisData } = this.processApiData(response || {})
350
+
351
+          // 生成描述文本
352
+          this.completionTrendDescription = this.generateCompletionTrendDescription(processedData, xAxisData)
353
+
354
+          // 更新图表
355
+          this.updateCompletionTrendChart(processedData, xAxisData)
356
+        }
357
+
358
+      } catch (error) {
359
+        console.error('获取答题完成趋势数据失败:', error)
360
+        throw error
361
+      }
362
+    },
363
+
364
+    // 获取错题分布数据
365
+    async fetchWrongAnalysisOverviewData() {
366
+      try {
367
+        const processedParams = this.processQueryParams()
368
+        const response = await getWrongAnalysisOverview(processedParams)
369
+
370
+
371
+        const pieData = this.processOverviewData(response || {})
372
+
373
+        // 生成描述文本
374
+        this.errorDistributionDescription = this.generateErrorDistributionDescription(pieData)
375
+
376
+        // 更新图表
377
+        this.updateErrorDistributionChart(pieData)
378
+
379
+      } catch (error) {
380
+        console.error('获取错题分布数据失败:', error)
381
+        throw error
382
+      }
383
+    },
384
+
385
+    // 获取各科错题分布对比数据
386
+    async fetchWrongAnalysisRadarData() {
387
+      try {
388
+        const processedParams = this.processQueryParams()
389
+        const response = await getWrongAnalysisRadar(processedParams)
390
+
391
+        // if (response.code === 200) {
392
+        const radarData = this.processRadarData(response || {})
393
+
394
+        // 生成描述文本
395
+        this.departmentComparisonDescription = this.generateDepartmentComparisonDescription(radarData)
396
+
397
+        // 更新图表
398
+        this.updateDepartmentComparisonChart(radarData)
399
+        // }
400
+
401
+      } catch (error) {
402
+        console.error('获取各科错题分布对比数据失败:', error)
403
+        throw error
404
+      }
405
+    },
406
+
407
+    // 处理API数据
408
+    processApiData(apiData) {
409
+      if (!apiData || !apiData.model) {
410
+        return { processedData: [], xAxisData: [] }
411
+      }
412
+
413
+      const { xAxis, series } = apiData.model
414
+
415
+      // 处理x轴数据
416
+      const xAxisData = xAxis || []
417
+
418
+      // 处理系列数据
419
+      const processedData = []
420
+
421
+      if (series && series.length > 0) {
422
+        for (let i = 0; i < series.length; i++) {
423
+          const numericData = series[i].data.map(item => {
424
+            if (typeof item === 'string') {
425
+              return parseFloat(item) || 0
426
+            }
427
+            return Number(item) || 0
428
+          })
429
+
430
+          processedData.push({
431
+            name: series[i].name,
432
+            type: 'line',
433
+            data: numericData,
434
+            symbol: 'circle',
435
+            symbolSize: 6,
436
+            smooth: true,
437
+            lineStyle: {
438
+              width: 3
439
+            }
440
+          })
441
+        }
442
+      }
443
+
444
+      return { processedData, xAxisData }
445
+    },
446
+
447
+    // 处理总体问题分布数据
448
+    processOverviewData(apiData) {
449
+      const { categories } = apiData.model
450
+
451
+      if (!categories || !categories.length) return []
452
+
453
+      return categories.map(category => ({
454
+        id: category.id,
455
+        value: category.count,
456
+        name: category.name
457
+      }))
458
+    },
459
+
460
+    // 处理雷达图数据
461
+    processRadarData(apiData) {
462
+      const { indicators, series } = apiData.model
463
+
464
+      if (!apiData || !indicators || !series) return { indicators: [], series: [] }
465
+
466
+      return { indicators, series }
467
+    },
468
+
469
+    // 生成答题完成趋势描述
470
+    generateCompletionTrendDescription(processedData, xAxisData) {
471
+      if (!processedData.length || !xAxisData.length) {
472
+        return '暂无数据'
473
+      }
474
+
475
+
476
+
477
+      // 找到全站数据
478
+      let stationData = processedData.find(item => item.name === '全站')?.data || []
479
+
480
+      // 找到除了全站之外的其他主管数据
481
+      const otherDepartments = processedData.filter(item => item.name !== '全站')
482
+
483
+      let maxData = null
484
+      let maxDataPercent = '0.00'
485
+
486
+      if (otherDepartments.length > 0) {
487
+        // 如果有其他主管数据,找出完成人数最多的主管
488
+        maxData = otherDepartments.reduce((max, item) => {
489
+          if (item.data[item.data.length - 1] > max.data[max.data.length - 1]) {
490
+            return item
491
+          }
492
+          return max
493
+        }, otherDepartments[0])
494
+
495
+        // 防止除数为0或NaN
496
+        const stationLastValue = stationData[stationData.length - 1] || 0
497
+        if (stationLastValue > 0) {
498
+          maxDataPercent = ((maxData.data[maxData.data.length - 1] / stationLastValue) * 100).toFixed(2)
499
+        } else {
500
+          maxDataPercent = '0.00'
501
+        }
502
+      }
503
+
504
+      // 安全获取数组元素,防止NaN
505
+      const lastValue = stationData[stationData.length - 1] || 0
506
+      const firstValue = stationData[0] || 0
507
+      const secondValue = stationData[1] || 0
508
+
509
+      // 计算同比
510
+      let tongValue = 0
511
+      if (firstValue > 0) {
512
+        tongValue = ((lastValue - firstValue) / firstValue) * 100
513
+      }
514
+      let tong = firstValue === 0 ? '--' : (tongValue > 0 ? '+' : '') + tongValue.toFixed(2)
515
+
516
+      // 计算环比
517
+      let huanValue = 0
518
+      if (secondValue > 0) {
519
+        huanValue = ((lastValue - secondValue) / secondValue) * 100
520
+      }
521
+      let huan = secondValue === 0 ? '--' : (huanValue > 0 ? '+' : '') + huanValue.toFixed(2)
522
+
523
+      // 安全获取maxData数组元素,防止NaN
524
+      let maxDataLastValue = 0
525
+      let maxDataFirstValue = 0
526
+      let maxDataSecondValue = 0
527
+      let maxDataTong = '--'
528
+      let maxDataHuan = '--'
529
+
530
+      if (maxData && maxData.data) {
531
+        maxDataLastValue = maxData.data[maxData.data.length - 1] || 0
532
+        maxDataFirstValue = maxData.data[0] || 0
533
+        maxDataSecondValue = maxData.data[1] || 0
534
+
535
+        // 计算maxData同比
536
+        let maxDataTongValue = 0
537
+        if (maxDataFirstValue > 0) {
538
+          maxDataTongValue = ((maxDataLastValue - maxDataFirstValue) / maxDataFirstValue) * 100
539
+        }
540
+        maxDataTong = maxDataFirstValue === 0 ? '--' : (maxDataTongValue > 0 ? '+' : (maxDataTongValue < 0 ? '' : '')) + maxDataTongValue.toFixed(2)
541
+
542
+        // 计算maxData环比
543
+        let maxDataHuanValue = 0
544
+        if (maxDataSecondValue > 0) {
545
+          maxDataHuanValue = ((maxDataLastValue - maxDataSecondValue) / maxDataSecondValue) * 100
546
+        }
547
+        maxDataHuan = maxDataSecondValue === 0 ? '--' : (maxDataHuanValue > 0 ? '+' : (maxDataHuanValue < 0 ? '' : '')) + maxDataHuanValue.toFixed(2)
548
+      }
549
+
550
+      // 根据查询条件决定显示内容
551
+      let descriptionText = ''
552
+      let totalPerson = stationData[stationData.length - 1] || 0
553
+
554
+      if (maxData) {
555
+        // 有其他主管数据时显示主管信息
556
+        if (this.queryForm.dateRangeQueryType === 'YEAR') {
557
+          const firstYearStr = this.isStationType ? `${maxData.name}共答题${totalPerson}人次,同比${tong}%,` : ''
558
+          const secondYearStr = this.isStationType ? `${maxData.name}共答题${maxData.data[maxData.data.length - 1]}人次,占比最高为${maxDataPercent}%。` : `${maxData.name}完成${maxDataLastValue}人次,同比${maxDataTong}%。`
559
+          // 年查询:只显示同比,隐藏环比
560
+          descriptionText = `${firstYearStr}${secondYearStr}`
561
+        } else {
562
+          const firstNotYearStr = this.isStationType ? `${maxData.name}共答题${totalPerson}人次,同比${tong}%,环比${huan}%,` : ''
563
+          const secondNotYearStr = this.isStationType ? `${maxData.name}共答题${maxData.data[maxData.data.length - 1]}人次,占比最高为${maxDataPercent}%。` : `${maxData.name}完成${maxDataLastValue}人次,同比${maxDataTong}%,环比${maxDataHuan}%。`
564
+          // 季度或月查询:显示同比和环比
565
+          descriptionText = `${firstNotYearStr}${secondNotYearStr}`
566
+        }
567
+      } else {
568
+        // 只有全站数据时显示简化的描述
569
+        if (this.queryForm.dateRangeQueryType === 'YEAR') {
570
+          descriptionText = `${maxData.name}共答题${totalPerson}人次,同比${tong}%。`
571
+        } else {
572
+          descriptionText = `${maxData.name}共答题${totalPerson}人次,同比${tong}%,环比${huan}%。`
573
+        }
574
+      }
575
+
576
+      return descriptionText
577
+    },
578
+
579
+    // 生成错题分布描述
580
+    generateErrorDistributionDescription(pieData) {
581
+      if (!pieData.length) {
582
+        return '暂无数据'
583
+      }
584
+
585
+      const sortedData = pieData.sort((a, b) => b.value - a.value)
586
+      const total = pieData.reduce((acc, cur) => acc + cur.value, 0)
587
+
588
+      const firstPercentage = total > 0 ? ((sortedData[0]?.value / total) * 100).toFixed(2) : '0.00'
589
+      const secondPercentage = total > 0 ? ((sortedData[1]?.value / total) * 100).toFixed(2) : '0.00'
590
+
591
+      return `错题主要集中于:${sortedData[0]?.name || '--'},占比:${firstPercentage}%;其次为:${sortedData[1]?.name || '--'},占比:${secondPercentage}%,为靶向培训、题库优化提供核心依据。`
592
+    },
593
+
594
+    // 生成各科错题分布对比描述
595
+    generateDepartmentComparisonDescription(radarData) {
596
+      if (!radarData.series || !radarData.series.length) {
597
+        return '暂无数据'
598
+      }
599
+
600
+      const series = radarData.series.map(ele => {
601
+        if (ele.data.every(item => item === 0)) {
602
+          return ''
603
+        }
604
+        let maxIndex = ele.data.indexOf(Math.max(...ele.data))
605
+        return `${ele.name}:问题主要集中于${radarData.indicators[maxIndex]}`
606
+      }).filter(element => !!element).join(';')
607
+
608
+      return `${series}${series.length > 0 ? ',' : '暂无问题'},可精准识别主管管理短板,分配质控资源。`
609
+    },
610
+
611
+    // 更新答题完成趋势图表
612
+    updateCompletionTrendChart(processedData, xAxisData) {
613
+      if (this.completionTrendChart) {
614
+        const newOptions = {
615
+          legend: {
616
+            data: processedData.map(item => item.name)
617
+          },
618
+          xAxis: {
619
+            data: xAxisData
620
+          },
621
+          series: processedData
622
+        }
623
+
624
+        this.completionTrendChart.setOption(newOptions)
625
+      }
626
+    },
627
+
628
+    // 更新错题分布图表
629
+    updateErrorDistributionChart(pieData) {
630
+      if (this.errorDistributionChart) {
631
+        const newOptions = {
632
+          series: [
633
+            {
634
+              data: pieData
635
+            }
636
+          ]
637
+        }
638
+
639
+        this.errorDistributionChart.setOption(newOptions)
640
+      }
641
+    },
642
+
643
+    // 更新各科错题分布对比图表
644
+    updateDepartmentComparisonChart(radarData) {
645
+
646
+      if (this.departmentComparisonChart) {
647
+        const maxValue = this.calculateRadarMaxValue(radarData.series)
648
+
649
+        const newOptions = {
650
+          radar: {
651
+            indicator: radarData.indicators.map(indicator => ({
652
+              name: indicator,
653
+              max: maxValue || 100
654
+            }))
655
+          },
656
+          series: [
657
+            {
658
+              type: 'radar',
659
+              data: radarData.series.map(seriesItem => ({
660
+                value: seriesItem.data,
661
+                name: seriesItem.name
662
+              })),
663
+              areaStyle: {}
664
+            }
665
+          ]
666
+        }
667
+
668
+        this.departmentComparisonChart.setOption(newOptions)
669
+      }
670
+    },
671
+
672
+    // 计算雷达图数据的最大值
673
+    calculateRadarMaxValue(radarData) {
674
+      if (!radarData || radarData.length === 0) return 100
675
+
676
+      let maxValue = 0
677
+
678
+      radarData.forEach(series => {
679
+        if (series.data && Array.isArray(series.data)) {
680
+          const seriesMax = Math.max(...series.data)
681
+          if (seriesMax > maxValue) {
682
+            maxValue = seriesMax
683
+          }
684
+        }
685
+      })
686
+
687
+      return maxValue
688
+    },
689
+
690
+    // 设置模拟数据
691
+    setMockData() {
692
+      // 答题完成趋势数据
693
+      const mockCompletionData = {
694
+        processedData: [
695
+          {
696
+            name: '全站',
697
+            type: 'line',
698
+            data: [85, 88, 92, 90, 87, 89, 91, 93, 95, 94, 92, 90],
699
+            symbol: 'circle',
700
+            symbolSize: 6,
701
+            smooth: true,
702
+            lineStyle: { width: 3 }
703
+          }
704
+        ],
705
+        xAxisData: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
706
+      }
707
+
708
+      this.completionTrendDescription = '全站共答题120人次,同比+5.2%,环比+2.1%,旅检一科共答题45人次,占比最高为37.5%。'
709
+      this.updateCompletionTrendChart(mockCompletionData.processedData, mockCompletionData.xAxisData)
710
+
711
+      // 错题分布数据
712
+      const mockPieData = [
713
+        { value: 45.3, name: '操作执行类' },
714
+        { value: 28.7, name: '设施设备类' },
715
+        { value: 15.2, name: '勤务组织类' },
716
+        { value: 8.4, name: '安全知识类' },
717
+        { value: 2.4, name: '其他' }
718
+      ]
719
+
720
+      this.errorDistributionDescription = '错题主要集中于:操作执行类,占比:45.30%;其次为:设施设备类,占比:28.70%,为靶向培训、题库优化提供核心依据。'
721
+      this.updateErrorDistributionChart(mockPieData)
722
+
723
+      // 各科错题分布对比数据
724
+      const mockRadarData = {
725
+        indicators: ['操作执行类', '设施设备类', '勤务组织类', '安全知识类', '其他'],
726
+        series: [
727
+          { name: '旅检一科', data: [85, 45, 30, 60, 10] },
728
+          { name: '旅检二科', data: [40, 80, 55, 70, 15] },
729
+          { name: '旅检三科', data: [35, 50, 90, 45, 20] }
730
+        ]
731
+      }
732
+
733
+      this.departmentComparisonDescription = '旅检一科:问题主要集中于操作执行类;旅检二科:问题主要集中于设施设备类;旅检三科:问题主要集中于勤务组织类,可精准识别主管管理短板,分配质控资源。'
734
+      this.updateDepartmentComparisonChart(mockRadarData)
735
+    }
736
+  }
737
+}
738
+</script>
739
+
740
+<style lang="scss" scoped>
741
+.qa-analysis {
742
+  // padding: 24rpx;
743
+  background: #fff;
744
+  border-radius: 16rpx;
745
+  margin-bottom: 24rpx;
746
+}
747
+
748
+.section-title {
749
+  margin-bottom: 24rpx;
750
+
751
+  .title-text {
752
+    font-size: 32rpx;
753
+    font-weight: bold;
754
+    color: #333;
755
+  }
756
+}
757
+
758
+.three-panel-layout {
759
+  display: flex;
760
+  flex-direction: column;
761
+  gap: 32rpx;
762
+}
763
+
764
+.panel-item {
765
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
766
+  border-radius: 12rpx;
767
+  padding: 20rpx;
768
+  margin-bottom: 16rpx;
769
+}
770
+
771
+.panel-header {
772
+  margin-bottom: 16rpx;
773
+
774
+  .header-text {
775
+    font-size: 28rpx;
776
+    font-weight: 600;
777
+    color: #333;
778
+  }
779
+}
780
+
781
+.describe-card {
782
+  background: #f8f9fa;
783
+  border-radius: 8rpx;
784
+  padding: 16rpx;
785
+  margin-bottom: 16rpx;
786
+
787
+  .describe-content {
788
+    font-size: 24rpx;
789
+    line-height: 1.5;
790
+    color: #666;
791
+  }
792
+}
793
+
794
+.chart-container {
795
+  background: #fff;
796
+  border-radius: 8rpx;
797
+  padding: 16rpx;
798
+  height: 500rpx;
799
+
800
+  .echarts-chart {
801
+    width: 100%;
802
+    height: 100%;
803
+  }
804
+}
805
+
806
+@media (min-width: 768px) {
807
+  .three-panel-layout {
808
+    flex-direction: row;
809
+    flex-wrap: wrap;
810
+  }
811
+
812
+  .panel-item {
813
+    flex: 1 1 calc(33.333% - 32rpx);
814
+    min-width: 300rpx;
815
+  }
816
+}
817
+</style>

File diff suppressed because it is too large
+ 1050 - 0
src/pages/qualityControlAnalysisReport/components/qualityControl.vue


File diff suppressed because it is too large
+ 1131 - 0
src/pages/qualityControlAnalysisReport/components/riskHazard.vue


+ 297 - 0
src/pages/qualityControlAnalysisReport/index.vue

@@ -0,0 +1,297 @@
1
+<template>
2
+  <HomeContainer :customStyle="{ background: 'none' }">
3
+    <!-- 页面标题 -->
4
+    <view class="page-header">
5
+
6
+      <view class="report-type">{{ reportTypeText }}</view>
7
+      <!-- 班组和个人切换按钮 -->
8
+      <SelectTag v-if="isTeamLeader" :tags="roleTags" :selected-value="currentQueryParams.scopedType"
9
+        @change="handleRoleChange" class="role-selector" />
10
+    </view>
11
+
12
+    <!-- 四个分析组件 -->
13
+    <view class="analysis-sections">
14
+      <!-- 勤务组织组件 -->
15
+      <DutyOrganization :query-form="currentQueryParams" />
16
+
17
+      <!-- 质控活动组件 -->
18
+      <QualityControl :query-form="currentQueryParams" />
19
+
20
+      <!-- 风险隐患组件 -->
21
+      <RiskHazard :query-form="currentQueryParams" />
22
+
23
+      <!-- 抽问抽答组件 -->
24
+      <QaAnalysis :query-form="currentQueryParams" />
25
+    </view>
26
+  </HomeContainer>
27
+</template>
28
+
29
+<script>
30
+import HomeContainer from '@/components/HomeContainer.vue'
31
+import DutyOrganization from './components/dutyOrganization.vue'
32
+import QualityControl from './components/qualityControl.vue'
33
+import RiskHazard from './components/riskHazard.vue'
34
+import QaAnalysis from './components/qaAnalysis.vue'
35
+import SelectTag from '@/components/select-tag/select-tag.vue'
36
+// import { getUsageReport } from '@/api/qualityControlAnalysisReport/qualityControlAnalysisReport'
37
+
38
+export default {
39
+  name: 'QualityControlAnalysisReport',
40
+  components: {
41
+    HomeContainer,
42
+    DutyOrganization,
43
+    QualityControl,
44
+    RiskHazard,
45
+    QaAnalysis,
46
+    SelectTag
47
+  },
48
+  data() {
49
+    return {
50
+      // 查询参数
51
+      currentQueryParams: {
52
+        dateRangeQueryType: 'MONTH', // 默认月度
53
+        year: new Date().getFullYear(),
54
+        quarter: '',
55
+        month: new Date().getMonth() + 1,
56
+        scopedType: ''
57
+      },
58
+      // 页面加载状态
59
+      loading: false,
60
+      // 报告数据
61
+      reportData: {},
62
+      // 角色切换相关数据
63
+      roleTags: [
64
+        { label: '班组', value: 'TEAMS' },
65
+        { label: '个人', value: 'USER' }
66
+      ]
67
+    }
68
+  },
69
+
70
+  computed: {
71
+    // 报告类型文本
72
+    reportTypeText() {
73
+      const now = new Date()
74
+      const { dateRangeQueryType } = this.currentQueryParams
75
+
76
+      switch (dateRangeQueryType) {
77
+        case 'MONTH': {
78
+          const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
79
+          const year = lastMonth.getFullYear()
80
+          const month = lastMonth.getMonth() + 1
81
+          return `${year}年${month}月-月度报告`
82
+        }
83
+        case 'QUARTER': {
84
+          const currentQuarter = Math.floor((now.getMonth()) / 3) + 1
85
+          const lastQuarter = currentQuarter === 1 ? 4 : currentQuarter - 1
86
+          const year = currentQuarter === 1 ? now.getFullYear() - 1 : now.getFullYear()
87
+          return `${year}年第${lastQuarter}季度-季度报告`
88
+        }
89
+        case 'YEAR': {
90
+          const lastYear = now.getFullYear() - 1
91
+          return `${lastYear}年-年度报告`
92
+        }
93
+        default:
94
+          return '报告'
95
+      }
96
+    },
97
+
98
+    // 时间范围文本
99
+    timeRangeText() {
100
+      const { dateRangeQueryType, year, quarter, month } = this.currentQueryParams
101
+
102
+      switch (dateRangeQueryType) {
103
+        case 'YEAR':
104
+          return `${year}年`
105
+        case 'QUARTER':
106
+          return `${year}年第${quarter}季度`
107
+        case 'MONTH':
108
+          return `${year}年${month}月`
109
+        default:
110
+          return '未知时间范围'
111
+      }
112
+    },
113
+
114
+    // 计算属性:检查是否为班组长
115
+    isTeamLeader() {
116
+      const roles = this.$store.state?.user?.roles || []
117
+      return roles.includes('banzuzhang')
118
+    }
119
+  },
120
+
121
+  onLoad(options) {
122
+    // 设置查询参数
123
+    this.setQueryParams(options)
124
+
125
+
126
+  },
127
+
128
+  onShow() {
129
+
130
+  },
131
+
132
+  methods: {
133
+    // 设置查询参数
134
+    setQueryParams(options) {
135
+      const now = new Date()
136
+      const currentYear = now.getFullYear()
137
+      const currentMonth = now.getMonth() + 1
138
+      const currentQuarter = Math.ceil(currentMonth / 3)
139
+
140
+      // 从路由参数获取查询类型
141
+      const dateRangeQueryType = options.dateRangeQueryType || 'MONTH'
142
+
143
+      // 根据查询类型设置时间参数
144
+      let year, quarter, month
145
+
146
+      // 优先使用路由参数中的时间值,如果没有则使用默认值(取前一期)
147
+      switch (dateRangeQueryType) {
148
+        case 'YEAR':
149
+          if (options.year) {
150
+            year = parseInt(options.year)
151
+          } else {
152
+            year = currentYear - 1
153
+          }
154
+          quarter = ''
155
+          month = ''
156
+          break
157
+        case 'QUARTER':
158
+          if (options.year) {
159
+            year = parseInt(options.year)
160
+          } else {
161
+            year = currentYear
162
+          }
163
+          if (options.quarter) {
164
+            quarter = parseInt(options.quarter)
165
+          } else {
166
+            quarter = currentQuarter > 1 ? currentQuarter - 1 : 4
167
+            if (currentQuarter === 1) {
168
+              year = currentYear - 1
169
+            }
170
+          }
171
+          month = ''
172
+          break
173
+        case 'MONTH':
174
+        default:
175
+          if (options.year) {
176
+            year = parseInt(options.year)
177
+          } else {
178
+            year = currentYear
179
+          }
180
+          if (options.month) {
181
+            month = parseInt(options.month)
182
+          } else {
183
+            month = currentMonth > 1 ? currentMonth - 1 : 12
184
+            if (currentMonth === 1) {
185
+              year = currentYear - 1
186
+            }
187
+          }
188
+          quarter = ''
189
+          break
190
+      }
191
+
192
+      // 根据查询类型设置yearOnYear和chainRatio参数
193
+      let yearOnYear, chainRatio
194
+      if (dateRangeQueryType === 'YEAR') {
195
+        yearOnYear = true
196
+        chainRatio = undefined
197
+      } else {
198
+        yearOnYear = true
199
+        chainRatio = true
200
+      }
201
+
202
+      // 从store获取当前登录用户的deptId
203
+      const { roles, id } = this.$store.state?.user || {};
204
+      const { deptId, deptType } = this.$store.state?.user?.userInfo || {};
205
+
206
+      let stationType = roles.includes('test') || roles.includes('zhijianke')
207
+      let finalScopedId, finalScopedType
208
+      if (roles.includes('jingli') || roles.includes('xingzheng')) {
209
+        finalScopedId = deptId || ''
210
+        finalScopedType = 'BRIGADE'
211
+      }
212
+      // 如果是班组长,默认选择 TEAMS
213
+      if (roles.includes('banzuzhang')) {
214
+        finalScopedId = deptId || ''
215
+        finalScopedType = 'TEAMS'
216
+      } else {
217
+        // 其他情况按登录角色赋值
218
+        finalScopedId = stationType ? '' : roles.includes('SecurityCheck') ? id : (deptId || '')
219
+        finalScopedType = stationType ? '' : roles.includes('SecurityCheck') ? 'USER' : (deptType || '')
220
+      }
221
+
222
+      // 一次性赋值给currentQueryParams
223
+      this.currentQueryParams = {
224
+        dateRangeQueryType,
225
+        year,
226
+        quarter,
227
+        month,
228
+        scopedId: finalScopedId,
229
+        scopedType: finalScopedType,
230
+        yearOnYear,
231
+        chainRatio
232
+      }
233
+    },
234
+
235
+
236
+
237
+    // 处理角色切换
238
+    handleRoleChange(role) {
239
+
240
+      const { userId, deptId } = this.$store.state?.user?.userInfo || {};
241
+
242
+      if (role === 'TEAMS') {
243
+        this.currentQueryParams.scopedId = deptId;
244
+        this.currentQueryParams.scopedType = 'TEAMS'
245
+      } else {
246
+        this.currentQueryParams.scopedId = userId
247
+        this.currentQueryParams.scopedType = 'USER'
248
+      }
249
+    }
250
+  }
251
+}
252
+</script>
253
+
254
+<style lang="scss" scoped>
255
+.page-header {
256
+  border-radius: 20rpx;
257
+  padding: 32rpx;
258
+  text-align: center;
259
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
260
+  color: #fff;
261
+}
262
+
263
+
264
+
265
+.report-type {
266
+  font-size: 28rpx;
267
+  opacity: 0.9;
268
+}
269
+
270
+.query-section {
271
+  padding: 24rpx 32rpx;
272
+  background: #f8f9fa;
273
+  border-bottom: 1rpx solid #e9ecef;
274
+}
275
+
276
+.query-item {
277
+  display: flex;
278
+  align-items: center;
279
+  margin-bottom: 16rpx;
280
+
281
+  &:last-child {
282
+    margin-bottom: 0;
283
+  }
284
+}
285
+
286
+.query-label {
287
+  font-size: 28rpx;
288
+  color: #666;
289
+  min-width: 120rpx;
290
+}
291
+
292
+.query-value {
293
+  font-size: 28rpx;
294
+  color: #333;
295
+  font-weight: 500;
296
+}
297
+</style>

+ 103 - 57
src/pages/seizedReported/index.vue

@@ -143,6 +143,29 @@
143 143
                     v-model="formData.unit" @change="onUnitChange" name="unit" required /> -->
144 144
                 </view>
145 145
               </uni-forms-item>
146
+              <uni-forms-item label="是否常见违禁品" name="commonContraband" required>
147
+                <radio-group @change="onCommonForbiddenChange" class="radio-group"
148
+                  :class="{ 'disabled-group': isDetailMode }">
149
+                  <label class="radio-item" v-for="(item, index) in commonForbiddenOptions" :key="index">
150
+                    <radio :value="item.value" :checked="formData.commonContraband == item.value" color="#409EFF" />
151
+                    <text>{{ item.text }}</text>
152
+                  </label>
153
+                </radio-group>
154
+              </uni-forms-item>
155
+
156
+              <uni-forms-item v-if="formData.commonContraband == 0" label="违禁品描述" name="contrabandDesc" required>
157
+                <uni-easyinput :disabled="isDetailMode" placeholder="请输入违禁品详细描述" v-model="formData.contrabandDesc" />
158
+              </uni-forms-item>
159
+
160
+              <!-- 违禁品照片 (当选择"否"时显示) -->
161
+              <uni-forms-item
162
+                v-if="(!isDetailMode || isDetailMode && formData.images.length > 0) && formData.commonContraband == 0"
163
+                label="违禁品照片">
164
+                <view style="padding: 0 15px 15px 15px;">
165
+                  <uni-file-picker :disabled="isDetailMode" :readonly="isDetailMode" v-model="formData.images" limit="3"
166
+                    title="最多上传3张" :image-styles="imageStyles" fileMediatype="image" mode="grid" @select="onSelect" />
167
+                </view>
168
+              </uni-forms-item>
146 169
             </uni-collapse-item>
147 170
           </uni-collapse>
148 171
         </view>
@@ -191,7 +214,7 @@
191 214
         </view>
192 215
 
193 216
         <!-- 违禁品照片分组 -->
194
-        <view class="card" v-if="!isDetailMode || isDetailMode && formData.images.length > 0">
217
+        <!-- <view class="card" v-if="!isDetailMode || isDetailMode && formData.images.length > 0">
195 218
           <uni-collapse class="collapse" :accordion="false" :value="['group4']">
196 219
             <uni-collapse-item class="collapse-item" title="违禁品照片 (可选)" name="group4" :show-animation="true">
197 220
               <view style="padding: 0 15px 15px 15px;margin-bottom: 150rpx;">
@@ -200,7 +223,7 @@
200 223
               </view>
201 224
             </uni-collapse-item>
202 225
           </uni-collapse>
203
-        </view>
226
+        </view> -->
204 227
 
205 228
         <!-- 5. 审批历史分组 -->
206 229
         <view class="card" v-if="type !== 'add'">
@@ -305,6 +328,64 @@ export default {
305 328
     isDetailMode() {
306 329
 
307 330
       return !!this.businessId; // 如果有id则为详情模式,否则为新增模式
331
+    },
332
+    // 动态验证规则
333
+    rules() {
334
+      return {
335
+        seizureTime: {
336
+          rules: [{ required: true, errorMessage: '请选择查获时间' }]
337
+        },
338
+        checkMethodText: {
339
+          rules: [{ required: true, errorMessage: '请选择安检位置' }]
340
+        },
341
+        reportTeam: {
342
+          rules: [{ required: true, errorMessage: '请选择上报班组' }]
343
+        },
344
+        powerOnInstruction: {
345
+          rules: [{ required: true, errorMessage: '请选择开机指令' }]
346
+        },
347
+        xrayOperatorId: {
348
+          rules: [{ required: true, errorMessage: '请选择X光开机员' }]
349
+        },
350
+        forbiddenCategory: {
351
+          rules: [{ required: true, errorMessage: '请选择违禁品类别' }]
352
+        },
353
+        forbiddenType: {
354
+          rules: [{ required: true, errorMessage: '请选择违禁品类型' }]
355
+        },
356
+        quantity: {
357
+          rules: [
358
+            { required: true, errorMessage: '请输入数量' },
359
+            { format: 'number', errorMessage: '数量必须为数字' }
360
+          ]
361
+        },
362
+        partCategory: {
363
+          rules: [{ required: true, errorMessage: '请选择部位类别' }]
364
+        },
365
+        unit: {
366
+          rules: [{ required: true, errorMessage: '请选择违禁品数量单位' }]
367
+        },
368
+        partType: {
369
+          rules: [{ required: true, errorMessage: '请选择部位类型' }]
370
+        },
371
+        handlingMethod: {
372
+          rules: [{ required: true, errorMessage: '请选择处理方式' }]
373
+        },
374
+        isActiveConcealment: {
375
+          rules: [{ required: true, errorMessage: '请选择是否有意隐匿' }]
376
+        },
377
+        commonContraband: {
378
+          rules: [{ required: true, errorMessage: '请选择是否常见违禁品' }]
379
+        },
380
+        contrabandDesc: {
381
+          rules: [
382
+            {
383
+              required: this.formData.commonContraband == 0,
384
+              errorMessage: '请填写违禁品描述'
385
+            }
386
+          ]
387
+        }
388
+      }
308 389
     }
309 390
   },
310 391
   data() {
@@ -344,7 +425,8 @@ export default {
344 425
         quantity: '1',
345 426
         unit: '',
346 427
         unitText: '',
347
-
428
+        commonContraband: 1, // 是否常见违禁品:1-是,0-否
429
+        contrabandDesc: '', // 违禁品描述(选择否时必填)
348 430
         // 查获部位
349 431
         partCategory: '',
350 432
         partCategoryText: '',
@@ -386,60 +468,6 @@ export default {
386 468
         flightNumber: '',
387 469
 
388 470
       },
389
-      // 验证规则
390
-      rules: {
391
-        seizureTime: {
392
-          rules: [{ required: true, errorMessage: '请选择查获时间' }]
393
-        },
394
-        checkMethodText: {
395
-          rules: [{ required: true, errorMessage: '请选择安检位置' }]
396
-        },
397
-        reportTeam: {
398
-          rules: [{ required: true, errorMessage: '请选择上报班组' }]
399
-        },
400
-        powerOnInstruction: {
401
-          rules: [{ required: true, errorMessage: '请选择开机指令' }]
402
-        },
403
-        xrayOperatorId: {
404
-          rules: [{ required: true, errorMessage: '请选择X光开机员' }]
405
-        },
406
-        forbiddenCategory: {
407
-          rules: [{ required: true, errorMessage: '请选择违禁品类别' }]
408
-        },
409
-        forbiddenType: {
410
-          rules: [{ required: true, errorMessage: '请选择违禁品类型' }]
411
-        },
412
-        // forbiddenName: {
413
-        //   rules: [{ required: true, errorMessage: '请输入违禁品名称' }]
414
-        // },
415
-        quantity: {
416
-          rules: [
417
-            { required: true, errorMessage: '请输入数量' },
418
-            { format: 'number', errorMessage: '数量必须为数字' }
419
-          ]
420
-        },
421
-        partCategory: {
422
-          rules: [{ required: true, errorMessage: '请选择部位类别' }]
423
-        },
424
-        unit: {
425
-          rules: [{ required: true, errorMessage: '请选择违禁品数量单位' }]
426
-        },
427
-        partType: {
428
-          rules: [{ required: true, errorMessage: '请选择部位类型' }]
429
-        },
430
-        // location: {
431
-        //   rules: [{ required: true, errorMessage: '请选择或输入具体位置' }]
432
-        // },
433
-        handlingMethod: {
434
-          rules: [{ required: true, errorMessage: '请选择处理方式' }]
435
-        },
436
-        isActiveConcealment: {
437
-          rules: [{ required: true, errorMessage: '请选择是否有意隐匿' }]
438
-        },
439
-        // passengerName: {
440
-        //   rules: [{ required: true, errorMessage: '请输入旅客姓名' }]
441
-        // }
442
-      },
443 471
       // 数据选项
444 472
       item_check_method_options: [],   // 检查岗位
445 473
       position_options: [],  // 位置通道
@@ -462,6 +490,11 @@ export default {
462 490
         { text: '指令', value: '0' },
463 491
         { text: '非指令', value: '1' }
464 492
       ],
493
+      // 是否常见违禁品选项
494
+      commonForbiddenOptions: [
495
+        { text: '是', value: 1 },
496
+        { text: '否', value: 0 }
497
+      ],
465 498
       // X光开机员选项
466 499
       xrayOperatorOptions: [],
467 500
       // 其他数据
@@ -530,6 +563,15 @@ export default {
530 563
 
531 564
   },
532 565
   methods: {
566
+    // 处理是否常见违禁品选择变化
567
+    onCommonForbiddenChange(e) {
568
+      this.formData.commonContraband = e.detail.value
569
+      // 如果选择"是",清空违禁品描述
570
+      if (e.detail.value === '1') {
571
+        this.formData.contrabandDesc = ''
572
+      }
573
+    },
574
+
533 575
     // 处理驳回确认
534 576
     // handleRejectConfirm(rejectReason) {
535 577
     //   this.formData.comment = rejectReason
@@ -636,6 +678,8 @@ export default {
636 678
         //   name: img.attachmentName || img.name,
637 679
         //   extname: img.extname
638 680
         // })) : [],
681
+        commonContraband: detailData?.itemSeizureItemsList[0].commonContraband,
682
+        contrabandDesc: detailData?.itemSeizureItemsList[0].contrabandDesc,
639 683
         forbiddenCategory: detailData?.itemSeizureItemsList[0].categoryCodeOne,
640 684
         forbiddenCategoryText: detailData?.itemSeizureItemsList[0].categoryNameOne,
641 685
         forbiddenType: detailData?.itemSeizureItemsList[0].categoryCodeTwo,
@@ -1262,6 +1306,8 @@ export default {
1262 1306
         baseAttachmentList: this.formData.images,
1263 1307
         //暂时不要
1264 1308
         itemCode: '无',
1309
+        commonContraband: Number(this.formData.commonContraband),
1310
+        contrabandDesc: this.formData.contrabandDesc,
1265 1311
         isActiveConcealment: this.formData.isActiveConcealment,
1266 1312
 
1267 1313
         categoryCodeOne: this.formData.forbiddenCategory,

BIN
src/static/images/messagePush.png


BIN
src/static/images/monthReport.png


BIN
src/static/images/quarterReport.png


BIN
src/static/images/yearReport.png


+ 13 - 6
src/store/modules/user.js

@@ -13,6 +13,7 @@ const user = {
13 13
     token: getToken(),
14 14
     id: storage.get(constant.id),
15 15
     name: storage.get(constant.name),
16
+    user: storage.get(constant.user),
16 17
     userInfo: storage.get(constant.userInfo),
17 18
     avatar: storage.get(constant.avatar),
18 19
     roles: storage.get(constant.roles),
@@ -31,6 +32,10 @@ const user = {
31 32
       state.name = name
32 33
       storage.set(constant.name, name)
33 34
     },
35
+    SET_USER: (state, user) => {
36
+      state.user = user
37
+      storage.set(constant.user, user)
38
+    },
34 39
     SET_USERINFO: (state, userInfo) => {
35 40
       state.userInfo = userInfo
36 41
       storage.set(constant.userInfo, userInfo)
@@ -76,15 +81,16 @@ const user = {
76 81
       return new Promise((resolve, reject) => {
77 82
         getInfo().then(res => {
78 83
           const user = res.user
79
-          const userInfo = res.userInfo
80
-		  let avatar = user.avatar || ""
81
-		  if (!isHttp(avatar)) {
84
+
85
+          const userInfo = { ...res.userInfo, deptType: user?.dept?.deptType }
86
+          let avatar = user.avatar || ""
87
+          if (!isHttp(avatar)) {
82 88
             avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
83 89
           }
84 90
           const userid = (isEmpty(user) || isEmpty(user.userId)) ? "" : user.userId
85
-		  const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
86
-		  if (res.roles && res.roles.length > 0) {
87
-        
91
+          const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
92
+          if (res.roles && res.roles.length > 0) {
93
+
88 94
             commit('SET_ROLES', res.roles)
89 95
             commit('SET_PERMISSIONS', res.permissions)
90 96
           } else {
@@ -92,6 +98,7 @@ const user = {
92 98
           }
93 99
           commit('SET_ID', userid)
94 100
           commit('SET_NAME', username)
101
+          commit('SET_USER', user)
95 102
           commit('SET_USERINFO', userInfo)
96 103
           commit('SET_AVATAR', avatar)
97 104
           resolve(res)