Explorar el Código

feat: 添加大队级别检查功能并支持Electron桌面应用

refactor(utils): 新增大队级别选项构建函数
feat(api): 添加查询部门指定角色人员接口
feat(pages): 在问题整改和检查单页面支持大队级别检查
feat(electron): 新增Electron桌面应用支持
build: 添加electron-builder配置和打包脚本
style(login): 调整验证码输入框布局
chore: 更新.gitignore忽略electron打包目录
huoyi hace 4 meses
padre
commit
53b422724e

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@ node_modules/
3 3
 unpackage/
4 4
 dist/
5 5
 dist.zip
6
+dist_electron
6 7
 CLAUDE.md
7 8
 # local env files
8 9
 .env.local

+ 44 - 0
electron-builder.json

@@ -0,0 +1,44 @@
1
+{
2
+  "appId": "com.meilan.app",
3
+  "productName": "美兰机场应用",
4
+  "directories": {
5
+    "output": "dist_electron",
6
+    "buildResources": "build"
7
+  },
8
+  "files": [
9
+    "dist/build/h5/**/*",
10
+    "electron/**/*",
11
+    "!**/node_modules/**/*"
12
+  ],
13
+  "extraResources": [
14
+    {
15
+      "from": "dist/build/h5",
16
+      "to": "app/dist/build/h5",
17
+      "filter": ["**/*"]
18
+    }
19
+  ],
20
+  "mac": {
21
+    "category": "public.app-category.productivity",
22
+    "icon": "dist/build/h5/static/pc.png"
23
+  },
24
+  "win": {
25
+    "target": [
26
+      {
27
+        "target": "nsis",
28
+        "arch": ["x64"]
29
+      }
30
+    ],
31
+    "icon": "dist/build/h5/static/pc.png"
32
+  },
33
+  "nsis": {
34
+    "oneClick": false,
35
+    "allowToChangeInstallationDirectory": true,
36
+    "createDesktopShortcut": true,
37
+    "createStartMenuShortcut": true,
38
+    "shortcutName": "美兰机场应用"
39
+  },
40
+  "publish": {
41
+    "provider": "generic",
42
+    "url": "http://your-update-server.com/updates"
43
+  }
44
+}

+ 176 - 0
electron/main.js

@@ -0,0 +1,176 @@
1
+const { app, BrowserWindow, Menu } = require('electron')
2
+const path = require('path')
3
+const isDev = process.env.NODE_ENV === 'development'
4
+
5
+// 保持对窗口对象的全局引用,避免被垃圾回收
6
+let mainWindow
7
+
8
+function createWindow() {
9
+  // 创建浏览器窗口
10
+  mainWindow = new BrowserWindow({
11
+    width: 600,
12
+    height: 800,
13
+    minWidth: 100,
14
+    minHeight: 300,
15
+    webPreferences: {
16
+      nodeIntegration: false,
17
+      contextIsolation: true,
18
+      webSecurity: false, // 禁用webSecurity以允许跨域请求
19
+      allowRunningInsecureContent: true, // 允许不安全内容
20
+      enableRemoteModule: false, // 禁用远程模块,提高安全性
21
+      webgl: true,
22
+      images: true
23
+    },
24
+    icon: path.join(__dirname, '../../dist/build/h5/static/pc.png'), // 应用图标
25
+    show: false // 先隐藏窗口,等加载完成再显示
26
+  })
27
+
28
+  // 设置Content Security Policy
29
+  mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
30
+    callback({
31
+      responseHeaders: {
32
+        ...details.responseHeaders,
33
+        'Content-Security-Policy': [
34
+          "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; " +
35
+          "script-src * 'unsafe-inline' 'unsafe-eval'; " +
36
+          "connect-src * 'unsafe-inline'; " +
37
+          "img-src * data: blob: 'unsafe-inline'; " +
38
+          "frame-src *; " +
39
+          "style-src * 'unsafe-inline'; " +
40
+          "font-src * data:; " +
41
+          "media-src *"
42
+        ]
43
+      }
44
+    })
45
+  })
46
+
47
+  // 加载应用
48
+  if (isDev) {
49
+    // 开发环境:加载本地H5开发服务器
50
+    mainWindow.loadURL('http://localhost:9090')
51
+    // 打开开发者工具
52
+    mainWindow.webContents.openDevTools()
53
+  } else {
54
+    // 生产环境:加载打包后的文件
55
+    const fs = require('fs')
56
+    let indexPath
57
+    
58
+    // 尝试多种可能的路径
59
+    const possiblePaths = [
60
+      // 打包后路径1:resources/app/dist/build/h5
61
+      process.resourcesPath ? path.join(process.resourcesPath, 'app', 'dist', 'build', 'h5', 'index.html') : null,
62
+      // 打包后路径2:resources/app
63
+      process.resourcesPath ? path.join(process.resourcesPath, 'app', 'index.html') : null,
64
+      // 打包后路径3:直接位于应用目录
65
+      path.join(__dirname, '..', 'dist', 'build', 'h5', 'index.html'),
66
+      // 开发环境路径
67
+      path.join(__dirname, '..', '..', 'dist', 'build', 'h5', 'index.html'),
68
+      // 备用路径
69
+      path.join(__dirname, 'dist', 'build', 'h5', 'index.html')
70
+    ].filter(Boolean)
71
+    
72
+    // 查找存在的文件
73
+    for (const testPath of possiblePaths) {
74
+      console.log('Testing path:', testPath)
75
+      if (fs.existsSync(testPath)) {
76
+        indexPath = testPath
77
+        console.log('Found index file at:', indexPath)
78
+        break
79
+      }
80
+    }
81
+    
82
+    if (indexPath && fs.existsSync(indexPath)) {
83
+      // 使用 loadFile 方法加载文件
84
+      mainWindow.loadFile(indexPath)
85
+      console.log('Successfully loaded index file from:', indexPath)
86
+    } else {
87
+      console.error('Index file not found in any of the following paths:')
88
+      possiblePaths.forEach(p => console.error('  -', p))
89
+      
90
+      // 显示详细的错误信息
91
+      const errorHtml = `
92
+        <html>
93
+          <head><title>应用加载失败</title></head>
94
+          <body style="font-family: Arial, sans-serif; padding: 20px;">
95
+            <h1>应用文件加载失败</h1>
96
+            <p>无法找到 index.html 文件,请检查以下路径:</p>
97
+            <ul>
98
+              ${possiblePaths.map(p => `<li>${p}</li>`).join('')}
99
+            </ul>
100
+            <p>请重新安装应用或联系技术支持。</p>
101
+          </body>
102
+        </html>
103
+      `
104
+      mainWindow.loadURL(`data:text/html,${encodeURIComponent(errorHtml)}`)
105
+    }
106
+  }
107
+
108
+  // 窗口准备好后显示
109
+  mainWindow.once('ready-to-show', () => {
110
+    mainWindow.show()
111
+  })
112
+
113
+  // 窗口关闭时触发
114
+  mainWindow.on('closed', () => {
115
+    mainWindow = null
116
+  })
117
+
118
+  // 设置菜单(可选)
119
+  const template = [
120
+    {
121
+      label: '文件',
122
+      submenu: [
123
+        {
124
+          label: '退出',
125
+          accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
126
+          click() {
127
+            app.quit()
128
+          }
129
+        }
130
+      ]
131
+    },
132
+    {
133
+      label: '视图',
134
+      submenu: [
135
+        { role: 'reload', label: '重新加载' },
136
+        { role: 'forceReload', label: '强制重新加载' },
137
+        { role: 'toggleDevTools', label: '开发者工具' },
138
+        { type: 'separator' },
139
+        { role: 'resetZoom', label: '实际大小' },
140
+        { role: 'zoomIn', label: '放大' },
141
+        { role: 'zoomOut', label: '缩小' },
142
+        { type: 'separator' },
143
+        { role: 'togglefullscreen', label: '切换全屏' }
144
+      ]
145
+    }
146
+  ]
147
+
148
+  const menu = Menu.buildFromTemplate(template)
149
+  Menu.setApplicationMenu(menu)
150
+}
151
+
152
+// Electron 初始化完成时触发
153
+app.whenReady().then(createWindow)
154
+
155
+// 所有窗口关闭时退出应用(macOS除外)
156
+app.on('window-all-closed', () => {
157
+  if (process.platform !== 'darwin') {
158
+    app.quit()
159
+  }
160
+})
161
+
162
+// macOS 应用激活时重新创建窗口
163
+app.on('activate', () => {
164
+  if (BrowserWindow.getAllWindows().length === 0) {
165
+    createWindow()
166
+  }
167
+})
168
+
169
+// 安全设置:阻止新窗口创建
170
+app.on('web-contents-created', (event, contents) => {
171
+  contents.on('new-window', (event, navigationUrl) => {
172
+    event.preventDefault()
173
+    // 在默认浏览器中打开外部链接
174
+    require('electron').shell.openExternal(navigationUrl)
175
+  })
176
+})

+ 42 - 0
package.json

@@ -1,6 +1,8 @@
1 1
 {
2 2
   "name": "ruoyi-app-project",
3 3
   "version": "1.0.0",
4
+  "description": "美兰机场移动应用 - 基于uni-app开发的跨平台应用",
5
+  "author": "美兰机场技术团队",
4 6
   "private": true,
5 7
   "scripts": {
6 8
     "dev": "npm run dev:h5",
@@ -8,6 +10,10 @@
8 10
     "build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
9 11
     "build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
10 12
     "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
13
+    "electron:dev": "cross-env NODE_ENV=development electron electron/main.js",
14
+    "electron:build": "npm run build:h5 && electron-builder",
15
+    "electron:pack": "npm run build:h5 && electron-builder --dir",
16
+    "electron:dist": "npm run build:h5 && electron-builder --win --x64",
11 17
     "build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
12 18
     "build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build",
13 19
     "build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build",
@@ -95,6 +101,8 @@
95 101
     "autoprefixer": "^8.0.0",
96 102
     "babel-plugin-import": "^1.11.0",
97 103
     "cross-env": "^7.0.2",
104
+    "electron": "^19.0.0",
105
+    "electron-builder": "^23.0.0",
98 106
     "jest": "^25.4.0",
99 107
     "postcss-comment": "^2.0.0",
100 108
     "postcss-loader": "^8.1.1",
@@ -108,5 +116,39 @@
108 116
   ],
109 117
   "uni-app": {
110 118
     "scripts": {}
119
+  },
120
+  "main": "electron/main.js",
121
+  "homepage": "./",
122
+  "build": {
123
+    "appId": "com.meilan.app",
124
+    "productName": "美兰机场应用",
125
+    "directories": {
126
+      "output": "dist_electron"
127
+    },
128
+    "electronDownload": {
129
+      "mirror": "https://npmmirror.com/mirrors/electron/"
130
+    },
131
+    "files": [
132
+      "dist/build/h5/**/*",
133
+      "electron/**/*"
134
+    ],
135
+    "win": {
136
+      "target": [
137
+        {
138
+          "target": "nsis",
139
+          "arch": [
140
+            "x64"
141
+          ]
142
+        }
143
+      ],
144
+      "icon": "dist/build/h5/static/pc.png"
145
+    },
146
+    "nsis": {
147
+      "oneClick": false,
148
+      "allowToChangeInstallationDirectory": true,
149
+      "createDesktopShortcut": true,
150
+      "createStartMenuShortcut": true,
151
+      "shortcutName": "美兰机场应用"
152
+    }
111 153
   }
112 154
 }

+ 8 - 0
src/api/check/checklist.js

@@ -67,4 +67,12 @@ export function getChecklistStatistics(params) {
67 67
     method: 'get',
68 68
     params: params
69 69
   })
70
+}
71
+//查询部门指定角色人员
72
+export function getDeptRoleUser(deptId,data) {
73
+  return request({
74
+    url: `/system/dept/deptRole/${deptId}`,
75
+    method: 'post',
76
+    data: data
77
+  })
70 78
 }

+ 3 - 3
src/config.js

@@ -1,11 +1,11 @@
1 1
 // 应用全局配置
2 2
 module.exports = {
3 3
    // 接口地址  本地调试使用 http://192.168.3.222:38080  打包使用 /prod-api
4
-  //  baseUrl: process.env.NODE_ENV === 'development' ? 'http://192.168.3.221:82/prod-api' : '/prod-api', //生产
4
+ //  baseUrl: process.env.NODE_ENV === 'development' ? 'http://192.168.3.221:82/prod-api' : '/prod-api', //生产
5 5
   //  baseUrl: process.env.NODE_ENV === 'development' ? 'http://192.168.3.221:8080' : '/prod-api',
6 6
 // baseUrl: process.env.NODE_ENV === 'development' ? 'http://guangxi.qinghe.sundot.cn:8088' : 'http://guangxi.qinghe.sundot.cn:8088/prod-api',
7
-   baseUrl: 'http://guangxi.qinghe.sundot.cn:8088',
8
-
7
+// baseUrl: 'http://guangxi.qinghe.sundot.cn:8088',
8
+    baseUrl:'http://airport.samsundot.com:9021/prod-api',
9 9
   // 应用信息
10 10
   appInfo: {
11 11
     // 应用名称

+ 32 - 3
src/pages/checklist/index.vue

@@ -27,9 +27,13 @@
27 27
                                 <uni-data-picker v-if="getCheckLabel == '被检查科'" :localdata="departments"
28 28
                                     :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedDepartmentId"
29 29
                                     @change="handlecheckedDepartmentIdChange" :readonly="formDisabled" />
30
+                                <uni-data-picker v-if="getCheckLabel == '被检查大队'" :localdata="brigades"
31
+                                    :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedBrigadeId"
32
+                                    @change="handlecheckedBrigadeIdChange" :readonly="formDisabled" />
30 33
                                 <uni-data-picker v-if="getCheckLabel == '被检查班组'" :localdata="teams"
31 34
                                     :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedTeamId"
32 35
                                     @change="handlecheckedTeamIdChange" :readonly="formDisabled" />
36
+
33 37
                                 <fuzzy-select v-if="getCheckLabel == '被检查人'" v-model="formData.checkedPersonnelId"
34 38
                                     :options="userOptions" placeholder="请输入被检查人姓名搜索" data-value="userId"
35 39
                                     data-text="nickName" @change="handleCheckedSelect" :disabled="formDisabled" />
@@ -138,8 +142,9 @@ import TextSwitch from "@/components/text-switch/text-switch.vue"
138 142
 import { checkedLevelEnums } from "@/utils/enums.js"
139 143
 import UnqualifiedPersonnel from "./components/unqualified.vue"
140 144
 import { treeSelectByType } from "@/api/system/common"
141
-import { buildTeamOptions, uploadFile, buildDepartmentOptions } from '@/utils/common'
145
+import { buildTeamOptions, uploadFile, buildDepartmentOptions, buildBrigadeOptions } from '@/utils/common'
142 146
 import useDictMixin from '@/utils/dict'
147
+import { getDeptRoleUser } from "@/api/check/checklist.js"
143 148
 import { addDraftInspection, submitInspection, getInspectionListById } from '@/api/check/checkReward.js'
144 149
 import SubmitResult from './components/SubmitResult.vue'
145 150
 import { selectDeptLeaderByUserId } from '@/api/approve/approve.js'
@@ -196,6 +201,9 @@ export default {
196 201
             if (this.formData.checkedLevel == checkedLevelEnums.DEPARTMENT_LEVEL) {
197 202
                 return '被检查科'
198 203
             }
204
+            if (this.formData.checkedLevel == checkedLevelEnums.BRIGADE_LEVEL) {
205
+                return '被检查大队'
206
+            }
199 207
         },
200 208
         // 验证规则
201 209
         rules() {
@@ -254,6 +262,7 @@ export default {
254 262
             teams: [],//被检查班组选项
255 263
             departments: [],//被检查科选项
256 264
             userOptions: [],//全部的人
265
+            brigades: [],
257 266
             checkcheckedTeamIdOptions: [],
258 267
             // 表单数据
259 268
             formData: {
@@ -376,10 +385,12 @@ export default {
376 385
             this.formData.channelName = res[2]?.text;
377 386
             this.formData.channelCode = res[2]?.value;
378 387
         },
379
-        // 检查科/班组选择
388
+        // 检查班组选择
380 389
         handlecheckedTeamIdChange(e) {
381 390
             const { text, value } = e.detail.value[0];
382 391
             let newText = text.split('/')
392
+            this.$set(this.formData, 'checkedDeptId', value);
393
+            this.$set(this.formData, 'checkedDeptName', newText[newText.length - 1].trim());
383 394
             this.$set(this.formData, 'checkedTeamName', newText[newText.length - 1]);
384 395
 
385 396
             // 在树状结构中查找指定id的上一级id
@@ -430,6 +441,8 @@ export default {
430 441
             const { text, value } = e.detail.value[0];
431 442
 
432 443
             let newText = text.split('/')
444
+            this.$set(this.formData, 'checkedDeptId', value);
445
+            this.$set(this.formData, 'checkedDeptName', newText[newText.length - 1].trim());
433 446
             this.$set(this.formData, 'checkedDepartmentName', newText[newText.length - 1].trim());
434 447
             getDeptManager(value).then(res => {
435 448
                 console.log(res?.data?.userId, "111res")
@@ -437,6 +450,20 @@ export default {
437 450
                 this.$set(this.formData, 'responsibleUserName', res?.data?.nickName || '');
438 451
             })
439 452
         },
453
+        handlecheckedBrigadeIdChange(e) {
454
+            const { text, value } = e.detail.value[0];
455
+            let newText = text.split('/')
456
+            this.$set(this.formData, 'checkedBrigadeName', newText[newText.length - 1].trim());
457
+            this.$set(this.formData, 'checkedDeptId', value);
458
+            this.$set(this.formData, 'checkedDeptName', newText[newText.length - 1].trim());
459
+            this.$set(this.formData, 'checkedDepartmentId', value);
460
+            this.$set(this.formData, 'checkedDepartmentName', newText[newText.length - 1].trim());
461
+            getDeptRoleUser(value, ['xingzheng']).then(res => {
462
+
463
+                this.$set(this.formData, 'responsibleUserId', res?.data[0].userId || '');
464
+                this.$set(this.formData, 'responsibleUserName', res?.data[0].nickName || '');
465
+            })
466
+        },
440 467
         // 加载字典数据
441 468
         async loadDictData() {
442 469
             const user = await listAllUser();
@@ -448,6 +475,7 @@ export default {
448 475
             this.deptTree = deptTree.data || [];
449 476
             this.teams = buildTeamOptions(deptTree.data || []);
450 477
             this.departments = buildDepartmentOptions(deptTree.data || []);
478
+            this.brigades = buildBrigadeOptions(deptTree.data || []);
451 479
             console.log(this.departments, "this.departments")
452 480
             const [positionRes] = await Promise.all([
453 481
                 treeSelectByType("POSITION", 3),
@@ -654,7 +682,7 @@ export default {
654 682
 
655 683
             let payload = {
656 684
                 ...this.formData,
657
-                checkedTeamName: (this.formData.checkedTeamName|| '').trim() ,
685
+                checkedTeamName: (this.formData.checkedTeamName || '').trim(),
658 686
                 checkProjectItemList: originalCheckProjectItemList
659 687
             };
660 688
 
@@ -724,6 +752,7 @@ export default {
724 752
                 uni.showLoading({ title: '提交中...', mask: true });
725 753
 
726 754
                 const payload = this.formateData();
755
+                debugger
727 756
 
728 757
                 submitInspection({ ...payload, ...(this.type == 'task' ? {} : { id: this.taskId }) })
729 758
                     .then(() => {

+ 16 - 7
src/pages/login.vue

@@ -17,13 +17,17 @@
17 17
           <view class="iconfont icon-password icon"></view>
18 18
           <input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
19 19
         </view>
20
-        <view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
21
-          <view class="iconfont icon-code icon"></view>
22
-          <input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
20
+        <view style="display: flex; align-items: center;">
21
+          <view class="input-item flex align-center" style="flex:1;margin: 0px;" v-if="captchaEnabled">
22
+            <view class="iconfont icon-code icon"></view>
23
+            <input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
24
+
25
+          </view>
23 26
           <view class="login-code">
24 27
             <image :src="codeUrl" @click="getCode" class="login-code-img"></image>
25 28
           </view>
26 29
         </view>
30
+
27 31
         <view class="action-btn">
28 32
           <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
29 33
         </view>
@@ -210,7 +214,7 @@ page {
210 214
       }
211 215
 
212 216
       .input {
213
-        width: 100%;
217
+    
214 218
         font-size: 14px;
215 219
         line-height: 20px;
216 220
         text-align: left;
@@ -235,12 +239,17 @@ page {
235 239
 
236 240
     .login-code {
237 241
       height: 38px;
238
-      float: right;
242
+      margin-left: 10rpx;
243
+      // position: absolute;
244
+
245
+      // left: 470rpx;
246
+
247
+
239 248
 
240 249
       .login-code-img {
241 250
         height: 38px;
242
-        position: absolute;
243
-        margin-left: 10px;
251
+
252
+
244 253
         width: 200rpx;
245 254
       }
246 255
     }

+ 8 - 1
src/pages/myToDoList/index.vue

@@ -212,6 +212,9 @@ export default {
212 212
             if (this.getLabel(item.instance && item.instance.businessType) == '人') {
213 213
                 return this.getFormData(item, 'checkedPersonnelName');
214 214
             }
215
+            if (this.getLabel(item.instance && item.instance.businessType) == '大队') {
216
+                return this.getFormData(item, 'checkedBrigadeName');
217
+            }
215 218
             return '';
216 219
         },
217 220
         getLabel(key) {
@@ -224,9 +227,13 @@ export default {
224 227
             if (key === 'SECTION_CHECK') {
225 228
                 return '科室'
226 229
             }
230
+            if (key === 'BRIGADE_CHECK') {
231
+                return '大队'
232
+            }
227 233
             return ''
228 234
         },
229 235
         navigateToDetail(row) {
236
+            
230 237
             let type = 'view'; // 默认查看模式
231 238
             if (this.currentTab == 'msg') {
232 239
                 const { businessType, businessId, instanceId } = row;
@@ -271,7 +278,7 @@ export default {
271 278
                 url = `/pages/seizedReported/index?params=${encodeURIComponent(JSON.stringify(obj))}`;
272 279
             }
273 280
             // 巡检对应个人 班组  科室
274
-            if (['PERSONAL_CHECK', 'GROUP_CHECK', 'SECTION_CHECK'].includes(businessType) || this.currentTab == 'msg') {
281
+            if (['PERSONAL_CHECK', 'GROUP_CHECK', 'SECTION_CHECK', 'BRIGADE_CHECK'].includes(businessType) || this.currentTab == 'msg') {
275 282
                 url = `/pages/problemRect/index?params=${encodeURIComponent(JSON.stringify(obj))}`;
276 283
             }
277 284
             if (url) {

+ 45 - 2
src/pages/problemRect/index.vue

@@ -23,6 +23,9 @@
23 23
                                 <uni-data-picker v-if="getCheckLabel == '被检查科'" :localdata="departments"
24 24
                                     :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedDepartmentId"
25 25
                                     :readonly="true" />
26
+                                <uni-data-picker v-if="getCheckLabel == '被检查大队'" :localdata="brigades"
27
+                                    :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedBrigadeId"
28
+                                    :readonly="true" />
26 29
                                 <uni-data-picker v-if="getCheckLabel == '被检查班组'" :localdata="teams"
27 30
                                     :popup-title="`请选择${getCheckLabel}`" v-model="formData.checkedTeamId"
28 31
                                     :readonly="true" />
@@ -206,7 +209,7 @@ import { listAllUser } from "@/api/system/user.js"
206 209
 import { getProblemRectDetail, approvalAgree, approvalReject } from '@/api/problemRect/problemRect.js'
207 210
 import { getApprovelHistory } from '@/api/approve/approve.js'
208 211
 import { getDeptManager, getDeptDetail } from "@/api/system/dept/dept.js"
209
-import { buildTeamOptions, buildDepartmentOptions } from "@/utils/common.js"
212
+import { buildTeamOptions, buildDepartmentOptions,buildBrigadeOptions } from "@/utils/common.js"
210 213
 export default {
211 214
     components: { HomeContainer, RejectModal },
212 215
     mixins: [useDictMixin],
@@ -214,6 +217,13 @@ export default {
214 217
         currentUser() {
215 218
             return this.$store.state.user;
216 219
         },
220
+        currentUserId() {
221
+            return this.currentUser && this.currentUser.id;
222
+        },
223
+        //表单责任人是当前登陆人
224
+        isResponsiblePerson() {
225
+            return this.currentUserId == this.formData.responsibleUserId;
226
+        },
217 227
         userInfo() {
218 228
             return (this.$store.state.user && this.$store.state.user.userInfo) ? this.$store.state.user.userInfo : {}
219 229
         },
@@ -243,6 +253,9 @@ export default {
243 253
             if (this.formData.checkedLevel == checkedLevelEnums.DEPARTMENT_LEVEL) {
244 254
                 return '被检查科'
245 255
             }
256
+            if (this.formData.checkedLevel == checkedLevelEnums.BRIGADE_LEVEL) {
257
+                return '被检查大队'
258
+            }
246 259
         },
247 260
         // 动态验证规则
248 261
         rules() {
@@ -282,6 +295,7 @@ export default {
282 295
             userOptions: [],
283 296
             teams: [], // 班组选项数据
284 297
             departments: [], // 部门选项数据
298
+            brigades: [], // 大队选项数据
285 299
             // 表单数据
286 300
             formData: {
287 301
                 // 基本信息
@@ -301,6 +315,10 @@ export default {
301 315
                 selectTeamName: '', // 整改班组名称
302 316
                 selectTeamLeaderId: '', // 班组长ID
303 317
                 selectTeamLeaderName: '', // 班组长姓名
318
+                
319
+                // 被检查对象相关字段
320
+                checkedBrigadeId: '', // 被检查大队ID
321
+                checkedBrigadeName: '', // 被检查大队名称
304 322
 
305 323
                 // 整改信息
306 324
                 rectificationDetails: '',
@@ -349,6 +367,14 @@ export default {
349 367
         handlecheckedDepartmentIdChange(e) {
350 368
 
351 369
         },
370
+        
371
+        // 被检查大队选择
372
+        handlecheckedBrigadeIdChange(e) {
373
+            const { text, value } = e.detail.value[0];
374
+            let newText = text.split('/')
375
+            this.$set(this.formData, 'checkedBrigadeName', newText[newText.length - 1].trim());
376
+            this.$set(this.formData, 'checkedBrigadeId', value);
377
+        },
352 378
         async loadDictData() {
353 379
             const user = await listAllUser();
354 380
             this.userOptions = user.data.map(item => ({
@@ -385,6 +411,7 @@ export default {
385 411
                 const deptTree = await getDeptList();
386 412
                 this.teams = buildTeamOptions(deptTree.data || []);
387 413
                 this.departments = buildDepartmentOptions(deptTree.data || []);
414
+                this.brigades = buildBrigadeOptions(deptTree.data || []);
388 415
                 uni.hideLoading();
389 416
             } catch (err) {
390 417
                 uni.hideLoading();
@@ -528,7 +555,7 @@ export default {
528 555
 
529 556
             // 还原checkProjectItemList结构到后端原始格式
530 557
             const restoredCheckProjectItemList = this.restoreOriginalCheckProjectItemList();
531
-
558
+            
532 559
             const payload = {
533 560
                 ...this.formData,
534 561
                 checkProjectItemList: restoredCheckProjectItemList,
@@ -553,6 +580,22 @@ export default {
553 580
 
554 581
         // 提交表单
555 582
         submitForm() {
583
+            // 如果当前用户是责任人,校验问题人字段必填
584
+            if (this.isResponsiblePerson) {
585
+                const hasEmptyProblemUser = this.formData.checkProjectItemList?.some(problem => 
586
+                    !problem.checkUserList || !problem.checkUserList.userId
587
+                );
588
+                
589
+                if (hasEmptyProblemUser) {
590
+                    uni.showToast({ 
591
+                        title: '请填写所有问题的责任人', 
592
+                        icon: 'none',
593
+                        duration: 3000
594
+                    });
595
+                    return;
596
+                }
597
+            }
598
+            
556 599
             this.$refs.form.validate().then(res => {
557 600
                 uni.showLoading({ title: '提交中...', mask: true });
558 601
 

BIN
src/static/pc.png


+ 27 - 0
src/utils/common.js

@@ -141,6 +141,33 @@ export function buildDepartmentOptions(tree = []) {
141 141
   return result;
142 142
 }
143 143
 
144
+/**
145
+ * 找出deptType为BRIGADE的节点
146
+ * @param {Array} tree - 树形结构数据
147
+ * @returns {Array} 包含所有BRIGADE节点的数组
148
+ */
149
+export function buildBrigadeOptions(tree = []) {
150
+  const result = [];
151
+
152
+  function dfs(node, path = []) {
153
+    const currentPath = [...path, node.label];
154
+    // 如果是 DEPARTMENT 节点
155
+    if (node.deptType === 'BRIGADE') {
156
+      result.push({
157
+        text: currentPath.join(' / '),
158
+        value: node.id
159
+      });
160
+    }
161
+    // 继续递归子节点
162
+    if (node.children && Array.isArray(node.children)) {
163
+      node.children.forEach(child => dfs(child, currentPath));
164
+    }
165
+  }
166
+
167
+  tree.forEach(root => dfs(root));
168
+  return result;
169
+}
170
+
144 171
 //将数组格式的按照航站楼分成对象格式
145 172
 export function getHandleAreaData(data, notkezhang) {
146 173
   let louObj = {};

+ 3 - 2
src/utils/enums.js

@@ -1,8 +1,9 @@
1 1
 export const checkedLevelEnums = {
2 2
     TEAM_LEVEL: 'TEAM_LEVEL',
3 3
     PERSONNEL_LEVEL: 'PERSONNEL_LEVEL',
4
-    DEPARTMENT_LEVEL: 'DEPARTMENT_LEVEL',
5
-    STATION_LEVEL: 'STATION_LEVEL'
4
+    DEPARTMENT_LEVEL: 'DEPARTMENT_LEVEL',//主管级别
5
+    STATION_LEVEL: 'STATION_LEVEL',
6
+    BRIGADE_LEVEL: 'BRIGADE_LEVEL',//大队级别
6 7
 }
7 8
 
8 9
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1056 - 26
yarn.lock