Kaynağa Gözat

网页端:新增维修审批流程

余思翰 1 gün önce
ebeveyn
işleme
eae7641266

+ 214
- 0
oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/oa/CmcRepairController.java Dosyayı Görüntüle

@@ -0,0 +1,214 @@
1
+package com.ruoyi.web.controller.oa;
2
+
3
+import java.math.BigDecimal;
4
+import java.text.ParseException;
5
+import java.text.SimpleDateFormat;
6
+import java.util.Calendar;
7
+import java.util.HashMap;
8
+import java.util.List;
9
+import java.util.Map;
10
+import javax.servlet.http.HttpServletResponse;
11
+import org.springframework.beans.factory.annotation.Autowired;
12
+import org.springframework.web.bind.annotation.GetMapping;
13
+import org.springframework.web.bind.annotation.PostMapping;
14
+import org.springframework.web.bind.annotation.PutMapping;
15
+import org.springframework.web.bind.annotation.DeleteMapping;
16
+import org.springframework.web.bind.annotation.PathVariable;
17
+import org.springframework.web.bind.annotation.RequestBody;
18
+import org.springframework.web.bind.annotation.RequestMapping;
19
+import org.springframework.web.bind.annotation.RestController;
20
+import com.alibaba.fastjson2.JSONArray;
21
+import com.alibaba.fastjson2.JSONObject;
22
+import com.ruoyi.common.annotation.Log;
23
+import com.ruoyi.common.core.controller.BaseController;
24
+import com.ruoyi.common.core.domain.AjaxResult;
25
+import com.ruoyi.common.enums.BusinessType;
26
+import com.ruoyi.oa.domain.CmcRepair;
27
+import com.ruoyi.oa.service.impl.ICmcRepairService;
28
+import com.ruoyi.common.utils.poi.ExcelUtil;
29
+import com.ruoyi.common.core.page.TableDataInfo;
30
+
31
+/**
32
+ * 维修审批Controller
33
+ * 
34
+ * @author cmc
35
+ * @date 2026-07-01
36
+ */
37
+@RestController
38
+@RequestMapping("/oa/repair")
39
+public class CmcRepairController extends BaseController
40
+{
41
+    @Autowired
42
+    private ICmcRepairService cmcRepairService;
43
+
44
+    /**
45
+     * 查询维修审批列表
46
+     */
47
+    @GetMapping("/list")
48
+    public TableDataInfo list(CmcRepair cmcRepair)
49
+    {
50
+        startPage();
51
+        List<CmcRepair> list = cmcRepairService.selectCmcRepairList(cmcRepair);
52
+        return getDataTable(list);
53
+    }
54
+
55
+    /**
56
+     * 导出维修审批列表
57
+     */
58
+    @Log(title = "维修审批", businessType = BusinessType.EXPORT)
59
+    @PostMapping("/export")
60
+    public void export(HttpServletResponse response, CmcRepair cmcRepair)
61
+    {
62
+        List<CmcRepair> list = cmcRepairService.selectCmcRepairList(cmcRepair);
63
+        ExcelUtil<CmcRepair> util = new ExcelUtil<CmcRepair>(CmcRepair.class);
64
+        util.exportExcel(response, list, "维修审批数据");
65
+    }
66
+
67
+    /**
68
+     * 获取维修审批统计信息
69
+     */
70
+    @GetMapping("/statistic")
71
+    public AjaxResult getRepairStatistic(CmcRepair cmcRepair) throws ParseException
72
+    {
73
+        JSONObject jsonObject = new JSONObject();
74
+        JSONArray yearCountArray = new JSONArray();
75
+        JSONObject yearCountObject = new JSONObject();
76
+        JSONArray yearAmountArray = new JSONArray();
77
+        JSONObject yearAmountObject = new JSONObject();
78
+        JSONArray monthCountArray = new JSONArray();
79
+        JSONObject monthCountObject = new JSONObject();
80
+        JSONArray monthAmountArray = new JSONArray();
81
+        JSONObject monthAmountObject = new JSONObject();
82
+        JSONArray usageCountArray = new JSONArray();
83
+        JSONObject usageCountObject = new JSONObject();
84
+        JSONArray usageAmountArray = new JSONArray();
85
+        JSONObject usageAmountObject = new JSONObject();
86
+
87
+        if (cmcRepair.getApplyDate() == null)
88
+        {
89
+            int currentYear = Calendar.getInstance().get(Calendar.YEAR);
90
+            Integer minYear = cmcRepairService.selectMinApplyYear();
91
+            int startYear = minYear != null ? minYear : currentYear;
92
+            for (int i = startYear; i <= currentYear; i++)
93
+            {
94
+                CmcRepair query = new CmcRepair();
95
+                Map<String, Object> params = new HashMap<>();
96
+                params.put("statYear", String.valueOf(i));
97
+                query.setParams(params);
98
+                List<CmcRepair> yearList = cmcRepairService.selectCmcRepairList(query);
99
+                yearCountObject.put(String.valueOf(i), yearList.size());
100
+                yearAmountObject.put(String.valueOf(i), sumRepairMoney(yearList));
101
+            }
102
+        }
103
+        else
104
+        {
105
+            String year = new SimpleDateFormat("yyyy").format(cmcRepair.getApplyDate());
106
+            CmcRepair yearQuery = new CmcRepair();
107
+            Map<String, Object> yearParams = new HashMap<>();
108
+            yearParams.put("statYear", year);
109
+            yearQuery.setParams(yearParams);
110
+            List<CmcRepair> yearList = cmcRepairService.selectCmcRepairList(yearQuery);
111
+            yearCountObject.put(year, yearList.size());
112
+            yearAmountObject.put(year, sumRepairMoney(yearList));
113
+
114
+            for (int i = 1; i <= 12; i++)
115
+            {
116
+                String monthKey = year + "-" + String.format("%02d", i);
117
+                CmcRepair monthQuery = new CmcRepair();
118
+                Map<String, Object> monthParams = new HashMap<>();
119
+                monthParams.put("statMonth", monthKey);
120
+                monthQuery.setParams(monthParams);
121
+                List<CmcRepair> monthList = cmcRepairService.selectCmcRepairList(monthQuery);
122
+                monthCountObject.put(monthKey, monthList.size());
123
+                monthAmountObject.put(monthKey, sumRepairMoney(monthList));
124
+            }
125
+            fillUsageStatistic(yearQuery, usageCountObject, usageAmountObject);
126
+        }
127
+
128
+        if (cmcRepair.getApplyDate() == null)
129
+        {
130
+            CmcRepair allQuery = new CmcRepair();
131
+            fillUsageStatistic(allQuery, usageCountObject, usageAmountObject);
132
+        }
133
+
134
+        yearCountArray.add(yearCountObject);
135
+        yearAmountArray.add(yearAmountObject);
136
+        monthCountArray.add(monthCountObject);
137
+        monthAmountArray.add(monthAmountObject);
138
+        usageCountArray.add(usageCountObject);
139
+        usageAmountArray.add(usageAmountObject);
140
+        jsonObject.put("yearCount", yearCountArray);
141
+        jsonObject.put("yearAmount", yearAmountArray);
142
+        jsonObject.put("monthCount", monthCountArray);
143
+        jsonObject.put("monthAmount", monthAmountArray);
144
+        jsonObject.put("usageCount", usageCountArray);
145
+        jsonObject.put("usageAmount", usageAmountArray);
146
+        return success(jsonObject);
147
+    }
148
+
149
+    private BigDecimal sumRepairMoney(List<CmcRepair> list)
150
+    {
151
+        BigDecimal amount = BigDecimal.ZERO;
152
+        for (CmcRepair repair : list)
153
+        {
154
+            if (repair.getRepairMoney() != null)
155
+            {
156
+                amount = amount.add(repair.getRepairMoney());
157
+            }
158
+        }
159
+        return amount;
160
+    }
161
+
162
+    private void fillUsageStatistic(CmcRepair baseQuery, JSONObject usageCountObject, JSONObject usageAmountObject)
163
+    {
164
+        String[][] types = {{"0", "电脑"}, {"1", "打印机"}, {"2", "其他"}, {"3", "仪器设备"}};
165
+        for (String[] type : types)
166
+        {
167
+            CmcRepair query = new CmcRepair();
168
+            query.setParams(baseQuery.getParams());
169
+            query.setRepairUsage(type[0]);
170
+            List<CmcRepair> list = cmcRepairService.selectCmcRepairList(query);
171
+            usageCountObject.put(type[1], list.size());
172
+            usageAmountObject.put(type[1], sumRepairMoney(list));
173
+        }
174
+    }
175
+
176
+    /**
177
+     * 获取维修审批详细信息
178
+     */
179
+    @GetMapping(value = "/{repairId}")
180
+    public AjaxResult getInfo(@PathVariable("repairId") String repairId)
181
+    {
182
+        return success(cmcRepairService.selectCmcRepairByRepairId(repairId));
183
+    }
184
+
185
+    /**
186
+     * 新增维修审批
187
+     */
188
+    @Log(title = "维修审批", businessType = BusinessType.INSERT)
189
+    @PostMapping
190
+    public AjaxResult add(@RequestBody CmcRepair cmcRepair)
191
+    {
192
+        return toAjax(cmcRepairService.insertCmcRepair(cmcRepair));
193
+    }
194
+
195
+    /**
196
+     * 修改维修审批
197
+     */
198
+    @Log(title = "维修审批", businessType = BusinessType.UPDATE)
199
+    @PutMapping
200
+    public AjaxResult edit(@RequestBody CmcRepair cmcRepair)
201
+    {
202
+        return toAjax(cmcRepairService.updateCmcRepair(cmcRepair));
203
+    }
204
+
205
+    /**
206
+     * 删除维修审批
207
+     */
208
+    @Log(title = "维修审批", businessType = BusinessType.DELETE)
209
+	@DeleteMapping("/{repairIds}")
210
+    public AjaxResult remove(@PathVariable String[] repairIds)
211
+    {
212
+        return toAjax(cmcRepairService.deleteCmcRepairByRepairIds(repairIds));
213
+    }
214
+}

+ 295
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java Dosyayı Görüntüle

@@ -0,0 +1,295 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
5
+import com.ruoyi.common.constant.Constants;
6
+import com.ruoyi.common.core.controller.BaseController;
7
+import com.ruoyi.common.core.domain.AjaxResult;
8
+import com.ruoyi.common.utils.StringUtils;
9
+import com.ruoyi.common.utils.file.FileUploadUtils;
10
+import com.ruoyi.framework.config.ServerConfig;
11
+import com.ruoyi.llm.domain.CmcChat;
12
+import com.ruoyi.llm.service.ICmcAgentService;
13
+import com.ruoyi.llm.service.ICmcChatService;
14
+import com.ruoyi.llm.service.ICmcTopicService;
15
+import org.noear.solon.ai.chat.ChatModel;
16
+import org.noear.solon.ai.chat.ChatResponse;
17
+import org.noear.solon.ai.chat.ChatSession;
18
+import org.noear.solon.ai.chat.message.AssistantMessage;
19
+import org.noear.solon.ai.chat.message.ChatMessage;
20
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
21
+import org.noear.solon.ai.mcp.McpChannel;
22
+import org.noear.solon.ai.mcp.client.McpClientProvider;
23
+import org.slf4j.Logger;
24
+import org.slf4j.LoggerFactory;
25
+import org.springframework.beans.factory.annotation.Autowired;
26
+import org.springframework.beans.factory.annotation.Value;
27
+import org.springframework.web.bind.annotation.GetMapping;
28
+import org.springframework.web.bind.annotation.PostMapping;
29
+import org.springframework.web.bind.annotation.RequestMapping;
30
+import org.springframework.web.bind.annotation.RestController;
31
+import org.springframework.web.multipart.MultipartFile;
32
+
33
+import com.ruoyi.common.utils.http.HttpUtils;
34
+
35
+import java.io.BufferedReader;
36
+import java.io.File;
37
+import java.io.IOException;
38
+import java.io.InputStream;
39
+import java.io.InputStreamReader;
40
+import java.io.OutputStream;
41
+import java.net.HttpURLConnection;
42
+import java.net.URL;
43
+import java.nio.charset.StandardCharsets;
44
+import java.util.ArrayList;
45
+import java.util.List;
46
+
47
+
48
+/**
49
+ * mcp模型上下文协议Controller
50
+ * 
51
+ * @author cmc
52
+ * @date 2025-04-08
53
+ */
54
+@RestController
55
+@RequestMapping("/llm/mcp")
56
+public class McpController extends BaseController
57
+{
58
+    private static final Logger log = LoggerFactory.getLogger(McpController.class);
59
+
60
+    /** 仅允许上传 .docx 文件 */
61
+    private static final String[] DOCX_EXTENSION = {"docx"};
62
+
63
+    /** Python 脚本输出的文件名 */
64
+    private static final String FORMATTED_OUTPUT_NAME = "formatted_output.docx";
65
+
66
+    @Autowired
67
+    private ICmcChatService cmcChatService;
68
+
69
+    @Autowired
70
+    private ICmcTopicService cmcTopicService;
71
+
72
+    @Autowired
73
+    private ICmcAgentService cmcAgentService;
74
+
75
+    @Autowired
76
+    private ServerConfig serverConfig;
77
+
78
+    @Value("${cmc.llmService.url}")
79
+    private String llmServiceUrl;
80
+
81
+    @Value("${cmc.milvusService.url}")
82
+    private String milvusServiceUrl;
83
+
84
+    @Value("${cmc.pythonService.url:${cmc.docFormatter.url:}}")
85
+    private String pythonServiceUrl;
86
+
87
+    @Value("${cmc.docFormatter.templatePath}")
88
+    private String docFormatterTemplatePath;
89
+
90
+    /**
91
+     * 自动调用mcp工具问答
92
+     * @return
93
+     */
94
+    @GetMapping("/answer")
95
+    public AssistantMessage answer(String topicId, String question) throws IOException {
96
+        McpClientProvider clientProvider = McpClientProvider.builder()
97
+                .channel(McpChannel.STREAMABLE_STATELESS )
98
+                .url("http://localhost:8087/mcp/sse")
99
+                .build();
100
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
101
+                .model("Qwen")
102
+                .defaultToolAdd(clientProvider)
103
+                .build();
104
+
105
+        List<ChatMessage> messages = new ArrayList<>();
106
+        CmcChat cmcChat = new CmcChat();
107
+        cmcChat.setTopicId(topicId);
108
+        List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
109
+        for (CmcChat chat : cmcChatList) {
110
+            messages.add(ChatMessage.ofUser(chat.getInput()));
111
+            messages.add(ChatMessage.ofAssistant(chat.getOutput()));
112
+        }
113
+        messages.add(ChatMessage.ofUser(question));
114
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
115
+        ChatResponse response = chatModel.prompt(chatSession).call();
116
+        String resultContent = response.lastChoice().getMessage().getResultContent();
117
+        AssistantMessage assistantMessage;
118
+        if (resultContent.startsWith("<tool_call>")) {
119
+            String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
120
+            JSONObject jsonObject = JSONObject.parseObject(content);
121
+            String name = jsonObject.getString("name");
122
+            JSONObject arguments = jsonObject.getJSONObject("arguments");
123
+            if (arguments.getString("templatePath").contains("招标") || arguments.getString("templatePath").contains("询价")) {
124
+                arguments.put("collectionName", "technical");
125
+                String agentName = cmcAgentService.selectCmcAgentByAgentId(cmcTopicService.selectCmcTopicByTopicId(topicId).getAgentId()).getAgentName();
126
+                arguments.put("agentName", agentName);
127
+                arguments.put("title", question);
128
+            }
129
+            resultContent = clientProvider.callTool(name, arguments).getContent();
130
+            assistantMessage = ChatMessage.ofAssistant(resultContent);
131
+        }
132
+        else
133
+            throw new IOException("模型上下文工具未准备就绪,请重试");
134
+        return assistantMessage;
135
+    }
136
+
137
+    /**
138
+     * 上传 Word 文档并调用 Python 脚本进行格式化
139
+     *
140
+     * @param file 前端上传的 .docx 文件
141
+     * @return 格式化后的文件下载路径
142
+     */
143
+    @PostMapping("/formatDoc")
144
+    public AjaxResult formatDoc(MultipartFile file)
145
+    {
146
+        try
147
+        {
148
+            if (file == null || file.isEmpty())
149
+            {
150
+                return AjaxResult.error("上传文件不能为空");
151
+            }
152
+
153
+            // 1. 使用若依 FileUploadUtils 暂存上传文件到本地
154
+            String uploadBaseDir = RuoYiConfig.getUploadPath();
155
+            String resourcePath = FileUploadUtils.upload(uploadBaseDir, "docFormat", file, DOCX_EXTENSION);
156
+
157
+            // 2. 将资源路径转换为服务器上的绝对路径
158
+            String uploadedAbsolutePath = toAbsolutePath(resourcePath);
159
+            log.info("Word 文档已暂存,绝对路径: {}", uploadedAbsolutePath);
160
+
161
+            // 校验模版与脚本配置
162
+            validateFormatterConfig();
163
+
164
+            // 3. 调用 Python 格式化 HTTP 服务
165
+            String serviceOutput = callDocFormatterService(uploadedAbsolutePath, docFormatterTemplatePath);
166
+            log.info("文档格式化服务响应: {}", serviceOutput);
167
+
168
+            // 4. 定位格式化后的输出文件(与上传文件同目录下的 formatted_output.docx)
169
+            File outputFile = new File(new File(uploadedAbsolutePath).getParentFile(), FORMATTED_OUTPUT_NAME);
170
+            if (!outputFile.exists())
171
+            {
172
+                return AjaxResult.error("文档格式化失败,未找到输出文件");
173
+            }
174
+
175
+            // 构造前端可访问的资源路径与下载 URL
176
+            String outputResourcePath = buildOutputResourcePath(resourcePath);
177
+            String downloadUrl = serverConfig.getUrl() + outputResourcePath;
178
+
179
+            AjaxResult ajax = AjaxResult.success("文档格式化成功");
180
+            ajax.put("fileName", outputResourcePath);
181
+            ajax.put("url", downloadUrl);
182
+            ajax.put("newFileName", FORMATTED_OUTPUT_NAME);
183
+            ajax.put("originalFilename", file.getOriginalFilename());
184
+            return ajax;
185
+        }
186
+        catch (Exception e)
187
+        {
188
+            log.error("Word 文档格式化失败", e);
189
+            return AjaxResult.error("文档格式化失败: " + e.getMessage());
190
+        }
191
+    }
192
+
193
+    /**
194
+     * 将 /profile/... 形式的资源路径转为本地绝对路径
195
+     */
196
+    private String toAbsolutePath(String resourcePath)
197
+    {
198
+        return RuoYiConfig.getProfile() + StringUtils.substringAfter(resourcePath, Constants.RESOURCE_PREFIX);
199
+    }
200
+
201
+    /**
202
+     * 根据上传文件的资源路径,推导格式化输出文件的资源路径
203
+     */
204
+    private String buildOutputResourcePath(String uploadedResourcePath)
205
+    {
206
+        int lastSlash = uploadedResourcePath.lastIndexOf('/');
207
+        return uploadedResourcePath.substring(0, lastSlash + 1) + FORMATTED_OUTPUT_NAME;
208
+    }
209
+
210
+    /**
211
+     * 校验格式化服务与模版文件是否可用
212
+     */
213
+    private void validateFormatterConfig() throws IOException
214
+    {
215
+        if (StringUtils.isEmpty(pythonServiceUrl))
216
+        {
217
+            throw new IOException("Python 服务 URL 未配置: cmc.pythonService.url");
218
+        }
219
+        if (StringUtils.isEmpty(docFormatterTemplatePath) || !new File(docFormatterTemplatePath).exists())
220
+        {
221
+            throw new IOException("Word 模版文件路径未配置或文件不存在: " + docFormatterTemplatePath);
222
+        }
223
+
224
+        String healthUrl = pythonServiceUrl.endsWith("/")
225
+                ? pythonServiceUrl + "health"
226
+                : pythonServiceUrl + "/health";
227
+        String healthResponse = HttpUtils.sendGet(healthUrl);
228
+        if (StringUtils.isEmpty(healthResponse) || !healthResponse.contains("\"status\":\"ok\""))
229
+        {
230
+            throw new IOException("Python 服务不可用,请确认已启动: " + pythonServiceUrl);
231
+        }
232
+    }
233
+
234
+    /**
235
+     * 调用 Python 格式化 HTTP 服务
236
+     *
237
+     * @param uploadedAbsolutePath 上传文件的绝对路径
238
+     * @param templateAbsolutePath 模版文件的绝对路径
239
+     * @return 服务返回的消息内容
240
+     */
241
+    private String callDocFormatterService(String uploadedAbsolutePath, String templateAbsolutePath) throws IOException
242
+    {
243
+        JSONObject body = new JSONObject();
244
+        body.put("uploaded_file_path", uploadedAbsolutePath);
245
+        body.put("template_file_path", templateAbsolutePath);
246
+        body.put("output_filename", FORMATTED_OUTPUT_NAME);
247
+
248
+        String formatUrl = pythonServiceUrl.endsWith("/")
249
+                ? pythonServiceUrl + "doc/format"
250
+                : pythonServiceUrl + "/doc/format";
251
+
252
+        HttpURLConnection conn = (HttpURLConnection) new URL(formatUrl).openConnection();
253
+        conn.setRequestMethod("POST");
254
+        conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
255
+        conn.setConnectTimeout(30_000);
256
+        conn.setReadTimeout(300_000);
257
+        conn.setDoOutput(true);
258
+
259
+        try (OutputStream os = conn.getOutputStream())
260
+        {
261
+            os.write(body.toJSONString().getBytes(StandardCharsets.UTF_8));
262
+        }
263
+
264
+        int status = conn.getResponseCode();
265
+        InputStream responseStream = status >= 400 ? conn.getErrorStream() : conn.getInputStream();
266
+        if (responseStream == null)
267
+        {
268
+            throw new IOException("文档格式化服务无响应,HTTP 状态码: " + status);
269
+        }
270
+
271
+        StringBuilder responseBuilder = new StringBuilder();
272
+        try (BufferedReader reader = new BufferedReader(
273
+                new InputStreamReader(responseStream, StandardCharsets.UTF_8)))
274
+        {
275
+            String line;
276
+            while ((line = reader.readLine()) != null)
277
+            {
278
+                responseBuilder.append(line);
279
+            }
280
+        }
281
+
282
+        String responseBody = responseBuilder.toString();
283
+        if (status >= 400)
284
+        {
285
+            throw new IOException("文档格式化服务请求失败,HTTP " + status + ",响应: " + responseBody);
286
+        }
287
+
288
+        JSONObject result = JSONObject.parseObject(responseBody);
289
+        if (!Boolean.TRUE.equals(result.getBoolean("success")))
290
+        {
291
+            throw new IOException("文档格式化失败: " + result.getString("message"));
292
+        }
293
+        return result.getString("message");
294
+    }
295
+}

+ 339
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/oa/domain/CmcRepair.java Dosyayı Görüntüle

@@ -0,0 +1,339 @@
1
+package com.ruoyi.oa.domain;
2
+
3
+import java.math.BigDecimal;
4
+import java.util.Date;
5
+import com.fasterxml.jackson.annotation.JsonFormat;
6
+import org.apache.commons.lang3.builder.ToStringBuilder;
7
+import org.apache.commons.lang3.builder.ToStringStyle;
8
+import com.ruoyi.common.annotation.Excel;
9
+import com.ruoyi.common.core.domain.BaseEntity;
10
+
11
+/**
12
+ * 维修审批对象 cmc_repair
13
+ * 
14
+ * @author cmc
15
+ * @date 2026-07-01
16
+ */
17
+public class CmcRepair extends BaseEntity
18
+{
19
+    private static final long serialVersionUID = 1L;
20
+
21
+    /** 维修id */
22
+    private String repairId;
23
+
24
+    /** 申请人 */
25
+    @Excel(name = "申请人")
26
+    private Long applier;
27
+
28
+    /** 申请部门 */
29
+    @Excel(name = "申请部门")
30
+    private Long applyDept;
31
+
32
+    /** 情况说明 */
33
+    @Excel(name = "情况说明")
34
+    private String applyReason;
35
+
36
+    /** 申请日期 */
37
+    @JsonFormat(pattern = "yyyy-MM-dd")
38
+    @Excel(name = "申请日期", width = 30, dateFormat = "yyyy-MM-dd")
39
+    private Date applyDate;
40
+
41
+    /** 维修类型 */
42
+    @Excel(name = "维修类型")
43
+    private String repairUsage;
44
+
45
+    /** 附件 */
46
+    @Excel(name = "附件")
47
+    private String repairDocument;
48
+
49
+    /** 部门审批人 */
50
+    @Excel(name = "部门审批人")
51
+    private Long deptUserId;
52
+
53
+    /** 部门意见 */
54
+    @Excel(name = "部门意见")
55
+    private String deptComment;
56
+
57
+    /** 部门审批时间 */
58
+    @JsonFormat(pattern = "yyyy-MM-dd")
59
+    @Excel(name = "部门审批时间", width = 30, dateFormat = "yyyy-MM-dd")
60
+    private Date deptTime;
61
+
62
+    /** 分管审批人 */
63
+    @Excel(name = "分管审批人")
64
+    private Long managerUserId;
65
+
66
+    /** 分管审批意见 */
67
+    @Excel(name = "分管审批意见")
68
+    private String managerComment;
69
+
70
+    /** 分管审批时间 */
71
+    @JsonFormat(pattern = "yyyy-MM-dd")
72
+    @Excel(name = "分管审批时间", width = 30, dateFormat = "yyyy-MM-dd")
73
+    private Date managerTime;
74
+
75
+    /** 综合事务部审核人 */
76
+    @Excel(name = "综合事务部审核人")
77
+    private Long reviewerId;
78
+
79
+    /** 综合事务部审核意见 */
80
+    @Excel(name = "综合事务部审核意见")
81
+    private String reviewerComment;
82
+
83
+    /** 综合事务部审核时间 */
84
+    @JsonFormat(pattern = "yyyy-MM-dd")
85
+    @Excel(name = "综合事务部审核时间", width = 30, dateFormat = "yyyy-MM-dd")
86
+    private Date reviewerTime;
87
+
88
+    /** 综合事务部处理人 */
89
+    @Excel(name = "综合事务部处理人")
90
+    private Long handlerId;
91
+
92
+    /** 综合事务部处理情况 */
93
+    @Excel(name = "综合事务部处理情况")
94
+    private String handlerComment;
95
+
96
+    /** 综合事务部处理时间 */
97
+    @JsonFormat(pattern = "yyyy-MM-dd")
98
+    @Excel(name = "综合事务部处理时间", width = 30, dateFormat = "yyyy-MM-dd")
99
+    private Date repairTime;
100
+
101
+    /** 维修费用 */
102
+    @Excel(name = "维修费用")
103
+    private BigDecimal repairMoney;
104
+
105
+    /** 关联项目id */
106
+    @Excel(name = "关联项目")
107
+    private String projectId;
108
+
109
+    /** 关联设备id */
110
+    @Excel(name = "关联设备")
111
+    private Integer deviceId;
112
+
113
+    public void setRepairId(String repairId) 
114
+    {
115
+        this.repairId = repairId;
116
+    }
117
+
118
+    public String getRepairId() 
119
+    {
120
+        return repairId;
121
+    }
122
+    public void setApplier(Long applier) 
123
+    {
124
+        this.applier = applier;
125
+    }
126
+
127
+    public Long getApplier() 
128
+    {
129
+        return applier;
130
+    }
131
+    public void setApplyDept(Long applyDept) 
132
+    {
133
+        this.applyDept = applyDept;
134
+    }
135
+
136
+    public Long getApplyDept() 
137
+    {
138
+        return applyDept;
139
+    }
140
+    public void setApplyReason(String applyReason) 
141
+    {
142
+        this.applyReason = applyReason;
143
+    }
144
+
145
+    public String getApplyReason() 
146
+    {
147
+        return applyReason;
148
+    }
149
+    public void setApplyDate(Date applyDate) 
150
+    {
151
+        this.applyDate = applyDate;
152
+    }
153
+
154
+    public Date getApplyDate() 
155
+    {
156
+        return applyDate;
157
+    }
158
+    public void setRepairUsage(String repairUsage) 
159
+    {
160
+        this.repairUsage = repairUsage;
161
+    }
162
+
163
+    public String getRepairUsage() 
164
+    {
165
+        return repairUsage;
166
+    }
167
+    public void setRepairDocument(String repairDocument) 
168
+    {
169
+        this.repairDocument = repairDocument;
170
+    }
171
+
172
+    public String getRepairDocument() 
173
+    {
174
+        return repairDocument;
175
+    }
176
+    public void setDeptUserId(Long deptUserId) 
177
+    {
178
+        this.deptUserId = deptUserId;
179
+    }
180
+
181
+    public Long getDeptUserId() 
182
+    {
183
+        return deptUserId;
184
+    }
185
+    public void setDeptComment(String deptComment) 
186
+    {
187
+        this.deptComment = deptComment;
188
+    }
189
+
190
+    public String getDeptComment() 
191
+    {
192
+        return deptComment;
193
+    }
194
+    public void setDeptTime(Date deptTime) 
195
+    {
196
+        this.deptTime = deptTime;
197
+    }
198
+
199
+    public Date getDeptTime() 
200
+    {
201
+        return deptTime;
202
+    }
203
+    public void setManagerUserId(Long managerUserId) 
204
+    {
205
+        this.managerUserId = managerUserId;
206
+    }
207
+
208
+    public Long getManagerUserId() 
209
+    {
210
+        return managerUserId;
211
+    }
212
+    public void setManagerComment(String managerComment) 
213
+    {
214
+        this.managerComment = managerComment;
215
+    }
216
+
217
+    public String getManagerComment() 
218
+    {
219
+        return managerComment;
220
+    }
221
+    public void setManagerTime(Date managerTime) 
222
+    {
223
+        this.managerTime = managerTime;
224
+    }
225
+
226
+    public Date getManagerTime() 
227
+    {
228
+        return managerTime;
229
+    }
230
+    public void setReviewerId(Long reviewerId) 
231
+    {
232
+        this.reviewerId = reviewerId;
233
+    }
234
+
235
+    public Long getReviewerId() 
236
+    {
237
+        return reviewerId;
238
+    }
239
+    public void setReviewerComment(String reviewerComment) 
240
+    {
241
+        this.reviewerComment = reviewerComment;
242
+    }
243
+
244
+    public String getReviewerComment() 
245
+    {
246
+        return reviewerComment;
247
+    }
248
+    public void setReviewerTime(Date reviewerTime) 
249
+    {
250
+        this.reviewerTime = reviewerTime;
251
+    }
252
+
253
+    public Date getReviewerTime() 
254
+    {
255
+        return reviewerTime;
256
+    }
257
+    public void setHandlerId(Long handlerId) 
258
+    {
259
+        this.handlerId = handlerId;
260
+    }
261
+
262
+    public Long getHandlerId() 
263
+    {
264
+        return handlerId;
265
+    }
266
+    public void setHandlerComment(String handlerComment) 
267
+    {
268
+        this.handlerComment = handlerComment;
269
+    }
270
+
271
+    public String getHandlerComment() 
272
+    {
273
+        return handlerComment;
274
+    }
275
+    public void setRepairTime(Date repairTime) 
276
+    {
277
+        this.repairTime = repairTime;
278
+    }
279
+
280
+    public Date getRepairTime() 
281
+    {
282
+        return repairTime;
283
+    }
284
+    public void setRepairMoney(BigDecimal repairMoney) 
285
+    {
286
+        this.repairMoney = repairMoney;
287
+    }
288
+
289
+    public BigDecimal getRepairMoney() 
290
+    {
291
+        return repairMoney;
292
+    }
293
+    public void setProjectId(String projectId)
294
+    {
295
+        this.projectId = projectId;
296
+    }
297
+
298
+    public String getProjectId()
299
+    {
300
+        return projectId;
301
+    }
302
+    public void setDeviceId(Integer deviceId)
303
+    {
304
+        this.deviceId = deviceId;
305
+    }
306
+
307
+    public Integer getDeviceId()
308
+    {
309
+        return deviceId;
310
+    }
311
+
312
+    @Override
313
+    public String toString() {
314
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
315
+            .append("repairId", getRepairId())
316
+            .append("applier", getApplier())
317
+            .append("applyDept", getApplyDept())
318
+            .append("applyReason", getApplyReason())
319
+            .append("applyDate", getApplyDate())
320
+            .append("repairUsage", getRepairUsage())
321
+            .append("repairDocument", getRepairDocument())
322
+            .append("deptUserId", getDeptUserId())
323
+            .append("deptComment", getDeptComment())
324
+            .append("deptTime", getDeptTime())
325
+            .append("managerUserId", getManagerUserId())
326
+            .append("managerComment", getManagerComment())
327
+            .append("managerTime", getManagerTime())
328
+            .append("reviewerId", getReviewerId())
329
+            .append("reviewerComment", getReviewerComment())
330
+            .append("reviewerTime", getReviewerTime())
331
+            .append("handlerId", getHandlerId())
332
+            .append("handlerComment", getHandlerComment())
333
+            .append("repairTime", getRepairTime())
334
+            .append("repairMoney", getRepairMoney())
335
+            .append("projectId", getProjectId())
336
+            .append("deviceId", getDeviceId())
337
+            .toString();
338
+    }
339
+}

+ 68
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/oa/mapper/CmcRepairMapper.java Dosyayı Görüntüle

@@ -0,0 +1,68 @@
1
+package com.ruoyi.oa.mapper;
2
+
3
+import java.util.List;
4
+import com.ruoyi.oa.domain.CmcRepair;
5
+
6
+/**
7
+ * 维修审批Mapper接口
8
+ * 
9
+ * @author cmc
10
+ * @date 2026-07-01
11
+ */
12
+public interface CmcRepairMapper 
13
+{
14
+    /**
15
+     * 查询维修审批
16
+     * 
17
+     * @param repairId 维修审批主键
18
+     * @return 维修审批
19
+     */
20
+    public CmcRepair selectCmcRepairByRepairId(String repairId);
21
+
22
+    /**
23
+     * 查询维修审批列表
24
+     * 
25
+     * @param cmcRepair 维修审批
26
+     * @return 维修审批集合
27
+     */
28
+    public List<CmcRepair> selectCmcRepairList(CmcRepair cmcRepair);
29
+
30
+    /**
31
+     * 新增维修审批
32
+     * 
33
+     * @param cmcRepair 维修审批
34
+     * @return 结果
35
+     */
36
+    public int insertCmcRepair(CmcRepair cmcRepair);
37
+
38
+    /**
39
+     * 修改维修审批
40
+     * 
41
+     * @param cmcRepair 维修审批
42
+     * @return 结果
43
+     */
44
+    public int updateCmcRepair(CmcRepair cmcRepair);
45
+
46
+    /**
47
+     * 删除维修审批
48
+     * 
49
+     * @param repairId 维修审批主键
50
+     * @return 结果
51
+     */
52
+    public int deleteCmcRepairByRepairId(String repairId);
53
+
54
+    /**
55
+     * 批量删除维修审批
56
+     * 
57
+     * @param repairIds 需要删除的数据主键集合
58
+     * @return 结果
59
+     */
60
+    public int deleteCmcRepairByRepairIds(String[] repairIds);
61
+
62
+    /**
63
+     * 查询最早申请年份
64
+     * 
65
+     * @return 最早申请年份
66
+     */
67
+    public Integer selectMinApplyYear();
68
+}

+ 105
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/oa/service/CmcRepairServiceImpl.java Dosyayı Görüntüle

@@ -0,0 +1,105 @@
1
+/*
2
+ * @Author: ysh
3
+ * @Date: 2026-07-01 09:46:14
4
+ * @LastEditors: 
5
+ * @LastEditTime: 2026-07-01 10:55:02
6
+ */
7
+package com.ruoyi.oa.service;
8
+
9
+import java.util.List;
10
+import org.springframework.beans.factory.annotation.Autowired;
11
+import org.springframework.stereotype.Service;
12
+import com.ruoyi.oa.mapper.CmcRepairMapper;
13
+import com.ruoyi.oa.domain.CmcRepair;
14
+import com.ruoyi.oa.service.impl.ICmcRepairService;
15
+
16
+/**
17
+ * 维修审批Service业务层处理
18
+ * 
19
+ * @author cmc
20
+ * @date 2026-07-01
21
+ */
22
+@Service
23
+public class CmcRepairServiceImpl implements ICmcRepairService 
24
+{
25
+    @Autowired
26
+    private CmcRepairMapper cmcRepairMapper;
27
+
28
+    /**
29
+     * 查询维修审批
30
+     * 
31
+     * @param repairId 维修审批主键
32
+     * @return 维修审批
33
+     */
34
+    @Override
35
+    public CmcRepair selectCmcRepairByRepairId(String repairId)
36
+    {
37
+        return cmcRepairMapper.selectCmcRepairByRepairId(repairId);
38
+    }
39
+
40
+    /**
41
+     * 查询维修审批列表
42
+     * 
43
+     * @param cmcRepair 维修审批
44
+     * @return 维修审批
45
+     */
46
+    @Override
47
+    public List<CmcRepair> selectCmcRepairList(CmcRepair cmcRepair)
48
+    {
49
+        return cmcRepairMapper.selectCmcRepairList(cmcRepair);
50
+    }
51
+
52
+    /**
53
+     * 新增维修审批
54
+     * 
55
+     * @param cmcRepair 维修审批
56
+     * @return 结果
57
+     */
58
+    @Override
59
+    public int insertCmcRepair(CmcRepair cmcRepair)
60
+    {
61
+        return cmcRepairMapper.insertCmcRepair(cmcRepair);
62
+    }
63
+
64
+    /**
65
+     * 修改维修审批
66
+     * 
67
+     * @param cmcRepair 维修审批
68
+     * @return 结果
69
+     */
70
+    @Override
71
+    public int updateCmcRepair(CmcRepair cmcRepair)
72
+    {
73
+        return cmcRepairMapper.updateCmcRepair(cmcRepair);
74
+    }
75
+
76
+    /**
77
+     * 批量删除维修审批
78
+     * 
79
+     * @param repairIds 需要删除的维修审批主键
80
+     * @return 结果
81
+     */
82
+    @Override
83
+    public int deleteCmcRepairByRepairIds(String[] repairIds)
84
+    {
85
+        return cmcRepairMapper.deleteCmcRepairByRepairIds(repairIds);
86
+    }
87
+
88
+    /**
89
+     * 删除维修审批信息
90
+     * 
91
+     * @param repairId 维修审批主键
92
+     * @return 结果
93
+     */
94
+    @Override
95
+    public int deleteCmcRepairByRepairId(String repairId)
96
+    {
97
+        return cmcRepairMapper.deleteCmcRepairByRepairId(repairId);
98
+    }
99
+
100
+    @Override
101
+    public Integer selectMinApplyYear()
102
+    {
103
+        return cmcRepairMapper.selectMinApplyYear();
104
+    }
105
+}

+ 68
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/oa/service/impl/ICmcRepairService.java Dosyayı Görüntüle

@@ -0,0 +1,68 @@
1
+package com.ruoyi.oa.service.impl;
2
+
3
+import java.util.List;
4
+import com.ruoyi.oa.domain.CmcRepair;
5
+
6
+/**
7
+ * 维修审批Service接口
8
+ * 
9
+ * @author cmc
10
+ * @date 2026-07-01
11
+ */
12
+public interface ICmcRepairService 
13
+{
14
+    /**
15
+     * 查询维修审批
16
+     * 
17
+     * @param repairId 维修审批主键
18
+     * @return 维修审批
19
+     */
20
+    public CmcRepair selectCmcRepairByRepairId(String repairId);
21
+
22
+    /**
23
+     * 查询维修审批列表
24
+     * 
25
+     * @param cmcRepair 维修审批
26
+     * @return 维修审批集合
27
+     */
28
+    public List<CmcRepair> selectCmcRepairList(CmcRepair cmcRepair);
29
+
30
+    /**
31
+     * 新增维修审批
32
+     * 
33
+     * @param cmcRepair 维修审批
34
+     * @return 结果
35
+     */
36
+    public int insertCmcRepair(CmcRepair cmcRepair);
37
+
38
+    /**
39
+     * 修改维修审批
40
+     * 
41
+     * @param cmcRepair 维修审批
42
+     * @return 结果
43
+     */
44
+    public int updateCmcRepair(CmcRepair cmcRepair);
45
+
46
+    /**
47
+     * 批量删除维修审批
48
+     * 
49
+     * @param repairIds 需要删除的维修审批主键集合
50
+     * @return 结果
51
+     */
52
+    public int deleteCmcRepairByRepairIds(String[] repairIds);
53
+
54
+    /**
55
+     * 删除维修审批信息
56
+     * 
57
+     * @param repairId 维修审批主键
58
+     * @return 结果
59
+     */
60
+    public int deleteCmcRepairByRepairId(String repairId);
61
+
62
+    /**
63
+     * 查询最早申请年份
64
+     * 
65
+     * @return 最早申请年份
66
+     */
67
+    public Integer selectMinApplyYear();
68
+}

+ 160
- 0
oa-back/ruoyi-system/src/main/resources/mapper/oa/CmcRepairMapper.xml Dosyayı Görüntüle

@@ -0,0 +1,160 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.ruoyi.oa.mapper.CmcRepairMapper">
6
+    
7
+    <resultMap type="CmcRepair" id="CmcRepairResult">
8
+        <result property="repairId"    column="repair_id"    />
9
+        <result property="applier"    column="applier"    />
10
+        <result property="applyDept"    column="apply_dept"    />
11
+        <result property="applyReason"    column="apply_reason"    />
12
+        <result property="applyDate"    column="apply_date"    />
13
+        <result property="repairUsage"    column="repair_usage"    />
14
+        <result property="repairDocument"    column="repair_document"    />
15
+        <result property="deptUserId"    column="dept_user_id"    />
16
+        <result property="deptComment"    column="dept_comment"    />
17
+        <result property="deptTime"    column="dept_time"    />
18
+        <result property="managerUserId"    column="manager_user_id"    />
19
+        <result property="managerComment"    column="manager_comment"    />
20
+        <result property="managerTime"    column="manager_time"    />
21
+        <result property="reviewerId"    column="reviewer_id"    />
22
+        <result property="reviewerComment"    column="reviewer_comment"    />
23
+        <result property="reviewerTime"    column="reviewer_time"    />
24
+        <result property="handlerId"    column="handler_id"    />
25
+        <result property="handlerComment"    column="handler_comment"    />
26
+        <result property="repairTime"    column="repair_time"    />
27
+        <result property="repairMoney"    column="repair_money"    />
28
+        <result property="projectId"    column="project_id"    />
29
+        <result property="deviceId"    column="device_id"    />
30
+    </resultMap>
31
+
32
+    <sql id="selectCmcRepairVo">
33
+        select repair_id, applier, apply_dept, apply_reason, apply_date, repair_usage, repair_document, dept_user_id, dept_comment, dept_time, manager_user_id, manager_comment, manager_time, reviewer_id, reviewer_comment, reviewer_time, handler_id, handler_comment, repair_time, repair_money, project_id, device_id from cmc_repair
34
+    </sql>
35
+
36
+    <select id="selectCmcRepairList" parameterType="CmcRepair" resultMap="CmcRepairResult">
37
+        <include refid="selectCmcRepairVo"/>
38
+        <where>  
39
+            <if test="applier != null "> and applier = #{applier}</if>
40
+            <if test="applyDept != null "> and apply_dept = #{applyDept}</if>
41
+            <if test="applyReason != null  and applyReason != ''"> and apply_reason like concat('%', #{applyReason}, '%')</if>
42
+            <if test="applyDate != null and (params == null or params.statYear == null or params.statYear == '') and (params == null or params.statMonth == null or params.statMonth == '')"> and apply_date = #{applyDate}</if>
43
+            <if test="params != null and params.statYear != null and params.statYear != ''"> and YEAR(apply_date) = #{params.statYear}</if>
44
+            <if test="params != null and params.statMonth != null and params.statMonth != ''"> and DATE_FORMAT(apply_date,'%Y-%m') = #{params.statMonth}</if>
45
+            <if test="repairUsage != null  and repairUsage != ''"> and repair_usage = #{repairUsage}</if>
46
+            <if test="repairDocument != null  and repairDocument != ''"> and repair_document = #{repairDocument}</if>
47
+            <if test="deptUserId != null "> and dept_user_id = #{deptUserId}</if>
48
+            <if test="deptComment != null  and deptComment != ''"> and dept_comment = #{deptComment}</if>
49
+            <if test="managerUserId != null "> and manager_user_id = #{managerUserId}</if>
50
+            <if test="managerComment != null  and managerComment != ''"> and manager_comment = #{managerComment}</if>
51
+            <if test="reviewerId != null "> and reviewer_id = #{reviewerId}</if>
52
+            <if test="reviewerComment != null  and reviewerComment != ''"> and reviewer_comment = #{reviewerComment}</if>
53
+            <if test="handlerId != null "> and handler_id = #{handlerId}</if>
54
+            <if test="handlerComment != null  and handlerComment != ''"> and handler_comment = #{handlerComment}</if>
55
+            <if test="repairMoney != null "> and repair_money = #{repairMoney}</if>
56
+            <if test="projectId != null  and projectId != ''"> and project_id = #{projectId}</if>
57
+            <if test="deviceId != null "> and device_id = #{deviceId}</if>
58
+        </where>
59
+    </select>
60
+    
61
+    <select id="selectCmcRepairByRepairId" parameterType="String" resultMap="CmcRepairResult">
62
+        <include refid="selectCmcRepairVo"/>
63
+        where repair_id = #{repairId}
64
+    </select>
65
+        
66
+    <insert id="insertCmcRepair" parameterType="CmcRepair">
67
+        insert into cmc_repair
68
+        <trim prefix="(" suffix=")" suffixOverrides=",">
69
+            <if test="repairId != null">repair_id,</if>
70
+            <if test="applier != null">applier,</if>
71
+            <if test="applyDept != null">apply_dept,</if>
72
+            <if test="applyReason != null">apply_reason,</if>
73
+            <if test="applyDate != null">apply_date,</if>
74
+            <if test="repairUsage != null">repair_usage,</if>
75
+            <if test="repairDocument != null">repair_document,</if>
76
+            <if test="deptUserId != null">dept_user_id,</if>
77
+            <if test="deptComment != null">dept_comment,</if>
78
+            <if test="deptTime != null">dept_time,</if>
79
+            <if test="managerUserId != null">manager_user_id,</if>
80
+            <if test="managerComment != null">manager_comment,</if>
81
+            <if test="managerTime != null">manager_time,</if>
82
+            <if test="reviewerId != null">reviewer_id,</if>
83
+            <if test="reviewerComment != null">reviewer_comment,</if>
84
+            <if test="reviewerTime != null">reviewer_time,</if>
85
+            <if test="handlerId != null">handler_id,</if>
86
+            <if test="handlerComment != null">handler_comment,</if>
87
+            <if test="repairTime != null">repair_time,</if>
88
+            <if test="repairMoney != null">repair_money,</if>
89
+            <if test="projectId != null">project_id,</if>
90
+            <if test="deviceId != null">device_id,</if>
91
+         </trim>
92
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
93
+            <if test="repairId != null">#{repairId},</if>
94
+            <if test="applier != null">#{applier},</if>
95
+            <if test="applyDept != null">#{applyDept},</if>
96
+            <if test="applyReason != null">#{applyReason},</if>
97
+            <if test="applyDate != null">#{applyDate},</if>
98
+            <if test="repairUsage != null">#{repairUsage},</if>
99
+            <if test="repairDocument != null">#{repairDocument},</if>
100
+            <if test="deptUserId != null">#{deptUserId},</if>
101
+            <if test="deptComment != null">#{deptComment},</if>
102
+            <if test="deptTime != null">#{deptTime},</if>
103
+            <if test="managerUserId != null">#{managerUserId},</if>
104
+            <if test="managerComment != null">#{managerComment},</if>
105
+            <if test="managerTime != null">#{managerTime},</if>
106
+            <if test="reviewerId != null">#{reviewerId},</if>
107
+            <if test="reviewerComment != null">#{reviewerComment},</if>
108
+            <if test="reviewerTime != null">#{reviewerTime},</if>
109
+            <if test="handlerId != null">#{handlerId},</if>
110
+            <if test="handlerComment != null">#{handlerComment},</if>
111
+            <if test="repairTime != null">#{repairTime},</if>
112
+            <if test="repairMoney != null">#{repairMoney},</if>
113
+            <if test="projectId != null">#{projectId},</if>
114
+            <if test="deviceId != null">#{deviceId},</if>
115
+         </trim>
116
+    </insert>
117
+
118
+    <update id="updateCmcRepair" parameterType="CmcRepair">
119
+        update cmc_repair
120
+        <trim prefix="SET" suffixOverrides=",">
121
+            <if test="applier != null">applier = #{applier},</if>
122
+            <if test="applyDept != null">apply_dept = #{applyDept},</if>
123
+            <if test="applyReason != null">apply_reason = #{applyReason},</if>
124
+            <if test="applyDate != null">apply_date = #{applyDate},</if>
125
+            <if test="repairUsage != null">repair_usage = #{repairUsage},</if>
126
+            <if test="repairDocument != null">repair_document = #{repairDocument},</if>
127
+            <if test="deptUserId != null">dept_user_id = #{deptUserId},</if>
128
+            <if test="deptComment != null">dept_comment = #{deptComment},</if>
129
+            <if test="deptTime != null">dept_time = #{deptTime},</if>
130
+            <if test="managerUserId != null">manager_user_id = #{managerUserId},</if>
131
+            <if test="managerComment != null">manager_comment = #{managerComment},</if>
132
+            <if test="managerTime != null">manager_time = #{managerTime},</if>
133
+            <if test="reviewerId != null">reviewer_id = #{reviewerId},</if>
134
+            <if test="reviewerComment != null">reviewer_comment = #{reviewerComment},</if>
135
+            <if test="reviewerTime != null">reviewer_time = #{reviewerTime},</if>
136
+            <if test="handlerId != null">handler_id = #{handlerId},</if>
137
+            <if test="handlerComment != null">handler_comment = #{handlerComment},</if>
138
+            <if test="repairTime != null">repair_time = #{repairTime},</if>
139
+            <if test="repairMoney != null">repair_money = #{repairMoney},</if>
140
+            project_id = #{projectId},
141
+            device_id = #{deviceId},
142
+        </trim>
143
+        where repair_id = #{repairId}
144
+    </update>
145
+
146
+    <delete id="deleteCmcRepairByRepairId" parameterType="String">
147
+        delete from cmc_repair where repair_id = #{repairId}
148
+    </delete>
149
+
150
+    <delete id="deleteCmcRepairByRepairIds" parameterType="String">
151
+        delete from cmc_repair where repair_id in 
152
+        <foreach item="repairId" collection="array" open="(" separator="," close=")">
153
+            #{repairId}
154
+        </foreach>
155
+    </delete>
156
+
157
+    <select id="selectMinApplyYear" resultType="Integer">
158
+        select YEAR(MIN(apply_date)) from cmc_repair where apply_date is not null
159
+    </select>
160
+</mapper>

+ 53
- 0
oa-ui/src/api/oa/repair/repair.js Dosyayı Görüntüle

@@ -0,0 +1,53 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询维修审批列表
4
+export function listRepair(query) {
5
+  return request({
6
+    url: '/oa/repair/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询维修审批详细
13
+export function getRepair(repairId) {
14
+  return request({
15
+    url: '/oa/repair/' + repairId,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增维修审批
21
+export function addRepair(data) {
22
+  return request({
23
+    url: '/oa/repair',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改维修审批
30
+export function updateRepair(data) {
31
+  return request({
32
+    url: '/oa/repair',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除维修审批
39
+export function delRepair(repairId) {
40
+  return request({
41
+    url: '/oa/repair/' + repairId,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 维修审批统计
47
+export function getRepairStatistic(query) {
48
+  return request({
49
+    url: '/oa/repair/statistic',
50
+    method: 'get',
51
+    params: query
52
+  })
53
+}

+ 7
- 1
oa-ui/src/utils/deleteResource.js Dosyayı Görüntüle

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2024-06-13 17:07:59
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-08-19 09:30:31
5
+ * @LastEditTime: 2026-07-01 10:57:12
6 6
  */
7 7
 import request from '@/utils/request'
8 8
 
@@ -168,6 +168,12 @@ const apiEndpoints = [
168 168
       '/oa/recruit/:id',
169 169
     ]
170 170
   },
171
+  {
172
+    procDefName: '维修审批',
173
+    apiUrl: [
174
+      '/oa/repair/:id',
175
+    ]
176
+  },
171 177
 ]
172 178
 
173 179
 // 编写一个方法来处理删除请求,并同时发送所有API请求  

+ 19
- 9
oa-ui/src/views/flowable/form/components/auditorRow.vue Dosyayı Görüntüle

@@ -40,24 +40,34 @@ export default {
40 40
   },
41 41
   watch: {
42 42
     signature(newval) {
43
-      if (newval) {
43
+      if (newval && !this.isCurrent) {
44 44
         this.localSignature = this.getUserName(newval);
45 45
         this.localSignatureUserId = newval;
46 46
       }
47 47
     },
48 48
     signTime(newval) {
49
-      if (newval)
49
+      if (newval && !this.isCurrent) {
50 50
         this.loaclSignTime = newval;
51
+      }
52
+    },
53
+    isCurrent(val) {
54
+      this.syncDisplay(val);
51 55
     }
52 56
   },
53 57
   mounted() {
54
-    if (this.isCurrent) {
55
-      this.localSignature = this.$store.getters.name;
56
-      this.localSignatureUserId = this.$store.getters.userId;
57
-      this.loaclSignTime = parseTime(new Date(), '{y}-{m}-{d}');
58
-    } else {
59
-      this.localSignature = this.signature;
60
-      this.loaclSignTime = this.signTime;
58
+    this.syncDisplay(this.isCurrent);
59
+  },
60
+  methods: {
61
+    syncDisplay(isCurrent) {
62
+      if (isCurrent) {
63
+        this.localSignature = this.$store.getters.name;
64
+        this.localSignatureUserId = this.$store.getters.userId;
65
+        this.loaclSignTime = parseTime(new Date(), '{y}-{m}-{d}');
66
+      } else if (this.signature) {
67
+        this.localSignature = this.getUserName(this.signature);
68
+        this.localSignatureUserId = this.signature;
69
+        this.loaclSignTime = this.signTime || '';
70
+      }
61 71
     }
62 72
   }
63 73
 }

+ 8
- 4
oa-ui/src/views/flowable/form/components/conditionDisplay.vue Dosyayı Görüntüle

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2024-04-23 17:08:16
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-08-18 14:10:52
5
+ * @LastEditTime: 2026-07-01 10:07:18
6 6
 -->
7 7
 <template>
8 8
   <div>
@@ -63,10 +63,12 @@
63 63
       v-else-if="taskForm.procDefName == '项目核算'"></budget-adjust>
64 64
     <titles-form :key="'titles' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
65 65
       v-else-if="taskForm.procDefName == '职称评审'"></titles-form>
66
-    <transfer-form :key="'titles' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
66
+    <transfer-form :key="'transfer' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
67 67
       v-else-if="taskForm.procDefName == '调岗审批'"></transfer-form>
68
-    <recruit-form :key="'titles' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
68
+    <recruit-form :key="'recruit' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
69 69
       v-else-if="taskForm.procDefName == '招聘审批'"></recruit-form>
70
+    <repair-form :key="'repair' + taskForm.taskId" :taskForm="taskForm" :taskName="''" :isFlow="true"
71
+      v-else-if="taskForm.procDefName == '维修审批'"></repair-form>
70 72
   </div>
71 73
 </template>
72 74
 
@@ -104,6 +106,7 @@ import BudgetAdjust from '@/views/flowable/form/budget/adjust/budgetAdjust.vue';
104 106
 import TitlesForm from '@/views/flowable/form/oa/titlesForm.vue';
105 107
 import TransferForm from '@/views/flowable/form/oa/transferForm.vue';
106 108
 import recruitForm from '@/views/flowable/form/oa/recruitForm.vue';
109
+import repairForm from '@/views/flowable/form/oa/repairForm.vue';
107 110
 export default {
108 111
   props: {
109 112
     passingParam: {
@@ -147,7 +150,8 @@ export default {
147 150
     BudgetAdjust,
148 151
     TitlesForm,
149 152
     TransferForm,
150
-    recruitForm
153
+    recruitForm,
154
+    repairForm
151 155
   },
152 156
   data() {
153 157
     return {

+ 5
- 1
oa-ui/src/views/flowable/form/components/detailDisplay.vue Dosyayı Görüntüle

@@ -62,6 +62,8 @@
62 62
       @goBack="goBack"></transfer-form>
63 63
     <recruit-form :taskName="taskName" :taskForm="taskForm" v-else-if="taskForm.procDefName == '招聘审批'"
64 64
       @goBack="goBack"></recruit-form>
65
+    <repair-form :taskName="taskName" :taskForm="taskForm" v-else-if="taskForm.procDefName == '维修审批'"
66
+      @goBack="goBack"></repair-form>
65 67
   </div>
66 68
 </template>
67 69
 
@@ -99,6 +101,7 @@ import AdjustIndex from '@/views/flowable/form/budget/adjust/adjustIndex.vue';
99 101
 import TitlesForm from '@/views/flowable/form/oa/titlesForm.vue';
100 102
 import TransferForm from '@/views/flowable/form/oa/transferForm.vue';
101 103
 import recruitForm from '@/views/flowable/form/oa/recruitForm.vue';
104
+import repairForm from '@/views/flowable/form/oa/repairForm.vue';
102 105
 export default {
103 106
   components: {
104 107
     ScTable,
@@ -133,7 +136,8 @@ export default {
133 136
     AdjustIndex,
134 137
     TitlesForm,
135 138
     TransferForm,
136
-    recruitForm
139
+    recruitForm,
140
+    repairForm
137 141
   },
138 142
   props: {
139 143
     taskForm: {

+ 900
- 0
oa-ui/src/views/flowable/form/oa/repairForm.vue Dosyayı Görüntüle

@@ -0,0 +1,900 @@
1
+<template>
2
+  <div class="repair-form">
3
+    <el-row :gutter="20">
4
+      <el-col :span="isFlow ? 18 : 24" :xs="24">
5
+        <h2 class="form-title">维修审批表</h2>
6
+        <el-divider></el-divider>
7
+
8
+        <div class="mt20 mb20" v-if="showAlter && taskName">
9
+          <el-alert title="任务被退回,请修改后重新提交" type="error" :closable="false">
10
+            <return-comment :taskForm="taskForm" @isReturn="isReturn"></return-comment>
11
+          </el-alert>
12
+        </div>
13
+
14
+        <el-form ref="form" :model="form" :rules="formRules" :disabled="taskName == ''" class="borrow-table-form">
15
+          <div class="form-table-wrap">
16
+            <table class="form-table">
17
+              <tr>
18
+                <td colspan="2" class="label-cell">申请人</td>
19
+                <td colspan="2">
20
+                  <el-form-item prop="applier" class="table-form-item">
21
+                    <span class="cell-text">{{ getUserName(form.applier) }}</span>
22
+                  </el-form-item>
23
+                </td>
24
+                <td colspan="2" class="label-cell">申请部门</td>
25
+                <td colspan="2">
26
+                  <el-form-item prop="applyDept" class="table-form-item">
27
+                    <span class="cell-text">{{ getDeptName(form.applyDept) }}</span>
28
+                  </el-form-item>
29
+                </td>
30
+                <td colspan="2" class="label-cell">申请日期</td>
31
+                <td colspan="2">
32
+                  <el-form-item prop="applyDate" class="table-form-item">
33
+                    <el-date-picker v-model="form.applyDate" type="date" value-format="yyyy-MM-dd"
34
+                      placeholder="选择日期" style="width: 100%;" disabled>
35
+                    </el-date-picker>
36
+                  </el-form-item>
37
+                </td>
38
+              </tr>
39
+
40
+              <tr>
41
+                <td colspan="2" class="label-cell">维修类型</td>
42
+                <td colspan="10">
43
+                  <el-form-item prop="repairUsage" class="table-form-item">
44
+                    <el-radio-group v-model="form.repairUsage" :disabled="taskName != '维修申请'"
45
+                      class="repair-type-group">
46
+                      <el-radio label="0">电脑</el-radio>
47
+                      <el-radio label="1">打印机</el-radio>
48
+                      <el-radio label="2">其他</el-radio>
49
+                      <el-radio label="3">仪器设备</el-radio>
50
+                    </el-radio-group>
51
+                    <el-tag v-if="taskName != '维修申请' && form.repairUsage != null" :type="repairUsageTagType"
52
+                      effect="plain" size="small" class="type-tag">{{ repairUsageLabel }}</el-tag>
53
+                  </el-form-item>
54
+                </td>
55
+              </tr>
56
+
57
+              <tr v-if="showProjectDevice">
58
+                <td colspan="2" class="label-cell">
59
+                  关联项目<span class="required-mark" v-if="isInstrumentType && taskName == '维修申请'">*</span>
60
+                </td>
61
+                <td colspan="10">
62
+                  <el-form-item prop="projectId" class="table-form-item">
63
+                    <el-button type="primary" size="mini" @click="openProject = true"
64
+                      v-if="taskName == '维修申请'">选择项目</el-button>
65
+                    <el-button type="text" size="mini" class="ml10" @click="clearProject"
66
+                      v-if="taskName == '维修申请' && form.projectId">清除</el-button>
67
+                    <table class="inner-table" v-if="isProjectSelect && chooseProject">
68
+                      <tr>
69
+                        <td class="label-cell">项目编号</td>
70
+                        <td>{{ chooseProject.projectNumber }}</td>
71
+                        <td class="label-cell">项目名称</td>
72
+                        <td>{{ chooseProject.projectName }}</td>
73
+                      </tr>
74
+                      <tr>
75
+                        <td class="label-cell">项目负责人</td>
76
+                        <td>{{ chooseProject.projectLeaderUser ? chooseProject.projectLeaderUser.nickName : '' }}</td>
77
+                        <td class="label-cell">承担部门</td>
78
+                        <td>{{ chooseProject.undertakingDeptName }}</td>
79
+                      </tr>
80
+                    </table>
81
+                    <span v-if="!form.projectId && taskName != '维修申请'" class="text-muted">无</span>
82
+                  </el-form-item>
83
+                </td>
84
+              </tr>
85
+
86
+              <tr v-if="showProjectDevice">
87
+                <td colspan="2" class="label-cell">
88
+                  关联设备<span class="required-mark" v-if="isInstrumentType && taskName == '维修申请'">*</span>
89
+                </td>
90
+                <td colspan="10">
91
+                  <el-form-item prop="deviceId" class="table-form-item">
92
+                    <el-button type="primary" size="mini" @click="openDevice = true"
93
+                      v-if="taskName == '维修申请'">选择设备</el-button>
94
+                    <el-button type="text" size="mini" class="ml10" @click="clearDevice"
95
+                      v-if="taskName == '维修申请' && form.deviceId">清除</el-button>
96
+                    <table class="inner-table" v-if="chooseDevice">
97
+                      <tr>
98
+                        <td class="label-cell">出厂编号</td>
99
+                        <td>{{ chooseDevice.code }}</td>
100
+                        <td class="label-cell">设备名称</td>
101
+                        <td>{{ chooseDevice.name }}</td>
102
+                      </tr>
103
+                      <tr>
104
+                        <td class="label-cell">设备品牌</td>
105
+                        <td>{{ chooseDevice.brand }}</td>
106
+                        <td class="label-cell">规格型号</td>
107
+                        <td>{{ chooseDevice.series }}</td>
108
+                      </tr>
109
+                    </table>
110
+                    <span v-if="!form.deviceId && taskName != '维修申请'" class="text-muted">无</span>
111
+                  </el-form-item>
112
+                </td>
113
+              </tr>
114
+
115
+              <tr>
116
+                <td colspan="2" class="label-cell">情况说明</td>
117
+                <td colspan="10">
118
+                  <el-form-item prop="applyReason" class="table-form-item">
119
+                    <el-input v-model="form.applyReason" type="textarea" :rows="4" placeholder="请详细描述设备故障或维修需求"
120
+                      :disabled="taskName != '维修申请'" />
121
+                  </el-form-item>
122
+                </td>
123
+              </tr>
124
+
125
+              <tr>
126
+                <td colspan="2" class="label-cell">附件</td>
127
+                <td colspan="10">
128
+                  <el-form-item prop="repairDocument" class="table-form-item">
129
+                    <FileUpload v-if="!form.repairDocument && taskName == '维修申请'" :isShowTip="false" :limit="1"
130
+                      :filePathName="'维修/附件'"
131
+                      :fileType="['doc', 'docx', 'xls', 'xlsx', 'pdf', 'rar', 'zip', 'jpg', 'jpeg', 'png']"
132
+                      @input="getDocumentPath">
133
+                    </FileUpload>
134
+                    <div v-if="form.repairDocument" class="file-link-wrap">
135
+                      <el-link type="primary" icon="el-icon-document"
136
+                        @click="reviewWord(`${baseUrl}${'/profile/upload' + form.repairDocument}`)">
137
+                        {{ getFileName(form.repairDocument) }}
138
+                      </el-link>
139
+                      <el-link class="ml20" type="warning" icon="el-icon-download"
140
+                        :href="`${baseUrl}${'/profile/upload' + form.repairDocument}`" :underline="false"
141
+                        target="_blank">下载</el-link>
142
+                      <el-link class="ml20" type="danger" icon="el-icon-delete" @click="handleDeleteDocument"
143
+                        v-if="taskName == '维修申请'" :underline="false">删除</el-link>
144
+                    </div>
145
+                    <span v-if="!form.repairDocument && taskName != '维修申请'" class="text-muted">无附件</span>
146
+                  </el-form-item>
147
+                </td>
148
+              </tr>
149
+
150
+              <tr v-if="showDeptApproval">
151
+                <td colspan="2" class="label-cell">部门审批意见</td>
152
+                <td colspan="10">
153
+                  <el-form-item prop="deptComment" class="table-form-item">
154
+                    <el-input v-model="form.deptComment" type="textarea" :rows="3" placeholder="请输入部门审批意见"
155
+                      :disabled="taskName != '部门审批'" />
156
+                    <auditor-row ref="deptRef" :key="'dept-' + form.deptUserId + '-' + form.deptTime"
157
+                      :isCurrent="taskName == '部门审批'" :signature="form.deptUserId || null"
158
+                      :signTime="formatSignTime(form.deptTime)">
159
+                    </auditor-row>
160
+                  </el-form-item>
161
+                </td>
162
+              </tr>
163
+
164
+              <tr v-if="showManagerApproval">
165
+                <td colspan="2" class="label-cell">分管审批意见</td>
166
+                <td colspan="10">
167
+                  <el-form-item prop="managerComment" class="table-form-item">
168
+                    <el-input v-model="form.managerComment" type="textarea" :rows="3" placeholder="请输入分管审批意见"
169
+                      :disabled="taskName != '分管审批'" />
170
+                    <auditor-row ref="managerRef" :key="'mgr-' + form.managerUserId + '-' + form.managerTime"
171
+                      :isCurrent="taskName == '分管审批'" :signature="form.managerUserId || null"
172
+                      :signTime="formatSignTime(form.managerTime)">
173
+                    </auditor-row>
174
+                  </el-form-item>
175
+                </td>
176
+              </tr>
177
+
178
+              <tr v-if="showReviewerApproval">
179
+                <td colspan="2" class="label-cell">综合事务部审核</td>
180
+                <td colspan="10">
181
+                  <el-form-item prop="reviewerComment" class="table-form-item">
182
+                    <el-input v-model="form.reviewerComment" type="textarea" :rows="3" placeholder="请输入综合事务部审核意见"
183
+                      :disabled="taskName != '综合事务部审批'" />
184
+                    <auditor-row ref="reviewerRef" :key="'rev-' + form.reviewerId + '-' + form.reviewerTime"
185
+                      :isCurrent="taskName == '综合事务部审批'" :signature="form.reviewerId || null"
186
+                      :signTime="formatSignTime(form.reviewerTime)">
187
+                    </auditor-row>
188
+                  </el-form-item>
189
+                </td>
190
+              </tr>
191
+
192
+              <tr v-if="showHandlerApproval">
193
+                <td colspan="2" class="label-cell">维修处理情况</td>
194
+                <td colspan="10">
195
+                  <el-form-item prop="handlerComment" class="table-form-item">
196
+                    <el-input v-model="form.handlerComment" type="textarea" :rows="3" placeholder="请输入维修处理情况"
197
+                      :disabled="taskName != '维修处理'" />
198
+                    <auditor-row ref="handlerRef" :key="'hdl-' + form.handlerId + '-' + form.repairTime"
199
+                      :isCurrent="taskName == '维修处理'" :signature="form.handlerId || null"
200
+                      :signTime="formatSignTime(form.repairTime)">
201
+                    </auditor-row>
202
+                  </el-form-item>
203
+                </td>
204
+              </tr>
205
+
206
+              <tr v-if="showHandlerApproval && (taskName == '维修处理' || form.repairMoney != null)">
207
+                <td colspan="2" class="label-cell">维修费用</td>
208
+                <td colspan="10">
209
+                  <el-form-item prop="repairMoney" class="table-form-item">
210
+                    <el-input-number v-model="form.repairMoney" :min="0" :precision="2" :step="100"
211
+                      :disabled="taskName != '维修处理'" />
212
+                    <span class="money-unit">元</span>
213
+                  </el-form-item>
214
+                </td>
215
+              </tr>
216
+            </table>
217
+          </div>
218
+        </el-form>
219
+
220
+        <div class="form-footer" v-if="taskName">
221
+          <el-button type="warning" icon="el-icon-folder-checked" @click="saveDraft" v-if="taskName == '维修申请'">保存
222
+          </el-button>
223
+          <el-button type="danger" icon="el-icon-back" @click="returnOpen = true" v-if="taskName != '维修申请'">退回
224
+          </el-button>
225
+          <el-button type="primary" icon="el-icon-check" @click="submitForm">
226
+            {{ taskName != '维修处理' ? '提交下一个流程' : '完成' }}
227
+          </el-button>
228
+        </div>
229
+      </el-col>
230
+
231
+      <el-col :span="6" :xs="24" v-if="isFlow">
232
+        <el-card class="flow-card" shadow="hover">
233
+          <h2 class="flow-card-title">流程进度</h2>
234
+          <flow :flowData="flowData" />
235
+        </el-card>
236
+      </el-col>
237
+    </el-row>
238
+
239
+    <el-dialog title="退回" :visible.sync="returnOpen" width="40%" append-to-body>
240
+      <return-btn :taskForm="taskForm" :comment="commentByRole()" @goBack="$emit('goBack')" @saves=""
241
+        @cancel="returnOpen = false"></return-btn>
242
+    </el-dialog>
243
+    <el-dialog title="选择项目" :visible.sync="openProject" width="70%" append-to-body>
244
+      <project-choose @chooseProject="confirmProject"></project-choose>
245
+    </el-dialog>
246
+    <el-dialog title="选择设备" :visible.sync="openDevice" width="70%" append-to-body>
247
+      <choose-device :multiple="false" @chooseList="confirmDevice"></choose-device>
248
+    </el-dialog>
249
+  </div>
250
+</template>
251
+
252
+<script>
253
+import flow from '@/views/flowable/task/todo/detail/flow'
254
+import { flowXmlAndNode } from "@/api/flowable/definition";
255
+import { getRepair, addRepair, updateRepair } from "@/api/oa/repair/repair";
256
+import { getProject } from "@/api/oa/project/project";
257
+import { getDevice } from "@/api/oa/device/device";
258
+import { complete, getNextFlowNode } from "@/api/flowable/todo";
259
+import { getUsersDeptLeader, getUsersManageLeaderByDept } from '@/api/system/post.js'
260
+import { getUserByRole } from "@/api/system/role";
261
+import ReturnComment from "@/views/flowable/form/components/flowBtn/returnComment.vue";
262
+import ReturnBtn from "@/views/flowable/form/components/flowBtn/returnBtn.vue";
263
+import AuditorRow from '@/views/flowable/form/components/auditorRow.vue';
264
+import projectChoose from '@/views/flowable/form/components/chooseProject.vue';
265
+import ChooseDevice from '@/views/flowable/form/budget/components/chooseDevice.vue';
266
+
267
+const REPAIR_TYPE_MAP = {
268
+  '0': '电脑',
269
+  '1': '打印机',
270
+  '2': '其他',
271
+  '3': '仪器设备'
272
+};
273
+
274
+const REPAIR_TYPE_TAG = {
275
+  '0': 'primary',
276
+  '1': 'success',
277
+  '2': 'info',
278
+  '3': 'warning'
279
+};
280
+
281
+export default {
282
+  name: 'RepairForm',
283
+  components: {
284
+    flow,
285
+    ReturnComment,
286
+    ReturnBtn,
287
+    AuditorRow,
288
+    projectChoose,
289
+    ChooseDevice
290
+  },
291
+  props: {
292
+    taskForm: {
293
+      type: Object,
294
+      required: true
295
+    },
296
+    flowDisabled: {
297
+      type: Boolean,
298
+      default: true
299
+    },
300
+    taskName: {
301
+      type: String,
302
+      required: true
303
+    },
304
+    isFlow: {
305
+      type: Boolean,
306
+      default: true
307
+    }
308
+  },
309
+  watch: {
310
+    'taskForm.formId'() {
311
+      this.initFormData();
312
+    },
313
+    'form.repairUsage'(val) {
314
+      if (val !== '3' && this.taskName === '维修申请') {
315
+        this.$nextTick(() => {
316
+          this.$refs.form && this.$refs.form.clearValidate(['projectId', 'deviceId']);
317
+        });
318
+      }
319
+    }
320
+  },
321
+  data() {
322
+    return {
323
+      baseUrl: process.env.VUE_APP_BASE_API,
324
+      flowData: {},
325
+      isEdit: false,
326
+      form: {
327
+        repairId: null,
328
+        applier: null,
329
+        applyDept: null,
330
+        applyReason: null,
331
+        applyDate: null,
332
+        repairUsage: null,
333
+        repairDocument: null,
334
+        deptUserId: null,
335
+        deptComment: null,
336
+        deptTime: null,
337
+        managerUserId: null,
338
+        managerComment: null,
339
+        managerTime: null,
340
+        reviewerId: null,
341
+        reviewerComment: null,
342
+        reviewerTime: null,
343
+        handlerId: null,
344
+        handlerComment: null,
345
+        repairTime: null,
346
+        repairMoney: null,
347
+        projectId: null,
348
+        deviceId: null
349
+      },
350
+      returnOpen: false,
351
+      showAlter: true,
352
+      openProject: false,
353
+      openDevice: false,
354
+      isProjectSelect: false,
355
+      chooseProject: null,
356
+      chooseDevice: null
357
+    }
358
+  },
359
+  computed: {
360
+    repairUsageLabel() {
361
+      return REPAIR_TYPE_MAP[this.form.repairUsage] || '';
362
+    },
363
+    repairUsageTagType() {
364
+      return REPAIR_TYPE_TAG[this.form.repairUsage] || 'info';
365
+    },
366
+    isInstrumentType() {
367
+      return this.form.repairUsage === '3';
368
+    },
369
+    showProjectDevice() {
370
+      return this.isInstrumentType || this.form.projectId || this.form.deviceId || this.taskName !== '维修申请';
371
+    },
372
+    showDeptApproval() {
373
+      return this.taskName !== '维修申请';
374
+    },
375
+    showManagerApproval() {
376
+      return (this.needManagerApproval() && this.taskName !== '维修申请') ||
377
+        this.form.managerUserId || this.form.managerComment;
378
+    },
379
+    showReviewerApproval() {
380
+      return ['综合事务部审批', '维修处理', ''].includes(this.taskName) ||
381
+        this.form.reviewerId || this.form.reviewerComment;
382
+    },
383
+    showHandlerApproval() {
384
+      return this.taskName === '维修处理' || this.taskName === '' ||
385
+        this.form.handlerId || this.form.handlerComment || this.form.repairMoney != null;
386
+    },
387
+    formRules() {
388
+      if (this.taskName == '') {
389
+        return {};
390
+      } else if (this.taskName == '维修申请') {
391
+        return {
392
+          repairUsage: [
393
+            { required: true, message: '请选择维修类型', trigger: 'change' }
394
+          ],
395
+          applyReason: [
396
+            { required: true, message: '请输入情况说明', trigger: 'blur' }
397
+          ],
398
+          projectId: [
399
+            {
400
+              validator: (rule, value, callback) => {
401
+                if (this.form.repairUsage === '3' && !value) {
402
+                  callback(new Error('请选择关联项目'));
403
+                } else {
404
+                  callback();
405
+                }
406
+              },
407
+              trigger: 'change'
408
+            }
409
+          ],
410
+          deviceId: [
411
+            {
412
+              validator: (rule, value, callback) => {
413
+                if (this.form.repairUsage === '3' && !value) {
414
+                  callback(new Error('请选择关联设备'));
415
+                } else {
416
+                  callback();
417
+                }
418
+              },
419
+              trigger: 'change'
420
+            }
421
+          ]
422
+        };
423
+      } else if (this.taskName == '部门审批') {
424
+        return {
425
+          deptComment: [
426
+            { required: true, message: '请填写部门审批意见', trigger: 'blur' }
427
+          ]
428
+        };
429
+      } else if (this.taskName == '分管审批') {
430
+        return {
431
+          managerComment: [
432
+            { required: true, message: '请填写分管审批意见', trigger: 'blur' }
433
+          ]
434
+        };
435
+      } else if (this.taskName == '综合事务部审批') {
436
+        return {
437
+          reviewerComment: [
438
+            { required: true, message: '请填写综合事务部审核意见', trigger: 'blur' }
439
+          ]
440
+        };
441
+      } else if (this.taskName == '维修处理') {
442
+        return {
443
+          handlerComment: [
444
+            { required: true, message: '请填写维修处理情况', trigger: 'blur' }
445
+          ]
446
+        };
447
+      }
448
+      return {};
449
+    }
450
+  },
451
+  created() {
452
+    if (this.isFlow) {
453
+      flowXmlAndNode({ procInsId: this.taskForm.procInsId, deployId: this.taskForm.deployId }).then(res => {
454
+        this.flowData = res.data;
455
+      });
456
+    }
457
+    this.initFormData();
458
+  },
459
+  methods: {
460
+    initFormData() {
461
+      getRepair(this.taskForm.formId).then(res => {
462
+        if (res.data) {
463
+          this.form = res.data;
464
+          this.isEdit = true;
465
+          this.loadProjectDeviceInfo();
466
+        } else {
467
+          this.form.applier = this.$store.getters.userId;
468
+          this.form.applyDept = this.$store.getters.deptId;
469
+          this.form.applyDate = this.parseTime(new Date(), '{y}-{m}-{d}');
470
+          this.isProjectSelect = false;
471
+          this.chooseProject = null;
472
+          this.chooseDevice = null;
473
+        }
474
+      });
475
+    },
476
+    loadProjectDeviceInfo() {
477
+      if (this.form.projectId) {
478
+        getProject(this.form.projectId).then(response => {
479
+          this.chooseProject = response.data;
480
+          this.isProjectSelect = true;
481
+        });
482
+      } else {
483
+        this.chooseProject = null;
484
+        this.isProjectSelect = false;
485
+      }
486
+      if (this.form.deviceId) {
487
+        getDevice(this.form.deviceId).then(response => {
488
+          this.chooseDevice = response.data;
489
+        });
490
+      } else {
491
+        this.chooseDevice = null;
492
+      }
493
+    },
494
+    confirmProject(val) {
495
+      if (val.length > 1) {
496
+        this.$message.error('请选择一个项目');
497
+        return;
498
+      }
499
+      if (val.length === 1) {
500
+        this.chooseProject = val[0];
501
+        this.isProjectSelect = true;
502
+        this.$set(this.form, 'projectId', val[0].projectId);
503
+        this.$refs.form && this.$refs.form.validateField('projectId');
504
+      }
505
+      this.openProject = false;
506
+    },
507
+    clearProject() {
508
+      this.form.projectId = null;
509
+      this.chooseProject = null;
510
+      this.isProjectSelect = false;
511
+      this.$refs.form && this.$refs.form.validateField('projectId');
512
+    },
513
+    confirmDevice(val) {
514
+      if (val) {
515
+        const device = Array.isArray(val) ? val[0] : val;
516
+        if (device && device.deviceId) {
517
+          this.chooseDevice = device;
518
+          this.$set(this.form, 'deviceId', device.deviceId);
519
+          this.$refs.form && this.$refs.form.validateField('deviceId');
520
+        }
521
+      }
522
+      this.openDevice = false;
523
+    },
524
+    clearDevice() {
525
+      this.form.deviceId = null;
526
+      this.chooseDevice = null;
527
+      this.$refs.form && this.$refs.form.validateField('deviceId');
528
+    },
529
+    hasProject() {
530
+      return this.form.projectId != null && this.form.projectId !== '';
531
+    },
532
+    getRepairHandlerRoleId() {
533
+      return (this.isInstrumentType || this.hasProject()) ? 4 : 21;
534
+    },
535
+    async assignZhDeptReviewer() {
536
+      const roleId = this.hasProject() ? 4 : 21;
537
+      const result = await getUserByRole({ roleId });
538
+      this.$set(this.taskForm.variables, "approval", result.data[0]);
539
+    },
540
+    formatSignTime(val) {
541
+      if (!val) return '';
542
+      if (typeof val === 'string') {
543
+        return val.substring(0, 10);
544
+      }
545
+      return this.parseTime(val, '{y}-{m}-{d}');
546
+    },
547
+    getDocumentPath(val) {
548
+      if (val == "") {
549
+        this.form.repairDocument = "";
550
+        return;
551
+      }
552
+      let arr = val.split('/upload');
553
+      this.form.repairDocument = arr[1];
554
+    },
555
+    handleDeleteDocument() {
556
+      this.$modal.confirm('是否确认删除该附件?').then(() => {
557
+        this.form.repairDocument = '';
558
+      }).catch(() => { });
559
+    },
560
+    isComputerType() {
561
+      return this.form.repairUsage === '0';
562
+    },
563
+    /** 电脑、仪器设备需走分管审批(流程变量 isComputer 控制是否跳过分管节点) */
564
+    needManagerApproval() {
565
+      return this.form.repairUsage === '0' || this.form.repairUsage === '3';
566
+    },
567
+    async saveDraft() {
568
+      if (this.taskName == '') {
569
+        this.$message.info('当前为只读模式,无法保存');
570
+        return;
571
+      }
572
+      this.syncApprovalFields();
573
+      if (this.isEdit) {
574
+        await updateRepair(this.form);
575
+      } else {
576
+        this.form.repairId = this.taskForm.formId;
577
+        await addRepair(this.form);
578
+        this.isEdit = true;
579
+      }
580
+      this.$message.success('保存成功');
581
+    },
582
+    syncApprovalFields() {
583
+      if (this.taskName == '部门审批' && this.$refs.deptRef) {
584
+        this.form.deptUserId = this.$refs.deptRef.localSignatureUserId;
585
+        this.form.deptTime = this.$refs.deptRef.loaclSignTime;
586
+      } else if (this.taskName == '分管审批' && this.$refs.managerRef) {
587
+        this.form.managerUserId = this.$refs.managerRef.localSignatureUserId;
588
+        this.form.managerTime = this.$refs.managerRef.loaclSignTime;
589
+      } else if (this.taskName == '综合事务部审批' && this.$refs.reviewerRef) {
590
+        this.form.reviewerId = this.$refs.reviewerRef.localSignatureUserId;
591
+        this.form.reviewerTime = this.$refs.reviewerRef.loaclSignTime;
592
+      } else if (this.taskName == '维修处理' && this.$refs.handlerRef) {
593
+        this.form.handlerId = this.$refs.handlerRef.localSignatureUserId;
594
+        this.form.repairTime = this.$refs.handlerRef.loaclSignTime;
595
+      }
596
+    },
597
+    submitForm() {
598
+      if (this.taskName == '') {
599
+        this.$message.info('当前为只读模式,无法提交');
600
+        return;
601
+      }
602
+      this.$refs.form.validate((valid) => {
603
+        if (valid) {
604
+          let msg = '确认提交维修审批表?';
605
+          if (this.taskName == '维修处理') {
606
+            msg = '最后一个节点,提交将无法修改,是否确认提交?';
607
+          }
608
+          this.$confirm(msg, '提示', {
609
+            confirmButtonText: '确定',
610
+            cancelButtonText: '取消',
611
+            type: 'warning'
612
+          }).then(() => {
613
+            this.submitFlow();
614
+          }).catch(() => {
615
+            this.$message.info('已取消提交');
616
+          });
617
+        } else {
618
+          this.$message.error('请完善表单信息');
619
+        }
620
+      });
621
+    },
622
+    async submitFlow() {
623
+      await this.saveDraft();
624
+      const needManager = this.needManagerApproval();
625
+      this.$set(this.taskForm.variables, "isComputer", needManager);
626
+
627
+      if (this.taskName == '维修申请') {
628
+        const res = await getUsersDeptLeader({ userId: this.$store.getters.userId });
629
+        this.$set(this.taskForm.variables, "approval", res.data.userId);
630
+        if (!needManager) {
631
+          this.$set(this.taskForm.variables, "approvalList", []);
632
+        }
633
+      } else if (this.taskName == '部门审批') {
634
+        if (needManager) {
635
+          let managerList = [];
636
+          const result = await getUsersManageLeaderByDept({ deptId: this.form.applyDept });
637
+          result.data.forEach(element => { managerList.push(element.userId); });
638
+          this.$set(this.taskForm.variables, "approvalList", managerList);
639
+        } else {
640
+          this.$set(this.taskForm.variables, "approvalList", []);
641
+          await this.assignZhDeptReviewer();
642
+        }
643
+      } else if (this.taskName == '分管审批') {
644
+        await this.assignZhDeptReviewer();
645
+      } else if (this.taskName == '综合事务部审批') {
646
+        const result = await getUserByRole({ roleId: this.getRepairHandlerRoleId() });
647
+        this.$set(this.taskForm.variables, "approval", result.data[0]);
648
+      }
649
+
650
+      await getNextFlowNode({ taskId: this.taskForm.taskId });
651
+      const response = await complete(this.taskForm);
652
+      this.$modal.msgSuccess(response.msg);
653
+      this.$emit('goBack');
654
+    },
655
+    isReturn(val) {
656
+      this.showAlter = val;
657
+    },
658
+    commentByRole() {
659
+      if (this.taskName == '部门审批') {
660
+        return this.form.deptComment;
661
+      } else if (this.taskName == '分管审批') {
662
+        return this.form.managerComment;
663
+      } else if (this.taskName == '综合事务部审批') {
664
+        return this.form.reviewerComment;
665
+      } else if (this.taskName == '维修处理') {
666
+        return this.form.handlerComment;
667
+      }
668
+      return '';
669
+    }
670
+  }
671
+}
672
+</script>
673
+
674
+<style lang="scss" scoped>
675
+@import "@/assets/styles/element-reset.scss";
676
+
677
+$border-light: #dcdfe6;
678
+$border-soft: #e4e7ed;
679
+$label-bg: #f0f2f5;
680
+$label-color: #606266;
681
+$text-primary: #303133;
682
+$accent: #409eff;
683
+
684
+.repair-form {
685
+  padding: 20px;
686
+  background-color: #fff;
687
+  border-radius: 8px;
688
+}
689
+
690
+.form-title {
691
+  text-align: center;
692
+  font-size: 22px;
693
+  font-weight: 700;
694
+  color: $text-primary;
695
+  margin: 0;
696
+  letter-spacing: 2px;
697
+}
698
+
699
+.borrow-table-form {
700
+  margin-top: 16px;
701
+
702
+  .table-form-item {
703
+    margin-bottom: 0;
704
+
705
+    ::v-deep .el-form-item__content {
706
+      margin-left: 0 !important;
707
+      line-height: normal;
708
+    }
709
+
710
+    ::v-deep .el-form-item__error {
711
+      position: relative;
712
+      padding-top: 2px;
713
+    }
714
+  }
715
+
716
+  ::v-deep .el-input__inner,
717
+  ::v-deep .el-textarea__inner {
718
+    border-color: $border-light;
719
+    border-radius: 6px;
720
+    transition: border-color 0.2s, box-shadow 0.2s;
721
+
722
+    &:hover {
723
+      border-color: #c0c4cc;
724
+    }
725
+
726
+    &:focus {
727
+      border-color: $accent;
728
+      box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.12);
729
+    }
730
+  }
731
+
732
+  ::v-deep .el-input.is-disabled .el-input__inner,
733
+  ::v-deep .el-textarea.is-disabled .el-textarea__inner {
734
+    background-color: #f5f7fa;
735
+    color: #606266;
736
+  }
737
+}
738
+
739
+.form-table-wrap {
740
+  border: 1px solid $border-soft;
741
+  border-radius: 10px;
742
+  overflow: hidden;
743
+  background: #fff;
744
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
745
+}
746
+
747
+.form-table {
748
+  width: 100%;
749
+  border-collapse: collapse;
750
+  table-layout: fixed;
751
+  font-size: 14px;
752
+  color: $text-primary;
753
+
754
+  tr {
755
+    transition: background-color 0.15s;
756
+  }
757
+
758
+  tr:hover td:not(.label-cell) {
759
+    background-color: #fafcff;
760
+  }
761
+
762
+  td {
763
+    padding: 12px 14px;
764
+    border-bottom: 1px solid $border-light;
765
+    vertical-align: middle;
766
+    word-break: break-word;
767
+    background: #fff;
768
+  }
769
+
770
+  tr:last-child td {
771
+    border-bottom: none;
772
+  }
773
+
774
+  .label-cell {
775
+    width: 120px;
776
+    text-align: right;
777
+    font-size: 13px;
778
+    font-weight: 600;
779
+    color: $label-color;
780
+    background-color: $label-bg;
781
+    border-right: 1px solid $border-light;
782
+    white-space: nowrap;
783
+  }
784
+}
785
+
786
+.cell-text {
787
+  color: $text-primary;
788
+  font-weight: 500;
789
+}
790
+
791
+.repair-type-group {
792
+  display: inline-block;
793
+  vertical-align: middle;
794
+}
795
+
796
+.type-tag {
797
+  margin-left: 12px;
798
+  vertical-align: middle;
799
+}
800
+
801
+.required-mark {
802
+  color: #f56c6c;
803
+  margin-left: 2px;
804
+}
805
+
806
+.inner-table {
807
+  width: 100%;
808
+  border-collapse: separate;
809
+  border-spacing: 0;
810
+  margin-top: 8px;
811
+  font-size: 13px;
812
+  border: 1px solid $border-light;
813
+  border-radius: 8px;
814
+  overflow: hidden;
815
+
816
+  td {
817
+    padding: 8px 12px;
818
+    border-bottom: 1px solid $border-light;
819
+    border-right: 1px solid $border-light;
820
+    vertical-align: middle;
821
+    background: #fff;
822
+  }
823
+
824
+  tr:last-child td {
825
+    border-bottom: none;
826
+  }
827
+
828
+  td:last-child {
829
+    border-right: none;
830
+  }
831
+
832
+  .label-cell {
833
+    text-align: center;
834
+    font-weight: 600;
835
+    font-size: 12px;
836
+    color: $label-color;
837
+    background-color: $label-bg;
838
+    white-space: nowrap;
839
+  }
840
+}
841
+
842
+.ml10 {
843
+  margin-left: 10px;
844
+}
845
+
846
+.file-link-wrap {
847
+  line-height: 28px;
848
+}
849
+
850
+.money-unit {
851
+  margin-left: 8px;
852
+  color: #909399;
853
+  font-size: 14px;
854
+}
855
+
856
+.text-muted {
857
+  color: #c0c4cc;
858
+  font-size: 14px;
859
+}
860
+
861
+.form-footer {
862
+  margin-top: 24px;
863
+  padding-bottom: 20px;
864
+  text-align: center;
865
+}
866
+
867
+.flow-card {
868
+  border-radius: 8px;
869
+
870
+  .flow-card-title {
871
+    text-align: center;
872
+    font-size: 16px;
873
+    font-weight: 600;
874
+    color: $text-primary;
875
+    margin: 0 0 12px;
876
+  }
877
+}
878
+
879
+.mt20 {
880
+  margin-top: 20px;
881
+}
882
+
883
+.mb20 {
884
+  margin-bottom: 20px;
885
+}
886
+
887
+.ml20 {
888
+  margin-left: 20px;
889
+}
890
+</style>
891
+
892
+<style>
893
+.repair-form ::v-deep .el-input.is-disabled .el-input__inner {
894
+  color: #121212 !important;
895
+}
896
+
897
+.repair-form ::v-deep .el-textarea.is-disabled .el-textarea__inner {
898
+  color: #121212 !important;
899
+}
900
+</style>

+ 632
- 0
oa-ui/src/views/oa/repair/index.vue Dosyayı Görüntüle

@@ -0,0 +1,632 @@
1
+<template>
2
+  <div class="app-container repair-stat-page">
3
+    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
4
+      <!-- 按月查看分类 -->
5
+      <el-tab-pane label="按月查看" name="monthView">
6
+        <el-form :inline="true" size="small" class="filter-form">
7
+          <el-form-item label="选择月份">
8
+            <el-date-picker v-model="monthViewMonth" type="month" value-format="yyyy-MM" placeholder="选择月份"
9
+              @change="loadMonthView" />
10
+          </el-form-item>
11
+          <el-form-item label="维修类型">
12
+            <el-select v-model="monthViewType" clearable placeholder="全部类型" @change="loadMonthView">
13
+              <el-option v-for="item in repairTypeOptions" :key="item.value" :label="item.label"
14
+                :value="item.value" />
15
+            </el-select>
16
+          </el-form-item>
17
+          <el-form-item>
18
+            <el-button type="primary" icon="el-icon-search" @click="loadMonthView">查询</el-button>
19
+            <el-button icon="el-icon-download" @click="handleExportMonth">导出</el-button>
20
+          </el-form-item>
21
+        </el-form>
22
+
23
+        <el-row :gutter="16" class="summary-cards">
24
+          <el-col :span="4" v-for="item in monthCategorySummary" :key="item.type">
25
+            <el-card shadow="hover" class="summary-card">
26
+              <div class="card-label">{{ item.label }}</div>
27
+              <div class="card-value">{{ item.count }} 笔</div>
28
+              <div class="card-money">¥ {{ formatMoney(item.amount) }}</div>
29
+            </el-card>
30
+          </el-col>
31
+          <el-col :span="4">
32
+            <el-card shadow="hover" class="summary-card total-card">
33
+              <div class="card-label">当月合计</div>
34
+              <div class="card-value">{{ monthViewList.length }} 笔</div>
35
+              <div class="card-money">¥ {{ formatMoney(monthViewTotalAmount) }}</div>
36
+            </el-card>
37
+          </el-col>
38
+        </el-row>
39
+
40
+        <el-table v-loading="monthViewLoading" :data="monthViewList" border stripe>
41
+          <el-table-column label="申请日期" align="center" prop="applyDate" width="110">
42
+            <template slot-scope="scope">
43
+              <span>{{ parseTime(scope.row.applyDate, '{y}-{m}-{d}') }}</span>
44
+            </template>
45
+          </el-table-column>
46
+          <el-table-column label="维修类型" align="center" width="100">
47
+            <template slot-scope="scope">
48
+              <el-tag size="small" :type="repairTypeTag(scope.row.repairUsage)">
49
+                {{ repairTypeLabel(scope.row.repairUsage) }}
50
+              </el-tag>
51
+            </template>
52
+          </el-table-column>
53
+          <el-table-column label="申请人" align="center" width="100">
54
+            <template slot-scope="scope">
55
+              <span>{{ getUserName(scope.row.applier) }}</span>
56
+            </template>
57
+          </el-table-column>
58
+          <el-table-column label="申请部门" align="center" width="120">
59
+            <template slot-scope="scope">
60
+              <span>{{ getDeptName(scope.row.applyDept) }}</span>
61
+            </template>
62
+          </el-table-column>
63
+          <el-table-column label="情况说明" align="center" prop="applyReason" min-width="160" show-overflow-tooltip />
64
+          <el-table-column label="关联项目" align="center" prop="_projectName" min-width="140" show-overflow-tooltip />
65
+          <el-table-column label="关联设备" align="center" prop="_deviceName" min-width="140" show-overflow-tooltip />
66
+          <el-table-column label="维修费用" align="center" width="110">
67
+            <template slot-scope="scope">
68
+              <span class="money-text">{{ formatMoney(scope.row.repairMoney) }}</span>
69
+            </template>
70
+          </el-table-column>
71
+          <el-table-column label="处理情况" align="center" prop="handlerComment" min-width="140" show-overflow-tooltip />
72
+          <el-table-column label="操作" align="center" width="100" fixed="right">
73
+            <template slot-scope="scope">
74
+              <el-button size="mini" type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">查看详情</el-button>
75
+            </template>
76
+          </el-table-column>
77
+        </el-table>
78
+      </el-tab-pane>
79
+
80
+      <!-- 按月费用汇总 -->
81
+      <el-tab-pane label="按月费用汇总" name="monthAmount">
82
+        <el-form :inline="true" size="small" class="filter-form">
83
+          <el-form-item label="选择年份">
84
+            <el-date-picker v-model="monthAmountYear" type="year" value-format="yyyy" placeholder="选择年份"
85
+              @change="loadMonthAmount" />
86
+          </el-form-item>
87
+          <el-form-item>
88
+            <el-button type="primary" icon="el-icon-refresh" @click="loadMonthAmount">刷新</el-button>
89
+          </el-form-item>
90
+        </el-form>
91
+
92
+        <el-row :gutter="20">
93
+          <el-col :span="14">
94
+            <el-table :data="monthAmountTable" border stripe show-summary :summary-method="monthAmountSummary">
95
+              <el-table-column label="月份" align="center" prop="month" width="100" />
96
+              <el-table-column label="维修笔数" align="center" prop="count" width="100" />
97
+              <el-table-column label="维修费用(元)" align="center" prop="amount">
98
+                <template slot-scope="scope">
99
+                  <span class="money-text">{{ formatMoney(scope.row.amount) }}</span>
100
+                </template>
101
+              </el-table-column>
102
+            </el-table>
103
+          </el-col>
104
+          <el-col :span="10">
105
+            <div id="monthAmountChart" class="chart-box"></div>
106
+          </el-col>
107
+        </el-row>
108
+      </el-tab-pane>
109
+
110
+      <!-- 按年费用汇总 -->
111
+      <el-tab-pane label="按年费用汇总" name="yearAmount">
112
+        <el-form :inline="true" size="small" class="filter-form">
113
+          <el-form-item>
114
+            <el-button type="primary" icon="el-icon-refresh" @click="loadYearAmount">刷新</el-button>
115
+          </el-form-item>
116
+        </el-form>
117
+
118
+        <el-row :gutter="20">
119
+          <el-col :span="14">
120
+            <el-table v-loading="yearAmountLoading" :data="yearAmountTable" border stripe show-summary
121
+              :summary-method="yearAmountSummary">
122
+              <el-table-column label="年份" align="center" prop="year" width="120" />
123
+              <el-table-column label="维修笔数" align="center" prop="count" width="120" />
124
+              <el-table-column label="维修费用(元)" align="center" prop="amount">
125
+                <template slot-scope="scope">
126
+                  <span class="money-text">{{ formatMoney(scope.row.amount) }}</span>
127
+                </template>
128
+              </el-table-column>
129
+            </el-table>
130
+          </el-col>
131
+          <el-col :span="10">
132
+            <div id="yearAmountChart" class="chart-box"></div>
133
+          </el-col>
134
+        </el-row>
135
+      </el-tab-pane>
136
+
137
+      <!-- 按年查看分类 -->
138
+      <el-tab-pane label="按年查看分类" name="yearCategory">
139
+        <el-form :inline="true" size="small" class="filter-form">
140
+          <el-form-item label="选择年份">
141
+            <el-date-picker v-model="yearCategoryYear" type="year" value-format="yyyy" placeholder="选择年份"
142
+              @change="loadYearCategory" />
143
+          </el-form-item>
144
+          <el-form-item>
145
+            <el-button type="primary" icon="el-icon-search" @click="loadYearCategory">查询</el-button>
146
+          </el-form-item>
147
+        </el-form>
148
+
149
+        <el-row :gutter="20">
150
+          <el-col :span="12">
151
+            <el-table v-loading="yearCategoryLoading" :data="yearCategoryTable" border stripe show-summary
152
+              :summary-method="yearCategorySummary">
153
+              <el-table-column label="维修类型" align="center" prop="label" width="120" />
154
+              <el-table-column label="维修笔数" align="center" prop="count" width="120" />
155
+              <el-table-column label="维修费用(元)" align="center" prop="amount">
156
+                <template slot-scope="scope">
157
+                  <span class="money-text">{{ formatMoney(scope.row.amount) }}</span>
158
+                </template>
159
+              </el-table-column>
160
+              <el-table-column label="占比" align="center" width="100">
161
+                <template slot-scope="scope">
162
+                  <span>{{ scope.row.ratio }}%</span>
163
+                </template>
164
+              </el-table-column>
165
+            </el-table>
166
+          </el-col>
167
+          <el-col :span="12">
168
+            <div id="yearCategoryChart" class="chart-box"></div>
169
+          </el-col>
170
+        </el-row>
171
+      </el-tab-pane>
172
+
173
+      <!-- 按类别汇总 -->
174
+      <el-tab-pane label="按类别汇总" name="categoryTotal">
175
+        <el-form :inline="true" size="small" class="filter-form">
176
+          <el-form-item label="统计范围">
177
+            <el-radio-group v-model="categoryScope" @change="loadCategoryTotal">
178
+              <el-radio label="all">全部</el-radio>
179
+              <el-radio label="year">按年</el-radio>
180
+            </el-radio-group>
181
+          </el-form-item>
182
+          <el-form-item label="选择年份" v-if="categoryScope === 'year'">
183
+            <el-date-picker v-model="categoryYear" type="year" value-format="yyyy" placeholder="选择年份"
184
+              @change="loadCategoryTotal" />
185
+          </el-form-item>
186
+          <el-form-item>
187
+            <el-button type="primary" icon="el-icon-refresh" @click="loadCategoryTotal">刷新</el-button>
188
+          </el-form-item>
189
+        </el-form>
190
+
191
+        <el-row :gutter="20">
192
+          <el-col :span="12">
193
+            <el-table v-loading="categoryLoading" :data="categoryTable" border stripe show-summary
194
+              :summary-method="categorySummary">
195
+              <el-table-column label="维修类型" align="center" prop="label" width="120" />
196
+              <el-table-column label="维修笔数" align="center" prop="count" width="120" />
197
+              <el-table-column label="维修费用(元)" align="center" prop="amount">
198
+                <template slot-scope="scope">
199
+                  <span class="money-text">{{ formatMoney(scope.row.amount) }}</span>
200
+                </template>
201
+              </el-table-column>
202
+              <el-table-column label="占比" align="center" width="100">
203
+                <template slot-scope="scope">
204
+                  <span>{{ scope.row.ratio }}%</span>
205
+                </template>
206
+              </el-table-column>
207
+            </el-table>
208
+          </el-col>
209
+          <el-col :span="12">
210
+            <div id="categoryChart" class="chart-box"></div>
211
+          </el-col>
212
+        </el-row>
213
+      </el-tab-pane>
214
+    </el-tabs>
215
+
216
+    <el-dialog title="维修审批详情" :visible.sync="detailOpen" width="75%" append-to-body>
217
+      <repair-form v-if="detailOpen" :key="detailTaskForm.formId" :taskForm="detailTaskForm" :taskName="''"
218
+        :isFlow="false" />
219
+    </el-dialog>
220
+  </div>
221
+</template>
222
+
223
+<script>
224
+import * as echarts from 'echarts';
225
+import { listRepair, getRepairStatistic } from "@/api/oa/repair/repair";
226
+import { getProject } from "@/api/oa/project/project";
227
+import { getDevice } from "@/api/oa/device/device";
228
+import repairForm from '@/views/flowable/form/oa/repairForm.vue';
229
+
230
+const REPAIR_TYPES = [
231
+  { value: '0', label: '电脑' },
232
+  { value: '1', label: '打印机' },
233
+  { value: '2', label: '其他' },
234
+  { value: '3', label: '仪器设备' }
235
+];
236
+
237
+export default {
238
+  name: "Repair",
239
+  components: { repairForm },
240
+  data() {
241
+    const now = new Date();
242
+    const year = String(now.getFullYear());
243
+    const month = `${year}-${String(now.getMonth() + 1).padStart(2, '0')}`;
244
+    return {
245
+      activeTab: 'monthView',
246
+      repairTypeOptions: REPAIR_TYPES,
247
+      // 按月查看
248
+      monthViewMonth: month,
249
+      monthViewType: null,
250
+      monthViewLoading: false,
251
+      monthViewList: [],
252
+      detailOpen: false,
253
+      detailTaskForm: {
254
+        formId: ''
255
+      },
256
+      // 按月费用
257
+      monthAmountYear: year,
258
+      monthAmountTable: [],
259
+      monthAmountChart: null,
260
+      // 按年费用
261
+      yearAmountLoading: false,
262
+      yearAmountTable: [],
263
+      yearAmountChart: null,
264
+      // 按年分类
265
+      yearCategoryYear: year,
266
+      yearCategoryLoading: false,
267
+      yearCategoryTable: [],
268
+      yearCategoryChart: null,
269
+      // 按类别汇总
270
+      categoryScope: 'all',
271
+      categoryYear: year,
272
+      categoryLoading: false,
273
+      categoryTable: [],
274
+      categoryChart: null
275
+    };
276
+  },
277
+  computed: {
278
+    monthCategorySummary() {
279
+      return REPAIR_TYPES.map(type => {
280
+        const rows = this.monthViewList.filter(item => item.repairUsage === type.value);
281
+        return {
282
+          type: type.value,
283
+          label: type.label,
284
+          count: rows.length,
285
+          amount: rows.reduce((sum, row) => sum + Number(row.repairMoney || 0), 0)
286
+        };
287
+      });
288
+    },
289
+    monthViewTotalAmount() {
290
+      return this.monthViewList.reduce((sum, row) => sum + Number(row.repairMoney || 0), 0);
291
+    }
292
+  },
293
+  mounted() {
294
+    this.loadMonthView();
295
+    window.addEventListener('resize', this.resizeCharts);
296
+  },
297
+  beforeDestroy() {
298
+    window.removeEventListener('resize', this.resizeCharts);
299
+    this.disposeCharts();
300
+  },
301
+  methods: {
302
+    handleViewDetail(row) {
303
+      this.detailTaskForm = { formId: row.repairId };
304
+      this.detailOpen = true;
305
+    },
306
+    repairTypeLabel(val) {
307
+      const item = REPAIR_TYPES.find(t => t.value === val);
308
+      return item ? item.label : val || '-';
309
+    },
310
+    repairTypeTag(val) {
311
+      const map = { '0': 'primary', '1': 'success', '2': 'info', '3': 'warning' };
312
+      return map[val] || 'info';
313
+    },
314
+    formatMoney(val) {
315
+      const num = Number(val || 0);
316
+      return num.toFixed(2);
317
+    },
318
+    handleTabChange() {
319
+      this.$nextTick(() => {
320
+        if (this.activeTab === 'monthView') {
321
+          this.loadMonthView();
322
+        } else if (this.activeTab === 'monthAmount') {
323
+          this.loadMonthAmount();
324
+        } else if (this.activeTab === 'yearAmount') {
325
+          this.loadYearAmount();
326
+        } else if (this.activeTab === 'yearCategory') {
327
+          this.loadYearCategory();
328
+        } else if (this.activeTab === 'categoryTotal') {
329
+          this.loadCategoryTotal();
330
+        }
331
+      });
332
+    },
333
+    async loadMonthView() {
334
+      if (!this.monthViewMonth) return;
335
+      this.monthViewLoading = true;
336
+      try {
337
+        const params = {
338
+          pageNum: 1,
339
+          pageSize: 9999,
340
+          repairUsage: this.monthViewType,
341
+          params: { statMonth: this.monthViewMonth }
342
+        };
343
+        const res = await listRepair(params);
344
+        const list = res.rows || [];
345
+        await this.enrichProjectDeviceNames(list);
346
+        this.monthViewList = list;
347
+      } finally {
348
+        this.monthViewLoading = false;
349
+      }
350
+    },
351
+    async enrichProjectDeviceNames(list) {
352
+      const projectIds = [...new Set(list.map(row => row.projectId).filter(Boolean))];
353
+      const deviceIds = [...new Set(list.map(row => row.deviceId).filter(Boolean))];
354
+      const projectMap = {};
355
+      const deviceMap = {};
356
+      await Promise.all(projectIds.map(async id => {
357
+        const res = await getProject(id);
358
+        if (res.data) projectMap[id] = res.data;
359
+      }));
360
+      await Promise.all(deviceIds.map(async id => {
361
+        const res = await getDevice(id);
362
+        if (res.data) deviceMap[id] = res.data;
363
+      }));
364
+      list.forEach(row => {
365
+        row._projectName = row.projectId && projectMap[row.projectId]
366
+          ? projectMap[row.projectId].projectName : '';
367
+        row._deviceName = row.deviceId && deviceMap[row.deviceId]
368
+          ? `${deviceMap[row.deviceId].name || ''}${deviceMap[row.deviceId].code ? '(' + deviceMap[row.deviceId].code + ')' : ''}` : '';
369
+      });
370
+    },
371
+    async loadMonthAmount() {
372
+      if (!this.monthAmountYear) return;
373
+      const res = await getRepairStatistic({ applyDate: `${this.monthAmountYear}-01-01` });
374
+      const monthCount = res.data.monthCount ? res.data.monthCount[0] : {};
375
+      const monthAmount = res.data.monthAmount ? res.data.monthAmount[0] : {};
376
+      this.monthAmountTable = [];
377
+      for (let i = 1; i <= 12; i++) {
378
+        const key = `${this.monthAmountYear}-${String(i).padStart(2, '0')}`;
379
+        this.monthAmountTable.push({
380
+          month: `${i}月`,
381
+          monthKey: key,
382
+          count: monthCount[key] || 0,
383
+          amount: Number(monthAmount[key] || 0)
384
+        });
385
+      }
386
+      this.$nextTick(() => this.renderMonthAmountChart());
387
+    },
388
+    async loadYearAmount() {
389
+      this.yearAmountLoading = true;
390
+      try {
391
+        const res = await getRepairStatistic({});
392
+        const yearCount = res.data.yearCount ? res.data.yearCount[0] : {};
393
+        const yearAmount = res.data.yearAmount ? res.data.yearAmount[0] : {};
394
+        this.yearAmountTable = Object.keys(yearCount).sort().map(year => ({
395
+          year,
396
+          count: yearCount[year] || 0,
397
+          amount: Number(yearAmount[year] || 0)
398
+        }));
399
+        this.$nextTick(() => this.renderYearAmountChart());
400
+      } finally {
401
+        this.yearAmountLoading = false;
402
+      }
403
+    },
404
+    async loadYearCategory() {
405
+      if (!this.yearCategoryYear) return;
406
+      this.yearCategoryLoading = true;
407
+      try {
408
+        const res = await getRepairStatistic({ applyDate: `${this.yearCategoryYear}-01-01` });
409
+        this.yearCategoryTable = this.buildCategoryTable(
410
+          res.data.usageCount ? res.data.usageCount[0] : {},
411
+          res.data.usageAmount ? res.data.usageAmount[0] : {}
412
+        );
413
+        this.$nextTick(() => this.renderYearCategoryChart());
414
+      } finally {
415
+        this.yearCategoryLoading = false;
416
+      }
417
+    },
418
+    async loadCategoryTotal() {
419
+      this.categoryLoading = true;
420
+      try {
421
+        let res;
422
+        if (this.categoryScope === 'year' && this.categoryYear) {
423
+          res = await getRepairStatistic({ applyDate: `${this.categoryYear}-01-01` });
424
+        } else {
425
+          res = await getRepairStatistic({});
426
+        }
427
+        this.categoryTable = this.buildCategoryTable(
428
+          res.data.usageCount ? res.data.usageCount[0] : {},
429
+          res.data.usageAmount ? res.data.usageAmount[0] : {}
430
+        );
431
+        this.$nextTick(() => this.renderCategoryChart());
432
+      } finally {
433
+        this.categoryLoading = false;
434
+      }
435
+    },
436
+    buildCategoryTable(countObj, amountObj) {
437
+      const rows = REPAIR_TYPES.map(type => ({
438
+        label: type.label,
439
+        count: countObj[type.label] || 0,
440
+        amount: Number(amountObj[type.label] || 0)
441
+      }));
442
+      const totalAmount = rows.reduce((sum, row) => sum + row.amount, 0);
443
+      return rows.map(row => ({
444
+        ...row,
445
+        ratio: totalAmount > 0 ? ((row.amount / totalAmount) * 100).toFixed(1) : '0.0'
446
+      }));
447
+    },
448
+    monthAmountSummary({ columns, data }) {
449
+      const sums = [];
450
+      columns.forEach((column, index) => {
451
+        if (index === 0) {
452
+          sums[index] = '合计';
453
+          return;
454
+        }
455
+        if (column.property === 'count') {
456
+          sums[index] = data.reduce((sum, row) => sum + row.count, 0);
457
+        } else if (column.property === 'amount') {
458
+          const total = data.reduce((sum, row) => sum + row.amount, 0);
459
+          sums[index] = this.formatMoney(total);
460
+        } else {
461
+          sums[index] = '';
462
+        }
463
+      });
464
+      return sums;
465
+    },
466
+    yearAmountSummary(param) {
467
+      return this.monthAmountSummary(param);
468
+    },
469
+    yearCategorySummary(param) {
470
+      const sums = [];
471
+      param.columns.forEach((column, index) => {
472
+        if (index === 0) {
473
+          sums[index] = '合计';
474
+          return;
475
+        }
476
+        if (column.property === 'count') {
477
+          sums[index] = param.data.reduce((sum, row) => sum + row.count, 0);
478
+        } else if (column.property === 'amount') {
479
+          const total = param.data.reduce((sum, row) => sum + row.amount, 0);
480
+          sums[index] = this.formatMoney(total);
481
+        } else if (column.property === 'ratio') {
482
+          sums[index] = '100%';
483
+        } else {
484
+          sums[index] = '';
485
+        }
486
+      });
487
+      return sums;
488
+    },
489
+    categorySummary(param) {
490
+      return this.yearCategorySummary(param);
491
+    },
492
+    renderMonthAmountChart() {
493
+      const dom = document.getElementById('monthAmountChart');
494
+      if (!dom) return;
495
+      if (!this.monthAmountChart) {
496
+        this.monthAmountChart = echarts.init(dom);
497
+      }
498
+      this.monthAmountChart.setOption({
499
+        title: { text: `${this.monthAmountYear}年 月度维修费用`, left: 'center', textStyle: { fontSize: 14 } },
500
+        tooltip: { trigger: 'axis' },
501
+        grid: { left: 50, right: 20, bottom: 30, top: 50 },
502
+        xAxis: { type: 'category', data: this.monthAmountTable.map(item => item.month) },
503
+        yAxis: { type: 'value', name: '元' },
504
+        series: [{
505
+          type: 'bar',
506
+          data: this.monthAmountTable.map(item => item.amount),
507
+          itemStyle: { color: '#409EFF' },
508
+          barMaxWidth: 36
509
+        }]
510
+      });
511
+    },
512
+    renderYearAmountChart() {
513
+      const dom = document.getElementById('yearAmountChart');
514
+      if (!dom) return;
515
+      if (!this.yearAmountChart) {
516
+        this.yearAmountChart = echarts.init(dom);
517
+      }
518
+      this.yearAmountChart.setOption({
519
+        title: { text: '年度维修费用汇总', left: 'center', textStyle: { fontSize: 14 } },
520
+        tooltip: { trigger: 'axis' },
521
+        grid: { left: 50, right: 20, bottom: 30, top: 50 },
522
+        xAxis: { type: 'category', data: this.yearAmountTable.map(item => item.year) },
523
+        yAxis: { type: 'value', name: '元' },
524
+        series: [{
525
+          type: 'bar',
526
+          data: this.yearAmountTable.map(item => item.amount),
527
+          itemStyle: { color: '#67C23A' },
528
+          barMaxWidth: 40
529
+        }]
530
+      });
531
+    },
532
+    renderPieChart(chartRef, domId, title, tableData) {
533
+      const dom = document.getElementById(domId);
534
+      if (!dom) return;
535
+      if (!this[chartRef]) {
536
+        this[chartRef] = echarts.init(dom);
537
+      }
538
+      this[chartRef].setOption({
539
+        title: { text: title, left: 'center', textStyle: { fontSize: 14 } },
540
+        tooltip: { trigger: 'item', formatter: '{b}: ¥{c} ({d}%)' },
541
+        legend: { bottom: 0 },
542
+        series: [{
543
+          type: 'pie',
544
+          radius: ['40%', '65%'],
545
+          center: ['50%', '45%'],
546
+          data: tableData.map(item => ({ name: item.label, value: item.amount })),
547
+          emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.2)' } }
548
+        }]
549
+      });
550
+    },
551
+    renderYearCategoryChart() {
552
+      this.renderPieChart('yearCategoryChart', 'yearCategoryChart', `${this.yearCategoryYear}年 分类费用占比`, this.yearCategoryTable);
553
+    },
554
+    renderCategoryChart() {
555
+      const title = this.categoryScope === 'year' ? `${this.categoryYear}年 分类费用占比` : '全部分类费用占比';
556
+      this.renderPieChart('categoryChart', 'categoryChart', title, this.categoryTable);
557
+    },
558
+    resizeCharts() {
559
+      ['monthAmountChart', 'yearAmountChart', 'yearCategoryChart', 'categoryChart'].forEach(key => {
560
+        if (this[key]) this[key].resize();
561
+      });
562
+    },
563
+    disposeCharts() {
564
+      ['monthAmountChart', 'yearAmountChart', 'yearCategoryChart', 'categoryChart'].forEach(key => {
565
+        if (this[key]) {
566
+          this[key].dispose();
567
+          this[key] = null;
568
+        }
569
+      });
570
+    },
571
+    handleExportMonth() {
572
+      this.download('oa/repair/export', {
573
+        params: { statMonth: this.monthViewMonth },
574
+        repairUsage: this.monthViewType
575
+      }, `repair_${this.monthViewMonth}.xlsx`);
576
+    }
577
+  }
578
+};
579
+</script>
580
+
581
+<style lang="scss" scoped>
582
+.repair-stat-page {
583
+  .filter-form {
584
+    margin-bottom: 16px;
585
+  }
586
+
587
+  .summary-cards {
588
+    margin-bottom: 20px;
589
+  }
590
+
591
+  .summary-card {
592
+    text-align: center;
593
+    border-radius: 8px;
594
+
595
+    .card-label {
596
+      font-size: 14px;
597
+      color: #909399;
598
+      margin-bottom: 8px;
599
+    }
600
+
601
+    .card-value {
602
+      font-size: 22px;
603
+      font-weight: 600;
604
+      color: #303133;
605
+    }
606
+
607
+    .card-money {
608
+      margin-top: 6px;
609
+      font-size: 16px;
610
+      color: #E6A23C;
611
+      font-weight: 500;
612
+    }
613
+
614
+    &.total-card {
615
+      background: linear-gradient(135deg, #ecf5ff 0%, #f0f9ff 100%);
616
+    }
617
+  }
618
+
619
+  .money-text {
620
+    color: #E6A23C;
621
+    font-weight: 500;
622
+  }
623
+
624
+  .chart-box {
625
+    width: 100%;
626
+    height: 380px;
627
+    border: 1px solid #ebeef5;
628
+    border-radius: 8px;
629
+    background: #fff;
630
+  }
631
+}
632
+</style>

Loading…
İptal
Kaydet