jiaxing.liao преди 3 дни
родител
ревизия
2e6771e281
променени са 13 файла, в които са добавени 1050 реда и са изтрити 657 реда
  1. 1 1
      .env.production
  2. 3 0
      components.d.ts
  3. 1 0
      package.json
  4. 109 0
      pnpm-lock.yaml
  5. 6 1
      src/api/index.ts
  6. 1 1
      src/main.ts
  7. 13 10
      src/utils/request.ts
  8. 311 92
      src/views/home/index.vue
  9. 6 8
      src/views/process/ProjectMilestone.vue
  10. 285 346
      src/views/process/index.vue
  11. 121 31
      src/views/quality/index.vue
  12. 190 164
      src/views/safety/index.vue
  13. 3 3
      vite.config.ts

+ 1 - 1
.env.production

@@ -1,2 +1,2 @@
 # 服务器地址
-VITE_APP_SERVER='/api'
+VITE_APP_SERVER=''

+ 3 - 0
components.d.ts

@@ -10,9 +10,12 @@ declare module 'vue' {
   export interface GlobalComponents {
     CardTitle: typeof import('./src/components/CardTitle.vue')['default']
     Chart: typeof import('./src/components/Chart/index.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     Panel: typeof import('./src/components/Panel.vue')['default']

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "lodash-es": "^4.17.21",
     "normalize.css": "^8.0.1",
     "vue": "^3.5.18",
+    "vue-hooks-plus": "^2.4.1",
     "vue-router": "^4.5.1"
   },
   "devDependencies": {

+ 109 - 0
pnpm-lock.yaml

@@ -41,6 +41,9 @@ importers:
       vue:
         specifier: ^3.5.18
         version: 3.5.22(typescript@5.8.3)
+      vue-hooks-plus:
+        specifier: ^2.4.1
+        version: 2.4.1(vue@3.5.22(typescript@5.8.3))
       vue-router:
         specifier: ^4.5.1
         version: 4.6.3(vue@3.5.22(typescript@5.8.3))
@@ -1358,6 +1361,9 @@ packages:
   '@types/estree@1.0.8':
     resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
 
+  '@types/js-cookie@3.0.6':
+    resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
+
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
@@ -1588,6 +1594,15 @@ packages:
   '@vue/devtools-api@6.6.4':
     resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
 
+  '@vue/devtools-api@7.7.2':
+    resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==}
+
+  '@vue/devtools-kit@7.7.8':
+    resolution: {integrity: sha512-4Y8op+AoxOJhB9fpcEF6d5vcJXWKgHxC3B0ytUB8zz15KbP9g9WgVzral05xluxi2fOeAy6t140rdQ943GcLRQ==}
+
+  '@vue/devtools-shared@7.7.8':
+    resolution: {integrity: sha512-XHpO3jC5nOgYr40M9p8Z4mmKfTvUxKyRcUnpBAYg11pE78eaRFBKb0kG5yKLroMuJeeNH9LWmKp2zMU5LUc7CA==}
+
   '@vue/language-core@3.1.1':
     resolution: {integrity: sha512-qjMY3Q+hUCjdH+jLrQapqgpsJ0rd/2mAY02lZoHG3VFJZZZKLjAlV+Oo9QmWIT4jh8+Rx8RUGUi++d7T9Wb6Mw==}
     peerDependencies:
@@ -1744,6 +1759,9 @@ packages:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
 
+  birpc@2.8.0:
+    resolution: {integrity: sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==}
+
   boolbase@1.0.0:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
 
@@ -1840,6 +1858,10 @@ packages:
   copy-anything@2.0.6:
     resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
 
+  copy-anything@4.0.5:
+    resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
+    engines: {node: '>=18'}
+
   core-js-compat@3.46.0:
     resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==}
 
@@ -2319,6 +2341,9 @@ packages:
     resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
     engines: {node: '>= 0.4'}
 
+  hookable@5.5.3:
+    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
+
   iconv-lite@0.4.24:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
@@ -2469,6 +2494,10 @@ packages:
   is-what@3.14.1:
     resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
 
+  is-what@5.5.0:
+    resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
+    engines: {node: '>=18'}
+
   isarray@0.0.1:
     resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
 
@@ -2489,6 +2518,10 @@ packages:
   js-binary-schema-parser@2.0.3:
     resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==}
 
+  js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -2633,6 +2666,9 @@ packages:
   minimist@1.2.6:
     resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
 
+  mitt@3.0.1:
+    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+
   mlly@1.8.0:
     resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
 
@@ -2911,6 +2947,9 @@ packages:
     resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
+  rfdc@1.4.1:
+    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
   rollup@4.52.4:
     resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -2946,6 +2985,10 @@ packages:
   sax@1.4.1:
     resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
 
+  screenfull@5.2.0:
+    resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
+    engines: {node: '>=0.10.0'}
+
   scule@1.3.0:
     resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
 
@@ -3039,6 +3082,10 @@ packages:
     resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
     engines: {node: '>=0.10.0'}
 
+  speakingurl@14.0.1:
+    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
+    engines: {node: '>=0.10.0'}
+
   stop-iteration-iterator@1.1.0:
     resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
     engines: {node: '>= 0.4'}
@@ -3078,6 +3125,10 @@ packages:
   strip-literal@3.1.0:
     resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
 
+  superjson@2.2.5:
+    resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==}
+    engines: {node: '>=16'}
+
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -3332,6 +3383,11 @@ packages:
   vue-flow-layout@0.2.0:
     resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==}
 
+  vue-hooks-plus@2.4.1:
+    resolution: {integrity: sha512-DY9CW6U1ISeu10K3Hf7cKAvfG/S316rXt0Bt66BuTt9oplP+NuJYHtUuT/Ve5kWgNNEfyhrvguCY0JL0fBCzaw==}
+    peerDependencies:
+      vue: ^3.2.25
+
   vue-router@4.6.3:
     resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==}
     peerDependencies:
@@ -4646,6 +4702,8 @@ snapshots:
 
   '@types/estree@1.0.8': {}
 
+  '@types/js-cookie@3.0.6': {}
+
   '@types/json-schema@7.0.15': {}
 
   '@types/lodash-es@4.17.12':
@@ -5046,6 +5104,24 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
+  '@vue/devtools-api@7.7.2':
+    dependencies:
+      '@vue/devtools-kit': 7.7.8
+
+  '@vue/devtools-kit@7.7.8':
+    dependencies:
+      '@vue/devtools-shared': 7.7.8
+      birpc: 2.8.0
+      hookable: 5.5.3
+      mitt: 3.0.1
+      perfect-debounce: 1.0.0
+      speakingurl: 14.0.1
+      superjson: 2.2.5
+
+  '@vue/devtools-shared@7.7.8':
+    dependencies:
+      rfdc: 1.4.1
+
   '@vue/language-core@3.1.1(typescript@5.8.3)':
     dependencies:
       '@volar/language-core': 2.4.23
@@ -5252,6 +5328,8 @@ snapshots:
 
   binary-extensions@2.3.0: {}
 
+  birpc@2.8.0: {}
+
   boolbase@1.0.0: {}
 
   brace-expansion@1.1.12:
@@ -5356,6 +5434,10 @@ snapshots:
     dependencies:
       is-what: 3.14.1
 
+  copy-anything@4.0.5:
+    dependencies:
+      is-what: 5.5.0
+
   core-js-compat@3.46.0:
     dependencies:
       browserslist: 4.26.3
@@ -5984,6 +6066,8 @@ snapshots:
     dependencies:
       function-bind: 1.1.2
 
+  hookable@5.5.3: {}
+
   iconv-lite@0.4.24:
     dependencies:
       safer-buffer: 2.1.2
@@ -6133,6 +6217,8 @@ snapshots:
 
   is-what@3.14.1: {}
 
+  is-what@5.5.0: {}
+
   isarray@0.0.1: {}
 
   isarray@2.0.5: {}
@@ -6152,6 +6238,8 @@ snapshots:
 
   js-binary-schema-parser@2.0.3: {}
 
+  js-cookie@3.0.5: {}
+
   js-tokens@4.0.0: {}
 
   js-tokens@9.0.1: {}
@@ -6280,6 +6368,8 @@ snapshots:
 
   minimist@1.2.6: {}
 
+  mitt@3.0.1: {}
+
   mlly@1.8.0:
     dependencies:
       acorn: 8.15.0
@@ -6576,6 +6666,8 @@ snapshots:
 
   reusify@1.1.0: {}
 
+  rfdc@1.4.1: {}
+
   rollup@4.52.4:
     dependencies:
       '@types/estree': 1.0.8
@@ -6641,6 +6733,8 @@ snapshots:
 
   sax@1.4.1: {}
 
+  screenfull@5.2.0: {}
+
   scule@1.3.0: {}
 
   semver@5.7.2:
@@ -6779,6 +6873,8 @@ snapshots:
   source-map@0.6.1:
     optional: true
 
+  speakingurl@14.0.1: {}
+
   stop-iteration-iterator@1.1.0:
     dependencies:
       es-errors: 1.3.0
@@ -6842,6 +6938,10 @@ snapshots:
     dependencies:
       js-tokens: 9.0.1
 
+  superjson@2.2.5:
+    dependencies:
+      copy-anything: 4.0.5
+
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -7102,6 +7202,15 @@ snapshots:
 
   vue-flow-layout@0.2.0: {}
 
+  vue-hooks-plus@2.4.1(vue@3.5.22(typescript@5.8.3)):
+    dependencies:
+      '@types/js-cookie': 3.0.6
+      '@vue/devtools-api': 7.7.2
+      js-cookie: 3.0.5
+      lodash-es: 4.17.21
+      screenfull: 5.2.0
+      vue: 3.5.22(typescript@5.8.3)
+
   vue-router@4.6.3(vue@3.5.22(typescript@5.8.3)):
     dependencies:
       '@vue/devtools-api': 6.6.4

+ 6 - 1
src/api/index.ts

@@ -1 +1,6 @@
-// import { get, post } from '@/utils/request';
+import { post } from '@/utils/request';
+
+// 模块调用
+export const invoke = (data: any) => {
+  return post('/api/module/Invoke', data)
+}

+ 1 - 1
src/main.ts

@@ -3,7 +3,7 @@ import { createApp } from 'vue';
 import App from './App.vue';
 import router from './router/index';
 import 'virtual:uno.css';
-import 'element-plus/theme-chalk/el-message.css';
+import 'element-plus/theme-chalk/index.css';
 import './style.less';
 
 const app = createApp(App);

+ 13 - 10
src/utils/request.ts

@@ -9,9 +9,12 @@ interface RequestOptions extends RequestInit {
 // 请求返回数据格式
 interface ResponseData<T = any> {
   code: number;
-  status: boolean;
-  data: T;
-  message: string;
+  isSuccess: boolean;
+  result: T;
+  error?: string;
+  errors?: {
+    message: string;
+  };
 }
 
 class RequestError extends Error {
@@ -37,9 +40,9 @@ export async function request<T = any>(url: string, options: RequestOptions = {}
   }
 
   // 增加token
-  const token = localStorage.getItem('token');
+  const token = (localStorage.getItem('oauth2token') || "")?.replace("\"", '')?.replace("\"", '');
   if (token) {
-    headers.set('Authorization', `Bearer ${token}`);
+    headers.set('authorization', token);
   }
 
   // 创建超时终止
@@ -69,8 +72,8 @@ export async function request<T = any>(url: string, options: RequestOptions = {}
 
     // 401处理
     if (data.code === 401) {
-      localStorage.removeItem('token');
-      window.location.href = '/#/login';
+      // localStorage.removeItem('token');
+      // window.location.href = '/#/login';
       throw new Error('Unauthorized');
     }
 
@@ -79,11 +82,11 @@ export async function request<T = any>(url: string, options: RequestOptions = {}
       return data as unknown as T;
     }
 
-    if (data.code !== 200) {
-      throw new Error(data.message);
+    if (data.code !== 1) {
+      throw new Error(data.errors?.message);
     }
 
-    return data?.data;
+    return data?.result;
   } catch (error) {
     if (error instanceof DOMException && error.name === 'AbortError') {
       throw new Error(`Request timeout after ${timeout}ms`);

+ 311 - 92
src/views/home/index.vue

@@ -4,29 +4,29 @@
     <Panel>
       <CardTitle>工程概况</CardTitle>
       <ul class="list-none m-0 p-0 px-20px py-10px flex flex-col gap-y-10px">
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目名称</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目名称</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prj_name }}</span>
         </li>
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目经理</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目经理</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prj_manager_name }}</span>
         </li>
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目类型</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目类型</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prjs_type_desc }}</span>
         </li>
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目地点</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目地点</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prjs_address }}</span>
         </li>
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目规模</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目规模</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prjs_scale }}</span>
         </li>
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目投资额(万元)</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-180px">项目投资额(万元)</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.gckk?.prjs_amount }}</span>
         </li>
       </ul>
 
@@ -36,19 +36,28 @@
           <div class="flex items-center text-16px">
             <span class="w-120px text-right">检查总数:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.aqgl?.checkTotal
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
-            <span class="w-120px text-right">合格数:</span>
+            <span class="w-120px text-right">预期数:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.aqgl?.overdueCount
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
             <span class="w-120px text-right"><span class="point"></span>已整改:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.aqgl?.processedCount
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
@@ -56,16 +65,26 @@
               ><span class="point" style="background: #e86525"></span>未整改:</span
             >
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.aqgl?.unprocessedCount
+              }}</span
+              >项</span
             >
           </div>
         </div>
         <div class="flex-1 relative">
-          <Chart :options="getPieOptions(data)" />
+          <Chart
+            :options="
+              getPieOptions([
+                { label: '已整改', value: dataSource?.aqgl?.processedCount || 0 },
+                { label: '未整改', value: dataSource?.aqgl?.unprocessedCount || 0 },
+              ])
+            "
+          />
           <div
             class="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center"
           >
-            <div class="text-24px mb-10px">80.2%</div>
+            <div class="text-24px mb-10px">{{ dataSource?.aqgl?.passRate }}%</div>
             <div class="text-#ccc">整改通过率</div>
           </div>
         </div>
@@ -77,19 +96,28 @@
           <div class="flex items-center text-16px">
             <span class="w-120px text-right">检查总数:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.zlgl?.checkTotal
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
-            <span class="w-120px text-right">合格数:</span>
+            <span class="w-120px text-right">预期数:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.zlgl?.overdueCount
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
             <span class="w-120px text-right"><span class="point"></span>已整改:</span>
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.zlgl?.processedCount
+              }}</span
+              >项</span
             >
           </div>
           <div class="flex items-center text-16px">
@@ -97,16 +125,26 @@
               ><span class="point" style="background: #e86525"></span>未整改:</span
             >
             <span
-              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">320</span>项</span
+              ><span class="text-20px inline-block w-50px text-#3efafa mr-8px">{{
+                dataSource?.zlgl?.unprocessedCount
+              }}</span
+              >项</span
             >
           </div>
         </div>
         <div class="flex-1 relative">
-          <Chart :options="getPieOptions(data)" />
+          <Chart
+            :options="
+              getPieOptions([
+                { label: '已整改', value: dataSource?.zlgl?.processedCount || 0 },
+                { label: '未整改', value: dataSource?.zlgl?.unprocessedCount || 0 },
+              ])
+            "
+          />
           <div
             class="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center"
           >
-            <div class="text-24px mb-10px">50.9.2%</div>
+            <div class="text-24px mb-10px">{{ dataSource?.zlgl?.passRate }}%</div>
             <div class="text-#ccc">整改通过率</div>
           </div>
         </div>
@@ -115,18 +153,23 @@
 
     <!-- 中间区域 -->
     <div class="h-full box-border px-10px middle-content flex-1 flex flex-col gap-10px">
-      <CardTitle>全景概况</CardTitle>
-      <iframe
-        src="https://www.720yun.com/t/adejerhnzy3?scene_id=15644029"
-        frameborder="0"
-        class="w-full flex-1"
-      ></iframe>
+      <CardTitle>
+        <div class="w-full flex justify-between items-center">
+          <span>全景预览</span>
+          <el-button type="text" @click="setPanoUrl">
+            <el-icon size="26px" style="color: #fff"><Tools /></el-icon>
+          </el-button>
+        </div>
+      </CardTitle>
+      <iframe :src="dataSource?.drone" frameborder="0" class="w-full flex-1"></iframe>
       <div>
         <CardTitle>参建单位</CardTitle>
-        <el-table height="200">
-          <el-table-column label="序号"> </el-table-column>
-          <el-table-column label="单位名称"> </el-table-column>
-          <el-table-column label="单位地址"> </el-table-column>
+        <el-table height="200" :data="dataSource?.cjdw || []">
+          <el-table-column label="序号">
+            <template #default="scope">{{ scope.$index + 1 }}</template>
+          </el-table-column>
+          <el-table-column label="单位名称" prop="unit_name"> </el-table-column>
+          <el-table-column label="参建单位" prop="part_notes"> </el-table-column>
         </el-table>
       </div>
     </div>
@@ -135,25 +178,29 @@
     <Panel>
       <CardTitle>管理目标</CardTitle>
       <ul class="list-none m-0 p-0 px-20px py-10px flex flex-col gap-y-10px">
-        <li>
-          <span class="text-20px font-bold inline-block w-200px">项目目标</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-200px">工期目标</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_duration }}</span>
         </li>
-        <li>
+        <li class="flex">
           <span class="text-20px font-bold inline-block w-200px">文明工地目标</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_worksite }}</span>
         </li>
-        <li>
+        <li class="flex">
           <span class="text-20px font-bold inline-block w-200px">创新目标</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_innovation }}</span>
         </li>
-        <li>
+        <li class="flex">
           <span class="text-20px font-bold inline-block w-200px">质量目标</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_quality }}</span>
+        </li>
+        <li class="flex">
+          <span class="text-20px font-bold inline-block w-200px">绿色施工目标</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_safety }}</span>
         </li>
-        <li>
+        <li class="flex">
           <span class="text-20px font-bold inline-block w-200px">安全目标</span>
-          <span class="text-#ccc text-16px">xxxx</span>
+          <span class="text-#ccc text-16px truncate">{{ dataSource?.glmb?.prjs_green }}</span>
         </li>
       </ul>
 
@@ -162,44 +209,42 @@
         <div class="w-full flex justify-between px-20px box-border">
           <div class="text-center">
             <div class="text-#ccc text-14px mb-8px">在册人数</div>
-            <div class="text-20px font-bold">2550</div>
+            <div class="text-20px font-bold">{{ dataSource?.rysj.num }}</div>
           </div>
-          <div class="text-center">
+          <!-- <div class="text-center">
             <div class="text-#ccc text-14px mb-8px">今日未出勤人数</div>
             <div class="text-20px font-bold">2550</div>
           </div>
           <div class="text-center">
             <div class="text-#ccc text-14px mb-8px">今日出勤人数</div>
             <div class="text-20px font-bold">2550</div>
-          </div>
+          </div> -->
         </div>
         <div class="flex h-260px">
           <div class="flex-1">
-            <Chart :options="getPieOptions(data)" />
+            <Chart
+              :options="
+                getPieOptions(
+                  (dataSource?.rysj.data || []).map((item) => ({
+                    label: item.title,
+                    value: item.num,
+                  })),
+                )
+              "
+            />
           </div>
           <div class="flex-1 flex flex-col items-center justify-center gap-y-10px">
-            <div class="w-full flex items-center justify-between text-16px">
+            <div
+              class="w-full flex items-center justify-between text-16px"
+              v-for="item in dataSource?.rysj.data || []"
+              :key="item.title"
+            >
               <span class="text-right"
-                ><span class="point" style="background: #6ec44e"></span>管理人员</span
+                ><span class="point" style="background: #6ec44e"></span>{{ item.title }}</span
               >
               <span class="text-#6ec44e"
-                ><span class="text-20px inline-block mr-8px">320</span>人</span
-              >
-            </div>
-            <div class="w-full flex items-center justify-between text-16px">
-              <span class="text-right"
-                ><span class="point" style="background: #f4982c"></span>劳务人员</span
-              >
-              <span class="text-#f4982c"
-                ><span class="text-20px inline-block mr-8px">320</span>人</span
-              >
-            </div>
-            <div class="w-full flex items-center justify-between text-16px">
-              <span class="text-right"
-                ><span class="point" style="background: #14cee6"></span>其他</span
-              >
-              <span class="text-#14cee6"
-                ><span class="text-20px inline-block mr-8px">320</span>人</span
+                ><span class="text-20px inline-block mr-8px">{{ item.num }}</span
+                >人</span
               >
             </div>
           </div>
@@ -208,36 +253,180 @@
 
       <CardTitle>技术指标</CardTitle>
       <el-descriptions size="large" :column="2" class="mt-20px">
-        <el-descriptions-item label="用地面积(㎡)">80000</el-descriptions-item>
-        <el-descriptions-item label="建筑占地面积(㎡)">50000</el-descriptions-item>
-        <el-descriptions-item label="地上总建筑面积(㎡)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="地下总建筑面积(㎡)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="计容面积(㎡)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="建筑密度(%)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="容积率">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="机动车停车位(个)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="绿化面积(㎡)">50.9.2%</el-descriptions-item>
-        <el-descriptions-item label="绿化率(%)">50.9.2%</el-descriptions-item>
+        <el-descriptions-item label="用地面积(㎡)">{{
+          dataSource?.jszb?.prjs_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="建筑占地面积(㎡)">{{
+          dataSource?.jszb?.build_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="地上总建筑面积(㎡)">{{
+          dataSource?.jszb?.ongrd_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="地下总建筑面积(㎡)">{{
+          dataSource?.jszb?.ungrd_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="计容面积(㎡)">{{
+          dataSource?.jszb?.calc_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="建筑密度(%)">{{
+          dataSource?.jszb?.build_density
+        }}</el-descriptions-item>
+        <el-descriptions-item label="容积率">{{
+          dataSource?.jszb?.plot_ratio
+        }}</el-descriptions-item>
+        <el-descriptions-item label="机动车停车位(个)">{{
+          dataSource?.jszb?.park_space
+        }}</el-descriptions-item>
+        <el-descriptions-item label="绿化面积(㎡)">{{
+          dataSource?.jszb?.gree_area
+        }}</el-descriptions-item>
+        <el-descriptions-item label="绿化率(%)">{{
+          dataSource?.jszb?.gree_rate
+        }}</el-descriptions-item>
       </el-descriptions>
     </Panel>
   </div>
 </template>
 
 <script setup lang="ts">
+import { ref } from 'vue';
 import Panel from '@/components/Panel.vue';
 import CardTitle from '@/components/CardTitle.vue';
 import Chart from '@/components/Chart/index.vue';
 import type { EChartsOption } from 'echarts';
+import { useRoute } from 'vue-router';
+import { invoke } from '@/api';
+import { useRequest } from 'vue-hooks-plus';
+import { Tools } from '@element-plus/icons-vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
 
 type DataItem = {
   label: string;
   value: number;
 };
 
-const data = [
-  { label: '已整改', value: 320 },
-  { label: '未整改', value: 320 },
-];
+const route = useRoute();
+const dataSource = ref<{
+  // 无人机数据
+  drone: string;
+  // 人员数据
+  rysj: {
+    // 在册人数
+    num: number;
+    data: [
+      {
+        // 用户数量
+        num: number;
+        // 角色名称
+        title: string;
+      },
+    ];
+  };
+  // 技术指标
+  jszb: {
+    // 用地面积(㎡)
+    prjs_area: number;
+    // 建筑占地面积(㎡)
+    build_area: number;
+    // 机动车停车位(个)
+    park_space: number;
+    // 计容面积(㎡)
+    calc_area: 240000.0;
+    // 绿化面积(㎡)
+    gree_area: number;
+    // 地下总建筑面积(㎡)
+    ungrd_area: number;
+    // 绿化率(%):
+    gree_rate: number;
+    // 容积率
+    plot_ratio: number;
+    // 地上总建筑面积(㎡)
+    ongrd_area: number;
+    // 建筑密度(%)
+    build_density: number;
+  };
+  // 参建单位
+  cjdw: [
+    {
+      // 单位名称
+      unit_name: string;
+      // 参建单位
+      part_notes: string;
+    },
+  ];
+  // 安全管理
+  aqgl: {
+    // 检查总数
+    checkTotal: number;
+    // 未整数
+    unprocessedCount: number;
+    // 已整数
+    processedCount: number;
+    // 通过率
+    passRate: number;
+    // 逾期数
+    overdueCount: number;
+  };
+  // 管理目标
+  glmb: {
+    // 安全目标
+    prjs_safety: string;
+    // 文明工地目标
+    prjs_worksite: string;
+    // 创新目标
+    prjs_innovation: string;
+    // 质量目标
+    prjs_quality: string;
+    // 绿色施工目标
+    prjs_green: string;
+    // 工期目标
+    prjs_duration: string;
+  };
+  // 工程概况
+  gckk: {
+    // 项目规模
+    prjs_scale: string;
+    // 项目投资额(万元)
+    prjs_amount: number;
+    // 项目类型
+    prjs_type_desc: string;
+    // 项目地点
+    prjs_address: string;
+    // 项目名称
+    prj_name: string;
+    // 项目经理
+    prj_manager_name: string;
+  };
+  // 质量管理
+  zlgl: {
+    // 检查总数
+    checkTotal: number;
+    // 未整数
+    unprocessedCount: number;
+    // 已整数
+    processedCount: number;
+    // 通过率
+    passRate: number;
+    // 逾期数
+    overdueCount: number;
+  };
+}>();
+
+useRequest(invoke, {
+  defaultParams: [
+    {
+      // 固定值
+      interfaceCode: 'BigScreen.project',
+      // 编号代码
+      projectCode: route.query?.projectCode as string,
+    },
+  ],
+  onSuccess(res: any) {
+    dataSource.value = res;
+  },
+  pollingInterval: 10000
+});
+
 const getPieOptions = (data: DataItem[]): EChartsOption => {
   return {
     series: [
@@ -256,13 +445,43 @@ const getPieOptions = (data: DataItem[]): EChartsOption => {
           show: false,
         },
         data: [
-          { value: data[0].value, name: data[0].label, itemStyle: { color: '#3efafa' } },
-          { value: data[1].value, name: data[1].label, itemStyle: { color: '#e86525' } },
+          { value: data?.[0]?.value, name: data?.[0]?.label, itemStyle: { color: '#3efafa' } },
+          { value: data?.[1]?.value, name: data?.[1]?.label, itemStyle: { color: '#e86525' } },
         ],
       },
     ],
   };
 };
+
+const setPanoUrl = () => {
+   ElMessageBox.prompt('请输入无人机链接', '提示', {
+    confirmButtonText: '提交',
+    cancelButtonText: '取消',
+    inputPattern: /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/,  // 链接正则
+    inputErrorMessage: '地址校验失败',
+  })
+    .then(({ value }) => {
+      invoke({
+        "interfaceCode": "BigScreen.drone",
+        "projectCode": route.query?.projectCode,
+        "droneUrl": value
+      }).then(() => {
+        ElMessage({
+          type: 'success',
+          message: '修改成功',
+        });
+        if(dataSource.value) {
+          dataSource.value.drone = value;
+        }
+      })
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: '已取消',
+      })
+    })
+}
 </script>
 
 <style lang="less" scoped>

+ 6 - 8
src/views/process/ProjectMilestone.vue

@@ -22,7 +22,7 @@
       <div class="months-right">
         <div class="months-grid">
           <div
-            v-for="(m, idx) in monthLabels"
+            v-for="(m) in monthLabels"
             :key="m"
             class="month-cell"
             :style="{ width: monthWidth + 'px' }"
@@ -63,7 +63,7 @@
         </defs>
 
         <!-- vertical month lines -->
-        <g v-for="(m, i) in monthsArr" :key="'v' + i">
+        <g v-for="(_m, i) in monthsArr" :key="'v' + i">
           <line
             :x1="leftCol + i * monthWidth + monthWidth / 2"
             y1="0"
@@ -112,10 +112,10 @@
             :transform="`translate(${xOfMonth(n.month)}, ${rowY(rIdx)})`"
           >
             <template v-if="n.type === 'circle'">
-              <circle r="7" :fill="n.color || '#8feaff'" stroke="#ffffff" stroke-width="1.2" />
+              <circle r="7" :fill="'#8feaff'" stroke="#ffffff" stroke-width="1.2" />
             </template>
             <template v-else-if="n.type === 'dot'">
-              <circle r="5" :fill="n.color || '#ff6b6b'" stroke="#ffffff" stroke-width="1" />
+              <circle r="5" :fill="'#ff6b6b'" stroke="#ffffff" stroke-width="1" />
             </template>
             <template v-else-if="n.type === 'star'">
               <use
@@ -124,7 +124,7 @@
                 y="-10"
                 width="20"
                 height="20"
-                :fill="n.color || '#ffd36b'"
+                :fill="'#ffd36b'"
               />
             </template>
 
@@ -164,8 +164,6 @@ const leftCol = 160; // 左侧列宽
 const rightPadding = 24;
 const topPadding = 8;
 const bottomPadding = 10;
-const headerH = 48;
-const subHeaderH = 36;
 const rowH = 72;
 const barH = 14;
 const leftPad = 20;
@@ -203,7 +201,7 @@ const monthWidth = computed(() => {
 });
 const monthLabels = computed(() =>
   monthsArr.value.map((s) => {
-    const [y, m] = s.split('-');
+    const [_y, m] = s.split('-');
     return `${Number(m)}`;
   }),
 );

+ 285 - 346
src/views/process/index.vue

@@ -12,7 +12,7 @@
             </div>
             <div class="ml-10px">
               <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
-              <div class="text-20px mt-10px">2024-10-01</div>
+              <div class="text-20px mt-10px">{{ dataSource?.xmdjs?.start_time }}</div>
             </div>
           </div>
           <div
@@ -22,8 +22,8 @@
               <img src="@/assets/images/u1420.svg" />
             </div>
             <div class="ml-10px">
-              <div class="text-15px color-[#ffffffb2]">合同开工日期</div>
-              <div class="text-20px mt-10px">2024-10-01</div>
+              <div class="text-15px color-[#ffffffb2]">合同结束日期</div>
+              <div class="text-20px mt-10px">{{ dataSource?.xmdjs?.end_time }}</div>
             </div>
           </div>
         </div>
@@ -38,24 +38,24 @@
       </div>
 
       <CardTitle>CWS分解</CardTitle>
-      <div class="w-full h-260px flex gap-10px p-10px box-border justify-between items-center">
-        <Chart :options="chartOptions" />
+      <div class="w-full h-260px flex gap-10px p-10px box-border justify-between items-center left-bottom-board">
+        <ScrollBoard :config="config2" @mouseover="mouseoverHandler" @click="clickHandler" />
       </div>
     </Panel>
 
     <!-- 中间区域 -->
-    <div
+    <!-- <div
       class="h-full box-border pr-10px pl-10px middle-content flex-1 flex flex-col gap-10px justify-center"
     >
       <div class="text-white h-500px w-full center-board">
         <ProjectMilestone />
       </div>
-    </div>
+    </div> -->
 
     <!-- 右侧面板 -->
     <Panel>
       <div class="bg-[#348bc74c] w-full h-150px flex flex-col justify-center items-center">
-        <DigitalFlop :config="config5" style="height: 80px; width: 100%" />
+        <DigitalFlop :config="config6" style="height: 80px; width: 100%" />
         <div class="text-32px color-[#ffffffb2]">实际AC</div>
       </div>
       <CardTitle>单体完成比率统计</CardTitle>
@@ -72,379 +72,313 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, onMounted, ref } from 'vue';
+import { ref, computed } from 'vue';
 import { ScrollBoard, DigitalFlop } from '@kjgl77/datav-vue3';
 import Panel from '@/components/Panel.vue';
 import CardTitle from '@/components/CardTitle.vue';
 import Chart from '@/components/Chart/index.vue';
 import type { EChartsOption } from 'echarts';
-import ProjectMilestone from './ProjectMilestone.vue';
-
-const config1 = reactive({
-  header: ['单体', '当前状态', '所属分类'],
-  data: [
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-    ['高处作业安全', '253', '123'],
-  ],
-  headerBGC: '#348bc763',
-  evenRowBGC: 'rgba(29, 66, 110, 0.6)',
-  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
-  align: ['center'],
-  index: true,
-  columnWidth: [50],
-  rowNum: 6,
-  headerHeight: 35,
-  waitTime: 50005000,
-});
-
-const config5 = reactive({
-  number: [1132],
-  content: '{nt}%',
-});
-
-setInterval(() => {
-  config5.number = config5.number.map((num) => num + 100);
-}, 3000);
-
-let scrollInterval: ReturnType<typeof setInterval>;
-let currentZoom = { start: 0, end: 50 };
-
-const chartData = ref({
-  plan: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
-  actual: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
-  actual1: [12, 22, 28, 38, 55, 65, 72, 85, 95, 108, 115, 125],
-});
+import { useRoute } from 'vue-router';
+import { invoke } from '@/api';
+import { useRequest } from 'vue-hooks-plus';
 
-const chartOptions = ref<EChartsOption>({
-  tooltip: {
-    trigger: 'item',
-    textStyle: {
-      color: '#000',
-    },
-  },
-  xAxis: {
-    type: 'category',
-    data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
-    axisLabel: {
-      color: '#fff',
-    },
-  },
-  yAxis: {
-    type: 'value',
-    axisLabel: {
-      color: '#fff',
-    },
-  },
-  series: [
+const route = useRoute();
+const dataSource = ref<{
+  // ac
+  ac: string;
+  // 项目倒计时
+  xmdjs: {
+    // 结束时间
+    end_time: string;
+    // 开始时间
+    start_time: string;
+    // 工期
+    days: number;
+  };
+  // wbs分解
+  wbsfj: [
     {
-      name: '单体',
-      data: chartData.value.plan,
-      type: 'bar',
-      itemStyle: {
-        color: 'rgba(42, 254, 255, 1)',
-      },
-      label: {
-        show: true,
-        position: 'inside',
-        color: '#fff',
-        fontSize: 10,
-      },
+      // 状态
+      tas_state: string;
+      // 分类
+      tas_classify: string;
+      // 单体
+      tas_monomer: string;
     },
+  ];
+  // 单体完成比率
+  dtwcbl: [
     {
-      name: '状态',
-      data: chartData.value.actual,
-      type: 'bar',
-      itemStyle: {
-        color: 'rgba(225, 189, 97, 1)',
-      },
-      label: {
-        show: true,
-        position: 'inside',
-        color: '#fff',
-        fontSize: 10,
-      },
+      // 标题
+      title: string;
+      // 完成比例
+      value: number;
     },
+  ];
+  // cws分解
+  cwsfj: [
     {
-      name: '分类',
-      data: chartData.value.actual1,
-      type: 'bar',
-      itemStyle: {
-        color: 'rgba(0, 189, 97, 1)',
-      },
-      label: {
-        show: true,
-        position: 'inside',
-        color: '#fff',
-        fontSize: 10,
-      },
+      // 状态
+      tas_state: string;
+      // 分类
+      tas_classify: string;
+      // 单体预算金额(万元)
+      tas_budget_amount: number;
+      // 单体
+      tas_monomer: number;
     },
-  ],
-  legend: {
-    data: ['单体', '状态', '分类'],
-    top: '10px',
-    right: '10px',
-    textStyle: {
-      color: '#fff',
+  ];
+  // 单体-分部完成比率统计
+  dtbfbl: [
+    {
+      // 标题
+      title: string;
+      // 完成比例
+      value: number;
     },
-    itemWidth: 20,
-    itemHeight: 10,
-    icon: 'rect',
-  },
-  grid: {
-    top: '15%',
-    left: '3%',
-    right: '4%',
-    bottom: '3%',
-    containLabel: true,
-  },
-  dataZoom: [
+  ];
+}>();
+
+useRequest(invoke, {
+  defaultParams: [
     {
-      show: false,
-      type: 'slider',
-      start: currentZoom.start,
-      end: currentZoom.end,
+      // 固定值
+      interfaceCode: 'BigScreen.progressManagement',
+      // 编号代码
+      projectCode: route.query?.projectCode as string,
     },
   ],
+  onSuccess(res: any) {
+    dataSource.value = res;
+  },
+  pollingInterval: 10000
 });
 
-const startAutoScroll = () => {
-  scrollInterval = setInterval(() => {
-    if (currentZoom.end >= 100) {
-      currentZoom.start = 0;
-      currentZoom.end = 50;
-    } else {
-      currentZoom.start += 2;
-      currentZoom.end += 2;
-    }
+const config1 = computed(() => {
+  const list = dataSource.value?.wbsfj || [];
+  return {
+    header: ['单体', '当前状态', '所属分类'],
+    data: list.map((item) => [item.tas_monomer, item.tas_state, item.tas_classify]),
+    headerBGC: '#348bc763',
+    evenRowBGC: 'rgba(29, 66, 110, 0.6)',
+    oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+    align: ['center'],
+    index: true,
+    columnWidth: [50],
+    rowNum: 6,
+    headerHeight: 35,
+    waitTime: 50005000,
+  };
+});
 
-    chartOptions.value = {
-      ...chartOptions.value,
-      dataZoom: [
-        {
-          show: false,
-          type: 'slider',
-          start: currentZoom.start,
-          end: currentZoom.end,
-        },
-      ],
-    };
-  }, 2000); // 每2秒更新一次
-};
+const config5 = computed(() => {
+  return {
+    number: [dataSource.value?.xmdjs?.days || 0],
+    content: '{nt}%',
+  };
+});
+
+const config6 = computed(() => {
+  return {
+    number: [dataSource.value?.ac || 0],
+    content: '{nt}',
+  };
+});
+
+let currentZoom = { start: 0, end: 50 };
+
+const config2 = computed(() => {
+  const list = dataSource.value?.cwsfj || [];
+  return {
+    header: ['单体', '当前状态', '所属分类', '预算金额(万元)'],
+    data: list.map((item) => [
+      item.tas_monomer,
+      item.tas_state,
+      item.tas_classify,
+      item.tas_budget_amount,
+    ]),
+    headerBGC: '#348bc763',
+    evenRowBGC: 'rgba(29, 66, 110, 0.6)',
+    oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+    align: ['center'],
+    index: true,
+    columnWidth: [50],
+    rowNum: 6,
+    headerHeight: 35,
+    waitTime: 50005000,
+  };
+});
 
 // 右侧顶部柱状图
-let scrollIntervalRightTop: ReturnType<typeof setInterval>;
-let currentZoomeRightTop = { start: 0, end: 50 };
-const chartDataRightTop = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
-const chartOptionsRightTop = ref<EChartsOption>({
-  tooltip: {
-    trigger: 'item',
-    textStyle: { color: '#000' },
-  },
-  xAxis: {
-    type: 'value',
-    axisLabel: { color: '#fff' },
-  },
-  yAxis: {
-    type: 'category',
-    data: [
-      '项目A',
-      '项目B',
-      '项目C',
-      '项目D',
-      '项目E',
-      '项目F',
-      '项目G',
-      '项目H',
-      '项目I',
-      '项目J',
-      '项目K',
-      '项目L',
-    ],
-    axisLabel: { color: '#fff' },
-  },
-  series: [
-    {
-      name: '进度',
-      type: 'bar',
-      data: chartDataRightTop.value,
-      barWidth: 16,
-      itemStyle: {
-        color: 'rgba(42, 254, 255, 1)',
-        borderRadius: [4, 4, 4, 4],
-      },
-      label: {
-        show: true,
-        position: 'right',
-        color: '#fff',
-        fontSize: 10,
-      },
+// let scrollIntervalRightTop: ReturnType<typeof setInterval>;
+// let currentZoomeRightTop = { start: 0, end: 50 };
+
+const chartOptionsRightTop = computed((): EChartsOption => {
+  const list = dataSource.value?.dtbfbl || [];
+  return {
+    tooltip: {
+      trigger: 'item',
+      textStyle: { color: '#000' },
     },
-  ],
-  grid: {
-    top: '5%',
-    left: '3%',
-    right: '4%',
-    bottom: '3%',
-    containLabel: true,
-  },
-  dataZoom: [
-    {
-      show: false,
-      type: 'slider',
-      yAxisIndex: 0,
-      start: currentZoom.start,
-      end: currentZoom.end,
+    xAxis: {
+      type: 'value',
+      axisLabel: { color: '#fff' },
     },
-  ],
+    yAxis: {
+      type: 'category',
+      data: list.map((item) => item.title),
+      axisLabel: { color: '#fff' },
+    },
+    series: [
+      {
+        name: '进度',
+        type: 'bar',
+        data: list.map((item) => item.value),
+        barWidth: 16,
+        itemStyle: {
+          color: 'rgba(42, 254, 255, 1)',
+          borderRadius: [4, 4, 4, 4],
+        },
+        label: {
+          show: true,
+          position: 'right',
+          color: '#fff',
+          fontSize: 10,
+        },
+      },
+    ],
+    grid: {
+      top: '5%',
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true,
+    },
+    dataZoom: [
+      {
+        show: false,
+        type: 'slider',
+        yAxisIndex: 0,
+        start: currentZoom.start,
+        end: currentZoom.end,
+      },
+    ],
+  };
 });
 
-const startAutoScrollRightTop = () => {
-  clearInterval(scrollIntervalRightTop);
-  scrollIntervalRightTop = setInterval(() => {
-    if (currentZoomeRightTop.end >= 100) {
-      currentZoomeRightTop.start = 0;
-      currentZoomeRightTop.end = 50;
-    } else {
-      currentZoomeRightTop.start += 2;
-      currentZoomeRightTop.end += 2;
-    }
+// const startAutoScrollRightTop = () => {
+//   clearInterval(scrollIntervalRightTop);
+//   scrollIntervalRightTop = setInterval(() => {
+//     if (currentZoomeRightTop.end >= 100) {
+//       currentZoomeRightTop.start = 0;
+//       currentZoomeRightTop.end = 50;
+//     } else {
+//       currentZoomeRightTop.start += 2;
+//       currentZoomeRightTop.end += 2;
+//     }
 
-    chartOptionsRightTop.value = {
-      ...chartOptionsRightTop.value,
-      dataZoom: [
-        {
-          show: false,
-          type: 'slider',
-          yAxisIndex: 0,
-          start: currentZoomeRightTop.start,
-          end: currentZoomeRightTop.end,
-        },
-      ],
-    };
-  }, 2000);
-};
+//     chartOptionsRightTop.value = {
+//       ...chartOptionsRightTop.value,
+//       dataZoom: [
+//         {
+//           show: false,
+//           type: 'slider',
+//           yAxisIndex: 0,
+//           start: currentZoomeRightTop.start,
+//           end: currentZoomeRightTop.end,
+//         },
+//       ],
+//     };
+//   }, 2000);
+// };
 
 // 右侧底部
+// let scrollIntervalRightBottom: ReturnType<typeof setInterval>;
+// let currentZoomeRightBottom = { start: 0, end: 50 };
 
-let scrollIntervalRightBottom: ReturnType<typeof setInterval>;
-let currentZoomeRightBottom = { start: 0, end: 50 };
-const chartDataRightBottom = ref([30, 50, 70, 90, 60, 80, 100, 120, 140, 160, 180, 200]);
-const chartOptionsRightBottom = ref<EChartsOption>({
-  tooltip: {
-    trigger: 'item',
-    textStyle: { color: '#000' },
-  },
-  xAxis: {
-    type: 'value',
-    axisLabel: { color: '#fff' },
-  },
-  yAxis: {
-    type: 'category',
-    data: [
-      '项目A',
-      '项目B',
-      '项目C',
-      '项目D',
-      '项目E',
-      '项目F',
-      '项目G',
-      '项目H',
-      '项目I',
-      '项目J',
-      '项目K',
-      '项目L',
-    ],
-    axisLabel: { color: '#fff' },
-  },
-  series: [
-    {
-      name: '进度',
-      type: 'bar',
-      data: chartDataRightBottom.value,
-      barWidth: 16,
-      itemStyle: {
-        color: 'rgba(42, 254, 255, 1)',
-        borderRadius: [4, 4, 4, 4],
-      },
-      label: {
-        show: true,
-        position: 'right',
-        color: '#fff',
-        fontSize: 10,
-      },
+const chartOptionsRightBottom = computed((): EChartsOption => {
+  const list = dataSource.value?.dtbfbl || [];
+  return {
+    tooltip: {
+      trigger: 'item',
+      textStyle: { color: '#000' },
     },
-  ],
-  grid: {
-    top: '5%',
-    left: '3%',
-    right: '4%',
-    bottom: '3%',
-    containLabel: true,
-  },
-  dataZoom: [
-    {
-      show: false,
-      type: 'slider',
-      yAxisIndex: 0,
-      start: currentZoom.start,
-      end: currentZoom.end,
+    xAxis: {
+      type: 'value',
+      axisLabel: { color: '#fff' },
     },
-  ],
-});
-
-const startAutoScrollRightBottom = () => {
-  clearInterval(scrollIntervalRightBottom);
-  scrollIntervalRightBottom = setInterval(() => {
-    if (currentZoomeRightBottom.end >= 100) {
-      currentZoomeRightBottom.start = 0;
-      currentZoomeRightBottom.end = 50;
-    } else {
-      currentZoomeRightBottom.start += 2;
-      currentZoomeRightBottom.end += 2;
-    }
-
-    chartOptionsRightBottom.value = {
-      ...chartOptionsRightBottom.value,
-      dataZoom: [
-        {
-          show: false,
-          type: 'slider',
-          yAxisIndex: 0,
-          start: currentZoomeRightBottom.start,
-          end: currentZoomeRightBottom.end,
+    yAxis: {
+      type: 'category',
+      data: list.map((item) => item.title),
+      axisLabel: { color: '#fff' },
+    },
+    series: [
+      {
+        name: '进度',
+        type: 'bar',
+        data: list.map((item) => item.value),
+        barWidth: 16,
+        itemStyle: {
+          color: 'rgba(42, 254, 255, 1)',
+          borderRadius: [4, 4, 4, 4],
         },
-      ],
-    };
-  }, 2000);
-};
+        label: {
+          show: true,
+          position: 'right',
+          color: '#fff',
+          fontSize: 10,
+        },
+      },
+    ],
+    grid: {
+      top: '5%',
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true,
+    },
+    dataZoom: [
+      {
+        show: false,
+        type: 'slider',
+        yAxisIndex: 0,
+        start: currentZoom.start,
+        end: currentZoom.end,
+      },
+    ],
+  }
+});
 
-// 模拟数据更新
-setInterval(() => {
-  chartData.value.plan = chartData.value.plan.map((item) => item + Math.floor(Math.random() * 10));
-  chartData.value.actual = chartData.value.actual.map(
-    (item) => item + Math.floor(Math.random() * 10),
-  );
-  chartData.value.actual1 = chartData.value.actual1.map(
-    (item) => item + Math.floor(Math.random() * 10),
-  );
+// const startAutoScrollRightBottom = () => {
+//   clearInterval(scrollIntervalRightBottom);
+//   scrollIntervalRightBottom = setInterval(() => {
+//     if (currentZoomeRightBottom.end >= 100) {
+//       currentZoomeRightBottom.start = 0;
+//       currentZoomeRightBottom.end = 50;
+//     } else {
+//       currentZoomeRightBottom.start += 2;
+//       currentZoomeRightBottom.end += 2;
+//     }
 
-  chartDataRightTop.value = chartDataRightTop.value.map((v) => v + Math.floor(Math.random() * 10));
-  chartDataRightBottom.value = chartDataRightBottom.value.map(
-    (v) => v + Math.floor(Math.random() * 10),
-  );
-}, 5000); // 每5秒更新一次数据
+//     chartOptionsRightBottom.value = {
+//       ...chartOptionsRightBottom.value,
+//       dataZoom: [
+//         {
+//           show: false,
+//           type: 'slider',
+//           yAxisIndex: 0,
+//           start: currentZoomeRightBottom.start,
+//           end: currentZoomeRightBottom.end,
+//         },
+//       ],
+//     };
+//   }, 2000);
+// };
 
-onMounted(() => {
-  startAutoScroll();
-  startAutoScrollRightTop();
-  startAutoScrollRightBottom();
-});
+// onMounted(() => {
+//   startAutoScrollRightTop();
+//   startAutoScrollRightBottom();
+// });
 
 const mouseoverHandler = (e: any) => {
   console.log(e);
@@ -453,6 +387,11 @@ const mouseoverHandler = (e: any) => {
 const clickHandler = (e: any) => {
   console.log(e);
 };
+
+// onUnmounted(() => {
+//   clearInterval(scrollIntervalRightBottom);
+//   clearInterval(scrollIntervalRightTop);
+// });
 </script>
 
 <style lang="less" scoped>

+ 121 - 31
src/views/quality/index.vue

@@ -5,12 +5,15 @@
       <CardTitle>质量问题状态</CardTitle>
       <div class="flex h-260px">
         <div class="flex-1 relative">
-          <Chart :options="getPieOptions(data)" />
+          <Chart :options="getPieOptions([
+            { label: '已整改', value: dataSource?.zlwtzt.processedCount || 0 },
+            { label: '未整改', value: dataSource?.zlwtzt.unprocessedCount || 0 },
+          ])" />
           <div
             class="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center"
           >
             <img class="w-110px h-110px" src="@/assets/images/circle-dashboard.svg" alt="" />
-            <div class="text-28px font-bold text-#31e4cf absolute italic">920</div>
+            <div class="text-28px font-bold text-#31e4cf absolute italic">{{ dataSource?.zlwtzt.checkTotal || '-' }}</div>
           </div>
         </div>
         <div class="flex-1 flex flex-col items-center justify-center gap-y-10px">
@@ -18,31 +21,31 @@
             <span class="text-right"
               ><span class="point" style="background: #6ec44e"></span>检查数量</span
             >
-            <span class="text-#6ec44e"><span class="text-20px inline-block mr-8px">320</span></span>
+            <span class="text-#6ec44e"><span class="text-20px inline-block mr-8px">{{ dataSource?.zlwtzt.checkTotal }}</span></span>
           </div>
           <div class="w-full flex items-center justify-between text-16px">
             <span class="text-right"
               ><span class="point" style="background: #f4982c"></span>整改数量</span
             >
-            <span class="text-#f4982c"><span class="text-20px inline-block mr-8px">320</span></span>
+            <span class="text-#f4982c"><span class="text-20px inline-block mr-8px">{{ dataSource?.zlwtzt.processedCount }}</span></span>
           </div>
           <div class="w-full flex items-center justify-between text-16px">
             <span class="text-right"
               ><span class="point" style="background: #14cee6"></span>未整改数量</span
             >
-            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">320</span></span>
+            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">{{ dataSource?.zlwtzt.unprocessedCount }}</span></span>
           </div>
           <div class="w-full flex items-center justify-between text-16px">
             <span class="text-right"
-              ><span class="point" style="background: #14cee6"></span>闭合数量</span
+              ><span class="point" style="background: #14cee6"></span>逾期数量</span
             >
-            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">320</span></span>
+            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">{{ dataSource?.zlwtzt.overdueCount }}</span></span>
           </div>
           <div class="w-full flex items-center justify-between text-16px">
             <span class="text-right"
-              ><span class="point" style="background: #14cee6"></span>预期数量</span
+              ><span class="point" style="background: #14cee6"></span>通过率</span
             >
-            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">320</span></span>
+            <span class="text-#14cee6"><span class="text-20px inline-block mr-8px">{{ dataSource?.zlwtzt.passRate }}%</span></span>
           </div>
         </div>
       </div>
@@ -53,13 +56,13 @@
           <div class="text-center leading-30px border-b-1 border-b-solid border-b-#999">总计</div>
           <div class="flex-1 flex flex-col items-center justify-center gap-12px">
             <img src="@/assets/images/quality.svg" alt="" />
-            <div class="text-28px font-bold text-#31e4cf italic">920</div>
+            <div class="text-28px font-bold text-#31e4cf italic">{{ dataSource?.zlwtlx.total }}</div>
           </div>
         </div>
         <div class="flex-1">
-          <el-table stripe>
-            <el-table-column align="center" label="数量"></el-table-column>
-            <el-table-column align="center" label="名称"></el-table-column>
+          <el-table stripe :data="dataSource?.zlwtlx.data || []">
+            <el-table-column align="center" label="名称" prop="title"></el-table-column>
+            <el-table-column align="center" label="数量" prop="num"></el-table-column>
           </el-table>
         </div>
       </div>
@@ -67,12 +70,12 @@
       <CardTitle>问题预期单位排名</CardTitle>
       <ul class="quality-sort h-310px overflow-auto list-none p-10px m-0 flex flex-col gap-y-4px">
         <li
-          v-for="item in 10"
+          v-for="(item, index) in dataSource?.zgyqdwpm || []"
           class="w-full h-30px bg-#348bc730 flex justify-between pr-12px leading-30px"
         >
-          <span class="no inline-block w-30px h-30px text-center bg-#348bc730 mr-10px">1</span>
-          <span class="name flex-1">施工单位名称</span>
-          <span class="value text-#77bef9">300</span>
+          <span class="no inline-block w-30px h-30px text-center bg-#348bc730 mr-10px">{{ index + 1 }}</span>
+          <span class="name flex-1">{{ item.title }}</span>
+          <span class="value text-#77bef9">{{ item.num }}</span>
         </li>
       </ul>
     </Panel>
@@ -84,8 +87,8 @@
           <CardTitle>现场问题图片</CardTitle>
           <el-scrollbar>
             <div class="img-list h-240px w-full flex items-center gap-12px overflow-auto">
-            <img class="h-200px" v-for="item in 5" src="https://picsum.photos/400/300" alt=""></img>
-          </div>
+              <img class="h-200px" v-for="item in dataSource?.xcwttp || []" :src="`/api/File/Download?fileId=${item.che_upload_photo}`" :alt="item.che_problem_typename"></img>
+            </div>
           </el-scrollbar>
           
         </Panel>
@@ -95,24 +98,24 @@
     <!-- 右侧面板 -->
     <Panel>
       <CardTitle>质量问题通报</CardTitle>
-      <el-table height="400" stripe>
-        <el-table-column align="center" label="问题描述"></el-table-column>
-        <el-table-column align="center" label="发起时间"></el-table-column>
-        <el-table-column align="center" label="责任单位"></el-table-column>
+      <el-table height="400" stripe :data="dataSource?.zlwttb || []">
+        <el-table-column align="center" label="问题描述" prop="description"></el-table-column>
+        <el-table-column align="center" label="发起时间" prop="description"></el-table-column>
+        <el-table-column align="center" label="责任单位" prop="part_abbreviation"></el-table-column>
       </el-table>
 
       <CardTitle>分包问题数量统计</CardTitle>
       <ul class="quality-statistics h-420px overflow-auto list-none p-10px m-0 flex flex-col gap-y-8px">
         <li
-          v-for="item in 20"
+          v-for="(item, index) in dataSource?.fbwtsltj || []"
           class="w-full h-50px"
         >
           <div class="flex items-center justify-between mb-4px">
-            <span class="no">NO.1 责任单位1</span>
-            <span class="value">500</span>
+            <span class="no">NO.{{ index + 1 }} {{ item.title }}</span>
+            <span class="value">{{ item.num }}</span>
           </div>
           <div class="w-full h-15px bg-#e8e8e847 relative">
-            <div class="h-15px bg-#73ebe9" :style="{width: `${50}%`}"></div>
+            <div class="h-15px bg-#73ebe9" :style="{width: `${item.progress}%`}"></div>
           </div>
         </li>
       </ul>
@@ -121,20 +124,107 @@
 </template>
 
 <script setup lang="ts">
+import { ref } from 'vue';
 import Panel from '@/components/Panel.vue';
 import CardTitle from '@/components/CardTitle.vue';
 import Chart from '@/components/Chart/index.vue';
 import type { EChartsOption } from 'echarts';
+import { invoke } from '@/api';
+import { useRoute } from 'vue-router';
+import { useRequest } from 'vue-hooks-plus';
 
 type DataItem = {
   label: string;
   value: number;
 };
 
-const data = [
-  { label: '已整改', value: 320 },
-  { label: '未整改', value: 320 },
-];
+const route = useRoute();
+const dataSource = ref<{
+    // 质量问题状态
+    "zlwtzt": {
+      // 通过率
+      "passRate": number,
+      // 逾期数
+      "overdueCount": number,
+      // 检查总数
+      "checkTotal": number,
+      // 未整数
+      "unprocessedCount": number,
+      // 已整数
+      "processedCount": number
+    },
+    // 质量问题类型
+    "zlwtlx": {
+      // 总数
+      "total": number,
+      "data": [
+        {
+          // 标题
+          "title": string,
+          // 数量
+          "num": number
+        }
+      ]
+    },
+    // 质量问题通报
+    "zlwttb": [
+      {
+        // 发起时间
+        "creation_time": string,
+        // 责任单位
+        "part_abbreviation": string,
+        // 问题描述
+        "description": string
+      }
+    ],
+    // 现场问题图片
+    "xcwttp": [
+      {
+        // 整改状态 0未整改 1已整改
+        "che_status": 0 | 1,
+        // 图片id
+        "che_upload_photo": string,
+        // 问题类别
+        "che_problem_typename": string
+      }
+    ],
+    // 整改逾期单位排名
+    "zgyqdwpm": [
+      {
+        // 单位名称
+        "title": string,
+        // 问题数量
+        "num": number
+      }
+    ],
+    // 分包问题数量统计
+    "fbwtsltj": [
+      {
+        // 责任单位名称
+        "title": string,
+        // 责任单位数量
+        "num": number,
+        // 进度条
+        "progress": number
+      }
+    ]
+  }>();
+
+useRequest(invoke, {
+  defaultParams: [
+    {
+      // 固定值
+      interfaceCode: 'BigScreen.qualityManagement',
+      // 编号代码
+      projectCode: route.query?.projectCode as string,
+    },
+  ],
+  onSuccess(res: any) {
+    dataSource.value = res;
+  },
+  pollingInterval: 10000
+});
+
 const getPieOptions = (data: DataItem[]): EChartsOption => {
   return {
     series: [

+ 190 - 164
src/views/safety/index.vue

@@ -41,14 +41,14 @@
         </div>
       </div>
 
-      <CardTitle>安全问题排名</CardTitle>
-      <div class="text-white h-350px w-full pt-10px left-bottom-board">
+      <CardTitle>整改逾期单位排名</CardTitle>
+      <div class="text-white h-350px w-full pt-10px center-board right-top-board">
         <ScrollBoard :config="config1" @mouseover="mouseoverHandler" @click="clickHandler" />
       </div>
     </Panel>
 
     <!-- 中间区域 -->
-    <div
+    <!-- <div
       class="h-full box-border pr-10px pl-10px middle-content flex-1 flex flex-col gap-10px justify-center"
     >
       <div class="">
@@ -57,7 +57,7 @@
           <ScrollBoard :config="config2" @mouseover="mouseoverHandler" @click="clickHandler" />
         </div>
       </div>
-    </div>
+    </div> -->
 
     <!-- 右侧面板 -->
     <Panel>
@@ -75,12 +75,15 @@
 </template>
 
 <script setup lang="ts">
-import { reactive } from 'vue';
+import { ref, computed } from 'vue';
 import { ScrollBoard, ScrollRankingBoard, DigitalFlop } from '@kjgl77/datav-vue3';
 import Panel from '@/components/Panel.vue';
 import CardTitle from '@/components/CardTitle.vue';
 import Chart from '@/components/Chart/index.vue';
 import type { EChartsOption } from 'echarts';
+import { useRoute } from 'vue-router';
+import { invoke } from '@/api';
+import { useRequest } from 'vue-hooks-plus';
 
 type DataItem = {
   name: string;
@@ -88,12 +91,102 @@ type DataItem = {
   color: string;
 };
 
-const total = 1132;
-const data = [
-  { name: '未整改', value: 280, color: '#f56c6c' },
-  { name: '整改中', value: 320, color: '#409EFF' },
-  { name: '整改完成', value: 532, color: '#67C23A' },
-];
+const route = useRoute();
+const dataSource = ref<{
+    // 安全问题状态
+    "aqwtzt": {
+      // 通过率
+      "passRate": number,
+      // 逾期数
+      "overdueCount": number,
+      // 检查总数
+      "checkTotal": 13,
+      // 未整数
+      "unprocessedCount": 0,
+      // 已整数
+      "processedCount": number
+    },
+    // 安全问题类型
+    "aqwtlx": {
+      // 总数
+      "total": number,
+      "data": [
+        {
+          // 标题
+          "title": string,
+          // 数量
+          "num": number
+        }
+      ]
+    },
+    // 安全问题通报
+    "aqwttb": [
+      {
+        // 发起时间
+        "creation_time": string,
+        // 责任单位
+        "part_abbreviation": string,
+        // 问题描述
+        "description": string
+      }
+    ],
+    // 现场问题图片
+    "xcwttp": [
+      {
+        // 整改状态 0未整改 1已整改
+        "che_status": 0 | 1,
+        // 图片id
+        "che_upload_photo": string,
+        // 问题类别
+        "che_problem_typename": string
+      }
+    ],
+    // 整改逾期单位排名
+    "zgyqdwpm": [
+      {
+        // 单位名称
+        "title": number,
+        // 问题数量
+        "num": number
+      }
+    ],
+    // 分包问题数量统计
+    "fbwtsltj": [
+      {
+        // 责任单位名称
+        "title": string,
+        // 责任单位数量
+        "num": number,
+        // 进度条
+        "progress": number
+      }
+    ]
+  }>();
+
+const data = computed(() => {
+  const data = dataSource.value?.aqwtzt;
+
+  return [
+    { name: '未整改', value: data?.unprocessedCount || 0, color: '#f56c6c' },
+    { name: '整改中', value: data?.processedCount || 0, color: '#409EFF' },
+    { name: '逾期数', value: data?.overdueCount || 0, color: '#67C23A' },
+  ]
+});
+
+useRequest(invoke, {
+  defaultParams: [
+    {
+      // 固定值
+      interfaceCode: 'BigScreen.securityManagement',
+      // 编号代码
+      projectCode: route.query?.projectCode as string,
+    },
+  ],
+  onSuccess(res: any) {
+    dataSource.value = res;
+  },
+  pollingInterval: 10000
+});
 const getPieOptions = (data: DataItem[]): EChartsOption => {
   return {
     series: [
@@ -134,7 +227,7 @@ const getPieOptions = (data: DataItem[]): EChartsOption => {
         silent: true,
         label: {
           position: 'center',
-          formatter: `{c|${total}}`,
+          formatter: `{c|${dataSource.value?.aqwtzt.checkTotal || 0}}`,
           rich: {
             c: {
               fontSize: 36,
@@ -149,169 +242,102 @@ const getPieOptions = (data: DataItem[]): EChartsOption => {
   };
 };
 
-const config = reactive({
-  header: ['名称', '数量'],
-  data: [
-    ['高处作业安全', '253'],
-    ['机械使用', '209'],
-    ['电气安全', '320'],
-    ['消防安全', '123'],
-    ['特种设备', '642'],
-    ['xxxx', '234'],
-    ['xxx', '667'],
-    ['xxx', '334'],
-    ['xxx', '234'],
-    ['xxx', '567'],
-  ],
-  index: false,
-  headerBGC: 'rgba(29, 66, 110, 0.6)',
-  evenRowBGC: 'rgba(29, 66, 110, 0.6)',
-  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
-  align: ['center', 'center'],
-  headerHeight: 30,
-  rowNum: 5,
-  waitTime: 5000,
+const config = computed(() => {
+  const list = dataSource.value?.aqwtlx.data || [];
+  return {
+    header: ['名称', '数量'],
+    data: list.map(item => ([item.title, item.num])),
+    index: false,
+    headerBGC: 'rgba(29, 66, 110, 0.6)',
+    evenRowBGC: 'rgba(29, 66, 110, 0.6)',
+    oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+    align: ['center', 'center'],
+    headerHeight: 30,
+    rowNum: 5,
+    waitTime: 5000,
+  }
 });
 
-const config1 = reactive({
-  data: [
-    ['高处作业安全', '253'],
-    ['机械使用', '209'],
-    ['电气安全', '320'],
-    ['消防安全', '123'],
-    ['特种设备', '642'],
-    ['xxxx', '234'],
-    ['xxx', '667'],
-    ['xxx', '334'],
-    ['xxx', '234'],
-    ['xxx', '567'],
-  ],
-  evenRowBGC: 'rgba(29, 66, 110, 0.6)',
-  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
-  align: ['center', 'left', 'center'],
-  index: true,
-  columnWidth: [50, 300],
-  rowNum: 6,
-  waitTime: 50005000,
+const config1 = computed(() => {
+  const list = dataSource.value?.zgyqdwpm || [];
+  return {
+    header: ['名称', '数量'],
+    data: list.map(item => ([item.title, item.num])),
+    index: false,
+    headerBGC: 'rgba(29, 66, 110, 0.6)',
+    evenRowBGC: 'rgba(52, 139, 199, 0.298039215686275)',
+    oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+    align: ['left', 'center', 'center', 'center'],
+    columnWidth: [200],
+    headerHeight: 48,
+    rowNum: 9,
+    waitTime: 5000,
+  }
 });
 
-const config2 = reactive({
-  header: ['培训内容', '培训分部', '培训时间', '培训人数'],
-  data: [
-    ['《筑牢安全防线:施工安全基础1培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础2培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础3培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础4培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基234础培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础234培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础5培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础23培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础445培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础5培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础3培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基234础培训》', '部门一', '2023-12-11', '12'],
-    ['《筑牢安全防线:施工安全基础6培训》', '部门一', '2023-12-11', '12'],
-  ],
-  index: false,
-  headerBGC: 'rgba(29, 66, 110, 0.6)',
-  evenRowBGC: 'rgba(52, 139, 199, 0.298039215686275)',
-  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
-  align: ['left', 'center', 'center', 'center'],
-  columnWidth: [500],
-  headerHeight: 48,
-  rowNum: 10,
-  waitTime: 5000,
-});
+// const config2 = reactive({
+//   header: ['培训内容', '培训分部', '培训时间', '培训人数'],
+//   data: [
+//     ['《筑牢安全防线:施工安全基础1培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础2培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础3培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础4培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基234础培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础234培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础5培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础23培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础445培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础5培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础3培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基234础培训》', '部门一', '2023-12-11', '12'],
+//     ['《筑牢安全防线:施工安全基础6培训》', '部门一', '2023-12-11', '12'],
+//   ],
+//   index: false,
+//   headerBGC: 'rgba(29, 66, 110, 0.6)',
+//   evenRowBGC: 'rgba(52, 139, 199, 0.298039215686275)',
+//   oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+//   align: ['left', 'center', 'center', 'center'],
+//   columnWidth: [500],
+//   headerHeight: 48,
+//   rowNum: 10,
+//   waitTime: 5000,
+// });
 
-const config3 = reactive({
-  header: ['问题描述', '发起时间', '整改人'],
-  data: [
-    ['高处作业未系安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系1安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系324安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系23安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系23安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系s安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系34安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系s安全带', '2024-11-08', '分包3-张三'],
-    ['高处作业未系安全带', '2024-11-08', '分包3-张三'],
-  ],
-  index: false,
-  headerBGC: 'rgba(29, 66, 110, 0.6)',
-  evenRowBGC: 'rgba(52, 139, 199, 0.298039215686275)',
-  oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
-  align: ['left', 'center', 'center', 'center'],
-  columnWidth: [200],
-  headerHeight: 48,
-  rowNum: 9,
-  waitTime: 5000,
+const config3 = computed(() => {
+  const list = dataSource.value?.aqwttb || [];
+  return {
+    header: ['问题描述', '发起时间', '整改人'],
+    data: list.map((item) => [item.description, item.creation_time, item.part_abbreviation]),
+    index: false,
+    headerBGC: 'rgba(29, 66, 110, 0.6)',
+    evenRowBGC: 'rgba(52, 139, 199, 0.298039215686275)',
+    oddRowBGC: 'rgba(52, 139, 199, 0.0980392156862745)',
+    align: ['left', 'center', 'center', 'center'],
+    columnWidth: [200],
+    headerHeight: 48,
+    rowNum: 9,
+    waitTime: 5000,
+  }
 });
 
-const config4 = reactive({
-  data: [
-    {
-      name: 'F01',
-      value: 80,
-    },
-    {
-      name: 'A09',
-      value: 72,
-    },
-    {
-      name: 'A08',
-      value: 70,
-    },
-    {
-      name: 'A07',
-      value: 70,
-    },
-    {
-      name: 'A06',
-      value: 70,
-    },
-    {
-      name: 'A075',
-      value: 70,
-    },
-    {
-      name: 'A074',
-      value: 70,
-    },
-    {
-      name: 'A073',
-      value: 70,
-    },
-    {
-      name: 'A071',
-      value: 70,
-    },
-    {
-      name: 'A072',
-      value: 70,
-    },
-    {
-      name: 'A078',
-      value: 70,
-    },
-  ],
-  rowNum: 8,
-  waitTime: 5000,
-  color: '#fff',
-  fontSize: 17,
+const config4 = computed(() => {
+  const list = dataSource.value?.fbwtsltj || [];
+  return {
+    data: list.map((item) => ({ name: item.title, value: item.num })),
+    rowNum: 8,
+    waitTime: 5000,
+    color: '#fff',
+    fontSize: 17,
+  }
 });
 
-const config5 = reactive({
-  number: [1132],
-  content: '{nt}',
+const config5 = computed(() => {
+  return {
+    number: [dataSource.value?.aqwtlx?.total || 0],
+    content: '{nt}',
+  }
 });
 
-setInterval(() => {
-  config5.number = config5.number.map((num) => num + 100);
-}, 3000);
-
 const mouseoverHandler = (e: any) => {
   console.log(e);
 };

+ 3 - 3
vite.config.ts

@@ -71,12 +71,12 @@ export default defineConfig({
   },
   server: {
     proxy: {
-      '/api': {
+      '/api/api': {
         // target: 'http://172.18.50.86:8080',
         // target: 'http://172.26.28.188:8080',
-        target: 'http://localhost:8080',
+        target: 'http://10.0.0.200:7000',
         changeOrigin: true,
-        rewrite: (path) => path.replace('/api', ''),
+        rewrite: (path) => path.replace('/api/api', '/api'),
       },
     },
   },