Bladeren bron

Merge branch 'dev'

huoyi@samsundot.com 6 dagen geleden
bovenliggende
commit
25c6bb5269
38 gewijzigde bestanden met toevoegingen van 2704 en 546 verwijderingen
  1. 6 5
      src/components/SectionTitle.vue
  2. 32 8
      src/pages.json
  3. 809 0
      src/pages/ai-chat/index.vue
  4. 14 5
      src/pages/components/AreaDistribution.vue
  5. 2 2
      src/pages/components/AttendanceStatus.vue
  6. 35 16
      src/pages/components/DailySeizureChart.vue
  7. 6 6
      src/pages/components/DeptSelector.vue
  8. 8 8
      src/pages/components/DeptStats.vue
  9. 2 2
      src/pages/components/DutyInfo.vue
  10. 125 0
      src/pages/components/EmployeeTreeNode.vue
  11. 7 7
      src/pages/components/GroupMemberTable.vue
  12. 11 8
      src/pages/components/InterceptionDistribution.vue
  13. 20 10
      src/pages/components/ItemDistribution.vue
  14. 45 17
      src/pages/components/MemberBasicDistribution.vue
  15. 66 30
      src/pages/components/MemberPositionDistribution.vue
  16. 9 11
      src/pages/components/PassengerChart.vue
  17. 109 56
      src/pages/components/ProfileRadar.vue
  18. 36 6
      src/pages/components/SecurityTestCharts.vue
  19. 11 8
      src/pages/components/SeizedNumAll.vue
  20. 19 11
      src/pages/components/SeizureInfo.vue
  21. 12 9
      src/pages/components/SupervisionDistribution.vue
  22. 7 7
      src/pages/components/TeamMemberTable.vue
  23. 17 8
      src/pages/components/UnsafeItemsChart.vue
  24. 18 9
      src/pages/components/UnsafePositionChart.vue
  25. 17 8
      src/pages/components/UnsafeTypesChart.vue
  26. 28 30
      src/pages/deptProfile/index.vue
  27. 546 172
      src/pages/employeeProfile/index.vue
  28. 27 29
      src/pages/groupProfile/index.vue
  29. 3 0
      src/pages/home/index.vue
  30. 2 2
      src/pages/login.vue
  31. 499 0
      src/pages/organizationStruct/index.vue
  32. 0 0
      src/pages/organizationStruct/组织架构
  33. 105 0
      src/pages/profileManage/index.vue
  34. 20 27
      src/pages/stationProfile/index.vue
  35. 24 27
      src/pages/teamProfile/index.vue
  36. 7 2
      src/pages/work/index.vue
  37. BIN
      src/static/images/tabbar/ai.png
  38. BIN
      src/static/images/tabbar/ai_.png

+ 6 - 5
src/components/SectionTitle.vue

@@ -26,11 +26,12 @@ export default {
26
 
26
 
27
 <style lang="scss" scoped>
27
 <style lang="scss" scoped>
28
 .section-wrapper {
28
 .section-wrapper {
29
-    background: rgba(45, 42, 85, 0.6);
29
+    background: #fff;
30
     border-radius: 20rpx;
30
     border-radius: 20rpx;
31
     padding: 32rpx;
31
     padding: 32rpx;
32
     margin-bottom: 24rpx;
32
     margin-bottom: 24rpx;
33
-    border: 1rpx solid rgba(167, 139, 250, 0.2);
33
+    border: 1rpx solid #e0e0e0;
34
+    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
34
 }
35
 }
35
 
36
 
36
 .section-header {
37
 .section-header {
@@ -43,7 +44,7 @@ export default {
43
 .section-title {
44
 .section-title {
44
     font-size: 30rpx;
45
     font-size: 30rpx;
45
     font-weight: 600;
46
     font-weight: 600;
46
-    color: #A78BFA;
47
+    color: #333;
47
     position: relative;
48
     position: relative;
48
     padding-left: 16rpx;
49
     padding-left: 16rpx;
49
 
50
 
@@ -55,13 +56,13 @@ export default {
55
         transform: translateY(-50%);
56
         transform: translateY(-50%);
56
         width: 4rpx;
57
         width: 4rpx;
57
         height: 28rpx;
58
         height: 28rpx;
58
-        background: #A78BFA;
59
+        background: #60A5FA;
59
         border-radius: 2rpx;
60
         border-radius: 2rpx;
60
     }
61
     }
61
 }
62
 }
62
 
63
 
63
 .section-header-right {
64
 .section-header-right {
64
     font-size: 24rpx;
65
     font-size: 24rpx;
65
-    color: rgba(255, 255, 255, 0.5);
66
+    color: #666;
66
 }
67
 }
67
 </style>
68
 </style>

+ 32 - 8
src/pages.json

@@ -1,6 +1,13 @@
1
 {
1
 {
2
   "pages": [
2
   "pages": [
3
     {
3
     {
4
+      "path": "pages/ai-chat/index",
5
+      "style": {
6
+        "navigationBarTitleText": "AI数据助手",
7
+        "navigationStyle": "custom"
8
+      }
9
+    },
10
+    {
4
       "path": "pages/login",
11
       "path": "pages/login",
5
       "style": {
12
       "style": {
6
         "navigationBarTitleText": "登录"
13
         "navigationBarTitleText": "登录"
@@ -117,6 +124,12 @@
117
       }
124
       }
118
     },
125
     },
119
     {
126
     {
127
+      "path": "pages/profileManage/index",
128
+      "style": {
129
+        "navigationBarTitleText": "画像管理"
130
+      }
131
+    },
132
+    {
120
       "path": "pages/stationProfile/index",
133
       "path": "pages/stationProfile/index",
121
       "style": {
134
       "style": {
122
         "navigationBarTitleText": "站画像"
135
         "navigationBarTitleText": "站画像"
@@ -134,7 +147,7 @@
134
         "navigationBarTitleText": "班组画像"
147
         "navigationBarTitleText": "班组画像"
135
       }
148
       }
136
     },
149
     },
137
-        {
150
+    {
138
       "path": "pages/groupProfile/index",
151
       "path": "pages/groupProfile/index",
139
       "style": {
152
       "style": {
140
         "navigationBarTitleText": "小组画像"
153
         "navigationBarTitleText": "小组画像"
@@ -315,6 +328,12 @@
315
       }
328
       }
316
     },
329
     },
317
     {
330
     {
331
+      "path": "pages/organizationStruct/index",
332
+      "style": {
333
+        "navigationBarTitleText": "组织架构"
334
+      }
335
+    },
336
+    {
318
       "path": "pages/inspectionChecklist/index",
337
       "path": "pages/inspectionChecklist/index",
319
       "style": {
338
       "style": {
320
         "navigationBarTitleText": "巡视检查列表"
339
         "navigationBarTitleText": "巡视检查列表"
@@ -373,18 +392,23 @@
373
       //   "text": "首页"
392
       //   "text": "首页"
374
       // },
393
       // },
375
       {
394
       {
376
-        "pagePath": "pages/work/index",
377
-        "iconPath": "static/images/tabbar/work.png",
378
-        "selectedIconPath": "static/images/tabbar/work_.png",
379
-        "text": "工作台"
380
-      },
381
-      {
382
         "pagePath": "pages/myToDoList/index",
395
         "pagePath": "pages/myToDoList/index",
383
         "iconPath": "/static/images/tabbar/message.png",
396
         "iconPath": "/static/images/tabbar/message.png",
384
         "selectedIconPath": "/static/images/tabbar/message_.png",
397
         "selectedIconPath": "/static/images/tabbar/message_.png",
385
         "text": "消息"
398
         "text": "消息"
386
       },
399
       },
387
-      
400
+      {
401
+        "pagePath": "pages/ai-chat/index",
402
+        "iconPath": "static/images/tabbar/ai.png",
403
+        "selectedIconPath": "static/images/tabbar/ai_.png",
404
+        "text": "AI问答"
405
+      },
406
+      {
407
+        "pagePath": "pages/work/index",
408
+        "iconPath": "static/images/tabbar/work.png",
409
+        "selectedIconPath": "static/images/tabbar/work_.png",
410
+        "text": "工作台"
411
+      },
388
       {
412
       {
389
         "pagePath": "pages/mine/index",
413
         "pagePath": "pages/mine/index",
390
         "iconPath": "static/images/tabbar/mine.png",
414
         "iconPath": "static/images/tabbar/mine.png",

+ 809 - 0
src/pages/ai-chat/index.vue

@@ -0,0 +1,809 @@
1
+<template>
2
+  <view class="ai-chat-page">
3
+    <!-- 顶部导航 -->
4
+    <view class="nav-bar">
5
+      <view class="back-btn" @click="goBack">
6
+        <text class="back-icon">‹</text>
7
+      </view>
8
+      <text class="nav-title">AI数据助手</text>
9
+      <view class="nav-right" @click="clearChat">
10
+        <text class="clear-btn">清空</text>
11
+      </view>
12
+    </view>
13
+
14
+    <!-- 消息列表 -->
15
+    <scroll-view
16
+      class="msg-list"
17
+      scroll-y
18
+      :scroll-top="scrollTop"
19
+      :scroll-with-animation="true"
20
+      @scrolltolower="onScrollBottom"
21
+    >
22
+      <!-- 欢迎语 -->
23
+      <view v-if="messages.length === 0" class="welcome">
24
+        <image class="welcome-icon" src="/static/images/logo.png" mode="aspectFit" />
25
+        <text class="welcome-title">您好!我是机场安检数据助手</text>
26
+        <text class="welcome-sub">请问有什么可以帮您查询的?</text>
27
+        <view class="example-tags">
28
+          <view
29
+            v-for="tag in exampleTags"
30
+            :key="tag"
31
+            class="tag"
32
+            @click="sendExample(tag)"
33
+          >{{ tag }}</view>
34
+        </view>
35
+      </view>
36
+
37
+      <!-- 消息气泡 -->
38
+      <view
39
+        v-for="(msg, idx) in messages"
40
+        :key="idx"
41
+        :class="['msg-row', msg.role === 'user' ? 'msg-user' : 'msg-assistant']"
42
+      >
43
+        <!-- AI头像 -->
44
+        <view v-if="msg.role === 'assistant'" class="avatar">
45
+          <text class="avatar-text">AI</text>
46
+        </view>
47
+
48
+        <view class="bubble-wrap">
49
+          <!-- 进度提示(流式过程中) -->
50
+          <view v-if="msg.steps && msg.steps.length" class="steps">
51
+            <view v-for="(step, si) in msg.steps" :key="si" :class="['step', step.status]">
52
+              <text class="step-dot">{{ step.status === 'done' ? '✓' : step.status === 'running' ? '…' : '·' }}</text>
53
+              <text class="step-name">{{ step.name }}</text>
54
+              <text v-if="step.duration" class="step-time"> {{ step.duration }}s</text>
55
+            </view>
56
+          </view>
57
+
58
+          <!-- 文字回答(知识库/AI直接回答) -->
59
+          <view v-if="msg.answer" class="bubble knowledge">
60
+            <text class="bubble-text" selectable>{{ msg.answer }}</text>
61
+            <view v-if="msg.sources && msg.sources.length" class="sources">
62
+              <text class="source-label">来源:</text>
63
+              <text class="source-item" v-for="s in msg.sources" :key="s.filename">{{ s.filename }}</text>
64
+            </view>
65
+          </view>
66
+
67
+          <!-- 数据查询结果 -->
68
+          <view v-else-if="msg.role === 'assistant' && !msg.loading" class="bubble data">
69
+            <text v-if="msg.message" class="result-msg" :class="{ error: msg.isError }">{{ msg.message }}</text>
70
+
71
+            <!-- 数据表格 -->
72
+            <view v-if="msg.rows && msg.rows.length" class="data-table">
73
+              <scroll-view scroll-x class="table-scroll">
74
+                <view class="table-head">
75
+                  <text v-for="col in msg.columns" :key="col" class="th">{{ col }}</text>
76
+                </view>
77
+                <view v-for="(row, ri) in msg.rows" :key="ri" :class="['table-row', ri % 2 === 0 ? 'even' : '']">
78
+                  <text v-for="col in msg.columns" :key="col" class="td">{{ row[col] != null ? row[col] : '-' }}</text>
79
+                </view>
80
+              </scroll-view>
81
+            </view>
82
+
83
+            <!-- SQL 折叠 -->
84
+            <view v-if="msg.sql" class="sql-block">
85
+              <view class="sql-toggle" @click="toggleSql(idx)">
86
+                <text class="sql-label">SQL</text>
87
+                <text class="sql-arrow">{{ msg.showSql ? '▲' : '▼' }}</text>
88
+              </view>
89
+              <view v-if="msg.showSql" class="sql-code">
90
+                <text selectable>{{ msg.sql }}</text>
91
+              </view>
92
+            </view>
93
+          </view>
94
+
95
+          <!-- 用户消息气泡 -->
96
+          <view v-if="msg.role === 'user'" class="bubble user">
97
+            <text class="bubble-text" selectable>{{ msg.text }}</text>
98
+          </view>
99
+
100
+          <!-- 加载中 -->
101
+          <view v-if="msg.loading" class="bubble loading">
102
+            <view class="dot-loader">
103
+              <view class="dot" /><view class="dot" /><view class="dot" />
104
+            </view>
105
+          </view>
106
+        </view>
107
+      </view>
108
+
109
+      <!-- 底部留白 -->
110
+      <view style="height: 20rpx;" />
111
+    </scroll-view>
112
+
113
+    <!-- 意图澄清 -->
114
+    <view v-if="clarifyQuestion" class="clarify-bar">
115
+      <text class="clarify-text">{{ clarifyQuestion }}</text>
116
+    </view>
117
+
118
+    <!-- 输入区 -->
119
+    <view class="input-bar">
120
+      <!-- 麦克风按钮(同时支持触摸和鼠标,方便桌面浏览器测试) -->
121
+      <view
122
+        :class="['mic-btn', recording ? 'recording' : '']"
123
+        @touchstart.prevent="onPressStart"
124
+        @touchend.prevent="onPressEnd"
125
+        @touchcancel.prevent="onPressCancel"
126
+        @mousedown.prevent="onMouseDown"
127
+      >
128
+        <text class="mic-icon">{{ recording ? '●' : '🎤' }}</text>
129
+      </view>
130
+
131
+      <input
132
+        v-model="inputText"
133
+        class="input"
134
+        :placeholder="recording ? '正在录音...' : '请输入您的问题...'"
135
+        :disabled="loading || recording"
136
+        confirm-type="send"
137
+        @confirm="sendMessage"
138
+      />
139
+      <view :class="['send-btn', (loading || recording) ? 'disabled' : '']" @click="sendMessage">
140
+        <text class="send-text">{{ loading ? '…' : '发送' }}</text>
141
+      </view>
142
+    </view>
143
+
144
+    <!-- 录音提示浮层 -->
145
+    <view v-if="recording" class="recording-overlay">
146
+      <view class="recording-inner">
147
+        <text class="recording-icon">🎤</text>
148
+        <text class="recording-tip">松手发送,上滑取消</text>
149
+        <view class="recording-waves">
150
+          <view v-for="i in 5" :key="i" class="wave" :style="{ animationDelay: (i * 0.1) + 's' }" />
151
+        </view>
152
+      </view>
153
+    </view>
154
+  </view>
155
+</template>
156
+
157
+<script>
158
+const AI_BASE = 'http://airport-test.samsundot.com:9015'
159
+const AI_URL  = AI_BASE + '/api/query-stream'
160
+const ASR_URL = AI_BASE + '/api/asr'
161
+
162
+export default {
163
+  name: 'AiChat',
164
+  data() {
165
+    return {
166
+      inputText: '',
167
+      messages: [],
168
+      loading: false,
169
+      scrollTop: 0,
170
+      clarifyQuestion: '',
171
+      sessionId: 'app_' + Date.now(),
172
+      history: [],
173
+      exampleTags: ['各班组查获排名', '打火机查获数量', '旅检一部人员资质', '全站党员人数'],
174
+      // 录音相关(Web Audio 直接采集 PCM)
175
+      recording: false,
176
+      pressing: false,
177
+      recordCancelled: false,
178
+      audioCtx: null,
179
+      mediaStream: null,
180
+      sourceNode: null,
181
+      scriptNode: null,
182
+      pcmChunks: [],
183
+      pcmSampleRate: 16000,
184
+    }
185
+  },
186
+  methods: {
187
+    // ============ 按压事件包装 ============
188
+    onMouseDown(e) {
189
+      // 鼠标:松手可能在按钮外,用 window 级监听
190
+      this._winUp = () => this.onPressEnd()
191
+      window.addEventListener('mouseup', this._winUp, { once: true })
192
+      this.onPressStart(e)
193
+    },
194
+    onPressStart(e) {
195
+      if (this.loading || this.pressing) return
196
+      this.pressing = true
197
+      this.startRecord()
198
+    },
199
+    onPressEnd(e) {
200
+      if (!this.pressing) return
201
+      this.pressing = false
202
+      this.stopRecord()
203
+    },
204
+    onPressCancel(e) {
205
+      if (!this.pressing) return
206
+      this.pressing = false
207
+      this.recordCancelled = true
208
+      this.stopRecord()
209
+    },
210
+
211
+    // ============ 录音相关(Web Audio 直接采集原始 PCM)============
212
+    async startRecord() {
213
+      this.recordCancelled = false
214
+      this.pcmChunks = []
215
+
216
+      try {
217
+        const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
218
+        // 异步竞态:若 await 期间已松手,直接放弃
219
+        if (!this.pressing) { stream.getTracks().forEach(t => t.stop()); return }
220
+        this.mediaStream = stream
221
+
222
+        const AudioCtx = window.AudioContext || window.webkitAudioContext
223
+        const ctx = new AudioCtx()
224
+        if (ctx.state === 'suspended') await ctx.resume()
225
+        this.audioCtx = ctx
226
+        this.pcmSampleRate = ctx.sampleRate
227
+
228
+        const source = ctx.createMediaStreamSource(stream)
229
+        const node = ctx.createScriptProcessor(4096, 1, 1)
230
+        node.onaudioprocess = (ev) => {
231
+          // 只要节点还连着就采集(不依赖 recording 标志,避免漏帧)
232
+          this.pcmChunks.push(new Float32Array(ev.inputBuffer.getChannelData(0)))
233
+        }
234
+        source.connect(node)
235
+        node.connect(ctx.destination)
236
+
237
+        this.sourceNode = source
238
+        this.scriptNode = node
239
+        this.recording = true
240
+
241
+        // 若设置过程中已松手,立即停止并提交
242
+        if (!this.pressing) this.stopRecord()
243
+      } catch (err) {
244
+        this.recording = false
245
+        let msg = '无法访问麦克风'
246
+        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
247
+          msg = '当前环境不支持录音(需HTTPS或localhost)'
248
+        } else if (err && err.name === 'NotAllowedError') {
249
+          msg = '麦克风权限被拒绝,请在浏览器允许'
250
+        } else if (err && err.name === 'NotFoundError') {
251
+          msg = '未检测到麦克风设备'
252
+        }
253
+        uni.showToast({ title: msg, icon: 'none', duration: 2500 })
254
+      }
255
+    },
256
+
257
+    _teardownAudio() {
258
+      try { if (this.scriptNode) { this.scriptNode.disconnect(); this.scriptNode.onaudioprocess = null } } catch (e) {}
259
+      try { if (this.sourceNode) this.sourceNode.disconnect() } catch (e) {}
260
+      try { if (this.mediaStream) this.mediaStream.getTracks().forEach(t => t.stop()) } catch (e) {}
261
+      try { if (this.audioCtx) this.audioCtx.close() } catch (e) {}
262
+      this.scriptNode = null
263
+      this.sourceNode = null
264
+      this.mediaStream = null
265
+      this.audioCtx = null
266
+    },
267
+
268
+    stopRecord() {
269
+      if (!this.recording) return
270
+      this.recording = false
271
+      const rate = this.pcmSampleRate
272
+      const chunks = this.pcmChunks
273
+      this._teardownAudio()
274
+      if (!this.recordCancelled) this.submitAudio(chunks, rate)
275
+    },
276
+
277
+    async submitAudio(chunks, srcRate) {
278
+      // 合并所有 PCM 帧
279
+      let total = 0
280
+      for (const c of chunks) total += c.length
281
+      if (total < srcRate * 0.3) {  // 少于0.3秒视为无效
282
+        uni.showToast({ title: '录音太短,请长按说话', icon: 'none' })
283
+        return
284
+      }
285
+      const merged = new Float32Array(total)
286
+      let pos = 0
287
+      for (const c of chunks) { merged.set(c, pos); pos += c.length }
288
+
289
+      uni.showLoading({ title: '识别中...' })
290
+      try {
291
+        const wavBlob = this.encodeWav16k(merged, srcRate)
292
+        const formData = new FormData()
293
+        formData.append('file', wavBlob, 'voice.wav')
294
+
295
+        const resp = await fetch(ASR_URL, { method: 'POST', body: formData })
296
+        const data = await resp.json()
297
+        uni.hideLoading()
298
+        if (data.success && data.text) {
299
+          this.inputText = data.text
300
+          this.$nextTick(() => this.sendMessage())
301
+        } else {
302
+          uni.showToast({ title: data.message || '未识别到内容', icon: 'none' })
303
+        }
304
+      } catch (err) {
305
+        uni.hideLoading()
306
+        uni.showToast({ title: '识别失败:' + (err.message || '请重试'), icon: 'none' })
307
+      }
308
+    },
309
+
310
+    // Float32 PCM → 混(已单声道)→ 重采样16k → 16bit PCM WAV
311
+    encodeWav16k(mono, srcRate) {
312
+      const dstRate = 16000
313
+      const dstLen = Math.round(mono.length * dstRate / srcRate)
314
+      const out = new Float32Array(dstLen)
315
+      const ratio = srcRate / dstRate
316
+      for (let i = 0; i < dstLen; i++) {
317
+        const p = i * ratio
318
+        const idx = Math.floor(p)
319
+        const frac = p - idx
320
+        const s0 = mono[idx] || 0
321
+        const s1 = mono[idx + 1] !== undefined ? mono[idx + 1] : s0
322
+        out[i] = s0 + (s1 - s0) * frac
323
+      }
324
+
325
+      const buffer = new ArrayBuffer(44 + dstLen * 2)
326
+      const view = new DataView(buffer)
327
+      const writeStr = (off, str) => { for (let i = 0; i < str.length; i++) view.setUint8(off + i, str.charCodeAt(i)) }
328
+      writeStr(0, 'RIFF')
329
+      view.setUint32(4, 36 + dstLen * 2, true)
330
+      writeStr(8, 'WAVE')
331
+      writeStr(12, 'fmt ')
332
+      view.setUint32(16, 16, true)
333
+      view.setUint16(20, 1, true)
334
+      view.setUint16(22, 1, true)
335
+      view.setUint32(24, dstRate, true)
336
+      view.setUint32(28, dstRate * 2, true)
337
+      view.setUint16(32, 2, true)
338
+      view.setUint16(34, 16, true)
339
+      writeStr(36, 'data')
340
+      view.setUint32(40, dstLen * 2, true)
341
+      let off = 44
342
+      for (let i = 0; i < dstLen; i++) {
343
+        const s = Math.max(-1, Math.min(1, out[i]))
344
+        view.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
345
+        off += 2
346
+      }
347
+      return new Blob([view], { type: 'audio/wav' })
348
+    },
349
+
350
+    // ============ 导航 ============
351
+    goBack() {
352
+      uni.navigateBack()
353
+    },
354
+
355
+    clearChat() {
356
+      this.messages = []
357
+      this.history = []
358
+      this.clarifyQuestion = ''
359
+      this.sessionId = 'app_' + Date.now()
360
+    },
361
+
362
+    sendExample(text) {
363
+      this.inputText = text
364
+      this.sendMessage()
365
+    },
366
+
367
+    toggleSql(idx) {
368
+      this.$set(this.messages[idx], 'showSql', !this.messages[idx].showSql)
369
+    },
370
+
371
+    onScrollBottom() {},
372
+
373
+    scrollToBottom() {
374
+      this.$nextTick(() => {
375
+        this.scrollTop = 999999
376
+      })
377
+    },
378
+
379
+    getUserId() {
380
+      try {
381
+        const userStr = uni.getStorageSync('userInfo') || uni.getStorageSync('user')
382
+        if (userStr) {
383
+          const u = typeof userStr === 'string' ? JSON.parse(userStr) : userStr
384
+          return String(u.userId || u.user_id || u.id || 1)
385
+        }
386
+      } catch (e) {}
387
+      return '1'
388
+    },
389
+
390
+    async sendMessage() {
391
+      const question = this.inputText.trim()
392
+      if (!question || this.loading) return
393
+
394
+      this.inputText = ''
395
+      this.clarifyQuestion = ''
396
+      this.loading = true
397
+
398
+      // 添加用户消息
399
+      this.messages.push({ role: 'user', text: question })
400
+
401
+      // 添加 AI 占位消息
402
+      const aiIdx = this.messages.length
403
+      this.messages.push({
404
+        role: 'assistant',
405
+        loading: true,
406
+        steps: [],
407
+        message: '',
408
+        sql: '',
409
+        columns: [],
410
+        rows: [],
411
+        answer: '',
412
+        sources: [],
413
+        showSql: false,
414
+      })
415
+      this.scrollToBottom()
416
+
417
+      try {
418
+        const response = await fetch(AI_URL, {
419
+          method: 'POST',
420
+          headers: { 'Content-Type': 'application/json' },
421
+          body: JSON.stringify({
422
+            question,
423
+            user_id: this.getUserId(),
424
+            llm_type: 'qwen',
425
+            session_id: this.sessionId,
426
+            history: this.history.slice(-3),
427
+          }),
428
+        })
429
+
430
+        if (!response.ok) throw new Error('请求失败 ' + response.status)
431
+
432
+        const reader = response.body.getReader()
433
+        const decoder = new TextDecoder()
434
+        let buffer = ''
435
+
436
+        while (true) {
437
+          const { done, value } = await reader.read()
438
+          if (done) break
439
+
440
+          buffer += decoder.decode(value, { stream: true })
441
+          const lines = buffer.split('\n')
442
+          buffer = lines.pop()
443
+
444
+          for (const line of lines) {
445
+            if (line.startsWith('data: ')) {
446
+              try {
447
+                const data = JSON.parse(line.slice(6))
448
+                this.handleEvent(aiIdx, data)
449
+              } catch (e) {}
450
+            }
451
+          }
452
+        }
453
+      } catch (err) {
454
+        this.$set(this.messages, aiIdx, {
455
+          ...this.messages[aiIdx],
456
+          loading: false,
457
+          isError: true,
458
+          message: '请求失败,请检查网络后重试',
459
+        })
460
+      }
461
+
462
+      this.loading = false
463
+      this.scrollToBottom()
464
+    },
465
+
466
+    handleEvent(aiIdx, data) {
467
+      const msg = this.messages[aiIdx]
468
+
469
+      if (data.step !== undefined && data.name) {
470
+        // 进度步骤
471
+        const steps = [...(msg.steps || [])]
472
+        const si = steps.findIndex(s => s.name === data.name)
473
+        const step = { name: data.name, status: data.status, duration: data.duration }
474
+        if (si >= 0) steps[si] = step
475
+        else steps.push(step)
476
+        this.$set(this.messages, aiIdx, { ...msg, steps })
477
+        return
478
+      }
479
+
480
+      if (data.question) {
481
+        // 意图澄清
482
+        this.clarifyQuestion = data.question
483
+        this.$set(this.messages, aiIdx, { ...msg, loading: false })
484
+        return
485
+      }
486
+
487
+      if (data.columns !== undefined || data.answer !== undefined) {
488
+        // 最终结果
489
+        this.$set(this.messages, aiIdx, {
490
+          ...msg,
491
+          loading: false,
492
+          message: data.message || '',
493
+          sql: data.sql || '',
494
+          columns: data.columns || [],
495
+          rows: data.rows || [],
496
+          answer: data.answer || '',
497
+          sources: data.sources || [],
498
+          isError: !data.success,
499
+          showSql: false,
500
+        })
501
+        // 记录历史
502
+        this.history.push({
503
+          question: (this.messages[aiIdx - 1] && this.messages[aiIdx - 1].text) || '',
504
+          sql: data.sql || '',
505
+        })
506
+        this.scrollToBottom()
507
+      }
508
+    },
509
+  },
510
+}
511
+</script>
512
+
513
+<style lang="scss" scoped>
514
+$primary: #1890ff;
515
+$bg: #f5f6fa;
516
+$bubble-user: #1890ff;
517
+$bubble-ai: #ffffff;
518
+$text-main: #333;
519
+$text-sub: #999;
520
+
521
+.ai-chat-page {
522
+  display: flex;
523
+  flex-direction: column;
524
+  height: 100vh;
525
+  background: $bg;
526
+  font-size: 28rpx;
527
+}
528
+
529
+/* 导航栏 */
530
+.nav-bar {
531
+  display: flex;
532
+  align-items: center;
533
+  padding: 20rpx 30rpx;
534
+  padding-top: calc(20rpx + env(safe-area-inset-top));
535
+  background: #fff;
536
+  border-bottom: 1rpx solid #eee;
537
+  position: sticky;
538
+  top: 0;
539
+  z-index: 10;
540
+}
541
+.back-btn { width: 80rpx; display: flex; align-items: center; }
542
+.back-icon { font-size: 50rpx; color: $primary; line-height: 1; }
543
+.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; color: $text-main; }
544
+.nav-right { width: 80rpx; text-align: right; }
545
+.clear-btn { font-size: 26rpx; color: $text-sub; }
546
+
547
+/* 消息列表 */
548
+.msg-list {
549
+  flex: 1;
550
+  overflow: hidden;
551
+  padding: 20rpx 0;
552
+}
553
+
554
+/* 欢迎区 */
555
+.welcome {
556
+  display: flex;
557
+  flex-direction: column;
558
+  align-items: center;
559
+  padding: 80rpx 40rpx 40rpx;
560
+  gap: 16rpx;
561
+}
562
+.welcome-icon { width: 120rpx; height: 120rpx; border-radius: 24rpx; }
563
+.welcome-title { font-size: 32rpx; font-weight: 600; color: $text-main; }
564
+.welcome-sub { font-size: 26rpx; color: $text-sub; }
565
+.example-tags {
566
+  display: flex;
567
+  flex-wrap: wrap;
568
+  justify-content: center;
569
+  gap: 16rpx;
570
+  margin-top: 20rpx;
571
+}
572
+.tag {
573
+  padding: 12rpx 24rpx;
574
+  background: #fff;
575
+  border: 1rpx solid #d0e8ff;
576
+  border-radius: 40rpx;
577
+  font-size: 24rpx;
578
+  color: $primary;
579
+}
580
+
581
+/* 消息行 */
582
+.msg-row {
583
+  display: flex;
584
+  padding: 16rpx 24rpx;
585
+  gap: 16rpx;
586
+}
587
+.msg-user { flex-direction: row-reverse; }
588
+.msg-assistant { flex-direction: row; }
589
+
590
+/* 头像 */
591
+.avatar {
592
+  width: 72rpx;
593
+  height: 72rpx;
594
+  border-radius: 50%;
595
+  background: $primary;
596
+  display: flex;
597
+  align-items: center;
598
+  justify-content: center;
599
+  flex-shrink: 0;
600
+}
601
+.avatar-text { color: #fff; font-size: 22rpx; font-weight: 700; }
602
+
603
+/* 气泡包裹 */
604
+.bubble-wrap { flex: 1; display: flex; flex-direction: column; gap: 8rpx; max-width: 90%; }
605
+
606
+/* 气泡 */
607
+.bubble {
608
+  border-radius: 16rpx;
609
+  padding: 20rpx 24rpx;
610
+  word-break: break-all;
611
+}
612
+.bubble.user {
613
+  background: $bubble-user;
614
+  border-bottom-right-radius: 4rpx;
615
+  .bubble-text { color: #fff; }
616
+}
617
+.bubble.data, .bubble.knowledge {
618
+  background: $bubble-ai;
619
+  border-bottom-left-radius: 4rpx;
620
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,.06);
621
+}
622
+.bubble-text { line-height: 1.6; color: $text-main; }
623
+
624
+/* 进度步骤 */
625
+.steps {
626
+  display: flex;
627
+  flex-direction: column;
628
+  gap: 6rpx;
629
+  padding: 0 4rpx;
630
+}
631
+.step {
632
+  display: flex;
633
+  align-items: center;
634
+  gap: 8rpx;
635
+  font-size: 22rpx;
636
+  color: $text-sub;
637
+  &.done { color: #52c41a; }
638
+  &.running { color: $primary; }
639
+}
640
+.step-dot { font-size: 20rpx; width: 24rpx; }
641
+.step-time { font-size: 20rpx; }
642
+
643
+/* 结果状态 */
644
+.result-msg {
645
+  font-size: 24rpx;
646
+  color: $text-sub;
647
+  display: block;
648
+  margin-bottom: 12rpx;
649
+  &.error { color: #ff4d4f; }
650
+}
651
+
652
+/* 表格 */
653
+.data-table { margin-top: 8rpx; }
654
+.table-scroll { width: 100%; }
655
+.table-head, .table-row {
656
+  display: flex;
657
+  min-width: max-content;
658
+}
659
+.table-head { background: #f0f7ff; }
660
+.table-row.even { background: #fafafa; }
661
+.th, .td {
662
+  min-width: 120rpx;
663
+  padding: 12rpx 16rpx;
664
+  font-size: 24rpx;
665
+  white-space: nowrap;
666
+  border-bottom: 1rpx solid #f0f0f0;
667
+}
668
+.th { font-weight: 600; color: $primary; }
669
+.td { color: $text-main; }
670
+
671
+/* SQL 折叠 */
672
+.sql-block { margin-top: 12rpx; }
673
+.sql-toggle {
674
+  display: flex;
675
+  align-items: center;
676
+  gap: 8rpx;
677
+  padding: 8rpx 0;
678
+}
679
+.sql-label { font-size: 22rpx; color: $text-sub; background: #f0f0f0; padding: 2rpx 12rpx; border-radius: 6rpx; }
680
+.sql-arrow { font-size: 20rpx; color: $text-sub; }
681
+.sql-code {
682
+  background: #1e1e1e;
683
+  border-radius: 8rpx;
684
+  padding: 16rpx;
685
+  text { font-size: 22rpx; color: #d4d4d4; line-height: 1.5; }
686
+}
687
+
688
+/* 知识库来源 */
689
+.sources { margin-top: 12rpx; padding-top: 12rpx; border-top: 1rpx solid #f0f0f0; }
690
+.source-label { font-size: 22rpx; color: $text-sub; }
691
+.source-item { font-size: 22rpx; color: $primary; }
692
+
693
+/* 加载动画 */
694
+.loading { background: $bubble-ai !important; box-shadow: 0 2rpx 8rpx rgba(0,0,0,.06); }
695
+.dot-loader { display: flex; gap: 8rpx; align-items: center; height: 32rpx; }
696
+.dot {
697
+  width: 12rpx; height: 12rpx; border-radius: 50%; background: #ccc;
698
+  animation: bounce 1.2s infinite ease-in-out;
699
+  &:nth-child(2) { animation-delay: 0.2s; }
700
+  &:nth-child(3) { animation-delay: 0.4s; }
701
+}
702
+@keyframes bounce {
703
+  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
704
+  40% { transform: scale(1); opacity: 1; }
705
+}
706
+
707
+/* 澄清提示 */
708
+.clarify-bar {
709
+  background: #fff7e6;
710
+  border-top: 1rpx solid #ffe58f;
711
+  padding: 16rpx 30rpx;
712
+}
713
+.clarify-text { font-size: 26rpx; color: #d46b08; }
714
+
715
+/* 输入栏 */
716
+.input-bar {
717
+  display: flex;
718
+  align-items: center;
719
+  padding: 16rpx 24rpx;
720
+  padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
721
+  background: #fff;
722
+  border-top: 1rpx solid #eee;
723
+  gap: 16rpx;
724
+  margin-bottom: 93rpx;
725
+}
726
+.input {
727
+  flex: 1;
728
+  height: 72rpx;
729
+  padding: 0 24rpx;
730
+  background: #f5f6fa;
731
+  border-radius: 36rpx;
732
+  font-size: 28rpx;
733
+  color: $text-main;
734
+  border: 1rpx solid #e8e8e8;
735
+}
736
+.send-btn {
737
+  width: 100rpx;
738
+  height: 72rpx;
739
+  background: $primary;
740
+  border-radius: 36rpx;
741
+  display: flex;
742
+  align-items: center;
743
+  justify-content: center;
744
+  &.disabled { background: #ccc; }
745
+}
746
+.send-text { color: #fff; font-size: 26rpx; }
747
+
748
+/* 麦克风按钮 */
749
+.mic-btn {
750
+  width: 72rpx;
751
+  height: 72rpx;
752
+  border-radius: 50%;
753
+  background: #f0f0f0;
754
+  display: flex;
755
+  align-items: center;
756
+  justify-content: center;
757
+  flex-shrink: 0;
758
+  transition: background 0.2s;
759
+  &.recording {
760
+    background: #ff4d4f;
761
+    animation: micPulse 0.8s infinite;
762
+  }
763
+}
764
+.mic-icon { font-size: 32rpx; line-height: 1; }
765
+
766
+/* 用阴影呼吸代替 scale,避免缩放触发 mouseleave */
767
+@keyframes micPulse {
768
+  0%, 100% { box-shadow: 0 0 0 0 rgba(255,77,79,0.5); }
769
+  50% { box-shadow: 0 0 0 12rpx rgba(255,77,79,0); }
770
+}
771
+
772
+/* 录音浮层 */
773
+.recording-overlay {
774
+  position: fixed;
775
+  inset: 0;
776
+  background: rgba(0,0,0,.5);
777
+  display: flex;
778
+  align-items: center;
779
+  justify-content: center;
780
+  z-index: 200;
781
+}
782
+.recording-inner {
783
+  background: #fff;
784
+  border-radius: 24rpx;
785
+  padding: 60rpx 80rpx;
786
+  display: flex;
787
+  flex-direction: column;
788
+  align-items: center;
789
+  gap: 20rpx;
790
+}
791
+.recording-icon { font-size: 80rpx; }
792
+.recording-tip { font-size: 26rpx; color: #666; }
793
+.recording-waves {
794
+  display: flex;
795
+  gap: 8rpx;
796
+  align-items: center;
797
+  height: 50rpx;
798
+}
799
+.wave {
800
+  width: 8rpx;
801
+  background: #1890ff;
802
+  border-radius: 4rpx;
803
+  animation: wave 0.8s infinite ease-in-out alternate;
804
+}
805
+@keyframes wave {
806
+  0%  { height: 10rpx; }
807
+  100%{ height: 50rpx; }
808
+}
809
+</style>

+ 14 - 5
src/pages/components/AreaDistribution.vue

@@ -46,22 +46,31 @@ export default {
46
       const option = {
46
       const option = {
47
         responsive: true,
47
         responsive: true,
48
         maintainAspectRatio: false,
48
         maintainAspectRatio: false,
49
+        tooltip: {
50
+          trigger: 'axis',
51
+          axisPointer: { type: 'shadow' }
52
+        },
53
+        legend: {
54
+          data: ['查获数量'],
55
+          textStyle: { color: '#666', fontSize: 10 },
56
+          top: 0
57
+        },
49
         grid: {
58
         grid: {
50
           left: '15%',
59
           left: '15%',
51
           right: '5%',
60
           right: '5%',
52
           bottom: '15%',
61
           bottom: '15%',
53
-          top: '10%'
62
+          top: '22%'
54
         },
63
         },
55
         xAxis: {
64
         xAxis: {
56
           type: 'category',
65
           type: 'category',
57
           data: this.chartsData.labels,
66
           data: this.chartsData.labels,
58
           axisLine: {
67
           axisLine: {
59
             lineStyle: {
68
             lineStyle: {
60
-              color: 'rgba(255, 255, 255, 0.1)'
69
+              color: 'rgba(0, 0, 0, 0.1)'
61
             }
70
             }
62
           },
71
           },
63
           axisLabel: {
72
           axisLabel: {
64
-            color: 'rgba(255, 255, 255, 0.5)',
73
+            color: '#666',
65
             fontSize: 10,
74
             fontSize: 10,
66
             // rotate: 30
75
             // rotate: 30
67
           }
76
           }
@@ -76,11 +85,11 @@ export default {
76
           },
85
           },
77
           splitLine: {
86
           splitLine: {
78
             lineStyle: {
87
             lineStyle: {
79
-              color: 'rgba(255, 255, 255, 0.05)'
88
+              color: 'rgba(0, 0, 0, 0.05)'
80
             }
89
             }
81
           },
90
           },
82
           axisLabel: {
91
           axisLabel: {
83
-            color: 'rgba(255, 255, 255, 0.5)',
92
+            color: '#666',
84
             fontSize: 10
93
             fontSize: 10
85
           }
94
           }
86
         },
95
         },

+ 2 - 2
src/pages/components/AttendanceStatus.vue

@@ -39,13 +39,13 @@ export default {
39
   .attendance-number {
39
   .attendance-number {
40
     font-size: 48rpx;
40
     font-size: 48rpx;
41
     font-weight: bold;
41
     font-weight: bold;
42
-    color: rgba(255, 255, 255, 0.95);
42
+    color: black;
43
   }
43
   }
44
   
44
   
45
   .attendance-label {
45
   .attendance-label {
46
     margin-top: 8rpx;
46
     margin-top: 8rpx;
47
     font-size: 20rpx;
47
     font-size: 20rpx;
48
-    color: rgba(255, 255, 255, 0.5);
48
+    color: black;
49
     line-height: 1.4;
49
     line-height: 1.4;
50
   }
50
   }
51
 }
51
 }

+ 35 - 16
src/pages/components/DailySeizureChart.vue

@@ -5,7 +5,7 @@
5
       <div class="chart-container" ref="totalChart"></div>
5
       <div class="chart-container" ref="totalChart"></div>
6
     </div>
6
     </div>
7
     <div class="chart-section">
7
     <div class="chart-section">
8
-      <div class="sub-title">部门对比</div>
8
+      <div class="sub-title">{{title}}</div>
9
       <div class="chart-container" ref="deptChart"></div>
9
       <div class="chart-container" ref="deptChart"></div>
10
     </div>
10
     </div>
11
   </div>
11
   </div>
@@ -17,6 +17,10 @@ import * as echarts from 'echarts'
17
 export default {
17
 export default {
18
   name: 'DailySeizureChart',
18
   name: 'DailySeizureChart',
19
   props: {
19
   props: {
20
+    title: {
21
+      type: String,
22
+      default: '部门对比'
23
+    },
20
     chartsData: {
24
     chartsData: {
21
       type: Object,
25
       type: Object,
22
       default: () => ({
26
       default: () => ({
@@ -73,23 +77,33 @@ export default {
73
       const option = {
77
       const option = {
74
         responsive: true,
78
         responsive: true,
75
         maintainAspectRatio: false,
79
         maintainAspectRatio: false,
80
+        tooltip: {
81
+          trigger: 'axis',
82
+          axisPointer: { type: 'shadow' }
83
+        },
84
+        legend: {
85
+          data: ['查获总数'],
86
+          textStyle: { color: '#666', fontSize: 10 },
87
+          top: 0
88
+        },
76
         grid: {
89
         grid: {
77
           left: '10%',
90
           left: '10%',
78
           right: '5%',
91
           right: '5%',
79
-          bottom: '14%',
80
-          top: '10%'
92
+          bottom: '18%',
93
+          top: '22%'
81
         },
94
         },
82
         xAxis: {
95
         xAxis: {
83
           type: 'category',
96
           type: 'category',
84
           data: this.chartsData.total.labels,
97
           data: this.chartsData.total.labels,
85
           axisLine: {
98
           axisLine: {
86
             lineStyle: {
99
             lineStyle: {
87
-              color: 'rgba(255, 255, 255, 0.1)'
100
+              color: 'rgba(0, 0, 0, 0.1)'
88
             }
101
             }
89
           },
102
           },
90
           axisLabel: {
103
           axisLabel: {
91
-            color: 'rgba(255, 255, 255, 0.5)',
92
-            fontSize: 10
104
+            color: '#666',
105
+            fontSize: 10,
106
+            rotate: 30
93
           }
107
           }
94
         },
108
         },
95
         yAxis: {
109
         yAxis: {
@@ -102,11 +116,11 @@ export default {
102
           },
116
           },
103
           splitLine: {
117
           splitLine: {
104
             lineStyle: {
118
             lineStyle: {
105
-              color: 'rgba(255, 255, 255, 0.05)'
119
+              color: 'rgba(0, 0, 0, 0.05)'
106
             }
120
             }
107
           },
121
           },
108
           axisLabel: {
122
           axisLabel: {
109
-            color: 'rgba(255, 255, 255, 0.5)',
123
+            color: '#666',
110
             fontSize: 10
124
             fontSize: 10
111
           }
125
           }
112
         },
126
         },
@@ -152,10 +166,15 @@ export default {
152
       const option = {
166
       const option = {
153
         responsive: true,
167
         responsive: true,
154
         maintainAspectRatio: false,
168
         maintainAspectRatio: false,
169
+        tooltip: {
170
+          trigger: 'axis',
171
+          axisPointer: { type: 'shadow' }
172
+        },
155
         legend: {
173
         legend: {
174
+          type:'scroll',
156
           data: (this.chartsData.dept.deptData || []).map(d => d.name),
175
           data: (this.chartsData.dept.deptData || []).map(d => d.name),
157
           textStyle: {
176
           textStyle: {
158
-            color: 'rgba(255, 255, 255, 0.5)',
177
+            color: '#333',
159
             fontSize: 9
178
             fontSize: 9
160
           },
179
           },
161
           top: 0
180
           top: 0
@@ -175,7 +194,7 @@ export default {
175
             }
194
             }
176
           },
195
           },
177
           axisLabel: {
196
           axisLabel: {
178
-            color: 'rgba(255, 255, 255, 0.5)',
197
+            color: '#333',
179
             fontSize: 10
198
             fontSize: 10
180
           }
199
           }
181
         },
200
         },
@@ -193,7 +212,7 @@ export default {
193
             }
212
             }
194
           },
213
           },
195
           axisLabel: {
214
           axisLabel: {
196
-            color: 'rgba(255, 255, 255, 0.5)',
215
+            color: '#333',
197
             fontSize: 10
216
             fontSize: 10
198
           }
217
           }
199
         },
218
         },
@@ -214,20 +233,20 @@ export default {
214
 .daily-seizure-chart {
233
 .daily-seizure-chart {
215
   .chart-section {
234
   .chart-section {
216
     margin-bottom: 32rpx;
235
     margin-bottom: 32rpx;
217
-    
236
+
218
     &:last-child {
237
     &:last-child {
219
       margin-bottom: 0;
238
       margin-bottom: 0;
220
     }
239
     }
221
   }
240
   }
222
-  
241
+
223
   .sub-title {
242
   .sub-title {
224
     font-size: 24rpx;
243
     font-size: 24rpx;
225
-    color: rgba(255, 255, 255, 0.5);
244
+    color: #666;
226
     margin-bottom: 16rpx;
245
     margin-bottom: 16rpx;
227
   }
246
   }
228
-  
247
+
229
   .chart-container {
248
   .chart-container {
230
-    height:400rpx;
249
+    height:450rpx;
231
   }
250
   }
232
 }
251
 }
233
 </style>
252
 </style>

+ 6 - 6
src/pages/components/DeptSelector.vue

@@ -99,7 +99,7 @@ export default {
99
 
99
 
100
 <style lang="scss" scoped>
100
 <style lang="scss" scoped>
101
 .dept-picker {
101
 .dept-picker {
102
-    background: #1E1B4B;
102
+    background: #fff;
103
     display: flex;
103
     display: flex;
104
     flex-direction: column;
104
     flex-direction: column;
105
     border-radius: 16rpx 16rpx 0 0;
105
     border-radius: 16rpx 16rpx 0 0;
@@ -109,7 +109,7 @@ export default {
109
 
109
 
110
 .picker-header {
110
 .picker-header {
111
     padding: 24rpx 32rpx;
111
     padding: 24rpx 32rpx;
112
-    border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
112
+    border-bottom: 1rpx solid #eee;
113
     display: flex;
113
     display: flex;
114
     justify-content: center;
114
     justify-content: center;
115
     flex-shrink: 0;
115
     flex-shrink: 0;
@@ -118,7 +118,7 @@ export default {
118
 .picker-title {
118
 .picker-title {
119
     font-size: 32rpx;
119
     font-size: 32rpx;
120
     font-weight: 600;
120
     font-weight: 600;
121
-    color: #fff;
121
+    color: #333;
122
 }
122
 }
123
 
123
 
124
 .search-box {
124
 .search-box {
@@ -138,12 +138,12 @@ export default {
138
     align-items: center;
138
     align-items: center;
139
     justify-content: space-between;
139
     justify-content: space-between;
140
     padding: 24rpx 0;
140
     padding: 24rpx 0;
141
-    border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
141
+    border-bottom: 1rpx solid #f0f0f0;
142
 }
142
 }
143
 
143
 
144
 .dept-item-name {
144
 .dept-item-name {
145
     font-size: 28rpx;
145
     font-size: 28rpx;
146
-    color: rgba(255, 255, 255, 0.9);
146
+    color: #333;
147
 }
147
 }
148
 
148
 
149
 .empty-state,
149
 .empty-state,
@@ -152,7 +152,7 @@ export default {
152
     display: flex;
152
     display: flex;
153
     justify-content: center;
153
     justify-content: center;
154
     align-items: center;
154
     align-items: center;
155
-    color: rgba(255, 255, 255, 0.4);
155
+    color: #999;
156
     font-size: 24rpx;
156
     font-size: 24rpx;
157
 }
157
 }
158
 </style>
158
 </style>

+ 8 - 8
src/pages/components/DeptStats.vue

@@ -55,11 +55,12 @@ export default {
55
     align-items: center;
55
     align-items: center;
56
     justify-content: center;
56
     justify-content: center;
57
     padding: 24rpx 16rpx;
57
     padding: 24rpx 16rpx;
58
-    background: linear-gradient(135deg, rgba(33, 33, 58, 0.95), rgba(15, 70, 250, 0.2));
58
+    background: linear-gradient(135deg, #f5f7fa, #e8ecf1);
59
     border-radius: 16rpx;
59
     border-radius: 16rpx;
60
-    border: 1rpx solid rgba(159, 3, 193, 0.3);
60
+    border: 1rpx solid #e0e0e0;
61
     position: relative;
61
     position: relative;
62
     overflow: hidden;
62
     overflow: hidden;
63
+    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
63
 
64
 
64
     &::before {
65
     &::before {
65
         content: '';
66
         content: '';
@@ -68,7 +69,7 @@ export default {
68
         left: 0;
69
         left: 0;
69
         right: 0;
70
         right: 0;
70
         height: 2rpx;
71
         height: 2rpx;
71
-        background: linear-gradient(90deg, #0f46fa, transparent);
72
+        background: linear-gradient(90deg, #60A5FA, transparent);
72
     }
73
     }
73
 
74
 
74
     &::after {
75
     &::after {
@@ -78,21 +79,20 @@ export default {
78
         right: 0;
79
         right: 0;
79
         left: 0;
80
         left: 0;
80
         height: 2rpx;
81
         height: 2rpx;
81
-        background: linear-gradient(90deg, transparent, #9903C1);
82
+        background: linear-gradient(90deg, transparent, #A78BFA);
82
     }
83
     }
83
 }
84
 }
84
 
85
 
85
 .stat-value {
86
 .stat-value {
86
     font-size: 36rpx;
87
     font-size: 36rpx;
87
     font-weight: bold;
88
     font-weight: bold;
88
-    color: #fff;
89
-    text-shadow: 0 0 12rpx rgba(15, 70, 250, 0.8);
89
+    color: #333;
90
     line-height: 1;
90
     line-height: 1;
91
 }
91
 }
92
 
92
 
93
 .stat-label {
93
 .stat-label {
94
     font-size: 20rpx;
94
     font-size: 20rpx;
95
-    color: #a0c4ff;
95
+    color: #666;
96
     margin-top: 8rpx;
96
     margin-top: 8rpx;
97
 }
97
 }
98
 
98
 
@@ -102,7 +102,7 @@ export default {
102
     .dept-name {
102
     .dept-name {
103
         font-size: 28rpx;
103
         font-size: 28rpx;
104
         font-weight: bold;
104
         font-weight: bold;
105
-        color: #fff;
105
+        color: #333;
106
         text-align: center;
106
         text-align: center;
107
     }
107
     }
108
 }
108
 }

+ 2 - 2
src/pages/components/DutyInfo.vue

@@ -38,12 +38,12 @@ export default {
38
   
38
   
39
   .duty-label {
39
   .duty-label {
40
     font-size: 24rpx;
40
     font-size: 24rpx;
41
-    color: rgba(255, 255, 255, 0.5);
41
+    color: black;
42
   }
42
   }
43
   
43
   
44
   .duty-value {
44
   .duty-value {
45
     font-size: 24rpx;
45
     font-size: 24rpx;
46
-    color: rgba(255, 255, 255, 0.9);
46
+    color: black;
47
   }
47
   }
48
 }
48
 }
49
 </style>
49
 </style>

+ 125 - 0
src/pages/components/EmployeeTreeNode.vue

@@ -0,0 +1,125 @@
1
+<template>
2
+    <view class="tree-node">
3
+        <view 
4
+            :class="['node-row', { 'is-dept': isDept, 'is-user': isUser }]"
5
+            @click="handleClick"
6
+        >
7
+            <view class="toggle-icon" v-if="isDept">
8
+                <u-icon :name="expanded ? 'arrow-down' : 'arrow-right'" size="16" color="#999" />
9
+            </view>
10
+            <view class="toggle-icon" v-else>
11
+                <text class="user-dot">●</text>
12
+            </view>
13
+            <text class="node-label">{{ nodeLabel }}</text>
14
+            <u-icon v-if="isUser && nodeId === selectedId" name="checkmark" color="#34D399" size="18" />
15
+        </view>
16
+        <view v-if="isDept && expanded" class="node-children">
17
+            <template v-for="child in nodeChildren">
18
+                <employee-tree-node
19
+                    :key="child.id"
20
+                    :node="child"
21
+                    :expanded-ids="expandedIds"
22
+                    :selected-id="selectedId"
23
+                    @toggle="$emit('toggle', $event)"
24
+                    @select="$emit('select', $event)"
25
+                />
26
+            </template>
27
+        </view>
28
+    </view>
29
+</template>
30
+
31
+<script>
32
+export default {
33
+    name: 'EmployeeTreeNode',
34
+    props: {
35
+        node: {
36
+            type: Object,
37
+            required: true
38
+        },
39
+        expandedIds: {
40
+            type: [Array, Set],
41
+            default: () => []
42
+        },
43
+        selectedId: {
44
+            type: [String, Number],
45
+            default: null
46
+        }
47
+    },
48
+    computed: {
49
+        isDept() {
50
+            const hasChildren = this.node.children && this.node.children.length > 0
51
+            return hasChildren || this.node.nodeType === 'dept'
52
+        },
53
+        isUser() {
54
+            return !this.isDept
55
+        },
56
+        nodeId() {
57
+            return this.node.userId || this.node.id
58
+        },
59
+        nodeLabel() {
60
+            return this.node.nickName || this.node.label || this.node.userName || this.node.name || ''
61
+        },
62
+        nodeChildren() {
63
+            return this.node.children || []
64
+        },
65
+        expanded() {
66
+            if (this.expandedIds instanceof Set) {
67
+                return this.expandedIds.has(this.node.id)
68
+            }
69
+            return this.expandedIds.includes(this.node.id)
70
+        }
71
+    },
72
+    methods: {
73
+        handleClick() {
74
+            if (this.isDept) {
75
+                this.$emit('toggle', this.node.id)
76
+            } else {
77
+                this.$emit('select', {
78
+                    userId: this.nodeId,
79
+                    nickName: this.nodeLabel
80
+                })
81
+            }
82
+        }
83
+    }
84
+}
85
+</script>
86
+
87
+<style lang="scss" scoped>
88
+.tree-node {
89
+    width: 100%;
90
+}
91
+
92
+.node-row {
93
+    display: flex;
94
+    align-items: center;
95
+    padding: 20rpx 32rpx 20rpx 16rpx;
96
+    gap: 12rpx;
97
+
98
+    &.is-dept {
99
+        padding-left: 16rpx;
100
+    }
101
+}
102
+
103
+.toggle-icon {
104
+    width: 40rpx;
105
+    height: 40rpx;
106
+    display: flex;
107
+    align-items: center;
108
+    justify-content: center;
109
+}
110
+
111
+.user-dot {
112
+    font-size: 16rpx;
113
+    color: #999;
114
+}
115
+
116
+.node-label {
117
+    flex: 1;
118
+    font-size: 28rpx;
119
+    color: #333;
120
+}
121
+
122
+.node-children {
123
+    padding-left: 32rpx;
124
+}
125
+</style>

+ 7 - 7
src/pages/components/GroupMemberTable.vue

@@ -52,25 +52,25 @@ export default {
52
   .table-scroll {
52
   .table-scroll {
53
     width: 100%;
53
     width: 100%;
54
   }
54
   }
55
-  
55
+
56
   .data-table {
56
   .data-table {
57
     width: 100%;
57
     width: 100%;
58
     font-size: 24rpx;
58
     font-size: 24rpx;
59
     border-collapse: collapse;
59
     border-collapse: collapse;
60
-    
60
+
61
     th, td {
61
     th, td {
62
       padding: 16rpx 8rpx;
62
       padding: 16rpx 8rpx;
63
       text-align: left;
63
       text-align: left;
64
       white-space: nowrap;
64
       white-space: nowrap;
65
     }
65
     }
66
-    
66
+
67
     th {
67
     th {
68
-      color: rgba(255, 255, 255, 0.5);
69
-      border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
68
+      color: #666;
69
+      border-bottom: 1rpx solid #e0e0e0;
70
     }
70
     }
71
-    
71
+
72
     td {
72
     td {
73
-      color: rgba(255, 255, 255, 0.9);
73
+      color: #333;
74
     }
74
     }
75
   }
75
   }
76
 }
76
 }

+ 11 - 8
src/pages/components/InterceptionDistribution.vue

@@ -45,11 +45,16 @@ export default {
45
             const option = {
45
             const option = {
46
                 responsive: true,
46
                 responsive: true,
47
                 maintainAspectRatio: false,
47
                 maintainAspectRatio: false,
48
+                legend: {
49
+                    data: ['拦截数量'],
50
+                    textStyle: { color: '#666', fontSize: 10 },
51
+                    top: 0
52
+                },
48
                 grid: {
53
                 grid: {
49
                     left: '25%',
54
                     left: '25%',
50
                     right: '10%',
55
                     right: '10%',
51
                     bottom: '10%',
56
                     bottom: '10%',
52
-                    top: '10%'
57
+                    top: '22%'
53
                 },
58
                 },
54
                 xAxis: {
59
                 xAxis: {
55
                     type: 'value',
60
                     type: 'value',
@@ -57,11 +62,11 @@ export default {
57
                     axisTick: { show: false },
62
                     axisTick: { show: false },
58
                     splitLine: {
63
                     splitLine: {
59
                         lineStyle: {
64
                         lineStyle: {
60
-                            color: 'rgba(255, 255, 255, 0.05)'
65
+                            color: 'rgba(0, 0, 0, 0.05)'
61
                         }
66
                         }
62
                     },
67
                     },
63
                     axisLabel: {
68
                     axisLabel: {
64
-                        color: 'rgba(255, 255, 255, 0.5)',
69
+                        color: '#666',
65
                         fontSize: 9
70
                         fontSize: 9
66
                     }
71
                     }
67
                 },
72
                 },
@@ -72,11 +77,11 @@ export default {
72
                     axisTick: { show: false },
77
                     axisTick: { show: false },
73
                     splitLine: {
78
                     splitLine: {
74
                         lineStyle: {
79
                         lineStyle: {
75
-                            color: 'rgba(255, 255, 255, 0.05)'
80
+                            color: 'rgba(0, 0, 0, 0.05)'
76
                         }
81
                         }
77
                     },
82
                     },
78
                     axisLabel: {
83
                     axisLabel: {
79
-                        color: 'rgba(255, 255, 255, 0.6)',
84
+                        color: '#666',
80
                         fontSize: 9,
85
                         fontSize: 9,
81
                         formatter: function (value) {
86
                         formatter: function (value) {
82
                             return value.length > 5 ? value.substring(0, 6) + '...' : value
87
                             return value.length > 5 ? value.substring(0, 6) + '...' : value
@@ -105,9 +110,7 @@ export default {
105
                 ],
110
                 ],
106
                 tooltip: {
111
                 tooltip: {
107
                     trigger: 'axis',
112
                     trigger: 'axis',
108
-                    backgroundColor: 'rgba(13,80,122,0.95)',
109
-                    borderColor: '#70CFE7',
110
-                    textStyle: { color: '#fff' }
113
+                   axisPointer: { type: 'shadow' }
111
                 }
114
                 }
112
             }
115
             }
113
             this.chart.setOption(option)
116
             this.chart.setOption(option)

+ 20 - 10
src/pages/components/ItemDistribution.vue

@@ -1,12 +1,12 @@
1
 <template>
1
 <template>
2
   <div class="item-distribution">
2
   <div class="item-distribution">
3
     <div class="chart-container" ref="itemChart"></div>
3
     <div class="chart-container" ref="itemChart"></div>
4
-    <div class="legend-grid">
4
+    <!-- <div class="legend-grid">
5
       <div v-for="(item, index) in chartsData.items" :key="index" class="legend-item">
5
       <div v-for="(item, index) in chartsData.items" :key="index" class="legend-item">
6
         <div class="legend-dot" :style="{ backgroundColor: item.color }"></div>
6
         <div class="legend-dot" :style="{ backgroundColor: item.color }"></div>
7
         <div class="legend-text">{{ item.name }}: {{ item.value }}</div>
7
         <div class="legend-text">{{ item.name }}: {{ item.value }}</div>
8
       </div>
8
       </div>
9
-    </div>
9
+    </div> -->
10
   </div>
10
   </div>
11
 </template>
11
 </template>
12
 
12
 
@@ -53,9 +53,19 @@ export default {
53
       const option = {
53
       const option = {
54
         responsive: true,
54
         responsive: true,
55
         maintainAspectRatio: false,
55
         maintainAspectRatio: false,
56
+        tooltip: {
57
+          trigger: 'item',
58
+          formatter: '{b}: {c} ({d}%)'
59
+        },
60
+        legend: {
61
+          type: 'scroll',
62
+          data: this.chartsData.items.map(item => item.name),
63
+          textStyle: { color: '#666', fontSize: 10 },
64
+          top: 0
65
+        },
56
         series: [{
66
         series: [{
57
           type: 'pie',
67
           type: 'pie',
58
-          radius: ['50%', '70%'],
68
+          radius: ['30%', '47%'],
59
           data: this.chartsData.items.map(item => ({
69
           data: this.chartsData.items.map(item => ({
60
             name: item.name,
70
             name: item.name,
61
             value: item.value,
71
             value: item.value,
@@ -68,7 +78,7 @@ export default {
68
           },
78
           },
69
           label: {
79
           label: {
70
             show: true,
80
             show: true,
71
-            color: 'rgba(255, 255, 255, 0.7)',
81
+            color: '#333',
72
             fontSize: 10,
82
             fontSize: 10,
73
             formatter: '{b}\n{d}%'
83
             formatter: '{b}\n{d}%'
74
           }
84
           }
@@ -86,30 +96,30 @@ export default {
86
 <style lang="scss" scoped>
96
 <style lang="scss" scoped>
87
 .item-distribution {
97
 .item-distribution {
88
   .chart-container {
98
   .chart-container {
89
-    height: 300rpx;
99
+    height: 450rpx;
90
   }
100
   }
91
-  
101
+
92
   .legend-grid {
102
   .legend-grid {
93
     display: grid;
103
     display: grid;
94
     grid-template-columns: repeat(2, 1fr);
104
     grid-template-columns: repeat(2, 1fr);
95
     gap: 8rpx;
105
     gap: 8rpx;
96
     margin-top: 16rpx;
106
     margin-top: 16rpx;
97
   }
107
   }
98
-  
108
+
99
   .legend-item {
109
   .legend-item {
100
     display: flex;
110
     display: flex;
101
     align-items: center;
111
     align-items: center;
102
     font-size: 24rpx;
112
     font-size: 24rpx;
103
-    
113
+
104
     .legend-dot {
114
     .legend-dot {
105
       width: 12rpx;
115
       width: 12rpx;
106
       height: 12rpx;
116
       height: 12rpx;
107
       border-radius: 50%;
117
       border-radius: 50%;
108
       margin-right: 8rpx;
118
       margin-right: 8rpx;
109
     }
119
     }
110
-    
120
+
111
     .legend-text {
121
     .legend-text {
112
-      color: rgba(255, 255, 255, 0.7);
122
+      color: #666;
113
     }
123
     }
114
   }
124
   }
115
 }
125
 }

+ 45 - 17
src/pages/components/MemberBasicDistribution.vue

@@ -71,9 +71,18 @@ export default {
71
       const option = {
71
       const option = {
72
         responsive: true,
72
         responsive: true,
73
         maintainAspectRatio: false,
73
         maintainAspectRatio: false,
74
+        tooltip: {
75
+          trigger: 'item',
76
+          formatter: '{b}: {c} ({d}%)'
77
+        },
78
+        legend: {
79
+          data: this.chartsData.gender.labels,
80
+          textStyle: { color: '#666', fontSize: 10 },
81
+          top: 0
82
+        },
74
         series: [{
83
         series: [{
75
           type: 'pie',
84
           type: 'pie',
76
-          radius: ['40%', '60%'],
85
+          radius: ['25%', '40%'],
77
           data: this.chartsData.gender.data.map((value, index) => ({
86
           data: this.chartsData.gender.data.map((value, index) => ({
78
             name: this.chartsData.gender.labels[index],
87
             name: this.chartsData.gender.labels[index],
79
             value: value
88
             value: value
@@ -86,7 +95,7 @@ export default {
86
           },
95
           },
87
           label: {
96
           label: {
88
             show: true,
97
             show: true,
89
-            color: 'rgba(255, 255, 255, 0.7)',
98
+            color: '#333',
90
             fontSize: 10,
99
             fontSize: 10,
91
             formatter: '{b}\n{d}%'
100
             formatter: '{b}\n{d}%'
92
           }
101
           }
@@ -100,22 +109,32 @@ export default {
100
       const option = {
109
       const option = {
101
         responsive: true,
110
         responsive: true,
102
         maintainAspectRatio: false,
111
         maintainAspectRatio: false,
112
+        tooltip: {
113
+          trigger: 'item',
114
+          formatter: '{b}: {c} ({d}%)'
115
+        },
116
+        legend: {
117
+          type: 'scroll',
118
+          data: this.chartsData.ethnicity.labels,
119
+          textStyle: { color: '#666', fontSize: 10 },
120
+          top: 0
121
+        },
103
         series: [{
122
         series: [{
104
           type: 'pie',
123
           type: 'pie',
105
-          radius: ['40%', '60%'],
124
+          radius: ['25%', '40%'],
106
           data: this.chartsData.ethnicity.data.map((value, index) => ({
125
           data: this.chartsData.ethnicity.data.map((value, index) => ({
107
             name: this.chartsData.ethnicity.labels[index],
126
             name: this.chartsData.ethnicity.labels[index],
108
             value: value
127
             value: value
109
           })),
128
           })),
110
-          itemStyle: {
111
-            color: function(params) {
112
-              const colors = ['#A78BFA', '#34D399']
113
-              return colors[params.dataIndex]
114
-            }
115
-          },
129
+          // itemStyle: {
130
+          //   color: function(params) {
131
+          //     const colors = ['#A78BFA', '#34D399']
132
+          //     return colors[params.dataIndex]
133
+          //   }
134
+          // },
116
           label: {
135
           label: {
117
             show: true,
136
             show: true,
118
-            color: 'rgba(255, 255, 255, 0.7)',
137
+            color: '#333',
119
             fontSize: 10,
138
             fontSize: 10,
120
             formatter: '{b}\n{d}%'
139
             formatter: '{b}\n{d}%'
121
           }
140
           }
@@ -129,9 +148,18 @@ export default {
129
       const option = {
148
       const option = {
130
         responsive: true,
149
         responsive: true,
131
         maintainAspectRatio: false,
150
         maintainAspectRatio: false,
151
+        tooltip: {
152
+          trigger: 'item',
153
+          formatter: '{b}: {c} ({d}%)'
154
+        },
155
+        legend: {
156
+          data: this.chartsData.political.labels,
157
+          textStyle: { color: '#666', fontSize: 10 },
158
+          top: 0
159
+        },
132
         series: [{
160
         series: [{
133
           type: 'pie',
161
           type: 'pie',
134
-          radius: ['40%', '60%'],
162
+          radius: ['25%', '40%'],
135
           data: this.chartsData.political.data.map((value, index) => ({
163
           data: this.chartsData.political.data.map((value, index) => ({
136
             name: this.chartsData.political.labels[index],
164
             name: this.chartsData.political.labels[index],
137
             value: value
165
             value: value
@@ -144,7 +172,7 @@ export default {
144
           },
172
           },
145
           label: {
173
           label: {
146
             show: true,
174
             show: true,
147
-            color: 'rgba(255, 255, 255, 0.7)',
175
+            color: '#333',
148
             fontSize: 10,
176
             fontSize: 10,
149
             formatter: '{b}\n{d}%'
177
             formatter: '{b}\n{d}%'
150
           }
178
           }
@@ -168,22 +196,22 @@ export default {
168
     flex-direction: column;
196
     flex-direction: column;
169
     gap: 32rpx;
197
     gap: 32rpx;
170
   }
198
   }
171
-  
199
+
172
   .chart-item {
200
   .chart-item {
173
     display: flex;
201
     display: flex;
174
     align-items: center;
202
     align-items: center;
175
     gap: 24rpx;
203
     gap: 24rpx;
176
-    
204
+
177
     .chart-label {
205
     .chart-label {
178
       width: 160rpx;
206
       width: 160rpx;
179
       flex-shrink: 0;
207
       flex-shrink: 0;
180
       font-size: 24rpx;
208
       font-size: 24rpx;
181
-      color: rgba(255, 255, 255, 0.5);
209
+      color: #666;
182
     }
210
     }
183
-    
211
+
184
     .chart-container {
212
     .chart-container {
185
       flex: 1;
213
       flex: 1;
186
-      height:250rpx;
214
+      height:450rpx;
187
     }
215
     }
188
   }
216
   }
189
 }
217
 }

+ 66 - 30
src/pages/components/MemberPositionDistribution.vue

@@ -69,23 +69,33 @@ export default {
69
       const option = {
69
       const option = {
70
         responsive: true,
70
         responsive: true,
71
         maintainAspectRatio: false,
71
         maintainAspectRatio: false,
72
+        tooltip: {
73
+          trigger: 'axis',
74
+          axisPointer: { type: 'shadow' }
75
+        },
76
+        legend: {
77
+          data: ['职业资格等级'],
78
+          textStyle: { color: '#666', fontSize: 10 },
79
+          top: 0
80
+        },
72
         grid: {
81
         grid: {
73
           left: '10%',
82
           left: '10%',
74
           right: '5%',
83
           right: '5%',
75
           bottom: '14%',
84
           bottom: '14%',
76
-          top: '10%'
85
+          top: '22%'
77
         },
86
         },
78
         xAxis: {
87
         xAxis: {
79
           type: 'category',
88
           type: 'category',
80
           data: this.chartsData.qualification.labels,
89
           data: this.chartsData.qualification.labels,
81
           axisLine: {
90
           axisLine: {
82
             lineStyle: {
91
             lineStyle: {
83
-              color: 'rgba(255, 255, 255, 0.1)'
92
+              color: 'rgba(0, 0, 0, 0.1)'
84
             }
93
             }
85
           },
94
           },
86
           axisLabel: {
95
           axisLabel: {
87
-            color: 'rgba(255, 255, 255, 0.5)',
88
-            fontSize: 10
96
+            color: '#666',
97
+            fontSize: 10,
98
+            rotate: 30
89
           }
99
           }
90
         },
100
         },
91
         yAxis: {
101
         yAxis: {
@@ -98,15 +108,17 @@ export default {
98
           },
108
           },
99
           splitLine: {
109
           splitLine: {
100
             lineStyle: {
110
             lineStyle: {
101
-              color: 'rgba(255, 255, 255, 0.05)'
111
+              color: 'rgba(0, 0, 0, 0.05)'
102
             }
112
             }
103
           },
113
           },
104
           axisLabel: {
114
           axisLabel: {
105
-            color: 'rgba(255, 255, 255, 0.5)',
106
-            fontSize: 10
115
+            color: '#666',
116
+            fontSize: 10,
117
+            rotate: 30
107
           }
118
           }
108
         },
119
         },
109
         series: [{
120
         series: [{
121
+          name: '职业资格等级',
110
           type: 'bar',
122
           type: 'bar',
111
           data: this.chartsData.qualification.data,
123
           data: this.chartsData.qualification.data,
112
           itemStyle: {
124
           itemStyle: {
@@ -134,23 +146,34 @@ export default {
134
       const option = {
146
       const option = {
135
         responsive: true,
147
         responsive: true,
136
         maintainAspectRatio: false,
148
         maintainAspectRatio: false,
149
+        tooltip: {
150
+          trigger: 'axis',
151
+          axisPointer: { type: 'shadow' }
152
+        },
153
+        legend: {
154
+          show: true,
155
+          data: ['开机年限'],
156
+          textStyle: { color: '#333', fontSize: 10 },
157
+          top: 0
158
+        },
137
         grid: {
159
         grid: {
138
           left: '10%',
160
           left: '10%',
139
           right: '5%',
161
           right: '5%',
140
           bottom: '14%',
162
           bottom: '14%',
141
-          top: '10%'
163
+          top: '22%'
142
         },
164
         },
143
         xAxis: {
165
         xAxis: {
144
           type: 'category',
166
           type: 'category',
145
           data: this.chartsData.experience.labels,
167
           data: this.chartsData.experience.labels,
146
           axisLine: {
168
           axisLine: {
147
             lineStyle: {
169
             lineStyle: {
148
-              color: 'rgba(255, 255, 255, 0.1)'
170
+              color: 'rgba(0, 0, 0, 0.1)'
149
             }
171
             }
150
           },
172
           },
151
           axisLabel: {
173
           axisLabel: {
152
-            color: 'rgba(255, 255, 255, 0.5)',
153
-            fontSize: 10
174
+            color: '#666',
175
+            fontSize: 10,
176
+            rotate: 30
154
           }
177
           }
155
         },
178
         },
156
         yAxis: {
179
         yAxis: {
@@ -163,15 +186,17 @@ export default {
163
           },
186
           },
164
           splitLine: {
187
           splitLine: {
165
             lineStyle: {
188
             lineStyle: {
166
-              color: 'rgba(255, 255, 255, 0.05)'
189
+              color: 'rgba(0, 0, 0, 0.05)'
167
             }
190
             }
168
           },
191
           },
169
           axisLabel: {
192
           axisLabel: {
170
-            color: 'rgba(255, 255, 255, 0.5)',
171
-            fontSize: 10
193
+            color: '#666',
194
+            fontSize: 10,
195
+            rotate: 30
172
           }
196
           }
173
         },
197
         },
174
         series: [{
198
         series: [{
199
+          name: '开机年限',
175
           type: 'bar',
200
           type: 'bar',
176
           data: this.chartsData.experience.data,
201
           data: this.chartsData.experience.data,
177
           itemStyle: {
202
           itemStyle: {
@@ -199,25 +224,34 @@ export default {
199
       const option = {
224
       const option = {
200
         responsive: true,
225
         responsive: true,
201
         maintainAspectRatio: false,
226
         maintainAspectRatio: false,
227
+        tooltip: {
228
+          trigger: 'axis',
229
+          axisPointer: { type: 'shadow' }
230
+        },
231
+        legend: {
232
+          data: ['岗位资质'],
233
+          textStyle: { color: '#666', fontSize: 10 },
234
+          top: 0
235
+        },
202
         grid: {
236
         grid: {
203
           left: '10%',
237
           left: '10%',
204
           right: '5%',
238
           right: '5%',
205
           bottom: '20%',
239
           bottom: '20%',
206
-          top: '10%'
240
+          top: '22%'
207
         },
241
         },
208
         xAxis: {
242
         xAxis: {
209
           type: 'category',
243
           type: 'category',
210
           data: this.chartsData.position.labels,
244
           data: this.chartsData.position.labels,
211
           axisLine: {
245
           axisLine: {
212
             lineStyle: {
246
             lineStyle: {
213
-              color: 'rgba(255, 255, 255, 0.1)'
247
+              color: 'rgba(0, 0, 0, 0.1)'
214
             }
248
             }
215
           },
249
           },
216
           axisLabel: {
250
           axisLabel: {
217
-            color: 'rgba(255, 255, 255, 0.5)',
251
+            color: '#666',
218
             fontSize: 10,
252
             fontSize: 10,
219
             rotate: 30,
253
             rotate: 30,
220
-            margin: 15
254
+         
221
           }
255
           }
222
         },
256
         },
223
         yAxis: {
257
         yAxis: {
@@ -230,12 +264,13 @@ export default {
230
           },
264
           },
231
           splitLine: {
265
           splitLine: {
232
             lineStyle: {
266
             lineStyle: {
233
-              color: 'rgba(255, 255, 255, 0.05)'
267
+              color: 'rgba(0, 0, 0, 0.05)'
234
             }
268
             }
235
           },
269
           },
236
           axisLabel: {
270
           axisLabel: {
237
-            color: 'rgba(255, 255, 255, 0.5)',
238
-            fontSize: 10
271
+            color: '#666',
272
+            fontSize: 10,
273
+            rotate: 30
239
           }
274
           }
240
         },
275
         },
241
         dataZoom: [{
276
         dataZoom: [{
@@ -244,17 +279,18 @@ export default {
244
           xAxisIndex: 0,
279
           xAxisIndex: 0,
245
           height: 20,
280
           height: 20,
246
           bottom: 0,
281
           bottom: 0,
247
-          borderColor: 'rgba(255, 255, 255, 0.1)',
248
-          backgroundColor: 'rgba(45, 42, 85, 0.8)',
249
-          fillerColor: 'rgba(167, 139, 250, 0.3)',
250
-          handleStyle: {
251
-            color: '#A78BFA'
252
-          },
253
-          labelStyle: {
254
-            color: 'rgba(255, 255, 255, 0.5)'
255
-          }
282
+          // borderColor: 'gray',
283
+          // backgroundColor: 'gray',
284
+          // fillerColor: 'gray',
285
+          // handleStyle: {
286
+          //   color: '#A78BFA'
287
+          // },
288
+          // labelStyle: {
289
+          //   color: 'rgba(255, 255, 255, 0.5)'
290
+          // }
256
         }],
291
         }],
257
         series: [{
292
         series: [{
293
+          name: '岗位资质',
258
           type: 'bar',
294
           type: 'bar',
259
           data: this.chartsData.position.data,
295
           data: this.chartsData.position.data,
260
           itemStyle: {
296
           itemStyle: {

+ 9 - 11
src/pages/components/PassengerChart.vue

@@ -82,13 +82,11 @@ export default {
82
       const option = {
82
       const option = {
83
         tooltip: {
83
         tooltip: {
84
           trigger: 'axis',
84
           trigger: 'axis',
85
-          backgroundColor: 'rgba(30, 27, 75, 0.95)',
86
-          borderColor: '#A78BFA',
87
-          textStyle: { color: '#fff' }
85
+   axisPointer: { type: 'shadow' }
88
         },
86
         },
89
         legend: {
87
         legend: {
90
           data: [...areaNames, '人流总数'],
88
           data: [...areaNames, '人流总数'],
91
-          textStyle: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
89
+          textStyle: { color: '#666', fontSize: 10 },
92
           top: 0
90
           top: 0
93
         },
91
         },
94
         grid: {
92
         grid: {
@@ -100,27 +98,27 @@ export default {
100
         xAxis: {
98
         xAxis: {
101
           type: 'category',
99
           type: 'category',
102
           data: this.chartsData.labels,
100
           data: this.chartsData.labels,
103
-          axisLabel: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
104
-          axisLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.1)' } }
101
+          axisLabel: { color: '#666', fontSize: 10 },
102
+          axisLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.1)' } }
105
         },
103
         },
106
         yAxis: [
104
         yAxis: [
107
           {
105
           {
108
             type: 'value',
106
             type: 'value',
109
             name: '人数',
107
             name: '人数',
110
-            nameTextStyle: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
108
+            nameTextStyle: { color: '#666', fontSize: 10 },
111
             position: 'left',
109
             position: 'left',
112
             max: Math.ceil(maxVal * 1.2),
110
             max: Math.ceil(maxVal * 1.2),
113
-            axisLabel: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
111
+            axisLabel: { color: '#666', fontSize: 10 },
114
             axisLine: { show: false },
112
             axisLine: { show: false },
115
-            splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.05)' } }
113
+            splitLine: { lineStyle: { color: 'rgba(0, 0, 0, 0.05)' } }
116
           },
114
           },
117
           {
115
           {
118
             type: 'value',
116
             type: 'value',
119
             name: '人流总数',
117
             name: '人流总数',
120
-            nameTextStyle: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
118
+            nameTextStyle: { color: '#666', fontSize: 10 },
121
             position: 'right',
119
             position: 'right',
122
             max: Math.ceil(maxVal * 1.2),
120
             max: Math.ceil(maxVal * 1.2),
123
-            axisLabel: { color: 'rgba(255, 255, 255, 0.5)', fontSize: 10 },
121
+            axisLabel: { color: '#666', fontSize: 10 },
124
             axisLine: { show: false },
122
             axisLine: { show: false },
125
             splitLine: { show: false }
123
             splitLine: { show: false }
126
           }
124
           }

+ 109 - 56
src/pages/components/ProfileRadar.vue

@@ -4,6 +4,12 @@
4
             <text>暂无数据</text>
4
             <text>暂无数据</text>
5
         </view>
5
         </view>
6
         <template v-else>
6
         <template v-else>
7
+            <div class="chart-legend">
8
+                <div class="legend-item legend-warning"><span></span>预警线(低于75分)</div>
9
+                <!-- <div class="legend-item legend-normal"><span></span>正常线(75~90分)</div> -->
10
+                <div class="legend-item legend-excellent"><span></span>优秀线(高于90分)</div>
11
+                <div class="legend-item legend-current"><span></span>当前分值</div>
12
+            </div>
7
             <div class="chart-container" ref="radarChart"></div>
13
             <div class="chart-container" ref="radarChart"></div>
8
             <view class="radar-list">
14
             <view class="radar-list">
9
                 <view v-for="(item, index) in radarData" :key="index" class="radar-item">
15
                 <view v-for="(item, index) in radarData" :key="index" class="radar-item">
@@ -92,77 +98,66 @@ export default {
92
                 this.chart.dispose()
98
                 this.chart.dispose()
93
                 this.chart = null
99
                 this.chart = null
94
             }
100
             }
95
-            
101
+
96
             // 没有数据时不渲染图表
102
             // 没有数据时不渲染图表
97
             if (!this.radarData || this.radarData.length === 0) {
103
             if (!this.radarData || this.radarData.length === 0) {
98
                 return
104
                 return
99
             }
105
             }
100
-            
101
-            const values = this.radarData.map(item => {
102
-                if (typeof item === 'object' && item !== null) {
103
-                    return item.finalScore || item.score || 0
104
-                }
105
-                return 0
106
-            })
107
-            
106
+
107
+
108
+
109
+
108
             this.chart = echarts.init(this.$refs.radarChart)
110
             this.chart = echarts.init(this.$refs.radarChart)
109
-            
110
-            const option = {
111
-                responsive: true,
112
-                maintainAspectRatio: false,
113
-                radar: {
114
-                    indicator: this.indicators,
115
-                    center: ['50%', '50%'],
116
-                    radius: '60%',
117
-                    splitNumber: 5,
118
-                    axisLine: {
119
-                        lineStyle: {
120
-                            color: 'rgba(255, 255, 255, 0.2)'
121
-                        }
111
+            const radarData = {
112
+                grounp: this.radarData.map(d => ({ name: d.name + '\n\n' + d.finalScore, max: 100 })),
113
+                data: [{
114
+                    name: '个人能力',
115
+                    value: this.radarData.map(item => item.finalScore || 0),
116
+                    symbolSize: 10,
117
+                    areaStyle: {
118
+                        show: false,
119
+                        opacity: 0
122
                     },
120
                     },
123
-                    splitLine: {
124
-                        lineStyle: {
125
-                            color: 'rgba(255, 255, 255, 0.1)'
126
-                        }
121
+                    lineStyle: {
122
+                        color: '#4DC8FE',
123
+                        width: 1
127
                     },
124
                     },
125
+                    itemStyle: { color: '#fff', borderWidth: 1, borderColor: '#00C8DA', borderJoin: 'round' }
126
+                }]
127
+            }
128
+
129
+            const option = {
130
+                radar: {
131
+                    indicator: radarData.grounp,
132
+                    center: ['50%', '52%'],
133
+                    radius: '50%',
134
+                    splitNumber: 8,
135
+                    axisLine: { lineStyle: { color: '#ccc' } },
136
+                    splitLine: { lineStyle: { color: ['#ccc', '#ccc', '#ccc', '#ccc', '#ccc', '#ccc', '#fe4322', '#8EC742', '#ccc'], width: 1 } },
128
                     splitArea: { show: false },
137
                     splitArea: { show: false },
129
                     axisName: {
138
                     axisName: {
130
-                        color: 'rgba(255, 255, 255, 0.6)',
131
-                        fontSize: 10
139
+                        color: '#333',
140
+                        fontSize: 12
132
                     }
141
                     }
133
                 },
142
                 },
134
                 series: [
143
                 series: [
135
                     {
144
                     {
136
                         type: 'radar',
145
                         type: 'radar',
146
+                        data: radarData.data,
137
                         symbol: 'circle',
147
                         symbol: 'circle',
138
-                        symbolSize: 8,
139
-                        data: [
140
-                            {
141
-                                value: values,
142
-                                name: '综合得分',
143
-                                lineStyle: {
144
-                                    color: '#4DC8FE',
145
-                                    width: 2
146
-                                },
147
-                                itemStyle: {
148
-                                    color: '#fff',
149
-                                    borderWidth: 1,
150
-                                    borderColor: '#00C8DA'
151
-                                },
152
-                                areaStyle: {
153
-                                    color: 'rgba(77, 200, 254, 0.2)'
154
-                                }
155
-                            }
156
-                        ]
148
+                        symbolSize: 13,
149
+                        label: {
150
+                            show: false,
151
+                            formatter: (p) => p.value,
152
+                            color: '#333',
153
+                            fontSize: 16,
154
+                            fontWeight: 'bold'
155
+                        }
157
                     }
156
                     }
158
                 ],
157
                 ],
159
                 tooltip: {
158
                 tooltip: {
160
                     trigger: 'item',
159
                     trigger: 'item',
161
-                    backgroundColor: 'rgba(13, 80, 122, 0.95)',
162
-                    borderColor: '#70CFE7',
163
-                    textStyle: {
164
-                        color: '#fff'
165
-                    }
160
+                    axisPointer: { type: 'shadow' }
166
                 }
161
                 }
167
             }
162
             }
168
             this.chart.setOption(option)
163
             this.chart.setOption(option)
@@ -186,7 +181,7 @@ export default {
186
         display: flex;
181
         display: flex;
187
         align-items: center;
182
         align-items: center;
188
         justify-content: center;
183
         justify-content: center;
189
-        color: rgba(255, 255, 255, 0.5);
184
+        color: #999;
190
         font-size: 28rpx;
185
         font-size: 28rpx;
191
     }
186
     }
192
 
187
 
@@ -195,6 +190,63 @@ export default {
195
         height: 350rpx;
190
         height: 350rpx;
196
     }
191
     }
197
 
192
 
193
+    .chart-legend {
194
+        display: flex;
195
+        justify-content: center;
196
+        align-items: center;
197
+        flex-wrap: wrap;
198
+        gap: 10px 18px;
199
+        padding-top: 8px;
200
+        color: #fff;
201
+        font-size: 14px;
202
+
203
+    }
204
+
205
+    .legend-item {
206
+        display: flex;
207
+        align-items: center;
208
+        gap: 6rpx;
209
+
210
+        span {
211
+            width: 24rpx;
212
+            height: 10rpx;
213
+            border: 2rpx solid currentColor;
214
+            border-radius: 2rpx;
215
+        }
216
+    }
217
+
218
+    .legend-warning {
219
+        color: #fe4322;
220
+
221
+        span {
222
+            background-color: #fe4322;
223
+        }
224
+    }
225
+
226
+    .legend-normal {
227
+        color: black;
228
+
229
+        span {
230
+            background-color: gray;
231
+        }
232
+    }
233
+
234
+    .legend-excellent {
235
+        color: #8EC742;
236
+
237
+        span {
238
+            background-color: #8EC742;
239
+        }
240
+    }
241
+
242
+    .legend-current {
243
+        color: #1890ff;
244
+
245
+        span {
246
+            background-color: #1890ff;
247
+        }
248
+    }
249
+
198
     .radar-list {
250
     .radar-list {
199
         display: flex;
251
         display: flex;
200
         flex-direction: column;
252
         flex-direction: column;
@@ -208,7 +260,7 @@ export default {
208
             font-size: 24rpx;
260
             font-size: 24rpx;
209
 
261
 
210
             .item-label {
262
             .item-label {
211
-                color: rgba(160, 196, 255, 0.9);
263
+                color: #333;
212
             }
264
             }
213
 
265
 
214
             .progress-row {
266
             .progress-row {
@@ -219,7 +271,7 @@ export default {
219
                 .progress-bar {
271
                 .progress-bar {
220
                     flex: 1;
272
                     flex: 1;
221
                     height: 10rpx;
273
                     height: 10rpx;
222
-                    background: rgba(255, 255, 255, 0.1);
274
+                    background: #f0f0f0;
223
                     border-radius: 0;
275
                     border-radius: 0;
224
                     overflow: visible;
276
                     overflow: visible;
225
 
277
 
@@ -237,6 +289,7 @@ export default {
237
                             width: 6rpx;
289
                             width: 6rpx;
238
                             height: 18rpx;
290
                             height: 18rpx;
239
                             background: #fff;
291
                             background: #fff;
292
+                            border: 2rpx solid currentColor;
240
                             border-radius: 4rpx;
293
                             border-radius: 4rpx;
241
                         }
294
                         }
242
                     }
295
                     }
@@ -245,7 +298,7 @@ export default {
245
                 .item-value {
298
                 .item-value {
246
                     width: 80rpx;
299
                     width: 80rpx;
247
                     text-align: right;
300
                     text-align: right;
248
-                    color: #fff;
301
+                    color: #333;
249
                     font-weight: bold;
302
                     font-weight: bold;
250
                     font-size: 24rpx;
303
                     font-size: 24rpx;
251
                 }
304
                 }

+ 36 - 6
src/pages/components/SecurityTestCharts.vue

@@ -79,9 +79,19 @@ export default {
79
       const option = {
79
       const option = {
80
         responsive: true,
80
         responsive: true,
81
         maintainAspectRatio: false,
81
         maintainAspectRatio: false,
82
+        tooltip: {
83
+          trigger: 'item',
84
+          formatter: '{b}: {c} ({d}%)'
85
+        },
86
+        legend: {
87
+          type: 'scroll',
88
+          data: this.chartsData.items.labels,
89
+          textStyle: { color: '#666', fontSize: 10 },
90
+          top: 0
91
+        },
82
         series: [{
92
         series: [{
83
           type: 'pie',
93
           type: 'pie',
84
-          radius: ['50%', '70%'],
94
+          radius: ['30%', '40%'],
85
           data: this.chartsData.items.labels.map((label, index) => ({
95
           data: this.chartsData.items.labels.map((label, index) => ({
86
             name: label,
96
             name: label,
87
             value: this.chartsData.items.data[index]
97
             value: this.chartsData.items.data[index]
@@ -94,7 +104,7 @@ export default {
94
           },
104
           },
95
           label: {
105
           label: {
96
             show: true,
106
             show: true,
97
-            color: 'rgba(255, 255, 255, 0.7)',
107
+            color: '#333',
98
             fontSize: 10,
108
             fontSize: 10,
99
             formatter: '{b}\n{d}%'
109
             formatter: '{b}\n{d}%'
100
           }
110
           }
@@ -108,9 +118,19 @@ export default {
108
       const option = {
118
       const option = {
109
         responsive: true,
119
         responsive: true,
110
         maintainAspectRatio: false,
120
         maintainAspectRatio: false,
121
+        legend: {
122
+          type: 'scroll',
123
+          data: this.chartsData.results.labels,
124
+          textStyle: { color: '#666', fontSize: 10 },
125
+          top: 0
126
+        },
127
+        tooltip: {
128
+          trigger: 'item',
129
+          formatter: '{b}: {c} ({d}%)'
130
+        },
111
         series: [{
131
         series: [{
112
           type: 'pie',
132
           type: 'pie',
113
-          radius: ['50%', '70%'],
133
+          radius: ['30%', '40%'],
114
           data: this.chartsData.results.labels.map((label, index) => ({
134
           data: this.chartsData.results.labels.map((label, index) => ({
115
             name: label,
135
             name: label,
116
             value: this.chartsData.results.data[index]
136
             value: this.chartsData.results.data[index]
@@ -123,7 +143,7 @@ export default {
123
           },
143
           },
124
           label: {
144
           label: {
125
             show: true,
145
             show: true,
126
-            color: 'rgba(255, 255, 255, 0.7)',
146
+            color: '#333',
127
             fontSize: 10,
147
             fontSize: 10,
128
             formatter: '{b}\n{d}%'
148
             formatter: '{b}\n{d}%'
129
           }
149
           }
@@ -137,9 +157,19 @@ export default {
137
       const option = {
157
       const option = {
138
         responsive: true,
158
         responsive: true,
139
         maintainAspectRatio: false,
159
         maintainAspectRatio: false,
160
+        tooltip: {
161
+          trigger: 'item',
162
+          formatter: '{b}: {c} ({d}%)'
163
+        },
164
+        legend: {
165
+          type: 'scroll',
166
+          data: this.chartsData.areas.labels,
167
+          textStyle: { color: '#666', fontSize: 10 },
168
+          top: 0
169
+        },
140
         series: [{
170
         series: [{
141
           type: 'pie',
171
           type: 'pie',
142
-          radius: ['50%', '70%'],
172
+          radius: ['30%', '40%'],
143
           data: this.chartsData.areas.labels.map((label, index) => ({
173
           data: this.chartsData.areas.labels.map((label, index) => ({
144
             name: label,
174
             name: label,
145
             value: this.chartsData.areas.data[index]
175
             value: this.chartsData.areas.data[index]
@@ -152,7 +182,7 @@ export default {
152
           },
182
           },
153
           label: {
183
           label: {
154
             show: true,
184
             show: true,
155
-            color: 'rgba(255, 255, 255, 0.7)',
185
+            color: '#333',
156
             fontSize: 10,
186
             fontSize: 10,
157
             formatter: '{b}\n{d}%'
187
             formatter: '{b}\n{d}%'
158
           }
188
           }

+ 11 - 8
src/pages/components/SeizedNumAll.vue

@@ -47,18 +47,23 @@ export default {
47
                     left: '15%',
47
                     left: '15%',
48
                     right: '5%',
48
                     right: '5%',
49
                     bottom: '15%',
49
                     bottom: '15%',
50
-                    top: '10%'
50
+                    top: '22%'
51
+                },
52
+                legend: {
53
+                    data: ['查获数'],
54
+                    textStyle: { color: '#666', fontSize: 10 },
55
+                    top: 0
51
                 },
56
                 },
52
                 xAxis: {
57
                 xAxis: {
53
                     type: 'category',
58
                     type: 'category',
54
                     data: this.chartsData.map(item => item.recordDate),
59
                     data: this.chartsData.map(item => item.recordDate),
55
                     axisLine: {
60
                     axisLine: {
56
                         lineStyle: {
61
                         lineStyle: {
57
-                            color: 'rgba(255, 255, 255, 0.1)'
62
+                            color: 'rgba(0, 0, 0, 0.1)'
58
                         }
63
                         }
59
                     },
64
                     },
60
                     axisLabel: {
65
                     axisLabel: {
61
-                        color: 'rgba(255, 255, 255, 0.5)',
66
+                        color: '#666',
62
                         fontSize: 9,
67
                         fontSize: 9,
63
                         rotate: 30
68
                         rotate: 30
64
                     }
69
                     }
@@ -69,11 +74,11 @@ export default {
69
                     axisTick: { show: false },
74
                     axisTick: { show: false },
70
                     splitLine: {
75
                     splitLine: {
71
                         lineStyle: {
76
                         lineStyle: {
72
-                            color: 'rgba(255, 255, 255, 0.05)'
77
+                            color: 'rgba(0, 0, 0, 0.05)'
73
                         }
78
                         }
74
                     },
79
                     },
75
                     axisLabel: {
80
                     axisLabel: {
76
-                        color: 'rgba(255, 255, 255, 0.5)',
81
+                        color: '#666',
77
                         fontSize: 9
82
                         fontSize: 9
78
                     }
83
                     }
79
                 },
84
                 },
@@ -106,9 +111,7 @@ export default {
106
                 ],
111
                 ],
107
                 tooltip: {
112
                 tooltip: {
108
                     trigger: 'axis',
113
                     trigger: 'axis',
109
-                    backgroundColor: 'rgba(13,80,122,0.95)',
110
-                    borderColor: '#70CFE7',
111
-                    textStyle: { color: '#fff' }
114
+                    axisPointer: { type: 'shadow' }
112
                 }
115
                 }
113
             }
116
             }
114
             this.chart.setOption(option)
117
             this.chart.setOption(option)

+ 19 - 11
src/pages/components/SeizureInfo.vue

@@ -97,7 +97,7 @@ export default {
97
             top: '38%',
97
             top: '38%',
98
             style: {
98
             style: {
99
               text: String(this.chartsData.total),
99
               text: String(this.chartsData.total),
100
-              fill: 'rgba(255, 255, 255, 0.9)',
100
+              fill: '#333',
101
               fontSize: 14,
101
               fontSize: 14,
102
               fontWeight: 'bold',
102
               fontWeight: 'bold',
103
               textAlign: 'center'
103
               textAlign: 'center'
@@ -110,7 +110,7 @@ export default {
110
             top: '58%',
110
             top: '58%',
111
             style: {
111
             style: {
112
               text: '查获总数',
112
               text: '查获总数',
113
-              fill: 'rgba(255, 255, 255, 0.5)',
113
+              fill: '#333',
114
               fontSize: 15,
114
               fontSize: 15,
115
               textAlign: 'center'
115
               textAlign: 'center'
116
             },
116
             },
@@ -126,25 +126,33 @@ export default {
126
       const option = {
126
       const option = {
127
         responsive: true,
127
         responsive: true,
128
         maintainAspectRatio: false,
128
         maintainAspectRatio: false,
129
+        tooltip: {
130
+          trigger: 'axis',
131
+          axisPointer: { type: 'shadow' }
132
+        },
133
+        legend: {
134
+          data: ['查获数量'],
135
+          textStyle: { color: '#666', fontSize: 10 },
136
+          top: 0
137
+        },
129
         grid: {
138
         grid: {
130
           left: '15%',
139
           left: '15%',
131
           right: '5%',
140
           right: '5%',
132
           bottom: '15%',
141
           bottom: '15%',
133
-          top: '10%'
142
+          top: '22%'
134
         },
143
         },
135
         xAxis: {
144
         xAxis: {
136
           type: 'category',
145
           type: 'category',
137
           data: this.chartsData.depts.labels,
146
           data: this.chartsData.depts.labels,
138
           axisLine: {
147
           axisLine: {
139
             lineStyle: {
148
             lineStyle: {
140
-              color: 'rgba(255, 255, 255, 0.1)'
149
+              color: 'rgba(0, 0, 0, 0.1)'
141
             }
150
             }
142
           },
151
           },
143
-          // axisLabel: {
144
-          //   color: 'rgba(255, 255, 255, 0.5)',
145
-          //   fontSize: 9,
146
-          //   rotate: 30
147
-          // }
152
+          axisLabel: {
153
+            color: '#666',
154
+            fontSize: 9
155
+          }
148
         },
156
         },
149
         yAxis: {
157
         yAxis: {
150
           type: 'value',
158
           type: 'value',
@@ -156,11 +164,11 @@ export default {
156
           },
164
           },
157
           splitLine: {
165
           splitLine: {
158
             lineStyle: {
166
             lineStyle: {
159
-              color: 'rgba(255, 255, 255, 0.05)'
167
+              color: 'rgba(0, 0, 0, 0.05)'
160
             }
168
             }
161
           },
169
           },
162
           axisLabel: {
170
           axisLabel: {
163
-            color: 'rgba(255, 255, 255, 0.5)',
171
+            color: '#666',
164
             fontSize: 9
172
             fontSize: 9
165
           }
173
           }
166
         },
174
         },

+ 12 - 9
src/pages/components/SupervisionDistribution.vue

@@ -47,18 +47,18 @@ export default {
47
                     left: '15%',
47
                     left: '15%',
48
                     right: '5%',
48
                     right: '5%',
49
                     bottom: '20%',
49
                     bottom: '20%',
50
-                    top: '10%'
50
+                    top: '22%'
51
                 },
51
                 },
52
                 xAxis: {
52
                 xAxis: {
53
                     type: 'category',
53
                     type: 'category',
54
                     data: this.chartsData.map(item => item.name),
54
                     data: this.chartsData.map(item => item.name),
55
                     axisLine: {
55
                     axisLine: {
56
                         lineStyle: {
56
                         lineStyle: {
57
-                            color: 'rgba(255, 255, 255, 0.1)'
57
+                            color: 'rgba(0, 0, 0, 0.1)'
58
                         }
58
                         }
59
                     },
59
                     },
60
                     axisLabel: {
60
                     axisLabel: {
61
-                        color: 'rgba(255, 255, 255, 0.5)',
61
+                        color: '#666',
62
                         fontSize: 9,
62
                         fontSize: 9,
63
                         rotate: 30
63
                         rotate: 30
64
                     }
64
                     }
@@ -67,18 +67,18 @@ export default {
67
                     type: 'value',
67
                     type: 'value',
68
                     name: '人数',
68
                     name: '人数',
69
                     nameTextStyle: {
69
                     nameTextStyle: {
70
-                        color: 'rgba(255, 255, 255, 0.5)',
70
+                        color: '#666',
71
                         fontSize: 9
71
                         fontSize: 9
72
                     },
72
                     },
73
                     axisLine: { show: false },
73
                     axisLine: { show: false },
74
                     axisTick: { show: false },
74
                     axisTick: { show: false },
75
                     splitLine: {
75
                     splitLine: {
76
                         lineStyle: {
76
                         lineStyle: {
77
-                            color: 'rgba(255, 255, 255, 0.05)'
77
+                            color: 'rgba(0, 0, 0, 0.05)'
78
                         }
78
                         }
79
                     },
79
                     },
80
                     axisLabel: {
80
                     axisLabel: {
81
-                        color: 'rgba(255, 255, 255, 0.5)',
81
+                        color: '#666',
82
                         fontSize: 9
82
                         fontSize: 9
83
                     }
83
                     }
84
                 },
84
                 },
@@ -101,11 +101,14 @@ export default {
101
                         barWidth: '40%'
101
                         barWidth: '40%'
102
                     }
102
                     }
103
                 ],
103
                 ],
104
+                legend: {
105
+                    data: ['查获数量'],
106
+                    textStyle: { color: '#666', fontSize: 10 },
107
+                    top: 0
108
+                },
104
                 tooltip: {
109
                 tooltip: {
105
                     trigger: 'axis',
110
                     trigger: 'axis',
106
-                    backgroundColor: 'rgba(13,80,122,0.95)',
107
-                    borderColor: '#70CFE7',
108
-                    textStyle: { color: '#fff' }
111
+                    axisPointer: { type: 'shadow' }
109
                 }
112
                 }
110
             }
113
             }
111
             this.chart.setOption(option)
114
             this.chart.setOption(option)

+ 7 - 7
src/pages/components/TeamMemberTable.vue

@@ -48,25 +48,25 @@ export default {
48
   .table-scroll {
48
   .table-scroll {
49
     width: 100%;
49
     width: 100%;
50
   }
50
   }
51
-  
51
+
52
   .data-table {
52
   .data-table {
53
     width: 100%;
53
     width: 100%;
54
     font-size: 24rpx;
54
     font-size: 24rpx;
55
     border-collapse: collapse;
55
     border-collapse: collapse;
56
-    
56
+
57
     th, td {
57
     th, td {
58
       padding: 16rpx 8rpx;
58
       padding: 16rpx 8rpx;
59
       text-align: left;
59
       text-align: left;
60
       white-space: nowrap;
60
       white-space: nowrap;
61
     }
61
     }
62
-    
62
+
63
     th {
63
     th {
64
-      color: rgba(255, 255, 255, 0.5);
65
-      border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
64
+      color: #666;
65
+      border-bottom: 1rpx solid #e0e0e0;
66
     }
66
     }
67
-    
67
+
68
     td {
68
     td {
69
-      color: rgba(255, 255, 255, 0.9);
69
+      color: #333;
70
     }
70
     }
71
   }
71
   }
72
 }
72
 }

+ 17 - 8
src/pages/components/UnsafeItemsChart.vue

@@ -57,6 +57,15 @@ export default {
57
       const option = {
57
       const option = {
58
         responsive: true,
58
         responsive: true,
59
         maintainAspectRatio: false,
59
         maintainAspectRatio: false,
60
+        tooltip: {
61
+          trigger: 'item',
62
+          formatter: '{b}: {c} ({d}%)'
63
+        },
64
+        legend: {
65
+          data: this.chartsData.items.map(item => item.name),
66
+          textStyle: { color: '#666', fontSize: 10 },
67
+          top: 0
68
+        },
60
         series: [{
69
         series: [{
61
           type: 'pie',
70
           type: 'pie',
62
           radius: ['50%', '70%'],
71
           radius: ['50%', '70%'],
@@ -72,7 +81,7 @@ export default {
72
           },
81
           },
73
           label: {
82
           label: {
74
             show: true,
83
             show: true,
75
-            color: 'rgba(255, 255, 255, 0.7)',
84
+            color: '#333',
76
             fontSize: 10,
85
             fontSize: 10,
77
             formatter: '{b}\n{d}%'
86
             formatter: '{b}\n{d}%'
78
           }
87
           }
@@ -90,25 +99,25 @@ export default {
90
 <style lang="scss" scoped>
99
 <style lang="scss" scoped>
91
 .unsafe-items-chart {
100
 .unsafe-items-chart {
92
   .chart-container {
101
   .chart-container {
93
-    height: 220rpx;
102
+    height: 400rpx;
94
   }
103
   }
95
-  
104
+
96
   .items-list {
105
   .items-list {
97
     margin-top: 16rpx;
106
     margin-top: 16rpx;
98
   }
107
   }
99
-  
108
+
100
   .list-item {
109
   .list-item {
101
     display: flex;
110
     display: flex;
102
     justify-content: space-between;
111
     justify-content: space-between;
103
     font-size: 24rpx;
112
     font-size: 24rpx;
104
     padding: 8rpx 0;
113
     padding: 8rpx 0;
105
-    
114
+
106
     .item-name {
115
     .item-name {
107
-      color: rgba(255, 255, 255, 0.7);
116
+      color: #666;
108
     }
117
     }
109
-    
118
+
110
     .item-value {
119
     .item-value {
111
-      color: rgba(255, 255, 255, 0.9);
120
+      color: #333;
112
     }
121
     }
113
   }
122
   }
114
 }
123
 }

+ 18 - 9
src/pages/components/UnsafePositionChart.vue

@@ -52,22 +52,31 @@ export default {
52
       const option = {
52
       const option = {
53
         responsive: true,
53
         responsive: true,
54
         maintainAspectRatio: false,
54
         maintainAspectRatio: false,
55
+        tooltip: {
56
+          trigger: 'axis',
57
+          axisPointer: { type: 'shadow' }
58
+        },
59
+        legend: {
60
+          data: ['人数'],
61
+          textStyle: { color: '#666', fontSize: 10 },
62
+          top: 0
63
+        },
55
         grid: {
64
         grid: {
56
           left: '15%',
65
           left: '15%',
57
           right: '5%',
66
           right: '5%',
58
           bottom: '15%',
67
           bottom: '15%',
59
-          top: '10%'
68
+          top: '22%'
60
         },
69
         },
61
         xAxis: {
70
         xAxis: {
62
           type: 'category',
71
           type: 'category',
63
           data: this.chartsData.positions.labels,
72
           data: this.chartsData.positions.labels,
64
           axisLine: {
73
           axisLine: {
65
             lineStyle: {
74
             lineStyle: {
66
-              color: 'rgba(255, 255, 255, 0.1)'
75
+              color: 'rgba(0, 0, 0, 0.1)'
67
             }
76
             }
68
           },
77
           },
69
           axisLabel: {
78
           axisLabel: {
70
-            color: 'rgba(255, 255, 255, 0.5)',
79
+            color: '#666',
71
             fontSize: 10,
80
             fontSize: 10,
72
             // rotate: 30
81
             // rotate: 30
73
           }
82
           }
@@ -82,11 +91,11 @@ export default {
82
           },
91
           },
83
           splitLine: {
92
           splitLine: {
84
             lineStyle: {
93
             lineStyle: {
85
-              color: 'rgba(255, 255, 255, 0.05)'
94
+              color: 'rgba(0, 0, 0, 0.05)'
86
             }
95
             }
87
           },
96
           },
88
           axisLabel: {
97
           axisLabel: {
89
-            color: 'rgba(255, 255, 255, 0.5)',
98
+            color: '#666',
90
             fontSize: 10
99
             fontSize: 10
91
           }
100
           }
92
         },
101
         },
@@ -122,16 +131,16 @@ export default {
122
 <style lang="scss" scoped>
131
 <style lang="scss" scoped>
123
 .unsafe-position-chart {
132
 .unsafe-position-chart {
124
   .chart-container {
133
   .chart-container {
125
-    height: 240rpx;
134
+    height: 400rpx;
126
   }
135
   }
127
-  
136
+
128
   .position-info {
137
   .position-info {
129
     margin-top: 16rpx;
138
     margin-top: 16rpx;
130
     text-align: center;
139
     text-align: center;
131
-    
140
+
132
     .info-text {
141
     .info-text {
133
       font-size: 24rpx;
142
       font-size: 24rpx;
134
-      color: rgba(255, 255, 255, 0.5);
143
+      color: #666;
135
     }
144
     }
136
   }
145
   }
137
 }
146
 }

+ 17 - 8
src/pages/components/UnsafeTypesChart.vue

@@ -57,6 +57,15 @@ export default {
57
       const option = {
57
       const option = {
58
         responsive: true,
58
         responsive: true,
59
         maintainAspectRatio: false,
59
         maintainAspectRatio: false,
60
+        tooltip: {
61
+          trigger: 'item',
62
+          formatter: '{b}: {c} ({d}%)'
63
+        },
64
+        legend: {
65
+          data: this.chartsData.types.map(item => item.name),
66
+          textStyle: { color: '#666', fontSize: 10 },
67
+          top: 0
68
+        },
60
         series: [{
69
         series: [{
61
           type: 'pie',
70
           type: 'pie',
62
           radius: ['50%', '70%'],
71
           radius: ['50%', '70%'],
@@ -72,7 +81,7 @@ export default {
72
           },
81
           },
73
           label: {
82
           label: {
74
             show: true,
83
             show: true,
75
-            color: 'rgba(255, 255, 255, 0.7)',
84
+            color: '#333',
76
             fontSize: 10,
85
             fontSize: 10,
77
             formatter: '{b}\n{d}%'
86
             formatter: '{b}\n{d}%'
78
           }
87
           }
@@ -90,25 +99,25 @@ export default {
90
 <style lang="scss" scoped>
99
 <style lang="scss" scoped>
91
 .unsafe-types-chart {
100
 .unsafe-types-chart {
92
   .chart-container {
101
   .chart-container {
93
-    height: 220rpx;
102
+    height: 400rpx;
94
   }
103
   }
95
-  
104
+
96
   .types-list {
105
   .types-list {
97
     margin-top: 16rpx;
106
     margin-top: 16rpx;
98
   }
107
   }
99
-  
108
+
100
   .list-item {
109
   .list-item {
101
     display: flex;
110
     display: flex;
102
     justify-content: space-between;
111
     justify-content: space-between;
103
     font-size: 24rpx;
112
     font-size: 24rpx;
104
     padding: 8rpx 0;
113
     padding: 8rpx 0;
105
-    
114
+
106
     .item-name {
115
     .item-name {
107
-      color: rgba(255, 255, 255, 0.7);
116
+      color: #666;
108
     }
117
     }
109
-    
118
+
110
     .item-value {
119
     .item-value {
111
-      color: rgba(255, 255, 255, 0.9);
120
+      color: #333;
112
     }
121
     }
113
   }
122
   }
114
 }
123
 }

+ 28 - 30
src/pages/deptProfile/index.vue

@@ -59,7 +59,7 @@
59
             </SectionTitle>
59
             </SectionTitle>
60
 
60
 
61
             <view v-if="activeTab === 'profile'">
61
             <view v-if="activeTab === 'profile'">
62
-                <SectionTitle title="维得分一览">
62
+                <SectionTitle title="维得分一览">
63
                     <ProfileRadar :chartsData="radarData" />
63
                     <ProfileRadar :chartsData="radarData" />
64
                 </SectionTitle>
64
                 </SectionTitle>
65
 
65
 
@@ -77,7 +77,7 @@
77
             </view>
77
             </view>
78
 
78
 
79
             <view v-if="activeTab === 'data'">
79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="每日通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81
                     <PassengerChart :chartsData="passengerData" />
81
                     <PassengerChart :chartsData="passengerData" />
82
                 </SectionTitle>
82
                 </SectionTitle>
83
 
83
 
@@ -94,7 +94,7 @@
94
                 </SectionTitle>
94
                 </SectionTitle>
95
 
95
 
96
                 <SectionTitle title="每日查获数量">
96
                 <SectionTitle title="每日查获数量">
97
-                    <DailySeizureChart :chartsData="dailySeizureData" />
97
+                    <DailySeizureChart :chartsData="dailySeizureData" :title="'班组对比'"/>
98
                 </SectionTitle>
98
                 </SectionTitle>
99
 
99
 
100
                 <SectionTitle title="查获工作区域分布">
100
                 <SectionTitle title="查获工作区域分布">
@@ -726,9 +726,9 @@ export default {
726
 </script>
726
 </script>
727
 
727
 
728
 <style lang="scss" scoped>
728
 <style lang="scss" scoped>
729
-    .dept-selector {
729
+.dept-selector {
730
     padding: 16rpx 32rpx;
730
     padding: 16rpx 32rpx;
731
-    background: rgba(30, 27, 75, 0.8);
731
+    background: #fff;
732
 }
732
 }
733
 
733
 
734
 .dept-select-trigger {
734
 .dept-select-trigger {
@@ -737,8 +737,8 @@ export default {
737
     justify-content: center;
737
     justify-content: center;
738
     gap: 12rpx;
738
     gap: 12rpx;
739
     padding: 16rpx 24rpx;
739
     padding: 16rpx 24rpx;
740
-    background: rgba(45, 42, 85, 0.8);
741
-    border: 1rpx solid rgba(167, 139, 250, 0.3);
740
+    background: #f5f5f5;
741
+    border: 1rpx solid #e0e0e0;
742
     border-radius: 50rpx;
742
     border-radius: 50rpx;
743
 }
743
 }
744
 
744
 
@@ -749,12 +749,12 @@ export default {
749
 
749
 
750
 .dept-name-text {
750
 .dept-name-text {
751
     font-size: 26rpx;
751
     font-size: 26rpx;
752
-    color: rgba(255, 255, 255, 0.9);
752
+    color: #333;
753
 }
753
 }
754
 
754
 
755
 .dept-profile-page {
755
 .dept-profile-page {
756
     min-height: 100vh;
756
     min-height: 100vh;
757
-    background: linear-gradient(135deg, #1E1B4B 0%, #312E81 100%);
757
+    background: #fff;
758
     padding-bottom: 40rpx;
758
     padding-bottom: 40rpx;
759
 }
759
 }
760
 
760
 
@@ -762,9 +762,9 @@ export default {
762
     position: sticky;
762
     position: sticky;
763
     top: 0;
763
     top: 0;
764
     z-index: 100;
764
     z-index: 100;
765
-    background: rgba(30, 27, 75, 0.9);
765
+    background: #fff;
766
     backdrop-filter: blur(10px);
766
     backdrop-filter: blur(10px);
767
-    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
767
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
768
 }
768
 }
769
 
769
 
770
 .header-title {
770
 .header-title {
@@ -776,10 +776,7 @@ export default {
776
     .title-main {
776
     .title-main {
777
         font-size: 36rpx;
777
         font-size: 36rpx;
778
         font-weight: bold;
778
         font-weight: bold;
779
-        background: linear-gradient(90deg, #A78BFA, #60A5FA);
780
-        -webkit-background-clip: text;
781
-        -webkit-text-fill-color: transparent;
782
-        background-clip: text;
779
+        color: #333;
783
     }
780
     }
784
 }
781
 }
785
 
782
 
@@ -790,13 +787,13 @@ export default {
790
 
787
 
791
     .current-time {
788
     .current-time {
792
         font-size: 24rpx;
789
         font-size: 24rpx;
793
-        color: rgba(255, 255, 255, 0.6);
790
+        color: #999;
794
     }
791
     }
795
 }
792
 }
796
 
793
 
797
 .time-filter {
794
 .time-filter {
798
     padding: 16rpx 32rpx;
795
     padding: 16rpx 32rpx;
799
-    background: rgba(30, 27, 75, 0.8);
796
+    background: #fff;
800
 }
797
 }
801
 
798
 
802
 .time-scroll {
799
 .time-scroll {
@@ -812,14 +809,14 @@ export default {
812
 .time-tag {
809
 .time-tag {
813
     padding: 8rpx 10rpx;
810
     padding: 8rpx 10rpx;
814
     border-radius: 50rpx;
811
     border-radius: 50rpx;
815
-    background: rgba(45, 42, 85, 0.8);
812
+    background: #f0f0f0;
816
     font-size: 24rpx;
813
     font-size: 24rpx;
817
-    color: rgba(255, 255, 255, 0.6);
814
+    color: #666;
818
     transition: all 0.3s;
815
     transition: all 0.3s;
819
 
816
 
820
     &.active {
817
     &.active {
821
-        background: #A78BFA;
822
-        color: #1E1B4B;
818
+        background: #60A5FA;
819
+        color: #fff;
823
         font-weight: 500;
820
         font-weight: 500;
824
     }
821
     }
825
 }
822
 }
@@ -830,26 +827,26 @@ export default {
830
     gap: 16rpx;
827
     gap: 16rpx;
831
     margin-top: 16rpx;
828
     margin-top: 16rpx;
832
     padding-top: 16rpx;
829
     padding-top: 16rpx;
833
-    border-top: 1rpx solid rgba(255, 255, 255, 0.1);
830
+    border-top: 1rpx solid #e0e0e0;
834
 }
831
 }
835
 
832
 
836
 .date-input {
833
 .date-input {
837
     flex: 1;
834
     flex: 1;
838
     padding: 12rpx 24rpx;
835
     padding: 12rpx 24rpx;
839
     border-radius: 12rpx;
836
     border-radius: 12rpx;
840
-    background: rgba(45, 42, 85, 0.8);
837
+    background: #f5f5f5;
841
     font-size: 24rpx;
838
     font-size: 24rpx;
842
-    color: rgba(255, 255, 255, 0.4);
839
+    color: #999;
843
     text-align: center;
840
     text-align: center;
844
 
841
 
845
     &.filled {
842
     &.filled {
846
-        color: rgba(255, 255, 255, 0.9);
843
+        color: #333;
847
     }
844
     }
848
 }
845
 }
849
 
846
 
850
 .date-separator {
847
 .date-separator {
851
     font-size: 24rpx;
848
     font-size: 24rpx;
852
-    color: rgba(255, 255, 255, 0.5);
849
+    color: #999;
853
     flex-shrink: 0;
850
     flex-shrink: 0;
854
 }
851
 }
855
 
852
 
@@ -862,14 +859,14 @@ export default {
862
 
859
 
863
 .tab-item {
860
 .tab-item {
864
     font-size: 28rpx;
861
     font-size: 28rpx;
865
-    color: rgba(255, 255, 255, 0.5);
862
+    color: #999;
866
     padding-bottom: 8rpx;
863
     padding-bottom: 8rpx;
867
     border-bottom: 2rpx solid transparent;
864
     border-bottom: 2rpx solid transparent;
868
     transition: all 0.3s;
865
     transition: all 0.3s;
869
 
866
 
870
     &.active {
867
     &.active {
871
-        color: #A78BFA;
872
-        border-bottom-color: #A78BFA;
868
+        color: #333;
869
+        border-bottom-color: #333;
873
     }
870
     }
874
 }
871
 }
875
 
872
 
@@ -877,5 +874,6 @@ export default {
877
     padding: 32rpx;
874
     padding: 32rpx;
878
     display: flex;
875
     display: flex;
879
     flex-direction: column;
876
     flex-direction: column;
877
+    background-color: #fff;
880
 }
878
 }
881
-</style>
879
+</style>

File diff suppressed because it is too large
+ 546 - 172
src/pages/employeeProfile/index.vue


+ 27 - 29
src/pages/groupProfile/index.vue

@@ -77,7 +77,7 @@
77
             </view>
77
             </view>
78
 
78
 
79
             <view v-if="activeTab === 'data'">
79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="当日开航每小时通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81
                     <PassengerChart :chartsData="passengerData" />
81
                     <PassengerChart :chartsData="passengerData" />
82
                 </SectionTitle>
82
                 </SectionTitle>
83
 
83
 
@@ -89,12 +89,12 @@
89
                     <ItemDistribution :chartsData="itemDistributionData" />
89
                     <ItemDistribution :chartsData="itemDistributionData" />
90
                 </SectionTitle>
90
                 </SectionTitle>
91
 
91
 
92
-                <SectionTitle title="每日查获数量(总表)">
92
+                <!-- <SectionTitle title="每日查获数量(总表)">
93
                     <SeizedNumAll :chartsData="dailySeizureTotalData" />
93
                     <SeizedNumAll :chartsData="dailySeizureTotalData" />
94
-                </SectionTitle>
94
+                </SectionTitle> -->
95
 
95
 
96
                 <SectionTitle title="每日查获数量">
96
                 <SectionTitle title="每日查获数量">
97
-                    <DailySeizureChart :chartsData="dailySeizureData" />
97
+                    <DailySeizureChart :chartsData="dailySeizureData" :title="'个人对比'"/>
98
                 </SectionTitle>
98
                 </SectionTitle>
99
 
99
 
100
                 <SectionTitle title="查获工作区域分布">
100
                 <SectionTitle title="查获工作区域分布">
@@ -735,7 +735,7 @@ export default {
735
 <style lang="scss" scoped>
735
 <style lang="scss" scoped>
736
     .dept-selector {
736
     .dept-selector {
737
     padding: 16rpx 32rpx;
737
     padding: 16rpx 32rpx;
738
-    background: rgba(30, 27, 75, 0.8);
738
+    background: #fff;
739
 }
739
 }
740
 
740
 
741
 .dept-select-trigger {
741
 .dept-select-trigger {
@@ -744,8 +744,8 @@ export default {
744
     justify-content: center;
744
     justify-content: center;
745
     gap: 12rpx;
745
     gap: 12rpx;
746
     padding: 16rpx 24rpx;
746
     padding: 16rpx 24rpx;
747
-    background: rgba(45, 42, 85, 0.8);
748
-    border: 1rpx solid rgba(167, 139, 250, 0.3);
747
+    background: #f5f5f5;
748
+    border: 1rpx solid #e0e0e0;
749
     border-radius: 50rpx;
749
     border-radius: 50rpx;
750
 }
750
 }
751
 
751
 
@@ -756,12 +756,12 @@ export default {
756
 
756
 
757
 .dept-name-text {
757
 .dept-name-text {
758
     font-size: 26rpx;
758
     font-size: 26rpx;
759
-    color: rgba(255, 255, 255, 0.9);
759
+    color: #333;
760
 }
760
 }
761
 
761
 
762
 .dept-profile-page {
762
 .dept-profile-page {
763
     min-height: 100vh;
763
     min-height: 100vh;
764
-    background: linear-gradient(135deg, #1E1B4B 0%, #312E81 100%);
764
+    background: #fff;
765
     padding-bottom: 40rpx;
765
     padding-bottom: 40rpx;
766
 }
766
 }
767
 
767
 
@@ -769,9 +769,9 @@ export default {
769
     position: sticky;
769
     position: sticky;
770
     top: 0;
770
     top: 0;
771
     z-index: 100;
771
     z-index: 100;
772
-    background: rgba(30, 27, 75, 0.9);
772
+    background: #fff;
773
     backdrop-filter: blur(10px);
773
     backdrop-filter: blur(10px);
774
-    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
774
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
775
 }
775
 }
776
 
776
 
777
 .header-title {
777
 .header-title {
@@ -783,10 +783,7 @@ export default {
783
     .title-main {
783
     .title-main {
784
         font-size: 36rpx;
784
         font-size: 36rpx;
785
         font-weight: bold;
785
         font-weight: bold;
786
-        background: linear-gradient(90deg, #A78BFA, #60A5FA);
787
-        -webkit-background-clip: text;
788
-        -webkit-text-fill-color: transparent;
789
-        background-clip: text;
786
+        color: #333;
790
     }
787
     }
791
 }
788
 }
792
 
789
 
@@ -797,13 +794,13 @@ export default {
797
 
794
 
798
     .current-time {
795
     .current-time {
799
         font-size: 24rpx;
796
         font-size: 24rpx;
800
-        color: rgba(255, 255, 255, 0.6);
797
+        color: #999;
801
     }
798
     }
802
 }
799
 }
803
 
800
 
804
 .time-filter {
801
 .time-filter {
805
     padding: 16rpx 32rpx;
802
     padding: 16rpx 32rpx;
806
-    background: rgba(30, 27, 75, 0.8);
803
+    background: #fff;
807
 }
804
 }
808
 
805
 
809
 .time-scroll {
806
 .time-scroll {
@@ -819,14 +816,14 @@ export default {
819
 .time-tag {
816
 .time-tag {
820
     padding: 8rpx 10rpx;
817
     padding: 8rpx 10rpx;
821
     border-radius: 50rpx;
818
     border-radius: 50rpx;
822
-    background: rgba(45, 42, 85, 0.8);
819
+    background: #f0f0f0;
823
     font-size: 24rpx;
820
     font-size: 24rpx;
824
-    color: rgba(255, 255, 255, 0.6);
821
+    color: #666;
825
     transition: all 0.3s;
822
     transition: all 0.3s;
826
 
823
 
827
     &.active {
824
     &.active {
828
-        background: #A78BFA;
829
-        color: #1E1B4B;
825
+        background: #60A5FA;
826
+        color: #fff;
830
         font-weight: 500;
827
         font-weight: 500;
831
     }
828
     }
832
 }
829
 }
@@ -837,26 +834,26 @@ export default {
837
     gap: 16rpx;
834
     gap: 16rpx;
838
     margin-top: 16rpx;
835
     margin-top: 16rpx;
839
     padding-top: 16rpx;
836
     padding-top: 16rpx;
840
-    border-top: 1rpx solid rgba(255, 255, 255, 0.1);
837
+    border-top: 1rpx solid #e0e0e0;
841
 }
838
 }
842
 
839
 
843
 .date-input {
840
 .date-input {
844
     flex: 1;
841
     flex: 1;
845
     padding: 12rpx 24rpx;
842
     padding: 12rpx 24rpx;
846
     border-radius: 12rpx;
843
     border-radius: 12rpx;
847
-    background: rgba(45, 42, 85, 0.8);
844
+    background: #f5f5f5;
848
     font-size: 24rpx;
845
     font-size: 24rpx;
849
-    color: rgba(255, 255, 255, 0.4);
846
+    color: #999;
850
     text-align: center;
847
     text-align: center;
851
 
848
 
852
     &.filled {
849
     &.filled {
853
-        color: rgba(255, 255, 255, 0.9);
850
+        color: #333;
854
     }
851
     }
855
 }
852
 }
856
 
853
 
857
 .date-separator {
854
 .date-separator {
858
     font-size: 24rpx;
855
     font-size: 24rpx;
859
-    color: rgba(255, 255, 255, 0.5);
856
+    color: #999;
860
     flex-shrink: 0;
857
     flex-shrink: 0;
861
 }
858
 }
862
 
859
 
@@ -869,14 +866,14 @@ export default {
869
 
866
 
870
 .tab-item {
867
 .tab-item {
871
     font-size: 28rpx;
868
     font-size: 28rpx;
872
-    color: rgba(255, 255, 255, 0.5);
869
+    color: #999;
873
     padding-bottom: 8rpx;
870
     padding-bottom: 8rpx;
874
     border-bottom: 2rpx solid transparent;
871
     border-bottom: 2rpx solid transparent;
875
     transition: all 0.3s;
872
     transition: all 0.3s;
876
 
873
 
877
     &.active {
874
     &.active {
878
-        color: #A78BFA;
879
-        border-bottom-color: #A78BFA;
875
+        color: #333;
876
+        border-bottom-color: #333;
880
     }
877
     }
881
 }
878
 }
882
 
879
 
@@ -884,5 +881,6 @@ export default {
884
     padding: 32rpx;
881
     padding: 32rpx;
885
     display: flex;
882
     display: flex;
886
     flex-direction: column;
883
     flex-direction: column;
884
+    background-color: #fff;
887
 }
885
 }
888
 </style>
886
 </style>

+ 3 - 0
src/pages/home/index.vue

@@ -37,6 +37,7 @@
37
       </div>
37
       </div>
38
     </div>
38
     </div>
39
 
39
 
40
+
40
     <Notice ref="notice" />
41
     <Notice ref="notice" />
41
     <!--  今日待办  -->
42
     <!--  今日待办  -->
42
     <!-- <myToDo /> -->
43
     <!-- <myToDo /> -->
@@ -118,6 +119,7 @@ export default {
118
 
119
 
119
   },
120
   },
120
   methods: {
121
   methods: {
122
+
121
     goworkDocu() {
123
     goworkDocu() {
122
       uni.navigateTo({
124
       uni.navigateTo({
123
         url: '/pages/workDocu/index'
125
         url: '/pages/workDocu/index'
@@ -367,4 +369,5 @@ img {
367
   width: 100%;
369
   width: 100%;
368
   margin-bottom: 32rpx;
370
   margin-bottom: 32rpx;
369
 }
371
 }
372
+
370
 </style>
373
 </style>

+ 2 - 2
src/pages/login.vue

@@ -78,7 +78,7 @@ export default {
78
   onLoad() {
78
   onLoad() {
79
     //#ifdef H5
79
     //#ifdef H5
80
     if (getToken()) {
80
     if (getToken()) {
81
-      this.$tab.reLaunch('/pages/work/index')
81
+      this.$tab.reLaunch('/pages/myToDoList/index')
82
     }
82
     }
83
     this.getStorage()
83
     this.getStorage()
84
     //#endif
84
     //#endif
@@ -164,7 +164,7 @@ export default {
164
       this.$store.dispatch('GetInfo').then(res => {
164
       this.$store.dispatch('GetInfo').then(res => {
165
         // 登录成功后调用showMessageTabRedDot更新消息tab红点状态
165
         // 登录成功后调用showMessageTabRedDot更新消息tab红点状态
166
         showMessageTabRedDot()
166
         showMessageTabRedDot()
167
-        this.$tab.reLaunch('/pages/work/index')
167
+        this.$tab.reLaunch('/pages/myToDoList/index')
168
       })
168
       })
169
     }
169
     }
170
   }
170
   }

+ 499 - 0
src/pages/organizationStruct/index.vue

@@ -0,0 +1,499 @@
1
+<template>
2
+    <view class="organization-struct">
3
+        <!-- 顶部搜索栏 -->
4
+        <view class="search-header">
5
+            <view class="search-box">
6
+                <u-icon name="search" color="#999" size="18" />
7
+                <input 
8
+                    v-model="searchKeyword" 
9
+                    class="search-input" 
10
+                    placeholder="搜索部门或人员" 
11
+                    @input="handleSearch"
12
+                />
13
+                <u-icon v-if="searchKeyword" name="close" color="#999" size="16" @click="clearSearch" />
14
+            </view>
15
+        </view>
16
+
17
+        <!-- 面包屑导航 -->
18
+        <view v-if="!searchKeyword && (breadcrumbs.length > 0 || currentNode)" class="breadcrumb">
19
+            <view class="back-btn" v-if="breadcrumbs.length > 0 || currentNode" @click="goBack">
20
+                <u-icon name="arrow-left" color="#666" size="20" />
21
+            </view>
22
+            <view 
23
+                v-for="(item, index) in breadcrumbs" 
24
+                :key="item.id"
25
+                class="breadcrumb-item"
26
+                @click="navigateToBreadcrumb(index)"
27
+            >
28
+                <text :class="['breadcrumb-text', { active: index === breadcrumbs.length - 1 }]">
29
+                    {{ item.label }}
30
+                </text>
31
+                <u-icon v-if="index < breadcrumbs.length - 1" name="arrow-right" color="#999" size="14" />
32
+            </view>
33
+        </view>
34
+
35
+        <!-- 内容区域 -->
36
+        <scroll-view class="content-area" scroll-y>
37
+            <!-- 搜索结果 -->
38
+            <view v-if="searchKeyword" class="search-results">
39
+                <view v-if="searchResults.length === 0" class="empty-state">
40
+                    <text>暂无搜索结果</text>
41
+                </view>
42
+                <view 
43
+                    v-for="item in searchResults" 
44
+                    :key="item.id || item.userId"
45
+                    :class="['result-item', { 'is-dept': item.nodeType === 'dept' || item.children }]"
46
+                    @click="handleResultClick(item)"
47
+                >
48
+                    <view class="result-icon">
49
+                        <u-icon :name="item.nodeType === 'dept' || item.children ? 'folder' : 'account'" 
50
+                            :color="item.nodeType === 'dept' || item.children ? '#A78BFA' : '#60A5FA'" size="20" />
51
+                    </view>
52
+                    <view class="result-info">
53
+                        <text class="result-name">{{ item.label || item.nickName || item.name }}</text>
54
+                        <text v-if="item.nodeType === 'dept' || item.children" class="result-path">
55
+                            {{ getSearchResultPath(item) }}
56
+                        </text>
57
+                    </view>
58
+                </view>
59
+            </view>
60
+
61
+            <!-- 正常浏览 -->
62
+            <view v-else class="dept-list">
63
+                <!-- 部门项 -->
64
+                <view 
65
+                    v-for="item in displayDepts" 
66
+                    :key="item.id"
67
+                    class="dept-item"
68
+                    @click="navigateToDept(item)"
69
+                >
70
+                    <view class="dept-icon">
71
+                        <u-icon name="folder" color="#A78BFA" size="40" />
72
+                    </view>
73
+                    <view class="dept-info">
74
+                        <text class="dept-name">{{ item.label || item.name }}</text>
75
+                        <text class="dept-count">{{ getNodeCount(item) }}</text>
76
+                    </view>
77
+                    <u-icon name="arrow-right" color="#ccc" size="20" />
78
+                </view>
79
+
80
+                <!-- 人员项 -->
81
+                <view 
82
+                    v-for="item in displayUsers" 
83
+                    :key="item.userId"
84
+                    class="user-item"
85
+                >
86
+                    <view class="user-avatar">
87
+                        <image v-if="item.avatar" :src="item.avatar" mode="aspectFill" class="avatar-img" />
88
+                        <view v-else class="avatar-placeholder">
89
+                            <text>{{ (item.label || item.nickName || item.userName || '').slice(-1) }}</text>
90
+                        </view>
91
+                    </view>
92
+                    <view class="user-info">
93
+                        <text class="user-name">{{ item.label  }}</text>
94
+                        <text v-if="item.postNames" class="user-post">{{ item.postNames }}</text>
95
+                    </view>
96
+                </view>
97
+
98
+                <!-- 空状态 -->
99
+                <view v-if="!displayDepts.length && !displayUsers.length && !loading" class="empty-state">
100
+                    <text>暂无数据</text>
101
+                </view>
102
+
103
+                <!-- 加载中 -->
104
+                <view v-if="loading" class="loading-state">
105
+                    <u-loading-icon text="加载中" textSize="14" />
106
+                </view>
107
+            </view>
108
+        </scroll-view>
109
+    </view>
110
+</template>
111
+
112
+<script>
113
+import { getDeptUserTree } from '@/api/system/user'
114
+
115
+export default {
116
+    name: 'OrganizationStruct',
117
+    data() {
118
+        return {
119
+            loading: false,
120
+            searchKeyword: '',
121
+            treeData: [],
122
+            currentNode: null,
123
+            breadcrumbs: [],
124
+            searchResults: []
125
+        }
126
+    },
127
+    computed: {
128
+        displayDepts() {
129
+            if (!this.currentNode) {
130
+                return this.treeData.filter(item => item.nodeType === 'dept' || (item.children && item.children.length > 0))
131
+            }
132
+            return (this.currentNode.children || []).filter(item => item.nodeType === 'dept' || (item.children && item.children.length > 0))
133
+        },
134
+        displayUsers() {
135
+            if (!this.currentNode) {
136
+                return this.treeData.filter(item => item.nodeType === 'user')
137
+            }
138
+            return (this.currentNode.children || []).filter(item => item.nodeType === 'user')
139
+        }
140
+    },
141
+    mounted() {
142
+        this.fetchTreeData()
143
+    },
144
+    methods: {
145
+        async fetchTreeData() {
146
+            this.loading = true
147
+            try {
148
+                const res = await getDeptUserTree()
149
+                this.treeData = res.data || []
150
+                this.currentNode = null
151
+                this.breadcrumbs = []
152
+            } catch (error) {
153
+                console.error('获取组织架构失败:', error)
154
+                uni.showToast({ title: '获取数据失败', icon: 'none' })
155
+            } finally {
156
+                this.loading = false
157
+            }
158
+        },
159
+        getNodeCount(node) {
160
+            const children = node.children || []
161
+            const deptCount = children.filter(c => c.nodeType === 'dept' || (c.children && c.children.length > 0)).length
162
+            const userCount = children.filter(c => c.nodeType === 'user').length
163
+            const parts = []
164
+            if (deptCount > 0) parts.push(`${deptCount}个部门`)
165
+            if (userCount > 0) parts.push(`${userCount}人`)
166
+            return parts.join('、')
167
+        },
168
+        navigateToDept(node) {
169
+            this.breadcrumbs.push({
170
+                id: node.id,
171
+                label: node.label || node.name
172
+            })
173
+            this.currentNode = node
174
+        },
175
+        goBack() {
176
+            if (this.breadcrumbs.length <= 1) {
177
+                this.currentNode = null
178
+                this.breadcrumbs = []
179
+            } else {
180
+                this.breadcrumbs.pop()
181
+                if (this.breadcrumbs.length === 0) {
182
+                    this.currentNode = null
183
+                } else {
184
+                    const targetId = this.breadcrumbs[this.breadcrumbs.length - 1].id
185
+                    this.currentNode = this.findNodeById(this.treeData, targetId)
186
+                }
187
+            }
188
+        },
189
+        navigateToBreadcrumb(index) {
190
+            if (index === 0) {
191
+                this.currentNode = null
192
+                this.breadcrumbs = []
193
+            } else {
194
+                const targetId = this.breadcrumbs[index].id
195
+                let targetNode = this.findNodeById(this.treeData, targetId)
196
+                if (targetNode) {
197
+                    this.currentNode = targetNode
198
+                    this.breadcrumbs = this.breadcrumbs.slice(0, index + 1)
199
+                }
200
+            }
201
+        },
202
+        findNodeById(nodes, id) {
203
+            for (const node of nodes) {
204
+                if (node.id === id) return node
205
+                if (node.children && node.children.length > 0) {
206
+                    const found = this.findNodeById(node.children, id)
207
+                    if (found) return found
208
+                }
209
+            }
210
+            return null
211
+        },
212
+        handleSearch() {
213
+            if (!this.searchKeyword.trim()) {
214
+                this.searchResults = []
215
+                return
216
+            }
217
+            const keyword = this.searchKeyword.trim().toLowerCase()
218
+            this.searchResults = this.searchTree(this.treeData, keyword)
219
+        },
220
+        searchTree(nodes, keyword, path = []) {
221
+            let results = []
222
+            for (const node of nodes) {
223
+                const name = (node.label || node.nickName || node.name || '').toLowerCase()
224
+                if (name.includes(keyword)) {
225
+                    results.push({
226
+                        ...node,
227
+                        searchPath: [...path]
228
+                    })
229
+                }
230
+                if (node.children && node.children.length > 0) {
231
+                    results = results.concat(
232
+                        this.searchTree(node.children, keyword, [...path, node.label || node.name])
233
+                    )
234
+                }
235
+            }
236
+            return results
237
+        },
238
+        getSearchResultPath(item) {
239
+            if (!item.searchPath || !item.searchPath.length) return ''
240
+            return item.searchPath.join(' > ')
241
+        },
242
+        handleResultClick(item) {
243
+            if (item.children || item.nodeType === 'dept') {
244
+                this.searchKeyword = ''
245
+                this.searchResults = []
246
+                const targetNode = this.findNodeById(this.treeData, item.id)
247
+                if (targetNode) {
248
+                    this.navigateToNodeFromRoot(targetNode)
249
+                }
250
+            }
251
+        },
252
+        navigateToNodeFromRoot(targetNode) {
253
+            const path = this.findPathToNode(this.treeData, targetNode.id)
254
+            if (path.length > 0) {
255
+                this.breadcrumbs = path.slice(0, -1).map(node => ({
256
+                    id: node.id,
257
+                    label: node.label || node.name
258
+                }))
259
+                this.currentNode = targetNode
260
+            }
261
+        },
262
+        findPathToNode(nodes, id, path = []) {
263
+            for (const node of nodes) {
264
+                const newPath = [...path, node]
265
+                if (node.id === id) {
266
+                    return newPath
267
+                }
268
+                if (node.children && node.children.length > 0) {
269
+                    const found = this.findPathToNode(node.children, id, newPath)
270
+                    if (found.length > 0) {
271
+                        return found
272
+                    }
273
+                }
274
+            }
275
+            return []
276
+        },
277
+        clearSearch() {
278
+            this.searchKeyword = ''
279
+            this.searchResults = []
280
+        }
281
+    }
282
+}
283
+</script>
284
+
285
+<style lang="scss" scoped>
286
+.organization-struct {
287
+    min-height: 100vh;
288
+    background-color: #f5f7fa;
289
+    display: flex;
290
+    flex-direction: column;
291
+}
292
+
293
+.search-header {
294
+    padding: 24rpx 32rpx;
295
+    background-color: #fff;
296
+}
297
+
298
+.search-box {
299
+    display: flex;
300
+    align-items: center;
301
+    gap: 16rpx;
302
+    padding: 16rpx 24rpx;
303
+    background-color: #f5f7fa;
304
+    border-radius: 48rpx;
305
+}
306
+
307
+.search-input {
308
+    flex: 1;
309
+    font-size: 28rpx;
310
+    color: #333;
311
+}
312
+
313
+.breadcrumb {
314
+    display: flex;
315
+    align-items: center;
316
+    padding: 20rpx 32rpx;
317
+    background-color: #fff;
318
+    overflow-x: auto;
319
+    white-space: nowrap;
320
+    border-bottom: 1rpx solid #eee;
321
+}
322
+
323
+.back-btn {
324
+    display: flex;
325
+    align-items: center;
326
+    justify-content: center;
327
+    padding-right: 16rpx;
328
+    flex-shrink: 0;
329
+}
330
+
331
+.breadcrumb-item {
332
+    display: flex;
333
+    align-items: center;
334
+    gap: 8rpx;
335
+    flex-shrink: 0;
336
+}
337
+
338
+.breadcrumb-text {
339
+    font-size: 28rpx;
340
+    color: #999;
341
+
342
+    &.active {
343
+        color: #333;
344
+        font-weight: 500;
345
+    }
346
+}
347
+
348
+.content-area {
349
+    flex: 1;
350
+    padding: 24rpx 32rpx;
351
+}
352
+
353
+.search-results {
354
+    background-color: #fff;
355
+    border-radius: 16rpx;
356
+    padding: 16rpx 0;
357
+}
358
+
359
+.result-item {
360
+    display: flex;
361
+    align-items: center;
362
+    padding: 24rpx 32rpx;
363
+    gap: 20rpx;
364
+    border-bottom: 1rpx solid #f5f5f5;
365
+}
366
+
367
+.result-icon {
368
+    width: 64rpx;
369
+    height: 64rpx;
370
+    display: flex;
371
+    align-items: center;
372
+    justify-content: center;
373
+}
374
+
375
+.result-info {
376
+    flex: 1;
377
+    display: flex;
378
+    flex-direction: column;
379
+    gap: 8rpx;
380
+}
381
+
382
+.result-name {
383
+    font-size: 30rpx;
384
+    color: #333;
385
+}
386
+
387
+.result-path {
388
+    font-size: 24rpx;
389
+    color: #999;
390
+}
391
+
392
+.dept-list {
393
+    display: flex;
394
+    flex-direction: column;
395
+    gap: 20rpx;
396
+}
397
+
398
+.dept-item {
399
+    display: flex;
400
+    align-items: center;
401
+    padding: 24rpx 32rpx;
402
+    background-color: #fff;
403
+    border-radius: 16rpx;
404
+    gap: 20rpx;
405
+}
406
+
407
+.dept-icon {
408
+    width: 80rpx;
409
+    height: 80rpx;
410
+    display: flex;
411
+    align-items: center;
412
+    justify-content: center;
413
+    background-color: #f5f5f5;
414
+    border-radius: 12rpx;
415
+}
416
+
417
+.dept-info {
418
+    flex: 1;
419
+    display: flex;
420
+    flex-direction: column;
421
+    gap: 8rpx;
422
+}
423
+
424
+.dept-name {
425
+    font-size: 32rpx;
426
+    color: #333;
427
+    font-weight: 500;
428
+}
429
+
430
+.dept-count {
431
+    font-size: 24rpx;
432
+    color: #999;
433
+}
434
+
435
+.user-item {
436
+    display: flex;
437
+    align-items: center;
438
+    padding: 24rpx 32rpx;
439
+    background-color: #fff;
440
+    border-radius: 16rpx;
441
+    gap: 20rpx;
442
+}
443
+
444
+.user-avatar {
445
+    width: 80rpx;
446
+    height: 80rpx;
447
+    flex-shrink: 0;
448
+}
449
+
450
+.avatar-img {
451
+    width: 100%;
452
+    height: 100%;
453
+    border-radius: 50%;
454
+}
455
+
456
+.avatar-placeholder {
457
+    width: 100%;
458
+    height: 100%;
459
+    border-radius: 50%;
460
+    background: linear-gradient(135deg, #60A5FA, #A78BFA);
461
+    display: flex;
462
+    align-items: center;
463
+    justify-content: center;
464
+
465
+    text {
466
+        font-size: 32rpx;
467
+        color: #fff;
468
+        font-weight: 500;
469
+    }
470
+}
471
+
472
+.user-info {
473
+    flex: 1;
474
+    display: flex;
475
+    flex-direction: column;
476
+    gap: 6rpx;
477
+}
478
+
479
+.user-name {
480
+    font-size: 32rpx;
481
+    color: #333;
482
+    font-weight: 500;
483
+}
484
+
485
+.user-post {
486
+    font-size: 24rpx;
487
+    color: #999;
488
+}
489
+
490
+.empty-state,
491
+.loading-state {
492
+    padding: 80rpx 32rpx;
493
+    display: flex;
494
+    align-items: center;
495
+    justify-content: center;
496
+    color: #999;
497
+    font-size: 28rpx;
498
+}
499
+</style>

+ 0 - 0
src/pages/organizationStruct/组织架构


+ 105 - 0
src/pages/profileManage/index.vue

@@ -0,0 +1,105 @@
1
+<template>
2
+  <home-container :customStyle="{ background: 'none', backgroundColor: '#f5f5f5' }">
3
+    <view class="profile-manage-container">
4
+      <view class="grid-body">
5
+        <uni-section title="画像管理" type="line" class="uni-section-custom"></uni-section>
6
+        <uni-grid :column="3" :showBorder="false">
7
+          <uni-grid-item v-for="(item, index) in items" :key="index">
8
+            <view class="grid-item-box" @click="handleGridClick(item.appUrl)">
9
+              <img alt="" :src="item.workbenchIcon">
10
+              <text class="text">{{ item.appName }}</text>
11
+            </view>
12
+          </uni-grid-item>
13
+        </uni-grid>
14
+      </view>
15
+    </view>
16
+  </home-container>
17
+</template>
18
+
19
+<script>
20
+import HomeContainer from '@/components/HomeContainer.vue'
21
+import { getAppList } from '@/api/system/user'
22
+
23
+const profileAppNames = ['站画像', '部门画像', '班组画像', '小组画像', '员工画像']
24
+
25
+export default {
26
+  components: { HomeContainer },
27
+  data() {
28
+    return {
29
+      appList: []
30
+    }
31
+  },
32
+  computed: {
33
+    items() {
34
+      return this.appList.filter(item => profileAppNames.includes(item.appName))
35
+    }
36
+  },
37
+  onLoad() {
38
+    this.loadAppList()
39
+  },
40
+  methods: {
41
+    async loadAppList() {
42
+      try {
43
+        const res = await getAppList({ homePage: 0 })
44
+        if (res.code === 200) {
45
+          this.appList = res.data || []
46
+        }
47
+      } catch (error) {
48
+        console.error('获取画像应用列表失败:', error)
49
+        this.appList = []
50
+      }
51
+    },
52
+    handleGridClick(url) {
53
+      if (!url) return
54
+      uni.navigateTo({ url })
55
+    }
56
+  }
57
+}
58
+</script>
59
+
60
+<style lang="scss" scoped>
61
+.profile-manage-container {
62
+  padding: 20rpx;
63
+  background-color: #f5f5f5;
64
+}
65
+
66
+.grid-body {
67
+  background-color: #fff;
68
+  border-radius: 16rpx;
69
+  padding: 20rpx;
70
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
71
+
72
+  .uni-section-custom {
73
+    ::v-deep .uni-section-header {
74
+      padding: 12rpx 10rpx !important;
75
+    }
76
+  }
77
+}
78
+
79
+.grid-item-box {
80
+  flex: 1;
81
+  display: flex;
82
+  flex-direction: column;
83
+  align-items: center;
84
+  justify-content: center;
85
+  padding: 30rpx 0;
86
+
87
+  img {
88
+    display: inline-block;
89
+    width: 88rpx;
90
+    height: 88rpx;
91
+  }
92
+
93
+  &:active {
94
+    background-color: #f5f5f5;
95
+    border-radius: 12rpx;
96
+  }
97
+}
98
+
99
+.text {
100
+  text-align: center;
101
+  font-size: 25rpx;
102
+  margin-top: 15rpx;
103
+  color: #333;
104
+}
105
+</style>

+ 20 - 27
src/pages/stationProfile/index.vue

@@ -588,7 +588,7 @@ export default {
588
 <style lang="scss" scoped>
588
 <style lang="scss" scoped>
589
 .station-profile-page {
589
 .station-profile-page {
590
     min-height: 100vh;
590
     min-height: 100vh;
591
-    background: linear-gradient(135deg, #1E1B4B 0%, #312E81 100%);
591
+    background: #fff;
592
     padding-bottom: 40rpx;
592
     padding-bottom: 40rpx;
593
 }
593
 }
594
 
594
 
@@ -596,9 +596,9 @@ export default {
596
     position: sticky;
596
     position: sticky;
597
     top: 0;
597
     top: 0;
598
     z-index: 100;
598
     z-index: 100;
599
-    background: rgba(30, 27, 75, 0.9);
599
+    background: #fff;
600
     backdrop-filter: blur(10px);
600
     backdrop-filter: blur(10px);
601
-    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
601
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
602
 }
602
 }
603
 
603
 
604
 .header-title {
604
 .header-title {
@@ -610,10 +610,7 @@ export default {
610
     .title-main {
610
     .title-main {
611
         font-size: 36rpx;
611
         font-size: 36rpx;
612
         font-weight: bold;
612
         font-weight: bold;
613
-        background: linear-gradient(90deg, #A78BFA, #60A5FA);
614
-        -webkit-background-clip: text;
615
-        -webkit-text-fill-color: transparent;
616
-        background-clip: text;
613
+        color: #333;
617
     }
614
     }
618
 }
615
 }
619
 
616
 
@@ -624,18 +621,13 @@ export default {
624
 
621
 
625
     .current-time {
622
     .current-time {
626
         font-size: 24rpx;
623
         font-size: 24rpx;
627
-        color: rgba(255, 255, 255, 0.6);
628
-    }
629
-
630
-    .menu-icon {
631
-        display: flex;
632
-        align-items: center;
624
+        color: #999;
633
     }
625
     }
634
 }
626
 }
635
 
627
 
636
 .time-filter {
628
 .time-filter {
637
     padding: 16rpx 32rpx;
629
     padding: 16rpx 32rpx;
638
-    background: rgba(30, 27, 75, 0.8);
630
+    background: #fff;
639
 }
631
 }
640
 
632
 
641
 .time-scroll {
633
 .time-scroll {
@@ -651,14 +643,14 @@ export default {
651
 .time-tag {
643
 .time-tag {
652
     padding: 8rpx 10rpx;
644
     padding: 8rpx 10rpx;
653
     border-radius: 50rpx;
645
     border-radius: 50rpx;
654
-    background: rgba(45, 42, 85, 0.8);
646
+    background: #f0f0f0;
655
     font-size: 24rpx;
647
     font-size: 24rpx;
656
-    color: rgba(255, 255, 255, 0.6);
648
+    color: #666;
657
     transition: all 0.3s;
649
     transition: all 0.3s;
658
 
650
 
659
     &.active {
651
     &.active {
660
-        background: #A78BFA;
661
-        color: #1E1B4B;
652
+        background: #60A5FA;
653
+        color: #fff;
662
         font-weight: 500;
654
         font-weight: 500;
663
     }
655
     }
664
 }
656
 }
@@ -669,26 +661,26 @@ export default {
669
     gap: 16rpx;
661
     gap: 16rpx;
670
     margin-top: 16rpx;
662
     margin-top: 16rpx;
671
     padding-top: 16rpx;
663
     padding-top: 16rpx;
672
-    border-top: 1rpx solid rgba(255, 255, 255, 0.1);
664
+    border-top: 1rpx solid #e0e0e0;
673
 }
665
 }
674
 
666
 
675
 .date-input {
667
 .date-input {
676
     flex: 1;
668
     flex: 1;
677
     padding: 12rpx 24rpx;
669
     padding: 12rpx 24rpx;
678
     border-radius: 12rpx;
670
     border-radius: 12rpx;
679
-    background: rgba(45, 42, 85, 0.8);
671
+    background: #f5f5f5;
680
     font-size: 24rpx;
672
     font-size: 24rpx;
681
-    color: rgba(255, 255, 255, 0.4);
673
+    color: #999;
682
     text-align: center;
674
     text-align: center;
683
 
675
 
684
     &.filled {
676
     &.filled {
685
-        color: rgba(255, 255, 255, 0.9);
677
+        color: #333;
686
     }
678
     }
687
 }
679
 }
688
 
680
 
689
 .date-separator {
681
 .date-separator {
690
     font-size: 24rpx;
682
     font-size: 24rpx;
691
-    color: rgba(255, 255, 255, 0.5);
683
+    color: #999;
692
     flex-shrink: 0;
684
     flex-shrink: 0;
693
 }
685
 }
694
 
686
 
@@ -701,14 +693,14 @@ export default {
701
 
693
 
702
 .tab-item {
694
 .tab-item {
703
     font-size: 28rpx;
695
     font-size: 28rpx;
704
-    color: rgba(255, 255, 255, 0.5);
696
+    color: #999;
705
     padding-bottom: 8rpx;
697
     padding-bottom: 8rpx;
706
     border-bottom: 2rpx solid transparent;
698
     border-bottom: 2rpx solid transparent;
707
     transition: all 0.3s;
699
     transition: all 0.3s;
708
 
700
 
709
     &.active {
701
     &.active {
710
-        color: #A78BFA;
711
-        border-bottom-color: #A78BFA;
702
+        color: #333;
703
+        border-bottom-color: #333;
712
     }
704
     }
713
 }
705
 }
714
 
706
 
@@ -716,9 +708,10 @@ export default {
716
     padding: 32rpx;
708
     padding: 32rpx;
717
     display: flex;
709
     display: flex;
718
     flex-direction: column;
710
     flex-direction: column;
719
-    /* gap: 32rpx; */
711
+    background-color: #fff;
720
 }
712
 }
721
 
713
 
714
+
722
 .two-col-grid {
715
 .two-col-grid {
723
     display: grid;
716
     display: grid;
724
     grid-template-columns: repeat(2, 1fr);
717
     grid-template-columns: repeat(2, 1fr);

+ 24 - 27
src/pages/teamProfile/index.vue

@@ -77,7 +77,7 @@
77
             </view>
77
             </view>
78
 
78
 
79
             <view v-if="activeTab === 'data'">
79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="当日开航每小时通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81
                     <PassengerChart :chartsData="passengerData" />
81
                     <PassengerChart :chartsData="passengerData" />
82
                 </SectionTitle>
82
                 </SectionTitle>
83
 
83
 
@@ -94,7 +94,7 @@
94
                 </SectionTitle>
94
                 </SectionTitle>
95
 
95
 
96
                 <SectionTitle title="每日查获数量">
96
                 <SectionTitle title="每日查获数量">
97
-                    <DailySeizureChart :chartsData="dailySeizureData" />
97
+                    <DailySeizureChart :chartsData="dailySeizureData" :title="'小组对比'"/>
98
                 </SectionTitle>
98
                 </SectionTitle>
99
 
99
 
100
                 <SectionTitle title="查获工作区域分布">
100
                 <SectionTitle title="查获工作区域分布">
@@ -751,7 +751,7 @@ export default {
751
 <style lang="scss" scoped>
751
 <style lang="scss" scoped>
752
 .dept-selector {
752
 .dept-selector {
753
     padding: 16rpx 32rpx;
753
     padding: 16rpx 32rpx;
754
-    background: rgba(30, 27, 75, 0.8);
754
+    background: #fff;
755
 }
755
 }
756
 
756
 
757
 .dept-select-trigger {
757
 .dept-select-trigger {
@@ -760,8 +760,8 @@ export default {
760
     justify-content: center;
760
     justify-content: center;
761
     gap: 12rpx;
761
     gap: 12rpx;
762
     padding: 16rpx 24rpx;
762
     padding: 16rpx 24rpx;
763
-    background: rgba(45, 42, 85, 0.8);
764
-    border: 1rpx solid rgba(167, 139, 250, 0.3);
763
+    background: #f5f5f5;
764
+    border: 1rpx solid #e0e0e0;
765
     border-radius: 50rpx;
765
     border-radius: 50rpx;
766
 }
766
 }
767
 
767
 
@@ -772,12 +772,12 @@ export default {
772
 
772
 
773
 .dept-name-text {
773
 .dept-name-text {
774
     font-size: 26rpx;
774
     font-size: 26rpx;
775
-    color: rgba(255, 255, 255, 0.9);
775
+    color: #333;
776
 }
776
 }
777
 
777
 
778
 .dept-profile-page {
778
 .dept-profile-page {
779
     min-height: 100vh;
779
     min-height: 100vh;
780
-    background: linear-gradient(135deg, #1E1B4B 0%, #312E81 100%);
780
+    background: #fff;
781
     padding-bottom: 40rpx;
781
     padding-bottom: 40rpx;
782
 }
782
 }
783
 
783
 
@@ -785,9 +785,9 @@ export default {
785
     position: sticky;
785
     position: sticky;
786
     top: 0;
786
     top: 0;
787
     z-index: 100;
787
     z-index: 100;
788
-    background: rgba(30, 27, 75, 0.9);
788
+    background: #fff;
789
     backdrop-filter: blur(10px);
789
     backdrop-filter: blur(10px);
790
-    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
790
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
791
 }
791
 }
792
 
792
 
793
 .header-title {
793
 .header-title {
@@ -799,10 +799,7 @@ export default {
799
     .title-main {
799
     .title-main {
800
         font-size: 36rpx;
800
         font-size: 36rpx;
801
         font-weight: bold;
801
         font-weight: bold;
802
-        background: linear-gradient(90deg, #A78BFA, #60A5FA);
803
-        -webkit-background-clip: text;
804
-        -webkit-text-fill-color: transparent;
805
-        background-clip: text;
802
+        color: #333;
806
     }
803
     }
807
 }
804
 }
808
 
805
 
@@ -813,13 +810,13 @@ export default {
813
 
810
 
814
     .current-time {
811
     .current-time {
815
         font-size: 24rpx;
812
         font-size: 24rpx;
816
-        color: rgba(255, 255, 255, 0.6);
813
+        color: #999;
817
     }
814
     }
818
 }
815
 }
819
 
816
 
820
 .time-filter {
817
 .time-filter {
821
     padding: 16rpx 32rpx;
818
     padding: 16rpx 32rpx;
822
-    background: rgba(30, 27, 75, 0.8);
819
+    background: #fff;
823
 }
820
 }
824
 
821
 
825
 .time-scroll {
822
 .time-scroll {
@@ -835,14 +832,14 @@ export default {
835
 .time-tag {
832
 .time-tag {
836
     padding: 8rpx 10rpx;
833
     padding: 8rpx 10rpx;
837
     border-radius: 50rpx;
834
     border-radius: 50rpx;
838
-    background: rgba(45, 42, 85, 0.8);
835
+    background: #f0f0f0;
839
     font-size: 24rpx;
836
     font-size: 24rpx;
840
-    color: rgba(255, 255, 255, 0.6);
837
+    color: #666;
841
     transition: all 0.3s;
838
     transition: all 0.3s;
842
 
839
 
843
     &.active {
840
     &.active {
844
-        background: #A78BFA;
845
-        color: #1E1B4B;
841
+        background: #60A5FA;
842
+        color: #fff;
846
         font-weight: 500;
843
         font-weight: 500;
847
     }
844
     }
848
 }
845
 }
@@ -853,26 +850,26 @@ export default {
853
     gap: 16rpx;
850
     gap: 16rpx;
854
     margin-top: 16rpx;
851
     margin-top: 16rpx;
855
     padding-top: 16rpx;
852
     padding-top: 16rpx;
856
-    border-top: 1rpx solid rgba(255, 255, 255, 0.1);
853
+    border-top: 1rpx solid #e0e0e0;
857
 }
854
 }
858
 
855
 
859
 .date-input {
856
 .date-input {
860
     flex: 1;
857
     flex: 1;
861
     padding: 12rpx 24rpx;
858
     padding: 12rpx 24rpx;
862
     border-radius: 12rpx;
859
     border-radius: 12rpx;
863
-    background: rgba(45, 42, 85, 0.8);
860
+    background: #f5f5f5;
864
     font-size: 24rpx;
861
     font-size: 24rpx;
865
-    color: rgba(255, 255, 255, 0.4);
862
+    color: #999;
866
     text-align: center;
863
     text-align: center;
867
 
864
 
868
     &.filled {
865
     &.filled {
869
-        color: rgba(255, 255, 255, 0.9);
866
+        color: #333;
870
     }
867
     }
871
 }
868
 }
872
 
869
 
873
 .date-separator {
870
 .date-separator {
874
     font-size: 24rpx;
871
     font-size: 24rpx;
875
-    color: rgba(255, 255, 255, 0.5);
872
+    color: #999;
876
     flex-shrink: 0;
873
     flex-shrink: 0;
877
 }
874
 }
878
 
875
 
@@ -885,14 +882,14 @@ export default {
885
 
882
 
886
 .tab-item {
883
 .tab-item {
887
     font-size: 28rpx;
884
     font-size: 28rpx;
888
-    color: rgba(255, 255, 255, 0.5);
885
+    color: #999;
889
     padding-bottom: 8rpx;
886
     padding-bottom: 8rpx;
890
     border-bottom: 2rpx solid transparent;
887
     border-bottom: 2rpx solid transparent;
891
     transition: all 0.3s;
888
     transition: all 0.3s;
892
 
889
 
893
     &.active {
890
     &.active {
894
-        color: #A78BFA;
895
-        border-bottom-color: #A78BFA;
891
+        color: #333;
892
+        border-bottom-color: #333;
896
     }
893
     }
897
 }
894
 }
898
 
895
 

+ 7 - 2
src/pages/work/index.vue

@@ -33,13 +33,15 @@
33
         </uni-grid>
33
         </uni-grid>
34
       </view>
34
       </view>
35
     </div>
35
     </div>
36
+
37
+   
36
   </home-container>
38
   </home-container>
37
 </template>
39
 </template>
38
 <script>
40
 <script>
39
 import HomeContainer from "@/components/HomeContainer.vue";
41
 import HomeContainer from "@/components/HomeContainer.vue";
40
 import { checkRolePermission } from "@/utils/common.js";
42
 import { checkRolePermission } from "@/utils/common.js";
41
 import { getUserProfile, getAppListByRoleId,getAppList } from "@/api/system/user";
43
 import { getUserProfile, getAppListByRoleId,getAppList } from "@/api/system/user";
42
-
44
+const profileAppNames = ['站画像', '部门画像', '班组画像', '小组画像', '员工画像']
43
 export default {
45
 export default {
44
   components: { HomeContainer },
46
   components: { HomeContainer },
45
   data() {
47
   data() {
@@ -53,7 +55,7 @@ export default {
53
       return this.$store?.state?.user?.roles[0]
55
       return this.$store?.state?.user?.roles[0]
54
     },
56
     },
55
     items() {
57
     items() {
56
-      return this.appList
58
+      return this.appList.filter(item => !profileAppNames.includes(item.appName))
57
     }
59
     }
58
   },
60
   },
59
   onShow() {
61
   onShow() {
@@ -93,6 +95,7 @@ export default {
93
     updateCurrentDate() {
95
     updateCurrentDate() {
94
       this.currentDate = this.formatDate(new Date());
96
       this.currentDate = this.formatDate(new Date());
95
     },
97
     },
98
+   
96
     handleGridClick(url) {
99
     handleGridClick(url) {
97
       console.log('点击了宫格:', url, this.role);
100
       console.log('点击了宫格:', url, this.role);
98
       uni.navigateTo({
101
       uni.navigateTo({
@@ -218,4 +221,6 @@ export default {
218
   font-size: 24rpx;
221
   font-size: 24rpx;
219
   color: #999;
222
   color: #999;
220
 }
223
 }
224
+
225
+
221
 </style>
226
 </style>

BIN
src/static/images/tabbar/ai.png


BIN
src/static/images/tabbar/ai_.png